c.AI Search Sort

Sort search so cards with public definition stays on top and marked with a star

当前为 2024-10-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name c.AI Search Sort
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.0
  5. // @description Sort search so cards with public definition stays on top and marked with a star
  6. // @author EnergoStalin
  7. // @license GPL-3.0-or-later
  8. // @match https://character.ai/search*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=character.ai
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (async function() {
  14. 'use strict';
  15.  
  16. async function waitNotNull(func, timeout = 10000, interval = 1000) {
  17. return new Promise((res, rej) => {
  18. let time = timeout
  19. const i = setInterval(async () => {
  20. const c = await func()
  21. time -= interval
  22. if (time <= 0) {
  23. clearInterval(i)
  24. rej()
  25. }
  26. if (!c) return
  27.  
  28. clearInterval(i)
  29. res(c)
  30. }, interval)
  31. })
  32. }
  33.  
  34. const [pageProps, cardsContainer] = await Promise.all([
  35. waitNotNull(() => document.querySelector('#__NEXT_DATA__')).then(e => JSON.parse(e.textContent).props.pageProps),
  36. waitNotNull(() => document.evaluate('/html/body/div[1]/div/main/div/div/div/main/div/div[2]', document).iterateNext())
  37. ]);
  38. const token = pageProps.token
  39.  
  40. async function isDefinitionPublic(id) {
  41. const character = await fetch(`https://plus.character.ai/chat/character/info/`, {
  42. headers: {
  43. 'Authorization': `Token ${token}`,
  44. 'Origin': 'https://character.ai/',
  45. 'Referer': 'https://character.ai/',
  46. 'Content-Type': 'application/json',
  47. 'Accept': 'application/json'
  48. },
  49. method: 'POST',
  50. body: JSON.stringify({ external_id: id })
  51. })
  52. .then(e => e.json())
  53. .then(e => e.character)
  54. return !!character.definition
  55. }
  56.  
  57. function clearStatus(card) { card.removeChild(card.querySelector('div[data-status]')) }
  58.  
  59. function isStarred(card) { return !!card.querySelector('div[data-status="starred"]') }
  60. function setStarredStatus(card) {
  61. card.innerHTML += `
  62. <div data-status="starred" class="relative" style="min-height: 90px;">
  63. <svg style="margin-top: 10px; margin-right: 10px;" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#75FB4C"><path d="M371.01-324 480-390.22 589-324l-29-124 97-84-127-11-50-117-50 117-127 11 96.89 83.95L371.01-324ZM480-72 360-192H192v-168L72-480l120-120v-168h168l120-120 120 120h168v168l120 120-120 120v168H600L480-72Zm0-102 90-90h126v-126l90-90-90-90v-126H570l-90-90-90 90H264v126l-90 90 90 90v126h126l90 90Zm0-306Z"/></svg>
  64. </div>
  65. `
  66. }
  67.  
  68. function setPendingStatus(card) {
  69. card.innerHTML += `
  70. <div data-status="pending" class="relative" style="min-height: 90px;">
  71. <svg style="margin-top: 10px; margin-right: 10px;" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#5985E1"><path d="M288-420q25 0 42.5-17.5T348-480q0-25-17.5-42.5T288-540q-25 0-42.5 17.5T228-480q0 25 17.5 42.5T288-420Zm192 0q25 0 42.5-17.5T540-480q0-25-17.5-42.5T480-540q-25 0-42.5 17.5T420-480q0 25 17.5 42.5T480-420Zm192 0q25 0 42.5-17.5T732-480q0-25-17.5-42.5T672-540q-25 0-42.5 17.5T612-480q0 25 17.5 42.5T672-420ZM480.28-96Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Zm-.28-72q130 0 221-91t91-221q0-130-91-221t-221-91q-130 0-221 91t-91 221q0 130 91 221t221 91Zm0-312Z"/></svg>
  72. </div>
  73. `
  74. }
  75.  
  76. const cardsObserver = new MutationObserver(sortSearches)
  77. function sortSearches() {
  78. cardsObserver.disconnect()
  79. const nodes = Array.from(cardsContainer.childNodes)
  80.  
  81. Promise.all(nodes.map(async card => {
  82. if(isStarred(card)) return
  83.  
  84. setPendingStatus(card)
  85. const isPublic = await isDefinitionPublic(card.href.split('/').pop())
  86. clearStatus(card)
  87.  
  88. if(isPublic) {
  89. setStarredStatus(card)
  90. } else {
  91. cardsContainer.appendChild(card)
  92. }
  93. })).then(() => cardsObserver.observe(cardsContainer, { attributes: false, childList: true, subtree: false }))
  94. }
  95.  
  96. sortSearches()
  97. })();