Script Finder+

Script Finder allows you to find userscripts from greasyfork on any website.

Stan na 20-08-2024. Zobacz najnowsza wersja.

  1. // ==UserScript==
  2. // @name Script Finder+
  3. // @name:zh-CN Script Finder 油猴脚本查找
  4. // @namespace https://greatest.deepsurf.us/zh-CN/users/1169082
  5. // @version 0.1.6.48
  6. // @description Script Finder allows you to find userscripts from greasyfork on any website.
  7. // @description:zh-CN Script Finder 在任何网站上找到适用于该网站的greasyfork油猴脚本
  8. // @author shiquda & 人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  9.  
  10. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  11. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  12. // @match *://*/*
  13. // @connect greatest.deepsurf.us
  14. // @icon https://github.com/ChinaGodMan/UserScripts/raw/main/docs/icon/Scripts%20Icons/Finder.jpg
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_addStyle
  17. // @license AGPL-3.0
  18.  
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. const domainParts = window.location.hostname.split('.').slice(-2)
  23. const domain = domainParts.join('.')
  24. const errorMessage = "Failed to retrieve script information or there are no available scripts for this domain."
  25. let neverLoadedScripts = true
  26. let collapsed = true
  27. let loadedPages = 0
  28.  
  29. function getScriptsInfo(domain, page = 1) {
  30. var url = `https://greatest.deepsurf.us/scripts/by-site/${domain}?filter_locale=0&sort=updated&page=${page}`
  31.  
  32. GM_xmlhttpRequest({
  33. method: "GET",
  34. url: url,
  35. onload: (response) => {
  36. // 解析结果
  37. const parser = new DOMParser()
  38. const doc = parser.parseFromString(response.responseText, "text/html")
  39. const scripts = doc.querySelector("#browse-script-list")?.querySelectorAll('[data-script-id]')
  40. let scriptsInfo = []
  41.  
  42. if (!scripts) {
  43. scriptsInfo = errorMessage
  44. } else {
  45. for (var i = 0; i < scripts.length; i++) {
  46. scriptsInfo.push(parseScriptInfo(scripts[i]))
  47. }
  48. }
  49.  
  50. // 处理对象
  51. const loadMoreButton = document.querySelector('.load-more')
  52. console.log(doc.querySelector('.next_page'))
  53. if (doc.querySelector('.next_page') == null || doc.querySelector('.next_page')?.getAttribute('aria-disabled') === 'true') {
  54. loadedPages = 'max'
  55. loadMoreButton.disabled = true
  56. loadMoreButton.textContent = 'All scripts loaded'
  57. } else {
  58. loadMoreButton.disabled = false
  59. loadMoreButton.textContent = 'Load more'
  60. }
  61. // console.log(scriptsInfo);
  62. document.querySelector('.wait-loading').style.display = 'none'
  63. loadMoreButton.style.display = 'block'
  64. appendScriptsInfo(scriptsInfo)
  65. updateMatches()
  66.  
  67. typeof (loadedPages) === 'number' ? loadedPages += 1 : loadedPages = loadedPages
  68. // console.log(loadedPages)
  69. },
  70. onerror: () => {
  71. console.log("Some error occurred!")
  72. if (loadedPages === 0) {
  73. appendScriptsInfo(scriptsInfo)
  74. }
  75. const scriptsInfo = errorMessage
  76. document.querySelector('.wait-loading').style.display = 'none'
  77. }
  78. })
  79. }
  80.  
  81. // 解析脚本信息
  82. function parseScriptInfo(script) {
  83. return {
  84. id: script.getAttribute('data-script-id'),
  85. name: script.getAttribute('data-script-name'),
  86. author: script.querySelector("dd.script-list-author").textContent,
  87. description: script.querySelector(".script-description").textContent,
  88. version: script.getAttribute('data-script-version'),
  89. url: 'https://greatest.deepsurf.us/scripts/' + script.getAttribute('data-script-id'),
  90. createDate: script.getAttribute('data-script-created-date'),
  91. updateDate: script.getAttribute('data-script-updated-date'),
  92. installs: script.getAttribute('data-script-total-installs'),
  93. dailyInstalls: script.getAttribute('data-script-daily-installs'),
  94. ratingScore: script.getAttribute('data-script-rating-score')
  95. }
  96. }
  97.  
  98. // 插入脚本
  99. function appendScriptsInfo(scriptsInfo) {
  100. const infoList = document.querySelector('.info-list')
  101. if (scriptsInfo === errorMessage) {
  102. // infoList.innerHTML = errorMessage;
  103. const loadMoreButton = document.querySelector('.load-more')
  104. loadMoreButton.disabled = true
  105. loadMoreButton.textContent = 'All scripts loaded'
  106. loadMoreButton.innerHTML = errorMessage
  107. } else {
  108. for (var i = 0; i < scriptsInfo.length; i++) {
  109. var script = scriptsInfo[i]
  110. var listItem = document.createElement('li')
  111. listItem.className = 'info-item'
  112.  
  113. var scriptContainer = document.createElement('div')
  114. scriptContainer.className = 'script-container'
  115.  
  116. var nameElement = document.createElement('a')
  117. nameElement.className = 'mscript-link'
  118. nameElement.innerText = script.name
  119. nameElement.href = script.url
  120. nameElement.target = '_blank'
  121.  
  122. var descriptionElement = document.createElement('p')
  123. descriptionElement.className = 'script-description'
  124. descriptionElement.innerHTML = script.description
  125.  
  126. var detailsContainer = document.createElement('div')
  127. detailsContainer.className = 'details-container'
  128.  
  129. // 创建一键安装按钮
  130. var installButton = document.createElement('a')
  131. installButton.className = 'install-button'
  132. installButton.innerText = `Install ${script.version}`
  133. installButton.href = `https://greatest.deepsurf.us/scripts/${script.id}/code/script.user.js`
  134.  
  135. const details = [
  136. { key: 'Author', value: script.author },
  137. { key: 'Installs', value: script.installs },
  138. { key: 'Daily Installs', value: script.dailyInstalls },
  139. { key: 'Created', value: script.createDate },
  140. { key: 'Updated', value: script.updateDate },
  141. { key: 'Rating', value: script.ratingScore }
  142. ]
  143.  
  144. for (let i = 0; i < details.length; i++) {
  145. const spanElement = document.createElement('span')
  146. spanElement.className = 'script-details'
  147. spanElement.innerText = `${details[i].key}:\n${details[i].value}`
  148. detailsContainer.appendChild(spanElement)
  149. }
  150.  
  151. scriptContainer.appendChild(nameElement)
  152. scriptContainer.appendChild(descriptionElement)
  153. scriptContainer.appendChild(detailsContainer)
  154. scriptContainer.appendChild(installButton)
  155.  
  156. listItem.appendChild(scriptContainer)
  157. listItem.scriptId = script.id
  158. infoList.appendChild(listItem)
  159. }
  160. }
  161. }
  162.  
  163. function setupUI() {
  164. GM_addStyle(`
  165. scrbutton.script-button {
  166. position: fixed;
  167. bottom: 20%;
  168. right: -50px;
  169. transform: translateY(50%);
  170. padding: 20px;
  171. font-size: 16px;
  172. border: none;
  173. border-radius: 4px;
  174. background-color: #1e90ff;
  175. color: #ffffff;
  176. cursor: pointer;
  177. transition: right 0.3s;
  178. z-index: 9999999999999999;
  179. }
  180. div.info-container {
  181. display: none;
  182. position: fixed;
  183. top: 50%;
  184. left: 50%;
  185. transform: translate(-50%, -50%);
  186. width: 650px;
  187. padding: 12px;
  188. background-color: #ffffff;
  189. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
  190. border-radius: 4px;
  191. opacity: 0;
  192. transition: opacity 0.3s;
  193. z-index: 9999;
  194. max-height: 80vh;
  195. overflow-y: auto;
  196. }
  197. ul.info-list {
  198. list-style: none;
  199. margin: 0;
  200. padding: 0;
  201. }
  202. li.info-item {
  203. margin-bottom: 15px;
  204. padding: 12px;
  205. padding-bottom: 22px;
  206. display: flex;
  207. flex-direction: column;
  208. border: 1px solid #1e90ff;
  209. border-radius: 5px;
  210. }
  211. .div.script-container {
  212. display: flex;
  213. flex-direction: column;
  214. }
  215. a.mscript-link {
  216. font-size: 18px !important;
  217. font-weight: bold !important;
  218. margin-bottom: 5px !important;
  219. color: #1e90ff !important;
  220. }
  221. p.script-description {
  222. color: black !important;
  223. margin-top: 2px;
  224. margin-bottom: 5px;
  225. font-size: 16px;
  226. }
  227. div.details-container {
  228. font-size: 15px;
  229. font-weight: bold;
  230. display: flex;
  231. justify-content: space-between;
  232. margin-bottom: 15px;
  233. }
  234. span.script-details {
  235. font: !important;
  236. color: black !important;
  237. flex-grow: 1 !important;
  238. text-align: center !important;
  239. border: 1px solid #1e90ff !important;
  240. border-radius: 5px !important;
  241. margin: 4px !important;
  242. }
  243. div.table-header {
  244. color: #1e90ff !important;
  245. font-size: 25px;
  246. font-weight: bold;
  247. }
  248. input.script-search-input {
  249. width: 96% !important;
  250. padding: 10px !important;
  251. font-size: 18px !important;
  252. border: 1px solid #1e90ff !important;
  253. border-radius: 4px !important;
  254. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3) !important;
  255. margin-bottom: 15px !important;
  256. margin-top: 20px !important;
  257. }
  258. a.install-button {
  259. font-size: 20px;
  260. background-color: green;
  261. color: white;
  262. padding: 12px;
  263. }
  264. button.to-greasyfork {
  265. position: absolute;
  266. top: 12px;
  267. right: 12px;
  268. border-radius: 4px;
  269. padding: 8px;
  270. font-size: 16px;
  271. border: none;
  272. background-color: #1e90ff;
  273. color: #ffffff;
  274. cursor: pointer;
  275. }
  276. span.match-count {
  277. background-color: #1e90ff;
  278. font-size: 25px;
  279. font-weight: bold;
  280. color: white;
  281. padding: 6px;
  282. position: absolute;
  283. right: 50%;
  284. border-radius: 12px;
  285. top: 10px;
  286. }
  287. div.wait-loading {
  288. font-size: 20px;
  289. font-weight: bold;
  290. color: #1e90ff;
  291. animation: blink 1s infinite;
  292. }
  293. @keyframes fadeInOut {
  294. 0% {
  295. opacity: 0;
  296. }
  297. 50% {
  298. opacity: 1;
  299. }
  300. 100% {
  301. opacity: 0;
  302. }
  303. }
  304. @keyframes blink {
  305. 0%, 100% {
  306. opacity: 0;
  307. }
  308. 50% {
  309. opacity: 1;
  310. }
  311. }
  312. button.load-more {
  313. border-radius: 4px;
  314. padding: 8px;
  315. font-size: 16px;
  316. border: none;
  317. background-color: #1e90ff;
  318. color: #ffffff;
  319. cursor: pointer;
  320. position: relative;
  321. bottom: 5px;
  322. left: 50%;
  323. transform: translateX(-50%);
  324. }
  325. button.load-more:disabled {
  326. background-color: #cccccc;
  327. cursor: not-allowed;
  328. }
  329.  
  330. /* Mobile styles */
  331. @media (max-width: 600px) {
  332. scrbutton.script-button {
  333. right: -30px;
  334. padding: 8px;
  335. font-size: 14px;
  336. }
  337. span.script-details {
  338. font-size: 10px !important;
  339. margin: 2px !important;
  340. padding: 2px !important;
  341. }
  342. span.match-count {
  343. font-size: 20px;
  344. padding: 4px;
  345. }
  346.  
  347. button.to-greasyfork {
  348. padding: 6px;
  349. font-size: 14px;
  350. }
  351.  
  352. a.install-button {
  353. font-size: 12px;
  354. padding: 8px;
  355. }
  356. div.table-header {
  357. font-size: 20px;
  358. }
  359. div.script-container {
  360. padding: 10px;
  361. }
  362. div.info-container {
  363. top: 10%;
  364. left: 5%;
  365. right: 5%;
  366. transform: none;
  367. width: calc(90% - 10px); /* 自适应宽度,保持左右边距 */
  368. max-width: 100%; /* 确保不超出屏幕宽度 */
  369. }
  370. a.script-link {
  371. font-size: 16px !important;
  372. }
  373. p.script-description {
  374. font-size: 14px;
  375. }
  376. {
  377.  
  378. input.script-search-input {
  379. width: 92% !important;
  380. padding: 8px !important;
  381. font-size: 16px !important;
  382. }
  383. span.match-count {
  384. font-size: 20px;
  385. padding: 4px;
  386. }
  387. button.load-more {
  388. font-size: 14px;
  389. padding: 6px;
  390. }
  391. }
  392. `)
  393.  
  394.  
  395.  
  396. // 创建打开列表按钮
  397. var button = document.createElement('scrbutton')
  398. button.className = 'script-button'
  399. button.innerText = 'Scripts'
  400.  
  401. // 创建脚本容器
  402. var infoContainer = document.createElement('div')
  403. infoContainer.className = 'info-container'
  404.  
  405. // 创建搜索框
  406. var searchInput = document.createElement('input')
  407. searchInput.type = 'text'
  408. searchInput.placeholder = 'Search scripts...'
  409. searchInput.className = 'script-search-input'
  410.  
  411. // 创建指向greasyfork的链接
  412. var toGreasyfork = document.createElement('button')
  413. toGreasyfork.className = 'to-greasyfork'
  414. toGreasyfork.innerText = 'View on Greasyfork'
  415.  
  416. // 创建计数器
  417. var matchCount = document.createElement('span')
  418. matchCount.className = 'match-count'
  419.  
  420. // 创建表头
  421. var tableHeader = document.createElement('div')
  422. tableHeader.className = 'table-header'
  423. tableHeader.appendChild(document.createTextNode('Script Finder'))
  424. tableHeader.appendChild(matchCount)
  425. tableHeader.appendChild(searchInput)
  426. tableHeader.appendChild(toGreasyfork)
  427.  
  428. // 创建脚本列表
  429. var infoList = document.createElement('ul')
  430. infoList.className = 'info-list'
  431.  
  432. // 创建等待加载
  433. var waitLoading = document.createElement('div')
  434. waitLoading.className = 'wait-loading'
  435. waitLoading.innerText = 'Loading scripts...'
  436.  
  437. // 创建加载更多
  438. var loadMore = document.createElement('button')
  439. loadMore.className = 'load-more'
  440. loadMore.innerText = 'Load more'
  441. loadMore.style.display = 'none'
  442.  
  443. infoList.appendChild(waitLoading)
  444. infoList.appendChild(loadMore)
  445.  
  446. infoContainer.appendChild(tableHeader)
  447. infoContainer.appendChild(infoList)
  448.  
  449. var timeout
  450. button.addEventListener('mouseenter', function () {
  451. clearTimeout(timeout)
  452. button.style.right = '10px'
  453. })
  454.  
  455. button.addEventListener('mouseleave', function () {
  456. timeout = setTimeout(function () {
  457. button.style.right = '-50px'
  458. }, 500)
  459. })
  460.  
  461. button.addEventListener('click', function (event) {
  462. event.stopPropagation()
  463. if (collapsed) {
  464. infoContainer.style.display = "block"
  465. infoContainer.style.opacity = 1
  466. collapsed = false
  467. }
  468. else {
  469. infoContainer.style.display = "none"
  470. infoContainer.style.opacity = 0
  471. collapsed = true
  472. }
  473.  
  474. if (neverLoadedScripts) {
  475. getScriptsInfo(domain, 1)
  476. neverLoadedScripts = false
  477. }
  478.  
  479. })
  480.  
  481. infoContainer.addEventListener('click', function (event) {
  482. event.stopPropagation()
  483. })
  484.  
  485. searchInput.addEventListener('input', () => {
  486. searchScript()
  487. updateMatches()
  488. })
  489.  
  490. toGreasyfork.addEventListener('click', function () {
  491. window.open(`https://greatest.deepsurf.us/scripts/by-site/${domain}?q=${searchInput.value}&filter_locale=0&sort=updated`)
  492. })
  493.  
  494. loadMore.addEventListener('click', () => {
  495. if (loadedPages === 'max') {
  496. return
  497. }
  498. const loadMoreButton = document.querySelector('.load-more')
  499. loadMoreButton.disabled = true
  500. loadMoreButton.textContent = 'Loading...'
  501. document.querySelector('.wait-loading').style.display = 'block'
  502. getScriptsInfo(domain, loadedPages + 1)
  503. })
  504.  
  505. document.body.addEventListener('click', function () {
  506. clearTimeout(timeout)
  507. collapsed = true
  508. button.style.right = '-50px'
  509. infoContainer.style.opacity = 0
  510. infoContainer.style.display = "none"
  511. })
  512.  
  513. document.body.appendChild(button)
  514.  
  515. document.body.appendChild(infoContainer)
  516.  
  517. infoContainer.addEventListener('change', () => {
  518. updateMatches()
  519. })
  520. updateMatches()
  521. }
  522.  
  523. function searchScript() {
  524. const searchWord = document.querySelector('.script-search-input').value.toLowerCase() // 将要匹配的文本转换为小写
  525. const scriptList = document.querySelectorAll('.info-item')
  526. for (let i = 0; i < scriptList.length; i++) {
  527. const scriptText = scriptList[i].innerText.toLowerCase() // 将检索的文本转换为小写
  528. if (scriptText.includes(searchWord)) {
  529. scriptList[i].style.display = 'block'
  530. } else {
  531. scriptList[i].style.display = 'none'
  532. }
  533. }
  534. }
  535.  
  536. function updateMatches() {
  537. const matchCount = document.querySelectorAll('.info-item:not([style*="display: none"])').length
  538. const allCount = document.querySelectorAll('.info-item').length
  539. document.querySelector('.match-count').innerText = matchCount === allCount ? matchCount : `${matchCount}/${allCount}`
  540. }
  541.  
  542. function main() {
  543. if (window.self !== window.top) {
  544. // 在iframe中执行时,直接退出
  545. return
  546. }
  547. setupUI()
  548. }
  549.  
  550. main()
  551.  
  552.  
  553. })()