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.1
  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. `);
  167.  
  168.  
  169. // 创建按钮
  170. var button = document.createElement('button');
  171. button.className = 'script-button';
  172. button.innerText = 'Scripts';
  173.  
  174. // 创建脚本容器
  175. var infoContainer = document.createElement('div');
  176. infoContainer.className = 'info-container';
  177.  
  178. // 创建搜索框
  179. var searchInput = document.createElement('input');
  180. searchInput.type = 'text';
  181. searchInput.placeholder = 'Search scripts...';
  182. searchInput.className = 'script-search-input';
  183.  
  184. // 创建表头
  185. var tableHeader = document.createElement('div');
  186. tableHeader.className = 'table-header';
  187. tableHeader.appendChild(document.createTextNode('Script Finder'));
  188. tableHeader.appendChild(searchInput);
  189.  
  190. // 创建脚本列表
  191. var infoList = document.createElement('ul');
  192. infoList.className = 'info-list';
  193.  
  194. // 插入脚本
  195. if (scriptsInfo === errorMessage) {
  196. infoList.innerHTML = errorMessage;
  197. } else {
  198. for (var i = 0; i < scriptsInfo.length; i++) {
  199. var script = scriptsInfo[i];
  200. var listItem = document.createElement('li');
  201. listItem.className = 'info-item';
  202.  
  203. var scriptContainer = document.createElement('div');
  204. scriptContainer.className = 'script-container';
  205.  
  206. var nameElement = document.createElement('a');
  207. nameElement.className = 'script-link';
  208. nameElement.innerText = script.name;
  209. nameElement.href = script.url;
  210. nameElement.target = '_blank';
  211.  
  212. var descriptionElement = document.createElement('p');
  213. descriptionElement.className = 'script-description';
  214. descriptionElement.innerHTML = script.description;
  215.  
  216. var detailsContainer = document.createElement('div');
  217. detailsContainer.className = 'details-container';
  218.  
  219. // 创建一键安装按钮
  220. var installButton = document.createElement('a');
  221. installButton.className = 'install-button';
  222. installButton.innerText = `Install ${script.version}`;
  223. installButton.href = `https://greasyfork.org/scripts/${script.id}/code/script.user.js`;
  224.  
  225. var authorElement = document.createElement('span');
  226. authorElement.className = 'script-details';
  227. authorElement.innerText = 'Author: ' + script.author;
  228.  
  229. var installsElement = document.createElement('span');
  230. installsElement.className = 'script-details';
  231. installsElement.innerText = 'Installs: ' + script.installs;
  232.  
  233. var ratingElement = document.createElement('span');
  234. ratingElement.className = 'script-details';
  235. ratingElement.innerText = 'Rating: ' + script.ratingScore;
  236.  
  237. detailsContainer.appendChild(authorElement);
  238. detailsContainer.appendChild(installsElement);
  239. detailsContainer.appendChild(ratingElement);
  240.  
  241. scriptContainer.appendChild(nameElement);
  242. scriptContainer.appendChild(descriptionElement);
  243. scriptContainer.appendChild(detailsContainer);
  244. scriptContainer.appendChild(installButton);
  245.  
  246. listItem.appendChild(scriptContainer);
  247. listItem.scriptId = script.id;
  248. infoList.appendChild(listItem);
  249. }
  250. }
  251.  
  252. infoContainer.appendChild(tableHeader)
  253. infoContainer.appendChild(infoList);
  254.  
  255. var timeout;
  256. button.addEventListener('mouseenter', function () {
  257. clearTimeout(timeout);
  258. button.style.right = '10px';
  259. });
  260.  
  261. button.addEventListener('mouseleave', function () {
  262. timeout = setTimeout(function () {
  263. button.style.right = '-50px';
  264. }, 500);
  265. });
  266.  
  267. button.addEventListener('click', function (event) {
  268. event.stopPropagation();
  269. button.style.right = '10px';
  270. infoContainer.style.display = "block"
  271. infoContainer.style.opacity = 1
  272. });
  273.  
  274.  
  275.  
  276. infoContainer.addEventListener('click', function (event) {
  277. event.stopPropagation();
  278. });
  279.  
  280. searchInput.addEventListener('input', () => {
  281. searchScript()
  282. })
  283.  
  284. document.body.addEventListener('click', function () {
  285. clearTimeout(timeout);
  286. button.style.right = '-50px';
  287. infoContainer.style.opacity = 0
  288. infoContainer.style.display = "none"
  289. });
  290.  
  291. document.body.appendChild(button);
  292.  
  293. document.body.appendChild(infoContainer);
  294. }
  295.  
  296. function searchScript() {
  297. const searchWord = document.querySelector('.script-search-input').value;
  298. const scriptList = document.querySelectorAll('.info-item')
  299. for (let i = 0; i < scriptList.length; i++) {
  300. if (scriptList[i].innerText.includes(searchWord)) {
  301. scriptList[i].style.display = 'block'
  302. } else {
  303. scriptList[i].style.display = 'none'
  304. }
  305. }
  306. }
  307.  
  308.  
  309. // 一键安装 此处参考tampermonkey versioncheck.js 但是不知道为什么别的网站没有这个对象
  310. /*
  311. function oneClickInstall() {
  312. const scriptList = document.querySelectorAll('.script-container');
  313. // 获取tamppermonkey脚本信息
  314. let tm = window.external?.Tampermonkey
  315. let vm = window.external?.ViolentMonkey
  316. function getInstalledVersion(name, namespace) {
  317. return new Promise(function (resolve, reject) {
  318. if (tm) {
  319. tm.isInstalled(name, namespace, function (i) {
  320. if (i.installed) {
  321. resolve(i.version);
  322. } else {
  323. resolve(null);
  324. }
  325. });
  326. return;
  327. }
  328. if (vm) {
  329. vm.isInstalled(name, namespace).then(resolve);
  330. return;
  331. };
  332. reject()
  333. });
  334. }
  335. // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version/format
  336. function compareVersions(a, b) {
  337. if (a == b) {
  338. return 0;
  339. }
  340. let aParts = a.split('.');
  341. let bParts = b.split('.');
  342. for (let i = 0; i < aParts.length; i++) {
  343. let result = compareVersionPart(aParts[i], bParts[i]);
  344. if (result != 0) {
  345. return result;
  346. }
  347. }
  348. // If all of a's parts are the same as b's parts, but b has additional parts, b is greater.
  349. if (bParts.length > aParts.length) {
  350. return -1;
  351. }
  352. return 0;
  353. }
  354. function compareVersionPart(partA, partB) {
  355. let partAParts = parseVersionPart(partA);
  356. let partBParts = parseVersionPart(partB);
  357. for (let i = 0; i < partAParts.length; i++) {
  358. // "A string-part that exists is always less than a string-part that doesn't exist"
  359. if (partAParts[i].length > 0 && partBParts[i].length == 0) {
  360. return -1;
  361. }
  362. if (partAParts[i].length == 0 && partBParts[i].length > 0) {
  363. return 1;
  364. }
  365. if (partAParts[i] > partBParts[i]) {
  366. return 1;
  367. }
  368. if (partAParts[i] < partBParts[i]) {
  369. return -1;
  370. }
  371. }
  372. return 0;
  373. }
  374. // It goes number, string, number, string. If it doesn't exist, then
  375. // 0 for numbers, empty string for strings.
  376. function parseVersionPart(part) {
  377. if (!part) {
  378. return [0, "", 0, ""];
  379. }
  380. let partParts = /([0-9]*)([^0-9]*)([0-9]*)([^0-9]*)/.exec(part)
  381. return [
  382. partParts[1] ? parseInt(partParts[1]) : 0,
  383. partParts[2],
  384. partParts[3] ? parseInt(partParts[3]) : 0,
  385. partParts[4]
  386. ];
  387. }
  388. function handleInstallResult(installedVersion, version) {
  389. if (installedVersion == null) {
  390. // Not installed
  391. return `Install { version } `;
  392. }
  393. // installButton.removeAttribute("data-ping-url")
  394. switch (compareVersions(installedVersion, version)) {
  395. // Upgrade
  396. case -1:
  397. return `Upgrade ${version} `;
  398. break;
  399. // Downgrade
  400. case 1:
  401. return `Downgrade ${version} `;
  402. break;
  403. // Equal
  404. case 0:
  405. return `Reinstall ${version} `;
  406. break;
  407. }
  408. }
  409. for (let i = 0; i < scriptList.length; i++) {
  410. let script = scriptList[i];
  411. let id = script.scriptId
  412. GM_xmlhttpRequest({
  413. url: `https://greasyfork.org/scripts/${id}.json`,
  414. method: "GET",
  415. onload: (response) => {
  416. const data = JSON.parse(response.responseText)
  417. let latestVersion = data.version
  418. let namespace = data.namespace
  419. let installedVersion = getInstalledVersion(id, namespace)
  420. let versionInfo = handleInstallResult(installedVersion, latestVersion)
  421. script.querySelector('.install-button').innerText = versionInfo
  422. },
  423. onerror: (error) => {
  424. console.log(`An error occured when fetching script ${id}: ${error}`)
  425. }
  426. })
  427. }
  428. }
  429. */
  430.  
  431. getScriptsInfo(domain);
  432.  
  433. })();