JetBra

添加一个按钮,点击获取插件的激活码

当前为 2023-11-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name JetBra
  3. // @namespace https://github.com/novice88/jetbra
  4. // @version 0.2
  5. // @license MIT
  6. // @description 添加一个按钮,点击获取插件的激活码
  7. // @author novice.li
  8. // @match https://plugins.jetbrains.com/plugin/*
  9. // @grant GM_setClipboard
  10. // @grant GM_addStyle
  11. // @grant GM_xmlhttpRequest
  12. // @connect noviceli.win
  13. // @connect self
  14. // @connect localhost
  15. // ==/UserScript==
  16.  
  17. var elmGetter = function() {
  18. const win = window.unsafeWindow || document.defaultView || window;
  19. const doc = win.document;
  20. const listeners = new WeakMap();
  21. let mode = 'css';
  22. let $;
  23. const elProto = win.Element.prototype;
  24. const matches = elProto.matches ||
  25. elProto.matchesSelector ||
  26. elProto.webkitMatchesSelector ||
  27. elProto.mozMatchesSelector ||
  28. elProto.oMatchesSelector;
  29. const MutationObs = win.MutationObserver ||
  30. win.WebkitMutationObserver ||
  31. win.MozMutationObserver;
  32. function addObserver(target, callback) {
  33. const observer = new MutationObs(mutations => {
  34. for (const mutation of mutations) {
  35. if (mutation.type === 'attributes') {
  36. callback(mutation.target);
  37. if (observer.canceled) return;
  38. }
  39. for (const node of mutation.addedNodes) {
  40. if (node instanceof Element) callback(node);
  41. if (observer.canceled) return;
  42. }
  43. }
  44. });
  45. observer.canceled = false;
  46. observer.observe(target, {childList: true, subtree: true, attributes: true});
  47. return () => {
  48. observer.canceled = true;
  49. observer.disconnect();
  50. };
  51. }
  52. function addFilter(target, filter) {
  53. let listener = listeners.get(target);
  54. if (!listener) {
  55. listener = {
  56. filters: new Set(),
  57. remove: addObserver(target, el => listener.filters.forEach(f => f(el)))
  58. };
  59. listeners.set(target, listener);
  60. }
  61. listener.filters.add(filter);
  62. }
  63. function removeFilter(target, filter) {
  64. const listener = listeners.get(target);
  65. if (!listener) return;
  66. listener.filters.delete(filter);
  67. if (!listener.filters.size) {
  68. listener.remove();
  69. listeners.delete(target);
  70. }
  71. }
  72. function query(all, selector, parent, includeParent, curMode) {
  73. switch (curMode) {
  74. case 'css':
  75. const checkParent = includeParent && matches.call(parent, selector);
  76. if (all) {
  77. const queryAll = parent.querySelectorAll(selector);
  78. return checkParent ? [parent, ...queryAll] : [...queryAll];
  79. }
  80. return checkParent ? parent : parent.querySelector(selector);
  81. case 'jquery':
  82. let jNodes = $(includeParent ? parent : []);
  83. jNodes = jNodes.add([...parent.querySelectorAll('*')]).filter(selector);
  84. if (all) return $.map(jNodes, el => $(el));
  85. return jNodes.length ? $(jNodes.get(0)) : null;
  86. case 'xpath':
  87. const ownerDoc = parent.ownerDocument || parent;
  88. selector += '/self::*';
  89. if (all) {
  90. const xPathResult = ownerDoc.evaluate(selector, parent, null, 7, null);
  91. const result = [];
  92. for (let i = 0; i < xPathResult.snapshotLength; i++) {
  93. result.push(xPathResult.snapshotItem(i));
  94. }
  95. return result;
  96. }
  97. return ownerDoc.evaluate(selector, parent, null, 9, null).singleNodeValue;
  98. }
  99. }
  100. function isJquery(jq) {
  101. return jq && jq.fn && typeof jq.fn.jquery === 'string';
  102. }
  103. function getOne(selector, parent, timeout) {
  104. const curMode = mode;
  105. return new Promise(resolve => {
  106. const node = query(false, selector, parent, false, curMode);
  107. if (node) return resolve(node);
  108. let timer;
  109. const filter = el => {
  110. const node = query(false, selector, el, true, curMode);
  111. if (node) {
  112. removeFilter(parent, filter);
  113. timer && clearTimeout(timer);
  114. resolve(node);
  115. }
  116. };
  117. addFilter(parent, filter);
  118. if (timeout > 0) {
  119. timer = setTimeout(() => {
  120. removeFilter(parent, filter);
  121. resolve(null);
  122. }, timeout);
  123. }
  124. });
  125. }
  126. return {
  127. get currentSelector() {
  128. return mode;
  129. },
  130. get(selector, ...args) {
  131. let parent = typeof args[0] !== 'number' && args.shift() || doc;
  132. if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
  133. const timeout = args[0] || 0;
  134. if (Array.isArray(selector)) {
  135. return Promise.all(selector.map(s => getOne(s, parent, timeout)));
  136. }
  137. return getOne(selector, parent, timeout);
  138. },
  139. each(selector, ...args) {
  140. let parent = typeof args[0] !== 'function' && args.shift() || doc;
  141. if (mode === 'jquery' && parent instanceof $) parent = parent.get(0);
  142. const callback = args[0];
  143. const curMode = mode;
  144. const refs = new WeakSet();
  145. for (const node of query(true, selector, parent, false, curMode)) {
  146. refs.add(curMode === 'jquery' ? node.get(0) : node);
  147. if (callback(node, false) === false) return;
  148. }
  149. const filter = el => {
  150. for (const node of query(true, selector, el, true, curMode)) {
  151. const _el = curMode === 'jquery' ? node.get(0) : node;
  152. if (refs.has(_el)) break;
  153. refs.add(_el);
  154. if (callback(node, true) === false) {
  155. return removeFilter(parent, filter);
  156. }
  157. }
  158. };
  159. addFilter(parent, filter);
  160. },
  161. create(domString, ...args) {
  162. const returnList = typeof args[0] === 'boolean' && args.shift();
  163. const parent = args[0];
  164. const template = doc.createElement('template');
  165. template.innerHTML = domString;
  166. const node = template.content.firstElementChild;
  167. if (!node) return null;
  168. parent ? parent.appendChild(node) : node.remove();
  169. if (returnList) {
  170. const list = {};
  171. node.querySelectorAll('[id]').forEach(el => list[el.id] = el);
  172. list[0] = node;
  173. return list;
  174. }
  175. return node;
  176. },
  177. selector(desc) {
  178. switch (true) {
  179. case isJquery(desc):
  180. $ = desc;
  181. return mode = 'jquery';
  182. case !desc || typeof desc.toLowerCase !== 'function':
  183. return mode = 'css';
  184. case desc.toLowerCase() === 'jquery':
  185. for (const jq of [window.jQuery, window.$, win.jQuery, win.$]) {
  186. if (isJquery(jq)) {
  187. $ = jq;
  188. break;
  189. };
  190. }
  191. return mode = $ ? 'jquery' : 'css';
  192. case desc.toLowerCase() === 'xpath':
  193. return mode = 'xpath';
  194. default:
  195. return mode = 'css';
  196. }
  197. }
  198. };
  199. }();
  200.  
  201. (async function () {
  202. 'use strict';
  203. GM_addStyle(`
  204. .jetbra-button {
  205. background-color: #04AA6D;
  206. border: none;
  207. color: white;
  208. padding: 8px 24px;
  209. text-align: center;
  210. text-decoration: none;
  211. display: inline-block;
  212. border-radius: 16px;
  213. box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
  214. transition-duration: 0.4s;
  215. }
  216.  
  217. .jetbra-button:hover {
  218. background-color: #057e47;
  219. color: white;
  220. }
  221. `);
  222. const backendBaseUrl = 'https://jetbra.noviceli.win'
  223. const metaTag = document.querySelector('meta[name="pluginId"]')
  224. if (!metaTag) {
  225. return
  226. }
  227. const pluginId = metaTag.getAttribute('content')
  228.  
  229. let pluginDetail = await fetch('https://plugins.jetbrains.com/api/plugins/' + pluginId).then(r => r.json());
  230. const parentElement = await elmGetter.get('.plugin-header__controls-panel > div:first-child');
  231.  
  232. let newElement = document.createElement('div');
  233. newElement.classList.toggle('wt-col-inline');
  234. newElement.innerHTML = `<button class="jetbra-button" type="button">点击生成激活码</button>`;
  235. parentElement.appendChild(newElement)
  236.  
  237.  
  238. newElement.addEventListener('click', async () => {
  239. if (pluginDetail.purchaseInfo === undefined) {
  240. window.alert('此插件不是付费插件');
  241. return;
  242. }
  243. let data = {
  244. "licenseeName": "Test",
  245. "assigneeName": "novice.li",
  246. "assigneeEmail": "",
  247. "licenseRestriction": "",
  248. "checkConcurrentUse": false,
  249. "products": [{
  250. "code": pluginDetail.purchaseInfo.productCode,
  251. "fallbackDate": "2099-12-30",
  252. "paidUpTo": "2099-12-30",
  253. "extended": false
  254. }],
  255. "metadata": "0120230102PPAA013009",
  256. "hash": "41472961/0:1563609451",
  257. "gracePeriodDays": 7,
  258. "autoProlongated": true,
  259. "isAutoProlongated": true
  260. }
  261. GM_xmlhttpRequest({
  262. method: 'POST',
  263. url: backendBaseUrl + '/generateLicense',
  264. headers: {
  265. 'Content-Type': 'application/json'
  266. },
  267. data: JSON.stringify(data),
  268. onload: function (response) {
  269. let license = JSON.parse(response.responseText).license
  270. GM_setClipboard(license, 'text');
  271. window.alert('激活码已复制到剪切版');
  272. }
  273. });
  274.  
  275. })
  276. })();