Script Finder+

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

ของเมื่อวันที่ 29-08-2024 ดู เวอร์ชันล่าสุด

  1. // ==UserScript==
  2. // @name Script Finder+
  3. // @description Script Finder allows you to find userscripts from greasyfork on any website.
  4. // @name:en Script Finder+
  5. // @description:en Script Finder allows you to find userscripts from greasyfork on any website.
  6. // @name:zh-CN Script Finder 油猴脚本查找
  7. // @description:zh-CN Script Finder 在任何网站上找到适用于该网站的 greasyfork 油猴脚本。
  8. // @name:zh-TW 腳本搜尋器+
  9. // @description:zh-TW 腳本搜尋器可以讓你在任何網站上從 greasyfork 找到使用者腳本。
  10. // @name:vi Tìm kiếm Script+
  11. // @description:vi Script Finder cho phép bạn tìm các script người dùng từ greasyfork trên bất kỳ trang web nào.
  12. // @name:ja スクリプトファインダー+
  13. // @description:ja スクリプトファインダーを使うことで、greasyfork からユーザースクリプトを任意のウェブサイトで見つけることができます。
  14. // @name:ko 스크립트 파인더+
  15. // @description:ko Script Finder를 사용하면 greasyfork에서 사용자 스크립트를 어떤 웹사이트에서든 찾을 수 있습니다.
  16.  
  17. // @namespace https://greatest.deepsurf.us/zh-CN/users/1169082
  18. // @version 0.1.6.59
  19. // @author shiquda & 人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  20. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  21. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  22. // @match *://*/*
  23. // @connect greatest.deepsurf.us
  24. // @icon https://github.com/ChinaGodMan/UserScripts/raw/main/docs/icon/Scripts%20Icons/Finder.jpg
  25. // @grant GM_xmlhttpRequest
  26. // @grant GM_addStyle
  27. // @license AGPL-3.0
  28.  
  29. // ==/UserScript==
  30. const translate = (function () {
  31. const userLang = (navigator.languages && navigator.languages[0]) || navigator.language || 'en'
  32. const strings = {
  33. 'en': {
  34. Author: 'Author',
  35. Installs: 'Installs',
  36. DailyInstalls: 'Daily Installs',
  37. Created: 'Created',
  38. Updated: 'Updated',
  39. Rating: 'Rating',
  40. LoadingScripts: 'Loading scripts...',
  41. LoadMore: 'Load more',
  42. AllScriptsLoaded: 'All scripts loaded',
  43. SearchPlaceholder: 'Search scripts...',
  44. ViewOnGreasyfork: 'View on Greasyfork',
  45. errorMessage: 'Failed to retrieve script information or there are no available scripts for this domain.',
  46. Loading: 'Loading...',
  47. Scripts: 'Scripts'
  48. },
  49. 'zh-CN': {
  50. Author: '作者',
  51. Installs: '安装数量',
  52. DailyInstalls: '每日安装',
  53. Created: '创建日期',
  54. Updated: '更新时间',
  55. Rating: '评分',
  56. LoadingScripts: '正在加载脚本...',
  57. LoadMore: '加载更多',
  58. AllScriptsLoaded: '所有脚本已加载',
  59. SearchPlaceholder: '搜索脚本...',
  60. ViewOnGreasyfork: '在Greasyfork查看',
  61. errorMessage: '无法检索脚本信息或该域没有可用的脚本。',
  62. Loading: '载入中...',
  63. Scripts: '脚本'
  64.  
  65. },
  66. 'zh-TW': {
  67. Author: '作者',
  68. Installs: '安裝數量',
  69. DailyInstalls: '每日安裝',
  70. Created: '創建日期',
  71. Updated: '更新日期',
  72. Rating: '評分',
  73. LoadingScripts: '正在載入腳本...',
  74. LoadMore: '載入更多',
  75. AllScriptsLoaded: '所有腳本已載入',
  76. SearchPlaceholder: '搜尋腳本...',
  77. ViewOnGreasyfork: '在Greasyfork查看',
  78. errorMessage: '無法檢索腳本信息或該域沒有可用的腳本。',
  79. Loading: '載入中...',
  80. Scripts: '腳本'
  81. },
  82. 'ja': {
  83. Author: '著者',
  84. Installs: 'インストール数',
  85. DailyInstalls: '日次インストール数',
  86. Created: '作成日',
  87. Updated: '更新日',
  88. Rating: '評価',
  89. LoadingScripts: 'スクリプトを読み込んでいます...',
  90. LoadMore: 'もっと読む',
  91. AllScriptsLoaded: 'すべてのスクリプトが読み込まれました',
  92. SearchPlaceholder: 'スクリプトを検索...',
  93. ViewOnGreasyfork: 'Greasyforkで見る',
  94. errorMessage: 'スクリプト情報の取得に失敗するか、またはこのドメインには利用可能なスクリプトがありません。',
  95. Loading: '読み込み中...',
  96. Scripts: 'スクリプト'
  97. },
  98. 'vi': {
  99. Author: 'Tác giả',
  100. Installs: 'Số lượt cài đặt',
  101. DailyInstalls: 'Cài đặt hàng ngày',
  102. Created: 'Ngày tạo',
  103. Updated: 'Ngày cập nhật',
  104. Rating: 'Đánh giá',
  105. LoadingScripts: 'Đang tải các tập lệnh...',
  106. LoadMore: 'Tải thêm',
  107. AllScriptsLoaded: 'Đã tải tất cả các tập lệnh',
  108. SearchPlaceholder: 'Tìm kiếm tập lệnh...',
  109. ViewOnGreasyfork: 'Xem trên Greasyfork',
  110. errorMessage: 'Không thể truy xuất thông tin tập lệnh hoặc không có tập lệnh nào có sẵn cho miền này.',
  111. Loading: 'Đang tải...',
  112. Scripts: 'Tập lệnh'
  113. }
  114. }
  115. // 返回翻译函数
  116. return (id, lang = '') => {
  117. const selectedLang = lang || userLang
  118. return (strings[selectedLang] || strings.en)[id] || strings.en[id]
  119. }
  120. }());
  121. (function () {
  122. const domainParts = window.location.hostname.split('.').slice(-2)
  123. const domain = domainParts.join('.')
  124. const errorMessage = translate('errorMessage')
  125. let neverLoadedScripts = true
  126. let collapsed = true
  127. let loadedPages = 0
  128.  
  129. function getScriptsInfo(domain, page = 1) {
  130. var url = `https://greatest.deepsurf.us/scripts/by-site/${domain}?filter_locale=0&sort=updated&page=${page}`
  131.  
  132. GM_xmlhttpRequest({
  133. method: "GET",
  134. url: url,
  135. onload: (response) => {
  136. // 解析结果
  137. const parser = new DOMParser()
  138. const doc = parser.parseFromString(response.responseText, "text/html")
  139. const scripts = doc.querySelector("#browse-script-list")?.querySelectorAll('[data-script-id]')
  140. let scriptsInfo = []
  141.  
  142. if (!scripts) {
  143. scriptsInfo = errorMessage
  144. } else {
  145. for (var i = 0; i < scripts.length; i++) {
  146. scriptsInfo.push(parseScriptInfo(scripts[i]))
  147. }
  148. }
  149.  
  150. // 处理对象
  151. const loadMoreButton = document.querySelector('.load-more')
  152. console.log(doc.querySelector('.next_page'))
  153. if (doc.querySelector('.next_page') == null || doc.querySelector('.next_page')?.getAttribute('aria-disabled') === 'true') {
  154. loadedPages = 'max'
  155. loadMoreButton.disabled = true
  156. loadMoreButton.textContent = translate('AllScriptsLoaded')
  157. } else {
  158. loadMoreButton.disabled = false
  159. loadMoreButton.textContent = translate('LoadMore')
  160. }
  161. // console.log(scriptsInfo);
  162. document.querySelector('.wait-loading').style.display = 'none'
  163. loadMoreButton.style.display = 'block'
  164. appendScriptsInfo(scriptsInfo)
  165. updateMatches()
  166.  
  167. typeof (loadedPages) === 'number' ? loadedPages += 1 : loadedPages = loadedPages
  168. // console.log(loadedPages)
  169. },
  170. onerror: () => {
  171. console.log("Some error occurred!")
  172. if (loadedPages === 0) {
  173. appendScriptsInfo(scriptsInfo)
  174. }
  175. const scriptsInfo = errorMessage
  176. document.querySelector('.wait-loading').style.display = 'none'
  177. }
  178. })
  179. }
  180.  
  181. // 解析脚本信息
  182. function parseScriptInfo(script) {
  183. return {
  184. id: script.getAttribute('data-script-id'),
  185. name: script.getAttribute('data-script-name'),
  186. author: script.querySelector("dd.script-list-author").textContent,
  187. description: script.querySelector(".script-description").textContent,
  188. version: script.getAttribute('data-script-version'),
  189. url: 'https://greatest.deepsurf.us/scripts/' + script.getAttribute('data-script-id'),
  190. createDate: script.getAttribute('data-script-created-date'),
  191. updateDate: script.getAttribute('data-script-updated-date'),
  192. installs: script.getAttribute('data-script-total-installs'),
  193. dailyInstalls: script.getAttribute('data-script-daily-installs'),
  194. ratingScore: script.getAttribute('data-script-rating-score')
  195. }
  196. }
  197.  
  198. // 插入脚本
  199. function appendScriptsInfo(scriptsInfo) {
  200. const infoList = document.querySelector('.info-list')
  201. if (scriptsInfo === errorMessage) {
  202. // infoList.innerHTML = errorMessage;
  203. const loadMoreButton = document.querySelector('.load-more')
  204. loadMoreButton.disabled = true
  205. loadMoreButton.textContent = translate('AllScriptsLoaded')
  206. loadMoreButton.innerHTML = errorMessage
  207. } else {
  208. for (var i = 0; i < scriptsInfo.length; i++) {
  209. var script = scriptsInfo[i]
  210. var listItem = document.createElement('li')
  211. listItem.className = 'info-item'
  212.  
  213. var scriptContainer = document.createElement('div')
  214. scriptContainer.className = 'script-container'
  215.  
  216. var nameElement = document.createElement('a')
  217. nameElement.className = 'mscript-link'
  218. nameElement.innerText = script.name
  219. nameElement.href = script.url
  220. nameElement.target = '_blank'
  221.  
  222. var descriptionElement = document.createElement('p')
  223. descriptionElement.className = 'script-description'
  224. descriptionElement.innerHTML = script.description
  225.  
  226. var detailsContainer = document.createElement('div')
  227. detailsContainer.className = 'details-container'
  228.  
  229. // 创建一键安装按钮
  230. var installButton = document.createElement('a')
  231. installButton.className = 'install-button'
  232. installButton.innerText = `Install ${script.version}`
  233. installButton.href = `https://greatest.deepsurf.us/scripts/${script.id}/code/script.user.js`
  234.  
  235. const details = [
  236. { key: translate('Author'), value: script.author },
  237. { key: translate('Installs'), value: script.installs },
  238. { key: translate('DailyInstalls'), value: script.dailyInstalls },
  239. { key: translate('Created'), value: script.createDate },
  240. { key: translate('Updated'), value: script.updateDate },
  241. { key: translate('Rating'), value: script.ratingScore }
  242. ]
  243.  
  244. for (let i = 0; i < details.length; i++) {
  245. const spanElement = document.createElement('span')
  246. spanElement.className = 'script-details'
  247. spanElement.innerText = `${details[i].key}:\n${details[i].value}`
  248. detailsContainer.appendChild(spanElement)
  249. }
  250.  
  251. scriptContainer.appendChild(nameElement)
  252. scriptContainer.appendChild(descriptionElement)
  253. scriptContainer.appendChild(detailsContainer)
  254. scriptContainer.appendChild(installButton)
  255.  
  256. listItem.appendChild(scriptContainer)
  257. listItem.scriptId = script.id
  258. infoList.appendChild(listItem)
  259. }
  260. }
  261. }
  262.  
  263. function setupUI() {
  264. GM_addStyle(`
  265. scrbutton.script-button {
  266. position: fixed;
  267. bottom: 20%;
  268. right: -50px;
  269. transform: translateY(50%);
  270. padding: 20px;
  271. font-size: 16px;
  272. border: none;
  273. border-radius: 4px;
  274. background-color: #1e90ff;
  275. color: #ffffff;
  276. cursor: pointer;
  277. transition: right 0.3s;
  278. z-index: 9999999999999999;
  279. }
  280. div.info-container {
  281. display: none;
  282. position: fixed;
  283. top: 50%;
  284. left: 50%;
  285. transform: translate(-50%, -50%);
  286. width: 650px;
  287. padding: 12px;
  288. background-color: #ffffff;
  289. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
  290. border-radius: 4px;
  291. opacity: 0;
  292. transition: opacity 0.3s;
  293. z-index: 9999;
  294. max-height: 80vh;
  295. overflow-y: auto;
  296. }
  297. ul.info-list {
  298. list-style: none;
  299. margin: 0;
  300. padding: 0;
  301. }
  302. li.info-item {
  303. margin-bottom: 15px;
  304. padding: 12px;
  305. padding-bottom: 22px;
  306. display: flex;
  307. flex-direction: column;
  308. border: 1px solid #1e90ff;
  309. border-radius: 5px;
  310. }
  311. .div.script-container {
  312. display: flex;
  313. flex-direction: column;
  314. }
  315. a.mscript-link {
  316. font-size: 18px !important;
  317. font-weight: bold !important;
  318. margin-bottom: 5px !important;
  319. color: #1e90ff !important;
  320. }
  321. p.script-description {
  322. color: black !important;
  323. margin-top: 2px;
  324. margin-bottom: 5px;
  325. font-size: 16px;
  326. }
  327. div.details-container {
  328. font-size: 15px;
  329. font-weight: bold;
  330. display: flex;
  331. justify-content: space-between;
  332. margin-bottom: 15px;
  333. }
  334. span.script-details {
  335. font: !important;
  336. color: black !important;
  337. flex-grow: 1 !important;
  338. text-align: center !important;
  339. border: 1px solid #1e90ff !important;
  340. border-radius: 5px !important;
  341. margin: 4px !important;
  342. }
  343. div.table-header {
  344. color: #1e90ff !important;
  345. font-size: 25px;
  346. font-weight: bold;
  347. }
  348. input.script-search-input {
  349. width: 96% !important;
  350. padding: 10px !important;
  351. font-size: 18px !important;
  352. border: 1px solid #1e90ff !important;
  353. border-radius: 4px !important;
  354. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3) !important;
  355. margin-bottom: 15px !important;
  356. margin-top: 20px !important;
  357. }
  358. a.install-button {
  359. font-size: 20px;
  360. background-color: green;
  361. color: white;
  362. padding: 12px;
  363. }
  364. button.to-greasyfork {
  365. position: absolute;
  366. top: 12px;
  367. right: 12px;
  368. border-radius: 4px;
  369. padding: 8px;
  370. font-size: 16px;
  371. border: none;
  372. background-color: #1e90ff;
  373. color: #ffffff;
  374. cursor: pointer;
  375. }
  376. span.match-count {
  377. background-color: #1e90ff;
  378. font-size: 25px;
  379. font-weight: bold;
  380. color: white;
  381. padding: 6px;
  382. position: absolute;
  383. right: 50%;
  384. border-radius: 12px;
  385. top: 10px;
  386. }
  387. div.wait-loading {
  388. font-size: 20px;
  389. font-weight: bold;
  390. color: #1e90ff;
  391. animation: blink 1s infinite;
  392. }
  393. @keyframes fadeInOut {
  394. 0% {
  395. opacity: 0;
  396. }
  397. 50% {
  398. opacity: 1;
  399. }
  400. 100% {
  401. opacity: 0;
  402. }
  403. }
  404. @keyframes blink {
  405. 0%, 100% {
  406. opacity: 0;
  407. }
  408. 50% {
  409. opacity: 1;
  410. }
  411. }
  412. button.load-more {
  413. border-radius: 4px;
  414. padding: 8px;
  415. font-size: 16px;
  416. border: none;
  417. background-color: #1e90ff;
  418. color: #ffffff;
  419. cursor: pointer;
  420. position: relative;
  421. bottom: 5px;
  422. left: 50%;
  423. transform: translateX(-50%);
  424. }
  425. button.load-more:disabled {
  426. background-color: #cccccc;
  427. cursor: not-allowed;
  428. }
  429.  
  430. /* Mobile styles */
  431. @media (max-width: 600px) {
  432. scrbutton.script-button {
  433. right: -30px;
  434. padding: 8px;
  435. font-size: 14px;
  436. }
  437. span.script-details {
  438. font-size: 10px !important;
  439. margin: 2px !important;
  440. padding: 2px !important;
  441. }
  442. span.match-count {
  443. font-size: 20px;
  444. padding: 4px;
  445. }
  446.  
  447. button.to-greasyfork {
  448. padding: 6px;
  449. font-size: 14px;
  450. }
  451.  
  452. a.install-button {
  453. font-size: 12px;
  454. padding: 8px;
  455. }
  456. div.table-header {
  457. font-size: 20px;
  458. }
  459. div.script-container {
  460. padding: 10px;
  461. }
  462. div.info-container {
  463. top: 10%;
  464. left: 5%;
  465. right: 5%;
  466. transform: none;
  467. width: calc(90% - 10px); /* 自适应宽度,保持左右边距 */
  468. max-width: 100%; /* 确保不超出屏幕宽度 */
  469. }
  470. a.script-link {
  471. font-size: 16px !important;
  472. }
  473. p.script-description {
  474. font-size: 14px;
  475. }
  476. {
  477.  
  478. input.script-search-input {
  479. width: 92% !important;
  480. padding: 8px !important;
  481. font-size: 16px !important;
  482. }
  483. span.match-count {
  484. font-size: 20px;
  485. padding: 4px;
  486. }
  487. button.load-more {
  488. font-size: 14px;
  489. padding: 6px;
  490. }
  491. }
  492. `)
  493.  
  494.  
  495. // 创建打开列表按钮
  496. var button = document.createElement('scrbutton')
  497. button.className = 'script-button'
  498. button.innerText = translate('Scripts')
  499. document.addEventListener('fullscreenchange', function () {
  500. if (document.fullscreenElement) {
  501. button.style.display = 'none'
  502. } else {
  503. button.style.display = 'block'
  504. }
  505. })
  506. // 创建脚本容器
  507. var infoContainer = document.createElement('div')
  508. infoContainer.className = 'info-container'
  509.  
  510. // 创建搜索框
  511. var searchInput = document.createElement('input')
  512. searchInput.type = 'text'
  513. searchInput.placeholder = translate('SearchPlaceholder')
  514. searchInput.className = 'script-search-input'
  515.  
  516. // 创建指向greasyfork的链接
  517. var toGreasyfork = document.createElement('button')
  518. toGreasyfork.className = 'to-greasyfork'
  519. toGreasyfork.innerText = translate('ViewOnGreasyfork')
  520.  
  521. // 创建计数器
  522. var matchCount = document.createElement('span')
  523. matchCount.className = 'match-count'
  524.  
  525. // 创建表头
  526. var tableHeader = document.createElement('div')
  527. tableHeader.className = 'table-header'
  528. tableHeader.appendChild(document.createTextNode('Script Finder'))
  529. tableHeader.appendChild(matchCount)
  530. tableHeader.appendChild(searchInput)
  531. tableHeader.appendChild(toGreasyfork)
  532.  
  533. // 创建脚本列表
  534. var infoList = document.createElement('ul')
  535. infoList.className = 'info-list'
  536.  
  537. // 创建等待加载
  538. var waitLoading = document.createElement('div')
  539. waitLoading.className = 'wait-loading'
  540. waitLoading.innerText = translate('LoadingScripts')
  541.  
  542. // 创建加载更多
  543. var loadMore = document.createElement('button')
  544. loadMore.className = 'load-more'
  545. loadMore.innerText = 'Load more'
  546. loadMore.style.display = 'none'
  547.  
  548. infoList.appendChild(waitLoading)
  549. infoList.appendChild(loadMore)
  550.  
  551. infoContainer.appendChild(tableHeader)
  552. infoContainer.appendChild(infoList)
  553.  
  554. var timeout
  555. button.addEventListener('mouseenter', function () {
  556. clearTimeout(timeout)
  557. button.style.right = '10px'
  558. })
  559.  
  560. button.addEventListener('mouseleave', function () {
  561. timeout = setTimeout(function () {
  562. button.style.right = '-50px'
  563. }, 500)
  564. })
  565.  
  566. button.addEventListener('click', function (event) {
  567. event.stopPropagation()
  568. if (collapsed) {
  569. infoContainer.style.display = "block"
  570. infoContainer.style.opacity = 1
  571. collapsed = false
  572. }
  573. else {
  574. infoContainer.style.display = "none"
  575. infoContainer.style.opacity = 0
  576. collapsed = true
  577. }
  578.  
  579. if (neverLoadedScripts) {
  580. getScriptsInfo(domain, 1)
  581. neverLoadedScripts = false
  582. }
  583.  
  584. })
  585.  
  586. infoContainer.addEventListener('click', function (event) {
  587. event.stopPropagation()
  588. })
  589.  
  590. searchInput.addEventListener('input', () => {
  591. searchScript()
  592. updateMatches()
  593. })
  594.  
  595. toGreasyfork.addEventListener('click', function () {
  596. window.open(`https://greatest.deepsurf.us/scripts/by-site/${domain}?q=${searchInput.value}&filter_locale=0&sort=updated`)
  597. })
  598.  
  599. loadMore.addEventListener('click', () => {
  600. if (loadedPages === 'max') {
  601. return
  602. }
  603. const loadMoreButton = document.querySelector('.load-more')
  604. loadMoreButton.disabled = true
  605. loadMoreButton.textContent = translate('Loading')
  606. document.querySelector('.wait-loading').style.display = 'block'
  607. getScriptsInfo(domain, loadedPages + 1)
  608. })
  609.  
  610. document.body.addEventListener('click', function () {
  611. clearTimeout(timeout)
  612. collapsed = true
  613. button.style.right = '-50px'
  614. infoContainer.style.opacity = 0
  615. infoContainer.style.display = "none"
  616. })
  617.  
  618. document.body.appendChild(button)
  619.  
  620. document.body.appendChild(infoContainer)
  621.  
  622. infoContainer.addEventListener('change', () => {
  623. updateMatches()
  624. })
  625. updateMatches()
  626. }
  627.  
  628. function searchScript() {
  629. const searchWord = document.querySelector('.script-search-input').value.toLowerCase() // 将要匹配的文本转换为小写
  630. const scriptList = document.querySelectorAll('.info-item')
  631. for (let i = 0; i < scriptList.length; i++) {
  632. const scriptText = scriptList[i].innerText.toLowerCase() // 将检索的文本转换为小写
  633. if (scriptText.includes(searchWord)) {
  634. scriptList[i].style.display = 'block'
  635. } else {
  636. scriptList[i].style.display = 'none'
  637. }
  638. }
  639. }
  640.  
  641. function updateMatches() {
  642. const matchCount = document.querySelectorAll('.info-item:not([style*="display: none"])').length
  643. const allCount = document.querySelectorAll('.info-item').length
  644. document.querySelector('.match-count').innerText = matchCount === allCount ? matchCount : `${matchCount}/${allCount}`
  645. }
  646.  
  647. function main() {
  648. if (window.self !== window.top) {
  649. // 在iframe中执行时,直接退出
  650. return
  651. }
  652. setupUI()
  653. }
  654.  
  655. main()
  656.  
  657.  
  658. })()