JetBra

Add a button on the plugin homepage and click to get the plugin activation code

  1. // ==UserScript==
  2. // @name JetBra
  3. // @namespace https://github.com/novice88/jetbra
  4. // @version 5.0
  5. // @license MIT
  6. // @description Add a button on the plugin homepage and click to get the plugin activation code
  7. // @author novice.li
  8. // @match https://plugins.jetbrains.com/*
  9. // @grant GM_setClipboard
  10. // @grant GM_addStyle
  11. // @grant window.onurlchange
  12. // ==/UserScript==
  13.  
  14. const pemEncodedKey = `-----BEGIN PRIVATE KEY-----
  15. MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC3p6nBj9mcRpGK
  16. pigPXOB83/PmA9bJr5jsSo3fm5ky67rTP4V79XI9a1t/5asg7XQ5OyulvP0w6tQk
  17. axLfg6Opd9A8YQIgt+Gh/A5hsIKu+8RKC4prx+S6Xj8X5RfrWwdUWbRYBQziGC3U
  18. kGihR9iQ4FSsYS4ld0uo54j4ZArVlq07PhOr6uDdeQZtzZzOQCSC6o7VGzozX2sV
  19. aukazqE3NEdxaqqOsr8aP/iWGtlJxyAvq9nWyrgyzFK7YJ8nRFHSTV9Mx/RbXHRC
  20. 76+PLnPZmNN/E1lLGVCtaZ0G8QNmz8gOKp2CfSL1IDui7S17xhZtd+2EDEtTeNQB
  21. wwTq8KDSPFKA1/qiN6zPem4hThb5+xHZMu6wcs8m7dx/s8XaI3476S9RNDTvfAU/
  22. c37nxwGgMWbZZgzruSwyXtwhrq58kTERMW7XPkI5dZlIerRuJWpAMHbKMa8tBnRu
  23. 9smmMm/Yred5GhZLP0/7O2e20Sc1Rc0A1dWOG81LZON7yptr87QVZUJAZGWOX9iW
  24. 3uSIN2/LMEMwZzk9Jqy+Uj1IcJkOiMZBFs7Y+eThLowTJka/dqBErqvWWDGni4nS
  25. llUd//vhwPAUWWkRhCrUh2QmxRBXYoQ8cSb/V4ejbk/3sCeh2KftDUKRZ1Jye9p/
  26. MFfrbnaWpu2inHc1Zs+3DcDoRti7OwIDAQABAoICABM991zG6BtmD2ix+P+HESQ0
  27. SLcgTthJ1CFpvEyh3l7F8QiiHqe6szH5NhiD5TapemRmrS+LyhFegUShjVQq1DJ0
  28. 0bYJyfHIolTY9l7I4iBYU5wYcnPReUcHid/EiomHu5BcZ7dTLCLiOqcLTFMdlnSz
  29. dFutQOr/AUfcnm67+KChTVwoKGJ6VP4PaJuHj/bSJKEs1zM/y4zHYg5X6b17ycth
  30. aFzbOqyB0OD8s9xySrLesKIeBNBq4/q6iq6ENJimIVaB8cq3JoSN/sZmm4PKb6vs
  31. RbiKO/BQ4jGRH7ky9lLG0WSelWsvFkMNkgIDjKDrw7zLdHDB4wCHZ9sZZkIBXTAL
  32. 6ktSBFq2IMuyn3C6hhbYWHADOo7x/RStk88/sGF39TYSsK+76QuRQ8SBdvzlHS+e
  33. CNCJMIhZUHSUCn10mo17V6MDV/lXMuajSLlxzSsKxjzxFQxswIMCtEaxFMvqokZw
  34. pyZdsYs5aZGAaRQ0fBbCsVAR1neki5Z7hhChBFOf5DuMbU3djD6/efoqhyhscruD
  35. Vb8r2bslL244830ZhX3yJRKiyxvKNPvquuzORIG7BHi9kucU60zMrXZ8tGC2W07+
  36. KtPKxTc6SVe5QiPDUsi5okyM0qQJ/5oLxNaD9vOV2wCIKmfKotgC42svITDNLkvJ
  37. 6nLFYUELHQcqVHlsmwW1AoIBAQD50eyXAk4FwlBxWDO8zJush7J3pRMPndWMDoL1
  38. 5XEPCzENEz8FZoBCNCup6CSUSL8WjnOVrMyBRQgWfZhB7u2T3NBRsMLt66RCu2L5
  39. BjZHRi6F3nuIS9Xfs7CNi9D2tcI/FT7xPe3AEgJKuGIKaySUip1Q+dAAh3XyPlco
  40. 1EPYpPJAY+FnYNStcBHLXa2v+v212GCMGB9WLhegBWt+FWjn/tMyqJifUIQy7M7k
  41. 5dCLO6kmo0VPWMngspUPcX77JtJLOA/JgeBAO91uDJMWHelPS2zIkPZb3pG8L+yz
  42. K33Ry+YY0SUqzBLFfQmD6HWz7sivcv2aCHD0PcY8GcCQxAJNAoIBAQC8Mrl8w7vE
  43. LMDvbiAMoK6iYJr0FbFGoiDfJhLU5MkIRRv5439qtXmF3EVFcQTsXf6km0U/YaYq
  44. /e4b44YCjIQDiD/LJPjZThHiCyYduNl9RUeVYbAubtBK7MJ2KQxNVfG2XOYJmxg1
  45. j5/McX5v5JA+bTdtRp0OH0OYPiA/ilM2+Gp1m/qOD85OS+Z1Np+jNQ0UXY7LYZP3
  46. NbFdBRnil1obeZKxqOxdAuate+cioKrvHRvbHLF6GNWde9+f8q+2cfNZijyJwE6R
  47. vURwDCwdNUaPCTtc7s9NSP2WNHaOM4pkmlu6mgZl2PLzZimUCeev8EovGH3VAMl3
  48. i2ytNEJ+56enAoIBAQDGbZaFj6AXdPNeRBe8M6zHCnWYEPcl5VEUUQZ2eAsoTtRk
  49. NVBOYs8nRrcT2r8LRQj6yqVGUp2RZBp7esDwRe5RDwgsisEaJ5wuIRcJA4UjcbxM
  50. Op5WcR3s9JYcp9yPyWkDoEWBapYohGVroi7FZbsFfWBdTD+J3A60Hg4u8QL+1m1Q
  51. 9cS4zzG+nRCVPtBRwoO456gwPozNcAj14rgxyqGr/D0WtNGdYV/P70aai2vs27OM
  52. bA0GbFjVcCNzw8t/g6NveZUYkl9jxekomzZNT+7cO+WpHXOBHzUUi+Bvo/DpLhKS
  53. zbS+3J9gW+Ot8XtkMxsWOLj0mxXU+ig13qKUmgvVAoIBAB2k08jOP/5HmmBcdVnn
  54. 2XokQ2QdIp5gnVLo+WBlZTETSbPT3NcfHLQ0HQkyIzdkGt8swfyY0gbFlsL31L0E
  55. CytPQ9UozrXT8UcswGVAH6n2xq7GA21c8RxMLNlV3+Uym29BNM7gijCtndsjKWpQ
  56. k1Px+iARVl3KGOibKJM5o5/uAz7hQdcssC9vDy75Wq3nhlbl4b8xcJAo+fYP/qLN
  57. elkHjk7Dr+96rIE5GhA/RI2DhUa/P0lfLg6vW2sjXAAd9Nnux1hfXUDhki0gDbbQ
  58. FHwlVR9vUmH3FFKbku0VO0BbfAVpi4ZxZNtoBTaXVNJGxDik3/U0OYfGA2lI6Qx6
  59. StMCggEAV1XytpdVbCAlPitA5mkncFXXW6YhRufmkmzbYeTboPzYlNz9F2xmYjIo
  60. xNfzwiGepHyG38YdgNJ/h1NNo4a7JCLKRPReRca1V+td9BP7ZKAQEHAtAY6QwHJ1
  61. aJzZxmcohMWh9LXmUzeSnSIMbG/JNqIwy6W6EMmzC5eXL9FHaWCr3WQs05wE+CJF
  62. pJkXbmXkg+rbct9hAYKVw7zQjezTbfRPqcHdsHVOJBZCTbCSm44XWnLuu90jQ2Ku
  63. pTOTmM3h0mKOG8tVTaibJdeNHzk0+SDhUdOI5ORA0Q+iHZaEbPO39/c+sr0n9xLF
  64. 17M9lCizO9o9dONdHsHfNQi6y9Jcnw==
  65. -----END PRIVATE KEY-----`;
  66.  
  67.  
  68. const pemEncodedCrt = `-----BEGIN CERTIFICATE-----
  69. MIIEtTCCAp2gAwIBAgIUDyuccmylba71lZQAQic5TJiAhwwwDQYJKoZIhvcNAQEL
  70. BQAwGDEWMBQGA1UEAwwNSmV0UHJvZmlsZSBDQTAeFw0yMzA5MjkxNDA2MTJaFw0z
  71. MzA5MjcxNDA2MTJaMBExDzANBgNVBAMMBk5vdmljZTCCAiIwDQYJKoZIhvcNAQEB
  72. BQADggIPADCCAgoCggIBALenqcGP2ZxGkYqmKA9c4Hzf8+YD1smvmOxKjd+bmTLr
  73. utM/hXv1cj1rW3/lqyDtdDk7K6W8/TDq1CRrEt+Do6l30DxhAiC34aH8DmGwgq77
  74. xEoLimvH5LpePxflF+tbB1RZtFgFDOIYLdSQaKFH2JDgVKxhLiV3S6jniPhkCtWW
  75. rTs+E6vq4N15Bm3NnM5AJILqjtUbOjNfaxVq6RrOoTc0R3Fqqo6yvxo/+JYa2UnH
  76. IC+r2dbKuDLMUrtgnydEUdJNX0zH9FtcdELvr48uc9mY038TWUsZUK1pnQbxA2bP
  77. yA4qnYJ9IvUgO6LtLXvGFm137YQMS1N41AHDBOrwoNI8UoDX+qI3rM96biFOFvn7
  78. Edky7rByzybt3H+zxdojfjvpL1E0NO98BT9zfufHAaAxZtlmDOu5LDJe3CGurnyR
  79. MRExbtc+Qjl1mUh6tG4lakAwdsoxry0GdG72yaYyb9it53kaFks/T/s7Z7bRJzVF
  80. zQDV1Y4bzUtk43vKm2vztBVlQkBkZY5f2Jbe5Ig3b8swQzBnOT0mrL5SPUhwmQ6I
  81. xkEWztj55OEujBMmRr92oESuq9ZYMaeLidKWVR3/++HA8BRZaRGEKtSHZCbFEFdi
  82. hDxxJv9Xh6NuT/ewJ6HYp+0NQpFnUnJ72n8wV+tudpam7aKcdzVmz7cNwOhG2Ls7
  83. AgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIdeaQfKni7tXtcywC3zJvGzaaj242pS
  84. WB1y40HW8jub0uHjTLsBPX27iA/5rb+rNXtUWX/f2K+DU4IgaIiiHhkDrMsw7piv
  85. azqwA9h7/uA0A5nepmTYf/HY4W6P2stbeqInNsFRZXS7Jg4Q5LgEtHKo/H8USjtV
  86. w9apmE3BCElkXRuelXMsSllpR/JEVv/8NPLmnHSY02q4KMVW2ozXtaAxSYQmZswy
  87. P1YnBcnRukoI4igobpcKQXwGoQCIUlec8LbFXYM9V2eNCwgABqd4r67m7QJq31Y/
  88. 1TJysQdMH+hoPFy9rqNCxSq3ptpuzcYAk6qVf58PrrYH/6bHwiYPAayvvdzNPOhM
  89. 9OCwomfcazhK3y7HyS8aBLntTQYFf7vYzZxPMDybYTvJM+ClCNnVD7Q9fttIJ6eM
  90. XFsXb8YK1uGNjQW8Y4WHk1MCHuD9ZumWu/CtAhBn6tllTQWwNMaPOQvKf1kr1Kt5
  91. etrONY+B6O+Oi75SZbDuGz7PIF9nMPy4WB/8XgKdVFtKJ7/zLIPHgY8IKgbx/VTz
  92. 6uBhYo8wOf3xzzweMnn06UcfV3JGNvtMuV4vlkZNNxXeifsgzHugCvJX0nybhfBh
  93. fIqVyfK6t0eKJqrvp54XFEtJGR+lf3pBfTdcOI6QFEPKGZKoQz8Ck+BC/WBDtbjc
  94. /uYKczZ8DKZu
  95. -----END CERTIFICATE-----`;
  96.  
  97. function injectStyles() {
  98. GM_addStyle(`
  99. .jetbra-button {
  100. background-color: #04AA6D; border: none; color: white; padding: 8px 24px;
  101. text-align: center; text-decoration: none; display: inline-block;
  102. border-radius: 16px; box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19);
  103. transition-duration: 0.4s;
  104. }
  105. .jetbra-button:hover { background-color: #057e47; color: white; }
  106. `);
  107. }
  108.  
  109.  
  110. async function findElementWithRetry(cssSelector) {
  111. const maxAttempts = 50;
  112. for (let attempts = 0; attempts < maxAttempts; attempts++) {
  113. const element = document.querySelector(cssSelector);
  114. if (element) {
  115. return element;
  116. }
  117. await new Promise(resolve => setTimeout(resolve, 100));
  118. }
  119. throw new Error(`Element with selector '${cssSelector}' not found after ${maxAttempts} attempts.`);
  120. }
  121.  
  122. function pem2base64(pem) {
  123. return pem.split('\n').reduce((base64, line) => line.includes("--") ? base64 : base64 + line, '');
  124. }
  125.  
  126. function arrayBufferToBase64(buffer) {
  127. return btoa([...new Uint8Array(buffer)].map(b => String.fromCharCode(b)).join(''));
  128. }
  129.  
  130. function base64ToArrayBuffer(base64) {
  131. return Uint8Array.from(atob(base64), c => c.charCodeAt(0)).buffer;
  132. }
  133.  
  134. function genLicenseId() {
  135. const CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  136. return Array.from({length: 10}, () => {
  137. let idx = Math.floor(Math.random() * CHARSET.length);
  138. return CHARSET[idx];
  139. }).join('');
  140. }
  141.  
  142. function buildLicensePartJson(productCodes, licenseId) {
  143. // 获取当前日期
  144. let currentDate = new Date();
  145.  
  146. let formattedDateTwoYears = '2099-08-01';
  147.  
  148. if (document.getElementById('activateTwoYears').checked) {
  149. // 当前日期加两年
  150. let futureDateTwoYears = new Date(currentDate);
  151. futureDateTwoYears.setFullYear(futureDateTwoYears.getFullYear() + 2);
  152. formattedDateTwoYears = futureDateTwoYears.toISOString().split('T')[0];
  153. }
  154.  
  155. let fallbackDate = formattedDateTwoYears;
  156. let paidUpTo = formattedDateTwoYears;
  157.  
  158. let products = Array.from(productCodes.split(',')).map((code) => {
  159. return {
  160. code: code,
  161. fallbackDate: fallbackDate,
  162. paidUpTo: paidUpTo
  163. }
  164. });
  165. return JSON.stringify({
  166. "licenseId": licenseId,
  167. "licenseeName": "reborn",
  168. "assigneeName": "reborn",
  169. "assigneeEmail": "",
  170. "licenseRestriction": "",
  171. "checkConcurrentUse": false,
  172. "products": products,
  173. "metadata": "0120230102PPAA013009",
  174. "hash": "41472961/0:1563609451",
  175. "gracePeriodDays": 7,
  176. "autoProlongated": true,
  177. "isAutoProlongated": true
  178. });
  179. }
  180.  
  181. async function addButton() {
  182. injectStyles();
  183.  
  184. let url = window.location.href
  185. if (!url.startsWith('https://plugins.jetbrains.com/plugin/')) {
  186. return;
  187. }
  188.  
  189. let pluginId = url.split('/')[4].split('-')[0]
  190. console.log('pluginId: ' + pluginId);
  191.  
  192. let pluginDetail = await fetch('https://plugins.jetbrains.com/api/plugins/' + pluginId).then(r => r.json());
  193.  
  194. const parentElement = await findElementWithRetry('.plugin-header__controls-panel > div:first-child');
  195.  
  196. if (parentElement.querySelector('.jetbra-button')) {
  197. return;
  198. }
  199. let newElement = document.createElement('div');
  200. newElement.classList.toggle('wt-col-inline');
  201. // 产品code查询: https://data.services.jetbrains.com/products?fields=code,name,description
  202. // 所有产品code: ["YTD", "QDGO", "MF", "DG", "PS", "QA", "IIE", "YTWE", "FLS", "DLE", "RFU", "PPS", "PCWMP", "II", "TCC", "RSU", "PCC", "RC", "PCE", "FLIJ", "TBA", "DL", "SPP", "QDCLD", "SPA", "DMCLP", "PSW", "GW", "PSI", "IIU", "DMU", "PWS", "HB", "WS", "PCP", "KT", "DCCLT", "RSCLT", "WRS", "RSC", "RRD", "TC", "IIC", "QDPY", "DPK", "DC", "PDB", "DPPS", "QDPHP", "GO", "HCC", "RDCPPP", "QDJVMC", "CL", "DM", "CWML", "FLL", "RR", "QDJS", "RS", "RM", "DS", "MPS", "DPN", "US", "CLN", "DPCLT", "RSV", "MPSIIP", "DB", "QDANDC", "AC", "QDJVM", "PRB", "RD", "CWMR", "SP", "RS0", "DP", "RSF", "PGO", "QDPYC", "PPC", "PC", "EHS", "RSCHB", "FL", "QDNET", "JCD"]
  203. // 资料来自 https://linux.do/t/topic/1798
  204. newElement.innerHTML =
  205. `<div class="customize-btn" style="display: flex; flex-direction: column;white-space: nowrap;">
  206. <div class="generate-plugin-code" style="display: flex;height: 40px;">
  207. <button class="jetbra-button" type="button">CLICK TO GENERATE ACTIVATION CODE</button>
  208. <label style="margin-left: 10px;display: flex;align-items: center;"><input type="checkbox" id="activateTwoYears" style="margin-right: 5px;" title="默认到期时间 2099-08-01">2年</label>
  209. </div>
  210. <div class="generate-customize-product-code" style="position: relative;display: flex;margin: 10px;top: 5px;left: -63px;align-items: center;height: 40px;">
  211. <input title="自定义ProductCode, 默认包含所有IDE及相关插件:YTD,QDGO,MF,DG,PS,QA,IIE,YTWE,FLS,DLE,RFU,PPS,PCWMP,II,TCC,RSU,PCC,RC,PCE,FLIJ,TBA,DL,SPP,QDCLD,SPA,DMCLP,PSW,GW,PSI,IIU,DMU,PWS,HB,WS,PCP,KT,DCCLT,RSCLT,WRS,RSC,RRD,TC,IIC,QDPY,DPK,DC,PDB,DPPS,QDPHP,GO,HCC,RDCPPP,QDJVMC,CL,DM,CWML,FLL,RR,QDJS,RS,RM,DS,MPS,DPN,US,CLN,DPCLT,RSV,MPSIIP,DB,QDANDC,AC,QDJVM,PRB,RD,CWMR,SP,RS0,DP,RSF,PGO,QDPYC,PPC,PC,EHS,RSCHB,FL,QDNET,JCD" type="text" id="productCode" value="YTD,QDGO,MF,DG,PS,QA,IIE,YTWE,FLS,DLE,RFU,PPS,PCWMP,II,TCC,RSU,PCC,RC,PCE,FLIJ,TBA,DL,SPP,QDCLD,SPA,DMCLP,PSW,GW,PSI,IIU,DMU,PWS,HB,WS,PCP,KT,DCCLT,RSCLT,WRS,RSC,RRD,TC,IIC,QDPY,DPK,DC,PDB,DPPS,QDPHP,GO,HCC,RDCPPP,QDJVMC,CL,DM,CWML,FLL,RR,QDJS,RS,RM,DS,MPS,DPN,US,CLN,DPCLT,RSV,MPSIIP,DB,QDANDC,AC,QDJVM,PRB,RD,CWMR,SP,RS0,DP,RSF,PGO,QDPYC,PPC,PC,EHS,RSCHB,FL,QDNET,JCD" placeholder="Product Code" style="width: 95px;/* margin-left: 10px; */">
  212. <button class="jetbra-button" type="button" style="margin-left: 10px;height: 40px;" id="generateButton">Generate</button>
  213. <button class="jetbra-button" type="button" style="margin-left: 10px;height: 40px;" id="searchButton">产品Code查询</button>
  214. </div>
  215. </div>`;
  216.  
  217. parentElement.appendChild(newElement)
  218.  
  219. newElement.addEventListener('click', async () => {
  220. // 忽略input框的点击事件
  221. if (event.target.tagName === 'INPUT' || event.target.id === 'activateTwoYears') {
  222. return;
  223. }
  224. if (event.target.id === 'searchButton') {
  225. window.open('https://data.services.jetbrains.com/products?fields=code,name,description');
  226. return;
  227. }
  228.  
  229. let productCodes;
  230. // 如果是 generateButton 点击就从input中获取
  231. if (event.target.id === 'generateButton') {
  232. productCodes = document.getElementById('productCode').value;
  233. } else {
  234. if (pluginDetail.purchaseInfo === undefined || pluginDetail.purchaseInfo.productCode == undefined) {
  235. window.alert('This plugin is not a paid plugin in the market');
  236. return;
  237. }
  238. productCodes = pluginDetail.purchaseInfo.productCode;
  239. }
  240.  
  241. let licenseId = genLicenseId()
  242. let licensePartJson = buildLicensePartJson(productCodes, licenseId)
  243.  
  244. let privateKey = await window.crypto.subtle.importKey("pkcs8", base64ToArrayBuffer(pem2base64(pemEncodedKey)), {
  245. name: "RSASSA-PKCS1-v1_5", hash: "SHA-1",
  246. }, true, ["sign"]);
  247.  
  248. let licensePartBase64 = btoa(unescape(encodeURIComponent(licensePartJson)));
  249. let sigResultsBase64 = arrayBufferToBase64(await window.crypto.subtle.sign("RSASSA-PKCS1-v1_5", privateKey, new TextEncoder().encode(licensePartJson)));
  250. let cert_base64 = pem2base64(pemEncodedCrt);
  251.  
  252. GM_setClipboard(`${licenseId}-${licensePartBase64}-${sigResultsBase64}-${cert_base64}`, 'text');
  253. window.alert('The activation code has been copied to your clipboard');
  254. })
  255. }
  256.  
  257. addButton();
  258. if (window.onurlchange === null) {
  259. window.addEventListener('urlchange', (ignore) => {
  260. addButton();
  261. });
  262. }