Script Finder

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

当前为 2023-07-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Script Finder
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.2
  5. // @description Script Finder allows you to find userscripts from greasyfork on any website.
  6. // @author shiquda
  7. // @namespace https://github.com/shiquda/shiquda_UserScript
  8. // @supportURL https://github.com/shiquda/shiquda_UserScript/issues
  9. // @match *://*/*
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_addStyle
  12. // @license AGPL-3.0
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. const domainParts = window.location.hostname.split('.').slice(-2);
  17. const domain = domainParts.join('.');
  18. const errorMessage = "Failed to retrieve script information or there are no available scripts for this domain.";
  19.  
  20. function getScriptsInfo(domain) {
  21.  
  22. var url = `https://greasyfork.org/scripts/by-site/${domain}?filter_locale=0`;
  23. GM_xmlhttpRequest({
  24. method: "GET",
  25. url: url,
  26. onload: (response) => {
  27.  
  28. const parser = new DOMParser();
  29. const doc = parser.parseFromString(response.responseText, "text/html");
  30. const scripts = doc.querySelector("#browse-script-list")?.querySelectorAll('[data-script-id]');
  31. let scriptsInfo = [];
  32. if (!scripts) {
  33. scriptsInfo = errorMessage
  34. } else {
  35. for (var i = 0; i < scripts.length; i++) {
  36. scriptsInfo.push(parseScriptInfo(scripts[i]));
  37. }
  38. }
  39. console.log(scriptsInfo);
  40. showScriptsInfo(scriptsInfo); // 显示脚本信息
  41. },
  42. onerror: () => {
  43. console.log("Some error!");
  44. const scriptsInfo = errorMessage
  45. showScriptsInfo(scriptsInfo)
  46. }
  47. });
  48. // oneClickInstall();
  49. }
  50.  
  51. // 解析脚本信息
  52. function parseScriptInfo(script) {
  53. return {
  54. id: script.getAttribute('data-script-id'),
  55. name: script.getAttribute('data-script-name'),
  56. author: script.querySelector("dd.script-list-author").textContent,
  57. description: script.querySelector(".script-description").textContent,
  58. version: script.getAttribute('data-script-version'),
  59. url: 'https://greasyfork.org/scripts/' + script.getAttribute('data-script-id'),
  60. // createDate: script.getAttribute('data-script-created-date'),
  61. // updateDate: script.getAttribute('data-script-updated-date'),
  62. installs: script.getAttribute('data-script-total-installs'),
  63. // dailyInstalls: script.getAttribute('data-script-daily-installs'),
  64. ratingScore: script.getAttribute('data-script-rating-score')
  65. };
  66. }
  67.  
  68. function showScriptsInfo(scriptsInfo) {
  69. GM_addStyle(`
  70. button.script-button {
  71. position: fixed;
  72. bottom: 50%;
  73. right: -50px;
  74. transform: translateY(50%);
  75. padding: 12px;
  76. font-size: 16px;
  77. border: none;
  78. border-radius: 4px;
  79. background-color: #1e90ff;
  80. color: #ffffff;
  81. cursor: pointer;
  82. transition: right 0.3s;
  83. }
  84. div.info-container {
  85. display: none;
  86. position: fixed;
  87. top: 10%;
  88. right: 100px;
  89. width: 600px;
  90. padding: 12px;
  91. background-color: #ffffff;
  92. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
  93. border-radius: 4px;
  94. opacity: 0;
  95. transition: opacity 0.3s;
  96. z-index: 9999;
  97. max-height: 80vh;
  98. overflow-y: auto;
  99. }
  100. ul.info-list {
  101. list-style: none;
  102. margin: 0;
  103. padding: 0;
  104. }
  105.  
  106. li.info-item {
  107. margin-bottom: 10px;
  108. border: 1px solid #ddd;
  109. padding: 10px;
  110. display: flex;
  111. flex-direction: column;
  112. }
  113.  
  114. .div.script-container {
  115. display: flex;
  116. flex-direction: column;
  117. }
  118.  
  119. a.script-link {
  120. font-size: 18px !important;
  121. font-weight: bold !important;
  122. margin-bottom: 5px !important;
  123. color: #1e90ff !important;
  124. }
  125.  
  126. p.script-description {
  127. color: black !important;
  128. margin-top: 2px;
  129. margin-bottom: 5px;
  130. }
  131.  
  132. div.details-container {
  133. font-size: 18px ;
  134. font-weight: bold;
  135. display: flex;
  136. justify-content: space-between;
  137. margin-bottom: 15px;
  138. }
  139.  
  140. span.script-details {
  141. color: black !important;
  142. flex-grow: 1;
  143. text-align: left;
  144. }
  145. div.table-header {
  146. color: #1e90ff !important;
  147. font-size: 25px;
  148. }
  149.  
  150. input.script-search-input {
  151. width: 96% !important;
  152. padding: 10px !important;
  153. font-size: 18px !important;
  154. border: 1px solid #ddd !important;
  155. border-radius: 4px !important;
  156. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3) !important;
  157. margin-bottom: 15px !important;
  158. margin-top: 20px !important;
  159. }
  160. a.install-button {
  161. font-size: 25px;
  162. background-color: green;
  163. color: white;
  164. padding: 10px;
  165. }
  166. button.to-greasyfork {
  167. position: absolute;
  168. top: 10px;
  169. right: 10px;
  170. border-radius: 4px;
  171. padding: 8px;
  172. font-size: 16px;
  173. border: none;
  174. background-color: #1e90ff;
  175. color: #ffffff;
  176. cursor: pointer;
  177. }
  178. `);
  179.  
  180.  
  181. // 创建打开列表按钮
  182. var button = document.createElement('button');
  183. button.className = 'script-button';
  184. button.innerText = 'Scripts';
  185.  
  186. // 创建脚本容器
  187. var infoContainer = document.createElement('div');
  188. infoContainer.className = 'info-container';
  189.  
  190. // 创建搜索框
  191. var searchInput = document.createElement('input');
  192. searchInput.type = 'text';
  193. searchInput.placeholder = 'Search scripts...';
  194. searchInput.className = 'script-search-input';
  195.  
  196. // 创建指向greasyfork的链接
  197.  
  198. var toGreasyfork = document.createElement('button');
  199. toGreasyfork.className = 'to-greasyfork';
  200. toGreasyfork.innerText = 'View on Greasyfork';
  201.  
  202. // 创建表头
  203. var tableHeader = document.createElement('div');
  204. tableHeader.className = 'table-header';
  205. tableHeader.appendChild(document.createTextNode('Script Finder'));
  206. tableHeader.appendChild(searchInput);
  207. tableHeader.appendChild(toGreasyfork);
  208.  
  209. // 创建脚本列表
  210. var infoList = document.createElement('ul');
  211. infoList.className = 'info-list';
  212.  
  213. // 插入脚本
  214. if (scriptsInfo === errorMessage) {
  215. infoList.innerHTML = errorMessage;
  216. } else {
  217. for (var i = 0; i < scriptsInfo.length; i++) {
  218. var script = scriptsInfo[i];
  219. var listItem = document.createElement('li');
  220. listItem.className = 'info-item';
  221.  
  222. var scriptContainer = document.createElement('div');
  223. scriptContainer.className = 'script-container';
  224.  
  225. var nameElement = document.createElement('a');
  226. nameElement.className = 'script-link';
  227. nameElement.innerText = script.name;
  228. nameElement.href = script.url;
  229. nameElement.target = '_blank';
  230.  
  231. var descriptionElement = document.createElement('p');
  232. descriptionElement.className = 'script-description';
  233. descriptionElement.innerHTML = script.description;
  234.  
  235. var detailsContainer = document.createElement('div');
  236. detailsContainer.className = 'details-container';
  237.  
  238. // 创建一键安装按钮
  239. var installButton = document.createElement('a');
  240. installButton.className = 'install-button';
  241. installButton.innerText = `Install ${script.version}`;
  242. installButton.href = `https://greasyfork.org/scripts/${script.id}/code/script.user.js`;
  243.  
  244. var authorElement = document.createElement('span');
  245. authorElement.className = 'script-details';
  246. authorElement.innerText = 'Author: ' + script.author;
  247.  
  248. var installsElement = document.createElement('span');
  249. installsElement.className = 'script-details';
  250. installsElement.innerText = 'Installs: ' + script.installs;
  251.  
  252. var ratingElement = document.createElement('span');
  253. ratingElement.className = 'script-details';
  254. ratingElement.innerText = 'Rating: ' + script.ratingScore;
  255.  
  256. detailsContainer.appendChild(authorElement);
  257. detailsContainer.appendChild(installsElement);
  258. detailsContainer.appendChild(ratingElement);
  259.  
  260. scriptContainer.appendChild(nameElement);
  261. scriptContainer.appendChild(descriptionElement);
  262. scriptContainer.appendChild(detailsContainer);
  263. scriptContainer.appendChild(installButton);
  264.  
  265. listItem.appendChild(scriptContainer);
  266. listItem.scriptId = script.id;
  267. infoList.appendChild(listItem);
  268. }
  269. }
  270.  
  271. infoContainer.appendChild(tableHeader)
  272.  
  273. infoContainer.appendChild(infoList);
  274.  
  275. var timeout;
  276. button.addEventListener('mouseenter', function () {
  277. clearTimeout(timeout);
  278. button.style.right = '10px';
  279. });
  280.  
  281. button.addEventListener('mouseleave', function () {
  282. timeout = setTimeout(function () {
  283. button.style.right = '-50px';
  284. }, 500);
  285. });
  286.  
  287. button.addEventListener('click', function (event) {
  288. event.stopPropagation();
  289. button.style.right = '10px';
  290. infoContainer.style.display = "block"
  291. infoContainer.style.opacity = 1
  292. });
  293.  
  294. infoContainer.addEventListener('click', function (event) {
  295. event.stopPropagation();
  296. });
  297.  
  298. searchInput.addEventListener('input', () => {
  299. searchScript()
  300. })
  301.  
  302. toGreasyfork.addEventListener('click', function () {
  303. window.open(`https://greasyfork.org/scripts/by-site/${domain}?q=${searchInput.value}`)
  304. })
  305. document.body.addEventListener('click', function () {
  306. clearTimeout(timeout);
  307. button.style.right = '-50px';
  308. infoContainer.style.opacity = 0
  309. infoContainer.style.display = "none"
  310. });
  311.  
  312. document.body.appendChild(button);
  313.  
  314. document.body.appendChild(infoContainer);
  315. }
  316.  
  317. function searchScript() {
  318. const searchWord = document.querySelector('.script-search-input').value;
  319. const scriptList = document.querySelectorAll('.info-item')
  320. for (let i = 0; i < scriptList.length; i++) {
  321. if (scriptList[i].innerText.includes(searchWord)) {
  322. scriptList[i].style.display = 'block'
  323. } else {
  324. scriptList[i].style.display = 'none'
  325. }
  326. }
  327. }
  328.  
  329.  
  330. // 一键安装 此处参考tampermonkey versioncheck.js 但是不知道为什么别的网站没有这个对象
  331. /*
  332. function oneClickInstall() {
  333. const scriptList = document.querySelectorAll('.script-container');
  334. // 获取tamppermonkey脚本信息
  335. let tm = window.external?.Tampermonkey
  336. let vm = window.external?.ViolentMonkey
  337. function getInstalledVersion(name, namespace) {
  338. return new Promise(function (resolve, reject) {
  339. if (tm) {
  340. tm.isInstalled(name, namespace, function (i) {
  341. if (i.installed) {
  342. resolve(i.version);
  343. } else {
  344. resolve(null);
  345. }
  346. });
  347. return;
  348. }
  349. if (vm) {
  350. vm.isInstalled(name, namespace).then(resolve);
  351. return;
  352. };
  353. reject()
  354. });
  355. }
  356. // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version/format
  357. function compareVersions(a, b) {
  358. if (a == b) {
  359. return 0;
  360. }
  361. let aParts = a.split('.');
  362. let bParts = b.split('.');
  363. for (let i = 0; i < aParts.length; i++) {
  364. let result = compareVersionPart(aParts[i], bParts[i]);
  365. if (result != 0) {
  366. return result;
  367. }
  368. }
  369. // If all of a's parts are the same as b's parts, but b has additional parts, b is greater.
  370. if (bParts.length > aParts.length) {
  371. return -1;
  372. }
  373. return 0;
  374. }
  375. function compareVersionPart(partA, partB) {
  376. let partAParts = parseVersionPart(partA);
  377. let partBParts = parseVersionPart(partB);
  378. for (let i = 0; i < partAParts.length; i++) {
  379. // "A string-part that exists is always less than a string-part that doesn't exist"
  380. if (partAParts[i].length > 0 && partBParts[i].length == 0) {
  381. return -1;
  382. }
  383. if (partAParts[i].length == 0 && partBParts[i].length > 0) {
  384. return 1;
  385. }
  386. if (partAParts[i] > partBParts[i]) {
  387. return 1;
  388. }
  389. if (partAParts[i] < partBParts[i]) {
  390. return -1;
  391. }
  392. }
  393. return 0;
  394. }
  395. // It goes number, string, number, string. If it doesn't exist, then
  396. // 0 for numbers, empty string for strings.
  397. function parseVersionPart(part) {
  398. if (!part) {
  399. return [0, "", 0, ""];
  400. }
  401. let partParts = /([0-9]*)([^0-9]*)([0-9]*)([^0-9]*)/.exec(part)
  402. return [
  403. partParts[1] ? parseInt(partParts[1]) : 0,
  404. partParts[2],
  405. partParts[3] ? parseInt(partParts[3]) : 0,
  406. partParts[4]
  407. ];
  408. }
  409. function handleInstallResult(installedVersion, version) {
  410. if (installedVersion == null) {
  411. // Not installed
  412. return `Install { version } `;
  413. }
  414. // installButton.removeAttribute("data-ping-url")
  415. switch (compareVersions(installedVersion, version)) {
  416. // Upgrade
  417. case -1:
  418. return `Upgrade ${version} `;
  419. break;
  420. // Downgrade
  421. case 1:
  422. return `Downgrade ${version} `;
  423. break;
  424. // Equal
  425. case 0:
  426. return `Reinstall ${version} `;
  427. break;
  428. }
  429. }
  430. for (let i = 0; i < scriptList.length; i++) {
  431. let script = scriptList[i];
  432. let id = script.scriptId
  433. GM_xmlhttpRequest({
  434. url: `https://greasyfork.org/scripts/${id}.json`,
  435. method: "GET",
  436. onload: (response) => {
  437. const data = JSON.parse(response.responseText)
  438. let latestVersion = data.version
  439. let namespace = data.namespace
  440. let installedVersion = getInstalledVersion(id, namespace)
  441. let versionInfo = handleInstallResult(installedVersion, latestVersion)
  442. script.querySelector('.install-button').innerText = versionInfo
  443. },
  444. onerror: (error) => {
  445. console.log(`An error occured when fetching script ${id}: ${error}`)
  446. }
  447. })
  448. }
  449. }
  450. */
  451.  
  452. getScriptsInfo(domain);
  453.  
  454. })();