Script Finder 油猴脚本查找

Script Finder 在任何网站上找到适用于该网站的greasyfork油猴脚本

目前为 2024-08-09 提交的版本。查看 最新版本

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