ramewn3323

deeskord

Apr 6th, 2019
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 127.43 KB | None | 0 0
  1. // ==UserScript==
  2. // @name ⡒⠮⢑⠂⡐⣉⢄
  3. // @version 1.0
  4. // @description Deeskord-crypt
  5. // @downloadURL https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/Deeskord-crypt.user.js
  6. // @icon https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/logo.png
  7. // @match https://discordapp.com/channels/*
  8. // @match https://discordapp.com/activity
  9. // @match https://ptb.discordapp.com/channels/*
  10. // @match https://ptb.discordapp.com/activity
  11. // @match https://canary.discordapp.com/channels/*
  12. // @match https://canary.discordapp.com/activity
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant unsafeWindow
  16. // @grant GM_xmlhttpRequest
  17. // @connect cdn.discordapp.com
  18. // ==/UserScript==
  19.  
  20.  
  21. (function() {
  22.  
  23. 'use strict';
  24.  
  25. const BlacklistUrl = "pach";
  26.  
  27. const SavedLocalStorage = (typeof(localStorage) !== 'undefined') ? localStorage : null;
  28. const FixedCsp = (typeof(CspDisarmed) !== 'undefined') ? CspDisarmed : false;
  29.  
  30. const BaseColor = "#84b0e0";
  31. const BaseColorInt = 0x84B0E0;
  32.  
  33. const htmlEscapeCharacters = { "<": "&lt;", ">": "&gt;", "&": "&amp;" };
  34. function HtmlEscape(string) { return string.replace(/[<>&]/g, x => htmlEscapeCharacters[x]) }
  35.  
  36. const Style = {
  37. css: `
  38. /*fixes*/
  39. .headerBar-UHpsPw, .title-CZQTru { overflow: visible !important }
  40. .topic-2QX7LI { width: 0 }
  41. /*style*/
  42. .sdc * {
  43. font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
  44. font-size: 16px;
  45. font-weight: normal;
  46. line-height: 1;
  47. text-rendering: optimizeLegibility;
  48. color: #f6f6f7;
  49. user-select: none;
  50. display: flex;
  51. padding: 0;
  52. margin: 0;
  53. }
  54. .sdc-overlay,.sdc-cover {
  55. position: fixed;
  56. left: 0;
  57. bottom: 0;
  58. right: 0;
  59. top: 0;
  60. z-index: 1000;
  61. align-items: center;
  62. justify-content: center;
  63. pointer-events: none;
  64. }
  65. .sdc-cover {
  66. background: rgba(0,0,0,.85);
  67. pointer-events: auto;
  68. }
  69. .sdc-window {
  70. background-color: #36393f;
  71. flex-direction: column;
  72. border-radius: 5px;
  73. pointer-events: auto;
  74. position: relative;
  75. }
  76. .sdc-window > * { margin: 0 20px }
  77. .sdc-footer {
  78. margin: 0;
  79. padding: 20px;
  80. background-color: #2f3136;
  81. box-shadow: inset 0 1px 0 rgba(47,49,54,.6);
  82. border-radius: 0 0 5px 5px;
  83. justify-content: flex-end;
  84. }
  85. .sdc h4,.sdc h4 * {
  86. text-transform: uppercase;
  87. letter-spacing: .3px;
  88. font-weight: 600;
  89. line-height: 20px;
  90. }
  91. .sdc h5,.sdc h5 * {
  92. color: #b9bbbe;
  93. text-transform: uppercase;
  94. letter-spacing: .5px;
  95. font-weight: 600;
  96. font-size: 12px;
  97. line-height: 16px;
  98. }
  99. .sdc input {
  100. background: rgba(0,0,0,.1);
  101. border: solid 1px rgba(0,0,0,.3);
  102. border-radius: 3px;
  103. height: 38px;
  104. padding: 0 10px;
  105. outline: 0;
  106. transition: border .15s ease;
  107. }
  108. .sdc input:focus { border-color: #72dac7 }
  109. .sdc button {
  110. min-height: 38px;
  111. border-radius: 3px;
  112. justify-content: center;
  113. padding: 2px 16px;
  114. cursor: pointer;
  115. }
  116. .sdc button,.sdc button * {
  117. font-size: 14px;
  118. font-weight: 500;
  119. line-height: 16px;
  120. }
  121. .sdc-btn {
  122. border: 0;
  123. color: #fff;
  124. background-color: #72dac7;
  125. transition: background-color .17s ease;
  126. }
  127. .sdc-btn:hover { background-color: #67c4b3 }
  128. .sdc-lnkbtn {
  129. border: 0;
  130. color: #fff;
  131. background-color: transparent;
  132. }
  133. .sdc-lnkbtn:hover > * { background-image: linear-gradient(0,transparent,transparent 1px,#fff 0,#fff 2px,transparent 0); }
  134. .sdc-rbtn {
  135. color: #f04747;
  136. border: solid 1px rgba(240,71,71,.3);
  137. transition: border-color .17s ease;
  138. background-color: transparent;
  139. }
  140. .sdc-rbtn:hover { border-color: rgba(240,71,71,.6) }
  141. .sdc-rbtn:disabled {
  142. color: #8b8181;
  143. border-color: rgba(130,126,126,.6);
  144. cursor: default;
  145. }
  146. .sdc-wbtn {
  147. color: #f6f6f7;
  148. border: solid 1px rgba(240,240,242,.3);
  149. transition: border-color .17s ease;
  150. background-color: transparent;
  151. }
  152. .sdc-wbtn:hover { border-color: rgba(240,240,242,.6) }
  153. .sdc-wbtn:disabled {
  154. color: #a6a6a7;
  155. border-color: rgba(126,126,126,.6);
  156. cursor: default;
  157. }
  158. .sdc-select {
  159. background: rgba(0,0,0,.1);
  160. border-radius: 4px;
  161. position: relative;
  162. transition: border-color .15s ease;
  163. }
  164. .sdc-select:hover { border-color: #040405; }
  165. .sdc-select input + * {
  166. margin-right: 17px;
  167. width: 100%;
  168. align-items: center;
  169. }
  170. .sdc-select input + *::after {
  171. content: '';
  172. border-color: #999 transparent transparent;
  173. border-style: solid;
  174. border-width: 5px 5px 2.5px;
  175. position: absolute;
  176. right: 10px;
  177. margin-top: 2px;
  178. }
  179. .sdc-select:hover input + *::after { border-color: #f6f6f7 transparent transparent }
  180. .sdc-select input:checked + *::after {
  181. border-color: transparent transparent #f6f6f7;
  182. border-width: 0 5px 5px;
  183. }
  184. .sdc-select, .sdc-select > div {
  185. border: solid 1px rgba(0,0,0,.3);
  186. flex-direction: column;
  187. }
  188. .sdc-select > div {
  189. background: #303237;
  190. position: absolute;
  191. top: 100%;
  192. width: 100%;
  193. margin: -1px;
  194. margin-top: -2px;
  195. border-radius: 0 0 4px 4px;
  196. box-shadow: 0 1px 5px rgba(0,0,0,.3);
  197. z-index: 1;
  198. }
  199. .sdc-select > label,.sdc-select > div > * {
  200. height: 38px;
  201. padding: 0 10px;
  202. align-items: center;
  203. }
  204. .sdc-select > div > a:hover { background: rgba(0,0,0,.1) }
  205.  
  206. .sdc-tooltip {
  207. visibility: hidden;
  208. width: 124px;
  209. background-color: black;
  210. font-size: 15px;
  211. justify-content: center;
  212. border-radius: 6px;
  213. padding: 6px 0;
  214. position: absolute;
  215. z-index: 1;
  216. top: 150%;
  217. left: 50%;
  218. margin-left: -62px;
  219. }
  220. .sdc-tooltip::after {
  221. content: '';
  222. position: absolute;
  223. bottom: 100%;
  224. left: 50%;
  225. margin-left: -5px;
  226. border-width: 5px;
  227. border-style: solid;
  228. border-color: transparent transparent black transparent;
  229. }
  230. :hover > .sdc-tooltip { visibility: visible }
  231.  
  232. .sdc-menu {
  233. position: fixed;
  234. min-width: 170px;
  235. z-index: 1005;
  236. border-radius: 5px;
  237. background: #282b30;
  238. box-shadow: 0 0 1px rgba(0,0,0,.82), 0 1px 4px rgba(0,0,0,.1);
  239. }
  240. .sdc-menu a {
  241. font-size: 13px;
  242. font-weight: 500;
  243. line-height: 16px;
  244. margin: 2px 0;
  245. padding: 6px 10px;
  246. overflow: hidden;
  247. color: #fff;
  248. opacity: .6;
  249. border-radius: 5px;
  250.  
  251. transition: none;
  252. cursor: default;
  253. }
  254. .sdc-menu a:hover {
  255. background: #25282d;
  256. opacity: 1;
  257. }
  258. .sdc-menu > div {
  259. border-bottom: solid 1px hsla(0,0%,96%,.08);
  260. flex-direction: column;
  261. }
  262. .sdc-menu > div:last-child { border: 0 }
  263. .sdc-hidden {
  264. width: 0;
  265. height: 0;
  266. padding: 0;
  267. border: 0;
  268. overflow: hidden;
  269. outline: 0;
  270. margin: 0;
  271. }
  272. .sdc-scroll {
  273. display: block;
  274. overflow-x: hidden;
  275. overflow-y: auto;
  276. margin: 0;
  277. padding: 0 12px 0 20px;
  278. }
  279. .sdc-scroll::-webkit-scrollbar {
  280. width: 8px;
  281. }
  282. .sdc-scroll::-webkit-scrollbar-thumb {
  283. background-color: rgba(32,34,37,.6);
  284. border: 2px solid transparent;
  285. border-radius: 4px;
  286. background-clip: padding-box;
  287. }
  288. .sdc-list { flex-direction: column }
  289. .sdc-list > div {
  290. border: solid 1px rgba(32,34,37,.6);
  291. border-radius: 5px;
  292. margin: 4px 0;
  293. }
  294. .sdc-list > div:hover {
  295. background-color: rgba(32,34,37,.1);
  296. }
  297. .sdc-list > div > div:first-child {
  298. margin-right: auto;
  299. padding: 12px 0 8px 20px;
  300. flex-direction: column;
  301. }
  302. .sdc-list h6 {
  303. font-weight: 600;
  304. line-height: 20px;
  305. word-break: break-all; /*FF*/
  306. word-break: break-word;
  307. max-width: 400px;
  308. }
  309. .sdc-list p {
  310. line-height: 16px;
  311. font-size: 12px;
  312. font-weight: 400;
  313. color: #b9bbbe;
  314. }
  315. .sdc-edit {
  316. background: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="%23F6F6F7" viewBox="0 0 36 36"%3E%3Cpath d="M1,35.9L8.2,35l7-9l-5-5l-9,7l-0.9,7.1L3.4,32c-0.2-0.3-0.3-0.6-0.3-1c0-1.1,0.9-2,2-2s2,0.9,2,2s-0.9,2-2,2c-0.4,0-0.7-0.1-1-0.3L1,35.9z"/%3E%3Cpath d="M9.5,18.3l13-13c0,0,0,0,0,0c0,0,0.6-0.6,0.8-0.8l3.4-3.4l0,0c1.2-1.1,3-1.1,4.1,0L35,5.3c1.1,1.1,1.1,3,0,4.1l0,0l0,0c0,0,0,0,0,0l-4.2,4.2c0,0,0,0,0,0l-13,13c-1.1,1.2-3,1.2-4.2,0l-4.2-4.2C8.4,21.3,8.4,19.5,9.5,18.3z"/%3E%3C/svg>');
  317. background-size: cover;
  318. width: 20px;
  319. height: 20px;
  320. cursor: pointer;
  321. margin: -2px 0 0 6px;
  322. opacity: .6;
  323. }
  324. .sdc-edit:hover { opacity: 1 }
  325. .sdc-listbox {
  326. width: 76px;
  327. align-items: center;
  328. justify-content: center;
  329. }
  330. .sdc-listbox::before {
  331. content: '';
  332. width: 1px;
  333. height: 30px;
  334. background: rgba(32,34,37,.5);
  335. }
  336. .sdc-listbox > * { margin: auto }
  337. .sdc-listcheckbox > label { height: 24px }
  338. .sdc-listcheckbox input + *::after {
  339. content: '';
  340. border: solid 1px #62666d;
  341. border-radius: 3px;
  342. width: 24px;
  343. box-sizing: border-box;
  344. transition: background .17s ease,border-color .17s ease;
  345. }
  346. .sdc-listcheckbox input:enabled + * { cursor: pointer }
  347. .sdc-listcheckbox input:enabled:hover + *::after { border-color: #72767d }
  348. .sdc-listcheckbox input:checked + *::after {
  349. content: url('data:image/svg+xml,%3Csvg stroke="%23FFF" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"%3E%3Cpolyline stroke-width="2" fill="none" points="3.5 9.5 7 13 15 5"%3E%3C/polyline%3E%3C/svg%3E');
  350. border: 0;
  351. padding: 3px;
  352. background: #72dac7;
  353. }
  354. .sdc-listcheckbox input:enabled:checked:hover + *::after { background-color: #67c4b3 }
  355. .sdc-listcheckbox input:disabled + *::after {
  356. background: #72767d;
  357. border: 0;
  358. opacity: .5;
  359. }
  360. .sdc-listbox:last-child {
  361. background: rgba(0,0,0,.1);
  362. padding: 9px;
  363. }
  364. .sdc-listbox:last-child::before { display: none }
  365. .sdc-list > h5 > p {
  366. font-size: 10px;
  367. font-weight: 700;
  368. color: #dcddde;;
  369. padding: 0 8px 1px 8px;
  370. box-sizing: border-box;
  371. justify-content: center;
  372. }
  373. .sdc-close {
  374. content: url('data:image/svg+xml;utf8,%3Csvg fill="%23DCDDDE" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"%3E%3C/path%3E%3C/svg%3E');
  375. width: 18px;
  376. height: 18px;
  377. cursor: pointer;
  378. border-radius: 3px;
  379. margin: 0;
  380. padding: 4px;
  381. position: absolute;
  382. right: 16px;
  383. top: 16px;
  384. opacity: .5;
  385. }
  386. .sdc-close:hover {
  387. opacity: 1;
  388. background-color: hsla(210,3%,87%,.05);
  389. }`,
  390. Inject: function() {
  391. let style = document.createElement('style');
  392. style.innerHTML = this.css;
  393. document.head.appendChild(style);
  394. this.domElement = style;
  395. },
  396. Remove: function() {
  397. if(document.head.contains(this.domElement))
  398. document.head.removeChild(this.domElement);
  399. }
  400. };
  401. const UnlockWindow = {
  402. html: `<div class="sdc">
  403. <div class="SDC_CANCEL sdc-cover"></div>
  404. <div class="sdc-overlay">
  405. <form class="SDC_UNBLOCK sdc-window" style="min-width: 480px">
  406. <div style="margin-right:20px;margin-top:20px" >
  407. <div style="margin-right:20px" >
  408. <img style="border-radius: 32px;" src="https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/logo.png">
  409. </div>
  410. <div style="margin-top:25px"><h4>Unlock Database</h4></div>
  411. </div>
  412. <h5 style="margin-top:20px">Password</h5>
  413. <input class="SDC_PASSWORD" style="margin-top:8px;margin-bottom:20px;border-color:#84b0e0" type="password" name="sdc-password">
  414. <div class="sdc-footer"><button type="button" class="SDC_CANCEL sdc-lnkbtn"><p>Cancel</p></button><button type="button" class="SDC_NEWDB sdc-rbtn" style="margin:0 4px">New DB</button><button type="submit" class="sdc-btn" style="min-width:96px;background-color:#84b0e0">Unlock</button></div>
  415. </form>
  416. </div>
  417. </div>`,
  418. Show: function(passwordCallback, newdbCallback, cancelCallback) {
  419. let wrapper = document.createElement('div');
  420. wrapper.innerHTML = this.html;
  421.  
  422. Utils.AttachEventToClass(wrapper, 'SDC_UNBLOCK', 'submit', (e) => {
  423. e.preventDefault();
  424. this.Remove();
  425. passwordCallback(wrapper.getElementsByClassName('SDC_PASSWORD')[0].value);
  426. });
  427. Utils.AttachEventToClass(wrapper, 'SDC_NEWDB', 'click', () => {
  428. this.Remove();
  429. newdbCallback();
  430. });
  431. Utils.AttachEventToClass(wrapper, 'SDC_CANCEL', 'click', () => {
  432. this.Remove();
  433. if(cancelCallback) cancelCallback();
  434. });
  435.  
  436. document.body.appendChild(wrapper);
  437. this.domElement = wrapper;
  438. },
  439. Remove: function() {
  440. if(document.body.contains(this.domElement))
  441. document.body.removeChild(this.domElement);
  442. }
  443. };
  444. const NewdbWindow = {
  445. html: `<div class="sdc">
  446. <div class="SDC_CANCEL sdc-cover"></div>
  447. <div class="sdc-overlay">
  448. <form class="SDC_CREATEDB sdc-window" style="min-width: 480px">
  449. <div style="margin-right:20px;margin-top:20px" >
  450. <div style="margin-right:20px" >
  451. <img style="border-radius: 32px;" src="https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/logo.png">
  452. </div>
  453. <div style="margin-top:25px"><h4>Create Database</h4></div>
  454. </div>
  455. <h5 style="margin-top:20px">Password <p style="margin-left:5px;opacity:.6">(optional)</p></h5>
  456. <input class="SDC_PASSWORD" style="margin-top:8px;margin-bottom:20px;border-color:#84b0e0" type="password" name="sdc-password">
  457. <div class="sdc-footer"><button type="button" class="SDC_CANCEL sdc-lnkbtn"><p>Cancel</p></button><button type="button" class="SDC_IMPORT sdc-lnkbtn"><p>Import</p></button><button type="button" class="SDC_SECONDARY sdc-lnkbtn" style="padding-right:22px"><p>Secondary</p></button><button type="submit" class="sdc-btn" style="min-width:96px;background-color:#84b0e0">Create</button></div>
  458. </form>
  459. </div>
  460. </div>`,
  461. Show: function(newdbCallback, importCallback, secondaryCallback, cancelCallback) {
  462. let wrapper = document.createElement('div');
  463. wrapper.innerHTML = this.html;
  464.  
  465. Utils.AttachEventToClass(wrapper, 'SDC_CREATEDB', 'submit', (e) => {
  466. e.preventDefault();
  467. this.Remove();
  468. newdbCallback(wrapper.getElementsByClassName('SDC_PASSWORD')[0].value);
  469. });
  470. Utils.AttachEventToClass(wrapper, 'SDC_IMPORT', 'click', () => {
  471. importCallback();
  472. });
  473. Utils.AttachEventToClass(wrapper, 'SDC_SECONDARY', 'click', () => {
  474. secondaryCallback();
  475. });
  476. Utils.AttachEventToClass(wrapper, 'SDC_CANCEL', 'click', () => {
  477. this.Remove();
  478. if(cancelCallback) cancelCallback();
  479. });
  480.  
  481. document.body.appendChild(wrapper);
  482. this.domElement = wrapper;
  483. },
  484. Remove: function() {
  485. if(document.body.contains(this.domElement))
  486. document.body.removeChild(this.domElement);
  487. }
  488. };
  489. const NewPasswordWindow = {
  490. html: `<div class="sdc">
  491. <div class="SDC_CANCEL sdc-cover"></div>
  492. <div class="sdc-overlay">
  493. <form class="SDC_CHANGEPASSWORD sdc-window" style="min-width: 480px">
  494. <div style="margin-top:20px">
  495. <h4>Change Database Password</h4>
  496. </div>
  497. <h5 style="margin-top:20px">Password <p style="margin-left:5px;opacity:.6">(optional)</p></h5>
  498. <input class="SDC_PASSWORD" style="margin-top:8px;margin-bottom:20px" type="password" name="sdc-password">
  499. <div class="sdc-footer"><button type="button" class="SDC_CANCEL sdc-lnkbtn" style="min-width:96px"><p>Cancel</p><button type="submit" class="sdc-btn" style="min-width:96px">Change</button></div>
  500. </form>
  501. </div>
  502. </div>`,
  503. Show: function(newPasswordCallback, cancelCallback) {
  504. let wrapper = document.createElement('div');
  505. wrapper.innerHTML = this.html;
  506.  
  507. Utils.AttachEventToClass(wrapper, 'SDC_CHANGEPASSWORD', 'submit', (e) => {
  508. e.preventDefault();
  509. this.Remove();
  510. newPasswordCallback(wrapper.getElementsByClassName('SDC_PASSWORD')[0].value);
  511. });
  512. Utils.AttachEventToClass(wrapper, 'SDC_CANCEL', 'click', () => {
  513. this.Remove();
  514. if(cancelCallback) cancelCallback();
  515. });
  516.  
  517. document.body.appendChild(wrapper);
  518. this.domElement = wrapper;
  519. },
  520. Remove: function() {
  521. if(document.body.contains(this.domElement))
  522. document.body.removeChild(this.domElement);
  523. }
  524. };
  525. const KeyManagerWindow = {
  526. html: `<div class="sdc">
  527. <div class="SDC_CLOSE sdc-cover"></div>
  528. <div class="sdc-overlay">
  529. <div class="sdc-window" style="min-width: 580px">
  530. <div style="margin:20px">
  531. <h4>Key Manager</h4>
  532. </div>
  533. <a class="SDC_CLOSE sdc-close"></a>
  534. <div class="sdc-scroll" onscroll="this.style.boxShadow=this.scrollTop?'inset 0 1px 0 0 rgba(24,25,28,.3),inset 0 1px 2px 0 rgba(24,25,28,.3)':null" style="max-height:60vh">
  535. <div class="SDC_LIST sdc-list">
  536. <h5><p>Key</p><p style="margin-left:auto;width:76px">Hidden</p><p style="width:94px">Delete</p></h5>
  537.  
  538. </div>
  539. </div>
  540. <div class="sdc-footer">
  541. <button type="button" class="SDC_CLOSE sdc-btn" style="min-width:96px">Done</button>
  542. </div>
  543. </div>
  544. </div>
  545. </div>`,
  546. Show: function(keys, setKeyDescriptor, setKeyHidden, deleteKey) {
  547. let wrapper = document.createElement('div');
  548. wrapper.innerHTML = this.html;
  549.  
  550. Utils.AttachEventToClass(wrapper, 'SDC_CLOSE', 'click', () => {
  551. this.Remove();
  552. });
  553.  
  554. let list = wrapper.getElementsByClassName('SDC_LIST')[0];
  555. for(let key of keys) {
  556. let listItem = document.createElement('div');
  557. listItem.innerHTML = `<div>
  558. <h6 class="SDC_DESCRIPTOR">${HtmlEscape(key.descriptor)} <a class="SDC_EDITDESCRIPTOR sdc-edit"></a></h6>
  559. <p>${Utils.FormatTime(key.lastseen)}</p>
  560. </div>
  561. <div class="sdc-listbox sdc-listcheckbox"><label><input type="checkbox" class="SDC_SETHIDDEN" style="display:none"${key.hidden?' checked':''}${key.type!=='GROUP'?' disabled':''}><p></p></label></div>
  562. <div class="sdc-listbox"><button type="button" class="SDC_DELETE sdc-rbtn" style="margin:0 4px"${key.protected?' disabled':''}>Delete</button></div>`;
  563. const editDescriptor = (e) => {
  564. let descriptorElement = listItem.getElementsByClassName('SDC_DESCRIPTOR')[0];
  565. descriptorElement.innerHTML = `<input type="text" class="SDC_DESCRIPTORINPUT" style="width:320px"></input>`;
  566. const changeBack = () => {
  567. descriptorElement.innerHTML = `${HtmlEscape(key.descriptor)} <a class="SDC_EDITDESCRIPTOR sdc-edit"></a>`;
  568. Utils.AttachEventToClass(descriptorElement, 'SDC_EDITDESCRIPTOR', 'click', editDescriptor);
  569. };
  570. let descriptorInput = descriptorElement.getElementsByClassName('SDC_DESCRIPTORINPUT')[0];
  571. descriptorInput.value = key.rawDescriptor;
  572. descriptorInput.onkeydown = function(e) {
  573. if(e.keyCode === 13/*ENTER*/) {
  574. e.preventDefault();
  575. setKeyDescriptor(key, this.value)
  576. changeBack();
  577. }
  578. else if(e.keyCode === 27/*ESC*/)
  579. changeBack();
  580. };
  581. descriptorInput.focus();
  582. };
  583. Utils.AttachEventToClass(listItem, 'SDC_EDITDESCRIPTOR', 'click', editDescriptor);
  584. Utils.AttachEventToClass(listItem, 'SDC_SETHIDDEN', 'change', function() {
  585. setKeyHidden(key, this.checked);
  586. });
  587. Utils.AttachEventToClass(listItem, 'SDC_DELETE', 'click', () => {
  588. deleteKey(key);
  589. list.removeChild(listItem);
  590. });
  591.  
  592. list.appendChild(listItem);
  593. }
  594.  
  595. document.body.appendChild(wrapper);
  596. this.domElement = wrapper;
  597. },
  598. Remove: function() {
  599. if(document.body.contains(this.domElement))
  600. document.body.removeChild(this.domElement);
  601. }
  602. };
  603. const ChannelManagerWindow = {
  604. html: `<div class="sdc">
  605. <div class="SDC_CLOSE sdc-cover"></div>
  606. <div class="sdc-overlay">
  607. <div class="sdc-window" style="min-width: 580px">
  608. <div style="margin:20px">
  609. <h4>Channel Manager</h4>
  610. </div>
  611. <a class="SDC_CLOSE sdc-close"></a>
  612. <div class="sdc-scroll" onscroll="this.style.boxShadow=this.scrollTop?'inset 0 1px 0 0 rgba(24,25,28,.3),inset 0 1px 2px 0 rgba(24,25,28,.3)':null" style="max-height:60vh">
  613. <div class="SDC_LIST sdc-list">
  614. <h5><p>Config</p><p style="margin-left:auto;width:94px">Delete</p></h5>
  615.  
  616. </div>
  617. </div>
  618. <div class="sdc-footer">
  619. <button type="button" class="SDC_CLOSE sdc-btn" style="min-width:96px">Done</button>
  620. </div>
  621. </div>
  622. </div>
  623. </div>`,
  624. Show: function(channels, deleteChannel) {
  625. let wrapper = document.createElement('div');
  626. wrapper.innerHTML = this.html;
  627.  
  628. Utils.AttachEventToClass(wrapper, 'SDC_CLOSE', 'click', () => {
  629. this.Remove();
  630. });
  631.  
  632. let list = wrapper.getElementsByClassName('SDC_LIST')[0];
  633. for(let channel of channels) {
  634. let listItem = document.createElement('div');
  635. listItem.innerHTML = `<div>
  636. <h6 class="SDC_DESCRIPTOR">${HtmlEscape(channel.descriptor)}</h6>
  637. <p>${Utils.FormatTime(channel.lastseen)}</p>
  638. </div>
  639. <div class="sdc-listbox"><button type="button" class="SDC_DELETE sdc-rbtn" style="margin:0 4px">Delete</button></div>`;
  640. Utils.AttachEventToClass(listItem, 'SDC_DELETE', 'click', () => {
  641. deleteChannel(channel);
  642. list.removeChild(listItem);
  643. });
  644.  
  645. list.appendChild(listItem);
  646. }
  647.  
  648. document.body.appendChild(wrapper);
  649. this.domElement = wrapper;
  650. },
  651. Remove: function() {
  652. if(document.body.contains(this.domElement))
  653. document.body.removeChild(this.domElement);
  654. }
  655. };
  656. const ShareKeyWindow = {
  657. html: `<div class="sdc">
  658. <div class="SDC_CLOSE sdc-cover"></div>
  659. <div class="sdc-overlay">
  660. <div class="sdc-window" style="min-width: 580px">
  661. <div style="margin:20px">
  662. <h4>Share Keys</h4>
  663. </div>
  664. <a class="SDC_CLOSE sdc-close"></a>
  665. <div class="sdc-scroll" onscroll="this.style.boxShadow=this.scrollTop?'inset 0 1px 0 0 rgba(24,25,28,.3),inset 0 1px 2px 0 rgba(24,25,28,.3)':null" style="max-height:60vh">
  666. <div class="SDC_LIST sdc-list">
  667. <h5><p>Key</p><p style="margin-left:auto;width:94px">Share</p></h5>
  668.  
  669. </div>
  670. </div>
  671. <div class="sdc-footer">
  672. <button type="button" class="SDC_CLOSE sdc-btn" style="min-width:96px">Done</button>
  673. </div>
  674. </div>
  675. </div>
  676. </div>`,
  677. Show: function(keys, shareKey) {
  678. let wrapper = document.createElement('div');
  679. wrapper.innerHTML = this.html;
  680.  
  681. Utils.AttachEventToClass(wrapper, 'SDC_CLOSE', 'click', () => {
  682. this.Remove();
  683. });
  684.  
  685. let list = wrapper.getElementsByClassName('SDC_LIST')[0];
  686. for(let key of keys) {
  687. let listItem = document.createElement('div');
  688. listItem.innerHTML = `<div>
  689. <h6 class="SDC_DESCRIPTOR">${HtmlEscape(key.descriptor)}</h6>
  690. <p>${Utils.FormatTime(key.lastseen)}</p>
  691. </div>
  692. <div class="sdc-listbox"><button type="button" class="SDC_SHARE sdc-wbtn" style="margin:0 4px">Share</button></div>`;
  693. Utils.AttachEventToClass(listItem, 'SDC_SHARE', 'click', function() {
  694. shareKey(key);
  695. this.disabled = true;
  696. });
  697.  
  698. list.appendChild(listItem);
  699. }
  700.  
  701. document.body.appendChild(wrapper);
  702. this.domElement = wrapper;
  703. },
  704. Remove: function() {
  705. if(document.body.contains(this.domElement))
  706. document.body.removeChild(this.domElement);
  707. }
  708. };
  709. const MenuBar = {
  710. menuBarCss: `.SDC_TOGGLE{opacity:.6;fill:#fff;height:24px;cursor:pointer}.SDC_TOGGLE:hover{opacity:.8}`,
  711. toggleOnButtonHtml: `<div class="sdc" style="position:relative"><svg class="SDC_TOGGLE" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M18 0c-4.612 0-8.483 3.126-9.639 7.371l3.855 1.052C12.91 5.876 15.233 4 18 4c3.313 0 6 2.687 6 6v10h4V10c0-5.522-4.477-10-10-10z"/><path d="M31 32c0 2.209-1.791 4-4 4H9c-2.209 0-4-1.791-4-4V20c0-2.209 1.791-4 4-4h18c2.209 0 4 1.791 4 4v12z"/></svg><p class="sdc-tooltip">Encrypt Channel</p></div>`,
  712. toggleOffButtonHtml: `<div class="sdc" style="position:relative"><svg class="SDC_TOGGLE" style="opacity:1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M18 3C12.477 3 8 7.477 8 13v10h4V13c0-3.313 2.686-6 6-6s6 2.687 6 6v10h4V13c0-5.523-4.477-10-10-10z"/><path d="M31 32c0 2.209-1.791 4-4 4H9c-2.209 0-4-1.791-4-4V20c0-2.209 1.791-4 4-4h18c2.209 0 4 1.791 4 4v12z"/><p class="sdc-tooltip">Disable Encryption</p></svg>`,
  713. keySelectHtml: `<div class="sdc sdc-select" style="margin:-3px 0 -2px 5px"><label style="min-width:200px;max-width:300px;height:30px"><input class="SDC_DROPDOWN sdc-hidden" type="checkbox"><p class="SDC_SELECTED" style="justify-content:center;text-align:center"></p></label><div class="SDC_OPTIONS" style="visibility:hidden"></div></div>`,
  714. toggledOnCss: `.inner-zqa7da{box-shadow:0 0 0 1px ${BaseColor} !important}`,
  715. menuHtml: `<button type="button" class="SDC_FOCUS sdc-hidden"></button>
  716. <div class="sdc sdc-menu SDC_MENU" style="visibility:hidden">
  717. <div class="SDC_DMMENU">
  718. <a class="SDC_KEYART">Visualize Key</a>
  719. <a class="SDC_KEYEXCHANGE">Start Key Exchange</a>
  720. <a class="SDC_KEYSHARE">Share Keys</a>
  721. </div>
  722. <div class="SDC_GROUPMENU">
  723. <a class="SDC_NEWKEY">Create Group Key</a>
  724. </div>
  725. <div>
  726. <a class="SDC_KEYMANAGER">Key Manager</a>
  727. <a class="SDC_CHMANAGER">Channel Manager</a>
  728. </div>
  729. <div>
  730. <a class="SDC_EXPORTDB">Export Database</a>
  731. <a class="SDC_NEWDBKEY">Change Database Key</a>
  732. <a class="SDC_NEWDB" style="color:#ff4031">New Database</a>
  733. </div>
  734. </div>`,
  735. Show: function(getToggleStatus, toggle, getCurrentKeyDescriptor, getKeys, selectKey, getIsDmChannel, exportDb, exportDbRaw, newDb, newDbKey, keyExchange, groupKey, keyManager, channelManager, keyVisualizer, keyShare) {
  736. this.toggledOnStyle = document.createElement('style');
  737. this.toggledOnStyle.innerHTML = this.toggledOnCss;
  738.  
  739. this.menuBarStyle = document.createElement('style');
  740. this.menuBarStyle.innerHTML = this.menuBarCss;
  741. document.head.appendChild(this.menuBarStyle);
  742.  
  743. this.keySelect = document.createElement('div');
  744. this.keySelect.innerHTML = this.keySelectHtml;
  745. let keySelectSelected = this.keySelect.getElementsByClassName('SDC_SELECTED')[0];
  746. let keySelectDropdown = this.keySelect.getElementsByClassName('SDC_DROPDOWN')[0];
  747. let keySelectOptions = this.keySelect.getElementsByClassName('SDC_OPTIONS')[0];
  748.  
  749. this.menuWrapper = document.createElement('div');
  750. this.menuWrapper.innerHTML = this.menuHtml;
  751. document.body.appendChild(this.menuWrapper);
  752. let menu = this.menuWrapper.getElementsByClassName('SDC_MENU')[0];
  753. let menuFocus = this.menuWrapper.getElementsByClassName('SDC_FOCUS')[0];
  754. let menuDmGroup = this.menuWrapper.getElementsByClassName('SDC_DMMENU')[0];
  755. let menuNondmGroup = this.menuWrapper.getElementsByClassName('SDC_GROUPMENU')[0];
  756.  
  757. this.toggleOnButton = document.createElement('div');
  758. this.toggleOnButton.innerHTML = this.toggleOnButtonHtml;
  759. this.toggleOnButton.onclick = toggle;
  760.  
  761. this.toggleOffButton = document.createElement('div');
  762. this.toggleOffButton.innerHTML = this.toggleOffButtonHtml;
  763. this.toggleOffButton.onclick = toggle;
  764.  
  765. Utils.AttachEventToClass(menu, 'SDC_EXPORTDB', 'mousedown', (e) => e.ctrlKey ? exportDbRaw() : exportDb());
  766. Utils.AttachEventToClass(menu, 'SDC_NEWDB', 'mousedown', () => newDb());
  767. Utils.AttachEventToClass(menu, 'SDC_NEWDBKEY', 'mousedown', () => newDbKey());
  768. Utils.AttachEventToClass(menu, 'SDC_KEYEXCHANGE', 'mousedown', () => keyExchange());
  769. Utils.AttachEventToClass(menu, 'SDC_NEWKEY', 'mousedown', () => groupKey());
  770. Utils.AttachEventToClass(menu, 'SDC_KEYMANAGER', 'mousedown', () => keyManager());
  771. Utils.AttachEventToClass(menu, 'SDC_CHMANAGER', 'mousedown', () => channelManager());
  772. Utils.AttachEventToClass(menu, 'SDC_KEYART', 'mousedown', () => keyVisualizer());
  773. Utils.AttachEventToClass(menu, 'SDC_KEYSHARE', 'mousedown', () => keyShare());
  774.  
  775. const dropdownOn = () => {
  776. let keys = getKeys();
  777. keySelectOptions.innerText = "";
  778.  
  779. for(let key of keys) {
  780. let option = document.createElement('a');
  781. option.innerText = key.descriptor;
  782. if(key.selected)
  783. option.style.backgroundColor = "rgba(0,0,0,.2)";
  784. else
  785. option.onmousedown = () => selectKey(key);
  786. keySelectOptions.appendChild(option);
  787. };
  788. keySelectOptions.style.visibility = 'visible';
  789. };
  790. const dropdownOff = () => {
  791. keySelectOptions.style.visibility = 'hidden';
  792. };
  793. keySelectDropdown.onclick = () => {
  794. if(keySelectDropdown.checked) dropdownOn();
  795. else dropdownOff();
  796. };
  797. keySelectDropdown.onblur = () => {
  798. if(!keySelectDropdown.matches(':active')) {
  799. keySelectDropdown.checked = false;
  800. dropdownOff()
  801. }
  802. };
  803.  
  804. this.toggleOnButton.oncontextmenu = this.toggleOffButton.oncontextmenu = (e) => {
  805. e.preventDefault();
  806. menu.style.left = e.clientX+"px";
  807. menu.style.top = e.clientY+"px";
  808. menu.style.visibility = 'visible';
  809. menuFocus.focus();
  810. };
  811. menuFocus.onblur = () => { menu.style.visibility = 'hidden' };
  812.  
  813. this.Update = function(isRetry) {
  814. let titleElement = document.body.getElementsByClassName('titleText-3X-zRE')[0];
  815. if(titleElement == null) {
  816. if(!isRetry) this.retries = 0;
  817. if(this.retries < 10) {
  818. this.retries++;
  819. this.retryTimeout = setTimeout(() => { this.Update(true) }, this.retries * 400);
  820. }
  821. return;
  822. }
  823. clearTimeout(this.retryTimeout);
  824.  
  825. let styleEnabled = document.head.contains(this.toggledOnStyle);
  826. let keySelectEnabled = document.body.contains(this.keySelect);
  827. let toggleOnEnabled = document.body.contains(this.toggleOnButton);
  828. let toggleOffEnabled = document.body.contains(this.toggleOffButton);
  829. let toggledOn = getToggleStatus();
  830.  
  831. keySelectSelected.innerText = getCurrentKeyDescriptor();
  832. if(!keySelectEnabled) titleElement.insertAdjacentElement('afterend', this.keySelect);
  833.  
  834. if(toggledOn) {
  835. if(!styleEnabled) document.head.appendChild(this.toggledOnStyle);
  836. if(toggleOnEnabled) this.toggleOnButton.parentNode.removeChild(this.toggleOnButton);
  837. if(!toggleOffEnabled) titleElement.insertAdjacentElement('afterend', this.toggleOffButton);
  838. }
  839. else {
  840. if(styleEnabled) document.head.removeChild(this.toggledOnStyle);
  841. if(toggleOffEnabled) this.toggleOffButton.parentNode.removeChild(this.toggleOffButton);
  842. if(!toggleOnEnabled) titleElement.insertAdjacentElement('afterend', this.toggleOnButton);
  843. }
  844.  
  845. if(getIsDmChannel()) {
  846. menuDmGroup.style.display = null;
  847. menuNondmGroup.style.display = 'none';
  848. }
  849. else {
  850. menuDmGroup.style.display = 'none';
  851. menuNondmGroup.style.display = null;
  852. }
  853. };
  854. this.Update();
  855. },
  856. Remove: function() {
  857. if(document.head.contains(this.toggledOnStyle))
  858. document.head.removeChild(this.toggledOnStyle);
  859. if(document.head.contains(this.menuBarStyle))
  860. document.head.removeChild(this.menuBarStyle);
  861. if(document.body.contains(this.toggleOnButton))
  862. document.body.removeChild(this.toggleOnButton);
  863. if(document.body.contains(this.toggleOffButton))
  864. document.body.removeChild(this.toggleOffButton);
  865. if(document.body.contains(this.menuWrapper))
  866. document.body.removeChild(this.menuWrapper);
  867. }
  868. };
  869. const PopupManager = {
  870. Inject: function() {
  871. let wrapper = document.createElement('div');
  872.  
  873. document.body.appendChild(wrapper);
  874. this.domElement = wrapper;
  875. },
  876. Update: function() {
  877. if(!document.body.contains(this.domElement))
  878. document.body.appendChild(this.domElement);
  879. },
  880. New: function(message, okCallback, cancelCallback) {
  881. let popup = document.createElement('div');
  882. popup.innerHTML = `<div class="sdc sdc-window" style="width:280px;position:fixed;right:50px;bottom:60px">
  883. <div style="margin:20px;word-break:break-all;word-break:break-word">
  884. ${HtmlEscape(message)}
  885. </div>
  886. <div class="sdc-footer" style="padding:10px">
  887. <button type="button" class="SDC_CANCEL sdc-lnkbtn" style="min-width:96px"><p>Cancel</p></button>
  888. <button type="button" class="SDC_OK sdc-btn" style="min-width:96px">OK</button>
  889. </div>
  890. </div>
  891. <button type="button" class="SDC_FOCUS sdc-hidden"></button>`;
  892. Utils.AttachEventToClass(popup, 'SDC_OK', 'click', () => {
  893. this.domElement.removeChild(popup);
  894. okCallback();
  895. });
  896. Utils.AttachEventToClass(popup, 'SDC_CANCEL', 'click', () => {
  897. this.domElement.removeChild(popup);
  898. if(cancelCallback) cancelCallback();
  899. });
  900. this.domElement.appendChild(popup);
  901. return popup;
  902. },
  903. NewPromise: function(message, timeout) {
  904. return new Promise((resolve) => {
  905. if(timeout > 0) {
  906. let cancelTimeout;
  907. let popup = this.New(message,
  908. () => { clearTimeout(cancelTimeout); resolve(true); },
  909. () => { clearTimeout(cancelTimeout); resolve(false); }
  910. );
  911.  
  912. cancelTimeout = setTimeout(() => { this.domElement.removeChild(popup); resolve(false); }, timeout);
  913. }
  914. else {
  915. this.New(message, () => resolve(true), () => resolve(false));
  916. }
  917. });
  918. },
  919. Remove: function() {
  920. if(document.body.contains(this.domElement))
  921. document.body.removeChild(this.domElement);
  922. }
  923. };
  924. const KeyVisualizerWindow = {
  925. EmojiHash: function(canvas, hashBuffer) {
  926. var ctx;
  927.  
  928. function drawEmoji(emoji, x, y, size, rotation) {
  929. ctx.save();
  930. ctx.translate(x, y);
  931. if(rotation) ctx.rotate(rotation * Math.PI / 180);
  932. ctx.scale(size, size);
  933. ctx.fillText(emoji, 0, 0);
  934. ctx.restore();
  935. }
  936. function drawRectangle(color, x, y, width, height, rotation) {
  937. ctx.save();
  938. ctx.translate(x, y);
  939. if(rotation) ctx.rotate(rotation * Math.PI / 180);
  940. ctx.fillStyle = color;
  941. ctx.fillRect(-width/2, -height/2, width, height);
  942. ctx.restore();
  943. }
  944. var uintOffset = 0;
  945. var inUintOffset = 0;
  946. var uints = new DataView(hashBuffer);
  947. function popBits(count) { //max 32
  948. let bits;
  949. let newInUintOffset = inUintOffset + count;
  950. if(newInUintOffset > 31) {
  951. bits = uints.getUint32(uintOffset) & ~(~0 << (32 - inUintOffset));
  952. inUintOffset = newInUintOffset - 32;
  953. uintOffset += 4;
  954. if(uintOffset === hashBuffer.byteLength) uintOffset = 0;
  955. if(inUintOffset !== 0) {
  956. bits = (bits << inUintOffset) | (uints.getUint32(uintOffset) >>> (32 - inUintOffset));
  957. }
  958. }
  959. else {
  960. bits = (uints.getUint32(uintOffset) >>> (32 - newInUintOffset)) & ~(~0 << count);
  961. inUintOffset = newInUintOffset;
  962. }
  963.  
  964. return bits;
  965. }
  966.  
  967. ctx = canvas.getContext('2d');
  968. ctx.textAlign = "center";
  969. ctx.textBaseline = "middle";
  970. ctx.font = "1px sans-serif";
  971. ctx.translate(canvas.width / 2, canvas.height / 2);
  972. let scale = Math.min(canvas.width / 4, canvas.height / 3) / 100 * 4;
  973. ctx.scale(scale, scale);
  974.  
  975.  
  976. function drawSky() {
  977. let color;
  978. switch(popBits(3)) {
  979. case 0: //purple
  980. color = "#a254d3";
  981. break;
  982. case 1: //rose
  983. color = "#f6c4df";
  984. break;
  985. case 2: //blue
  986. color = "#2b7cb3";
  987. break;
  988. case 3: //darkblue
  989. color = "#154167";
  990. break;
  991. default: //lightblue
  992. color = "#9bcfea";
  993. }
  994.  
  995. drawRectangle(color, 0, 0, 1000, 1000);
  996. }
  997.  
  998. const airObjects = "\u{2600}\u{FE0F}|\u{1F311}|\u{2601}\u{FE0F}|\u{1F329}\u{FE0F}|\u{1F328}\u{FE0F}|\u{1F409}|\u{1F987}|\u{1F985}|\u{1F54A}\u{FE0F}|\u{1F41D}|\u{1F98B}|\u{1F343}|\u{1F681}|\u{2708}\u{FE0F}|\u{1F6E9}\u{FE0F}|\u{1F680}|\u{1F6F8}|\u{1F6F0}\u{FE0F}|\u{1F31C}|\u{2604}\u{FE0F}|\u{1F31F}|\u{2744}\u{FE0F}|\u{26A1}|\u{2728}|\u{1F388}|\u{1F47E}|\u{1F47B}".split("|");
  999. function drawAirObject(x, y, size, rotation) {
  1000. if(popBits(1)) return;
  1001.  
  1002. drawEmoji(airObjects[popBits(5) % 27], x, y, size, rotation); //32-27 first 5 have double probability
  1003. }
  1004. const handEmojis = "\u{1F44C}|\u{1F595}|\u{270C}|\u{1F44A}|\u{1F44D}|\u{1F44E}|\u{1F44B}|\u{1F448}|\u{1F449}|\u{261D}|\u{1F446}|\u{1F447}|\u{1F91E}|\u{1F596}|\u{1F91F}|\u{1F919}|\u{1F590}|\u{270B}|\u{270D}|\u{1F4B0}|\u{1F480}|\u{1F4A9}|\u{1F4A3}|\u{1F94A}|\u{1F3A8}|\u{1F3BA}|\u{1F4F7}|\u{1F3A5}|\u{1F526}|\u{1F4BC}|\u{1F50E}|\u{1F4D5}|\u{2709}\u{FE0F}|\u{270F}\u{FE0F}|\u{1F4CF}|\u{1F52B}|\u{1F528}|\u{1F527}|\u{1F489}|\u{1F6AC}|\u{1F5DD}\u{FE0F}|\u{1F4DE}|\u{1F3AE}|\u{1F393}|\u{1F346}".split("|");
  1005. function drawHandEmoji(x, y, size, rotation) {
  1006. drawEmoji(handEmojis[popBits(6) % 45], x, y, size, rotation);
  1007. }
  1008. const bodyEmojis = "\u{1F454}|\u{1F455}|\u{1F94B}|\u{1F3BD}|\u{1F9E5}|\u{1F457}|\u{1F458}|\u{1F459}|\u{1F45A}".split("|");
  1009. const headEmojis = "\u{1F914}|\u{1F602}|\u{1F624}|\u{1F62D}|\u{1F60B}|\u{1F917}|\u{1F60F}|\u{1F633}|\u{1F606}|\u{1F928}|\u{1F60E}|\u{1F644}|\u{1F611}|\u{1F60D}|\u{1F618}|\u{1F642}|\u{1F929}|\u{1F636}|\u{1F623}|\u{1F62E}|\u{1F910}|\u{1F62B}|\u{1F634}|\u{1F61C}|\u{1F924}|\u{1F612}|\u{1F643}|\u{1F911}|\u{2639}\u{FE0F}|\u{1F601}|\u{1F616}|\u{1F631}|\u{1F92F}|\u{1F62C}|\u{1F92A}|\u{1F635}|\u{1F621}|\u{1F912}|\u{1F922}|\u{1F92E}|\u{1F927}|\u{1F607}|\u{1F920}|\u{1F921}|\u{1F925}|\u{1F923}|\u{1F92B}|\u{1F9D0}|\u{1F913}|\u{1F608}|\u{1F4A9}|\u{1F916}|\u{1F47D}|\u{1F480}|\u{1F47A}|\u{1F476}|\u{1F469}|\u{1F468}|\u{1F474}|\u{1F475}|\u{1F934}|\u{1F473}|\u{1F472}|\u{1F9D4}|\u{1F471}|\u{1F385}|\u{1F435}|\u{1F436}|\u{1F43A}|\u{1F98A}|\u{1F431}|\u{1F981}|\u{1F42F}|\u{1F434}|\u{1F984}|\u{1F993}|\u{1F42E}|\u{1F437}|\u{1F417}|\u{1F42D}|\u{1F439}|\u{1F430}|\u{1F43B}|\u{1F438}|\u{1F432}|\u{1F414}|\u{1F383}".split("|");
  1010. function drawPerson(x, y, size) {
  1011. ctx.save();
  1012. ctx.translate(x, y);
  1013. let scale = size / 100;
  1014. ctx.scale(scale, scale);
  1015.  
  1016. drawRectangle("#000", 0, -10, 4, 50); //body
  1017. drawRectangle("#000", -9, 26, 4, 30, 35); //legs
  1018. drawRectangle("#000", 9, 26, 4, 30, -35);
  1019.  
  1020. switch(popBits(2)) { //left arm
  1021. case 0: //up
  1022. drawRectangle("#000", -12, -23, 4, 30, -55);
  1023. drawHandEmoji(-26, -34, 15);
  1024. break;
  1025. case 1: //middle
  1026. drawRectangle("#000", -15, -15, 30, 4);
  1027. drawHandEmoji(-30, -18, 15);
  1028. break;
  1029. default: //down
  1030. drawRectangle("#000", -12, -8, 4, 30, 55);
  1031. drawHandEmoji(-26, -4, 15);
  1032. }
  1033. switch(popBits(2)) { //right arm
  1034. case 0: //up
  1035. drawRectangle("#000", 12, -23, 4, 30, 55);
  1036. drawHandEmoji(26, -34, 15);
  1037. break;
  1038. case 1: //middle
  1039. drawRectangle("#000", 15, -15, 30, 4);
  1040. drawHandEmoji(30, -18, 15);
  1041. break;
  1042. default: //down
  1043. drawRectangle("#000", 12, -8, 4, 30, -55);
  1044. drawHandEmoji(26, -4, 15);
  1045. }
  1046.  
  1047. drawEmoji(bodyEmojis[popBits(4) % 9], 0, -5, 28); //chest
  1048. drawEmoji(headEmojis[popBits(12) % 87], 0, -35, 25); //face 4096 % 87 = first 7 have increased chance
  1049.  
  1050. if(popBits(1)) drawEmoji("\u{1F45F}", -13, 35, 15); //left foot
  1051. if(popBits(1)) drawEmoji("\u{1F45F}", 19, 35, 15); //right foot
  1052.  
  1053. ctx.restore();
  1054. }
  1055. const tableObjects = "\u{1F35E}|\u{1F453}|\u{1F6CD}\u{FE0F}|\u{1F48E}|\u{1F34E}|\u{1F347}|\u{1F349}|\u{1F34A}|\u{1F34B}|\u{1F34C}|\u{1F34F}|\u{1F350}|\u{1F351}|\u{1F352}|\u{1F353}|\u{1F95D}|\u{1F345}|\u{1F965}|\u{1F954}|\u{1F955}|\u{1F33D}|\u{1F336}\u{FE0F}|\u{1F952}|\u{1F966}|\u{1F344}|\u{1F95C}|\u{1F950}|\u{1F968}|\u{1F95E}|\u{1F9C0}|\u{1F356}|\u{1F357}|\u{1F969}|\u{1F953}|\u{1F354}|\u{1F35F}|\u{1F355}|\u{1F32D}|\u{1F96A}|\u{1F32E}|\u{1F95A}|\u{1F372}|\u{1F963}|\u{1F957}|\u{1F37F}|\u{1F96B}|\u{1F371}|\u{1F359}|\u{1F363}|\u{1F961}|\u{1F366}|\u{1F369}|\u{1F382}|\u{1F967}|\u{1F36B}|\u{1F36E}|\u{1F36F}|\u{1F37C}|\u{2615}|\u{1F377}|\u{1F378}|\u{1F37A}|\u{1F52A}|\u{1F3FA}|\u{1F6CE}\u{FE0F}|\u{23F0}|\u{231B}|\u{1F302}|\u{1F381}|\u{1F3C6}|\u{26BD}|\u{1F3B3}|\u{1F52E}|\u{1F579}\u{FE0F}|\u{1F3B2}|\u{1F4E2}|\u{1F4FB}|\u{1F3A7}|\u{1F3A4}|\u{260E}\u{FE0F}|\u{1F4BB}|\u{1F5A8}\u{FE0F}|\u{1F4FA}|\u{1F56F}\u{FE0F}|\u{1F4E6}|\u{1F52D}|\u{2697}\u{FE0F}|\u{1F52C}|\u{2696}\u{FE0F}|\u{1F964}|\u{1F4DA}".split("|");
  1056. function drawTableObject(x, y, size, rotation) {
  1057. if(popBits(1)) return;
  1058.  
  1059. drawEmoji(tableObjects[popBits(12) % 91], x, y, size, rotation);
  1060. }
  1061. function drawTable(x, y, size, rotation) {
  1062. ctx.save();
  1063. ctx.translate(x, y);
  1064. let scale = size / 100;
  1065. ctx.scale(scale, scale);
  1066.  
  1067. drawRectangle("#999", 0, 33, 4, 34);
  1068. drawRectangle("#f00", 0, 14, 70, 4);
  1069.  
  1070. drawTableObject(-25, 2, 20);
  1071. drawTableObject(-3, 2, 20);
  1072. drawTableObject(25, 2, 20);
  1073.  
  1074. ctx.restore();
  1075. }
  1076. const groundObjects = "\u{1F409}|\u{1F4A9}|\u{1F46F}|\u{1F46B}|\u{1F6B6}\u{1F3FB}|\u{1F3C3}\u{1F3FB}|\u{1F483}\u{1F3FB}|\u{1F57A}\u{1F3FB}|\u{1F9D8}\u{1F3FB}|\u{1F574}\u{1F3FB}|\u{1F93A}|\u{1F3C7}\u{1F3FB}|\u{1F3CC}\u{1F3FB}|\u{26F9}\u{1F3FB}|\u{1F3CB}\u{1F3FB}|\u{1F6B4}\u{1F3FB}|\u{1F938}\u{1F3FB}|\u{1F93C}|\u{1F93E}\u{1F3FB}|\u{1F412}|\u{1F98D}|\u{1F415}|\u{1F429}|\u{1F408}|\u{1F405}|\u{1F406}|\u{1F40E}|\u{1F98C}|\u{1F402}|\u{1F404}|\u{1F416}|\u{1F411}|\u{1F410}|\u{1F42A}|\u{1F418}|\u{1F98F}|\u{1F401}|\u{1F400}|\u{1F407}|\u{1F43F}\u{FE0F}|\u{1F994}|\u{1F983}|\u{1F413}|\u{1F427}|\u{1F424}|\u{1F426}|\u{1F986}|\u{1F989}|\u{1F40A}|\u{1F422}|\u{1F98E}|\u{1F40D}|\u{1F995}|\u{1F996}|\u{1F40C}|\u{1F41B}|\u{1F41C}|\u{1F339}|\u{1F940}|\u{1F33B}|\u{1F33C}|\u{1F337}|\u{1F331}|\u{1F332}|\u{1F333}|\u{1F334}|\u{1F335}|\u{1F33E}|\u{1F340}|\u{26E9}\u{FE0F}|\u{26F2}|\u{1F3AA}|\u{1F6E2}\u{FE0F}|\u{1F6F5}|\u{1F6B2}|\u{1F6F4}|\u{1F38F}|\u{1F945}|\u{1F5D1}\u{FE0F}|\u{1F5FF}|\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}|\u{1F6A9}|\u{1F3C1}|\u{1F3F4}|\u{1F3F3}\u{FE0F}".split("|");
  1077. function drawGroundObject(x, y, size, rotation) {
  1078. switch(popBits(2)) {
  1079. case 0:
  1080. drawPerson(x, y, size, rotation);
  1081. break;
  1082. case 1:
  1083. drawTable(x, y, size, rotation);
  1084. break;
  1085. case 2:
  1086. drawEmoji(groundObjects[popBits(8) % 85], x, y, size / 2, rotation);
  1087. break;
  1088. }
  1089. }
  1090.  
  1091.  
  1092. switch(popBits(2)) {
  1093. case 0: { //park
  1094. drawSky();
  1095. drawRectangle("#5ce64e", 0, 50, 200, 100, 1);
  1096. drawAirObject(-40, -27, 8);
  1097. drawAirObject(-16, -30, 10);
  1098. drawAirObject(17, -28, 9);
  1099. drawAirObject(41, -20, 10);
  1100. drawGroundObject(-37, 20, 30);
  1101. drawGroundObject(-10, 10, 23);
  1102. drawGroundObject(10, 0, 20);
  1103. drawGroundObject(15, 25, 20);
  1104. drawGroundObject(35, 20, 26);
  1105. } break;
  1106. case 1: { //beach
  1107. drawSky();
  1108. drawRectangle("#e5e886", 0, 50, 200, 100);
  1109. drawRectangle("#3cc", 0, 0, 200, 10, -.5);
  1110. drawAirObject(-40, -27, 8);
  1111. drawAirObject(-16, -30, 10);
  1112. drawAirObject(17, -28, 9);
  1113. drawAirObject(41, -20, 10);
  1114. drawGroundObject(-37, 20, 30);
  1115. drawGroundObject(-10, 10, 23);
  1116. drawGroundObject(10, 0, 20);
  1117. drawGroundObject(15, 25, 20);
  1118. drawGroundObject(35, 20, 26);
  1119.  
  1120. } break;
  1121. case 2: { //campfire
  1122. drawSky();
  1123. drawRectangle("#49be3d", 0, 50, 200, 100);
  1124. drawEmoji('\u{1F525}', 0, 10, 20);
  1125. drawRectangle("#333", 0, 20, 15, 5);
  1126. drawAirObject(-42, -20, 8);
  1127. drawAirObject(-25, -25, 10);
  1128. drawAirObject(22, -28, 9);
  1129. drawAirObject(41, -20, 10);
  1130. drawGroundObject(-37, 20, 30);
  1131. drawGroundObject(-13, 20, 23);
  1132. drawGroundObject(-35, -5, 20);
  1133. drawGroundObject(15, 25, 20);
  1134. drawGroundObject(35, 20, 26);
  1135.  
  1136. } break;
  1137. case 3: { //mountainslide
  1138. drawSky();
  1139. drawRectangle("#eff", 0, 50, 200, 100, -30);
  1140. drawAirObject(-42, -10, 8);
  1141. drawAirObject(-40, -25, 10);
  1142. drawAirObject(-15, -30, 9);
  1143. drawAirObject(15, -30, 10);
  1144. drawGroundObject(-37, 20, 30);
  1145. drawGroundObject(-10, 10, 23);
  1146. drawGroundObject(20, 0, 20);
  1147. drawGroundObject(15, 25, 20);
  1148. drawGroundObject(35, 20, 26);
  1149.  
  1150. } break;
  1151. }
  1152.  
  1153. },
  1154. html: `<div class="sdc">
  1155. <div class="SDC_CLOSE sdc-cover"></div>
  1156. <div class="sdc-overlay">
  1157. <div class="sdc-window">
  1158. <div style="margin:20px">
  1159. <h4>Key Visualizer v1.1</h4>
  1160. </div>
  1161. <a class="SDC_CLOSE sdc-close"></a>
  1162. <canvas class="SDC_ART" width="600" height="450"></canvas>
  1163. <div class="sdc-footer">
  1164. <button type="button" class="SDC_CLOSE sdc-btn" style="min-width:96px">Close</button>
  1165. </div>
  1166. </div>
  1167. </div>
  1168. </div>`,
  1169. Show: function(buffer) {
  1170. let wrapper = document.createElement('div');
  1171. wrapper.innerHTML = this.html;
  1172.  
  1173. Utils.AttachEventToClass(wrapper, 'SDC_CLOSE', 'click', () => {
  1174. this.Remove();
  1175. });
  1176.  
  1177. let canvas = wrapper.getElementsByClassName('SDC_ART')[0];
  1178. this.EmojiHash(canvas, buffer);
  1179.  
  1180. document.body.appendChild(wrapper);
  1181. this.domElement = wrapper;
  1182. },
  1183. Remove: function() {
  1184. if(document.body.contains(this.domElement))
  1185. document.body.removeChild(this.domElement);
  1186. }
  1187. };
  1188.  
  1189.  
  1190. var Discord;
  1191. var Utils = {
  1192. Log: (message) => { console.log(`%c[⡒⠮⢑⠂⡐⣉⢄] %c${message}`, `color:${BaseColor};font-weight:bold`, "") },
  1193. Warn: (message) => { console.warn(`%c[⡒⠮⢑⠂⡐⣉⢄] %c${message}`, `color:${BaseColor};font-weight:bold`, "") },
  1194. Error: (message) => { console.error(`%c[⡒⠮⢑⠂⡐⣉⢄] %c${message}`, `color:${BaseColor};font-weight:bold`, "") }
  1195. };
  1196. var DataBase;
  1197. var Cache;
  1198. var Blacklist;
  1199. var Patcher;
  1200.  
  1201. function Init(nonInvasive)
  1202. {
  1203. Discord = { window: (typeof(unsafeWindow) !== 'undefined') ? unsafeWindow : window };
  1204.  
  1205. if(Discord.window.webpackJsonp == null) { if(!nonInvasive) Utils.Error("Webpack not found."); return 0; }
  1206.  
  1207. const webpackExports = typeof(Discord.window.webpackJsonp) === 'function' ?
  1208. Discord.window.webpackJsonp(
  1209. [],
  1210. { '__extra_id__': (module, _export_, req) => { _export_.default = req } },
  1211. [ '__extra_id__' ]
  1212. ).default :
  1213. Discord.window.webpackJsonp.push( [
  1214. [],
  1215. { '__extra_id__': (_module_, exports, req) => { _module_.exports = req } },
  1216. [ [ '__extra_id__' ] ] ]
  1217. );
  1218.  
  1219. delete webpackExports.m['__extra_id__'];
  1220. delete webpackExports.c['__extra_id__'];
  1221.  
  1222. const findModule = (filter, nonInvasive) => {
  1223. for(let i in webpackExports.c) {
  1224. if(webpackExports.c.hasOwnProperty(i)) {
  1225. let m = webpackExports.c[i].exports;
  1226.  
  1227. if(!m) continue;
  1228.  
  1229. if(m.__esModule && m.default) m = m.default;
  1230.  
  1231. if(filter(m)) return m;
  1232. }
  1233. }
  1234.  
  1235. if (!nonInvasive) {
  1236. Utils.Warn("Couldn't find module in existing cache. Loading all modules.");
  1237.  
  1238. for (let i = 0; i < webpackExports.m.length; i++) {
  1239. try {
  1240. let m = webpackExports(i);
  1241.  
  1242. if(!m) continue;
  1243.  
  1244. if(m.__esModule && m.default) m = m.default;
  1245.  
  1246. if(filter(m)) return m;
  1247. }
  1248. catch (e) { }
  1249. }
  1250.  
  1251. Utils.Warn("Cannot find module.");
  1252. }
  1253.  
  1254. return null;
  1255. };
  1256.  
  1257. const findModuleByUniqueProperties = (propNames, nonInvasive) => findModule(module => propNames.every(prop => module[prop] !== undefined), nonInvasive);
  1258.  
  1259. let modules = {};
  1260.  
  1261. modules.MessageQueue = findModuleByUniqueProperties([ 'enqueue', 'handleSend', 'handleResponse' ], nonInvasive);
  1262. if(modules.MessageQueue == null) { if(!nonInvasive) Utils.Error("MessageQueue not found."); return 0; }
  1263.  
  1264. modules.MessageDispatcher = findModuleByUniqueProperties( [ 'dispatch', 'maybeDispatch', 'dirtyDispatch' ], nonInvasive);
  1265. if(modules.MessageDispatcher == null) { if(!nonInvasive) Utils.Error("MessageDispatcher not found."); return 0; }
  1266.  
  1267. modules.UserCache = findModuleByUniqueProperties( [ 'getUser', 'getUsers', 'getCurrentUser' ], nonInvasive);
  1268. if(modules.UserCache == null) { if(!nonInvasive) Utils.Error("UserCache not found."); return 0; }
  1269.  
  1270. modules.ChannelCache = findModuleByUniqueProperties( [ 'getChannel', 'getChannels', 'getDMFromUserId' ], nonInvasive);
  1271. if(modules.ChannelCache == null) { if(!nonInvasive) Utils.Error("ChannelCache not found."); return 0; }
  1272.  
  1273. modules.SelectedChannelStore = findModuleByUniqueProperties( [ 'getChannelId', 'getVoiceChannelId', 'getLastSelectedChannelId' ], nonInvasive);
  1274. if(modules.SelectedChannelStore == null) { if(!nonInvasive) Utils.Error("SelectedChannelStore not found."); return 0; }
  1275.  
  1276. modules.GuildCache = findModuleByUniqueProperties( [ 'getGuild', 'getGuilds' ], nonInvasive);
  1277. if(modules.GuildCache == null) { if(!nonInvasive) Utils.Error("GuildCache not found."); return 0; }
  1278.  
  1279. modules.FileUploader = findModuleByUniqueProperties( [ 'upload', 'cancel', 'instantBatchUpload' ], nonInvasive);
  1280. if(modules.FileUploader == null) { if(!nonInvasive) Utils.Error("FileUploader not found."); return 0; }
  1281.  
  1282. modules.PermissionEvaluator = findModuleByUniqueProperties( [ 'can', 'computePermissions', 'canEveryone' ], nonInvasive);
  1283. if(modules.PermissionEvaluator == null) { if(!nonInvasive) Utils.Error("PermissionEvaluator not found."); return 0; }
  1284.  
  1285. modules.RelationshipStore = findModuleByUniqueProperties( [ 'isFriend', 'isBlocked', 'getFriendIDs' ], nonInvasive);
  1286. if(modules.RelationshipStore == null) { if(!nonInvasive) Utils.Error("RelationshipStore not found."); return 0; }
  1287.  
  1288. modules.PrivateChannelManager = findModuleByUniqueProperties( [ 'openPrivateChannel', 'ensurePrivateChannel', 'closePrivateChannel' ], nonInvasive);
  1289. if(modules.PrivateChannelManager == null) { if(!nonInvasive) Utils.Error("PrivateChannelManager not found."); return 0; }
  1290.  
  1291. //modules.MessageCache = findModuleByUniqueProperties([ '_channelMessages', 'getOrCreate', 'clearCache' ], nonInvasive);
  1292. //if(modules.MessageCache == null) { if(!nonInvasive) Utils.Error("MessageCache not found."); return 0; }
  1293.  
  1294. Discord.modules = modules;
  1295.  
  1296. Object.assign(Utils, {
  1297.  
  1298. StorageSave:
  1299. (typeof(GM_getValue) !== 'undefined' && typeof(GM_setValue) !== 'undefined') ? (key, value) => new Promise((resolve) => {
  1300. resolve(GM_setValue(key, JSON.stringify(value)));
  1301. })
  1302. : (typeof(chrome) !== 'undefined' && chrome.storage != null) ? (key, value) => new Promise((resolve) => {
  1303. chrome.storage.sync.set({key: value}, resolve);
  1304. })
  1305. : (key, value) => new Promise((resolve) => {
  1306. resolve(SavedLocalStorage.setItem(key, JSON.stringify(value)));
  1307. }),
  1308. StorageLoad:
  1309. (typeof(GM_getValue) !== 'undefined' && typeof(GM_setValue) !== 'undefined') ? (key) => new Promise((resolve) => {
  1310. let jsonValue = GM_getValue(key);
  1311. if(jsonValue == null) resolve(null);
  1312. resolve(JSON.parse(jsonValue));
  1313. })
  1314. : (typeof(chrome) !== 'undefined' && chrome.storage != null) ? (key) => new Promise((resolve) => {
  1315. chrome.storage.sync.get(key, (result) => resolve(result[key]));
  1316. })
  1317. : (key) => new Promise((resolve) => {
  1318. let jsonValue = SavedLocalStorage.getItem(key);
  1319. if(jsonValue == null) resolve(null);
  1320. resolve(JSON.parse(jsonValue));
  1321. }),
  1322.  
  1323. Sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
  1324. ReadFile: (file) => new Promise((resolve, reject) => {
  1325. let fileReader = new FileReader();
  1326. fileReader.onload = () => resolve(fileReader.result);
  1327. fileReader.onerror = () => reject(fileReader.error);
  1328. fileReader.readAsArrayBuffer(file);
  1329. }),
  1330. DownloadFile:
  1331. (typeof(GM_xmlhttpRequest) !== 'undefined') ? (url) => new Promise((resolve, reject) => {
  1332. GM_xmlhttpRequest({
  1333. method: 'GET',
  1334. url,
  1335. responseType: 'arraybuffer',
  1336. onload: (result) => resolve(result.response),
  1337. onerror: reject
  1338. })
  1339. })
  1340. : (typeof(require) !== 'undefined') ? function(url) { return new Promise ((resolve, reject) => {
  1341. require('https').get(url, (response) => {
  1342. let data = [];
  1343. response.on('data', (chunk) => data.push(chunk));
  1344. response.on('end', () => resolve(this.ConcatBuffers(data)));
  1345. }).on('error', reject);
  1346. })}
  1347. : (url) => new Promise((resolve, reject) => {
  1348. let xhr = new XMLHttpRequest();
  1349. xhr.responseType = 'arraybuffer';
  1350. xhr.onload = () => resolve(xhr.response);
  1351. xhr.onerror = reject;
  1352. xhr.open('GET', url);
  1353. xhr.withCredentials = true;
  1354. xhr.send();
  1355. }),
  1356.  
  1357. DownloadBlob: (filename, blob) => {
  1358. let url = URL.createObjectURL(blob);
  1359. let a = document.createElement('a');
  1360. a.href = url;
  1361. a.download = filename;
  1362. a.click();
  1363. URL.revokeObjectURL(url);
  1364. },
  1365.  
  1366. TryCompress: (buffer) => new Promise((resolve) => {
  1367. let length = buffer.byteLength;
  1368. if(length < 1600) return resolve(buffer);
  1369. let bufferView = new DataView(buffer);
  1370. let pixelCount = Math.ceil(length / 3);
  1371. const maxSafePngWidth = 32767;
  1372. let lines = Math.ceil(pixelCount / maxSafePngWidth);
  1373. let width = Math.ceil(pixelCount / lines);
  1374. let fullPixelCount = lines * width;
  1375. let pixelBytes = new Uint8ClampedArray(fullPixelCount * 4);
  1376. let pixels = new DataView(pixelBytes.buffer);
  1377. let pixelMaxIndex = pixelCount - 1;
  1378. let remainingBytes = length - (pixelMaxIndex * 3);
  1379. let i = pixelMaxIndex;
  1380. while(i--) {
  1381. let pixel = bufferView.getUint32(i * 3, true) | 0xFF000000; //3 bytes per pixel, alpha is 255
  1382. pixels.setUint32(i * 4, pixel, true);
  1383. }
  1384. if(remainingBytes === 3) {
  1385. let pixel = bufferView.getUint16(length - 3, true) | (bufferView.getUint8(length - 1) << 16) | 0xFF000000;
  1386. pixels.setUint32(pixelMaxIndex * 4, pixel, true);
  1387. }
  1388. else if(remainingBytes === 2) {
  1389. let pixel = bufferView.getUint16(length - 2, true) | 0xFF000000;
  1390. pixels.setUint32(pixelMaxIndex * 4, pixel, true);
  1391. }
  1392. else if(remainingBytes === 1) {
  1393. let pixel = bufferView.getUint8(length - 1) | 0xFF000000;
  1394. pixels.setUint32(pixelMaxIndex * 4, pixel, true);
  1395. }
  1396. let canvas = document.createElement('canvas');
  1397. canvas.width = width;
  1398. canvas.height = lines;
  1399. let ctx = canvas.getContext('2d');
  1400. ctx.putImageData(new ImageData(pixelBytes, width, lines), 0, 0);
  1401. let fileReader = new FileReader();
  1402. fileReader.onload = () => {
  1403. let buffer = fileReader.result;
  1404. let view = new DataView(buffer);
  1405. view.setUint16(0, 0x5DC, false); //signature: 05 DC
  1406. view.setUint32(2, length, true);
  1407. resolve(buffer)
  1408. }
  1409. canvas.toBlob(blob => fileReader.readAsArrayBuffer(blob), 'image/png');
  1410. }),
  1411. TryDecompress: async (buffer) => {
  1412. let bufferView = new DataView(buffer);
  1413. if(buffer.byteLength < 2 || bufferView.getUint16(0, false) !== 0x5DC) return buffer;
  1414. let length = bufferView.getUint32(2, true);
  1415. bufferView.setUint16(0, 0x8950, false);
  1416. bufferView.setUint32(2, 0x4E470D0A, false); //restore original PNG signature
  1417.  
  1418. let bitmap = await createImageBitmap(new Blob([buffer], {type:'image/png'}));
  1419. let canvas = document.createElement('canvas');
  1420. let ctx = canvas.getContext('2d');
  1421. let width = canvas.width = bitmap.width;
  1422. let height = canvas.height = bitmap.height;
  1423. ctx.drawImage(bitmap, 0, 0);
  1424. let pxbuffer = ctx.getImageData(0, 0, width, height).data.buffer;
  1425. let pxbufferView = new DataView(pxbuffer);
  1426. let pixelCount = Math.ceil(length / 3);
  1427. for(let i = 0; i < pixelCount; i++) {
  1428. pxbufferView.setUint32(i * 3, pxbufferView.getUint32(i * 4, true), true);
  1429. }
  1430. return pxbuffer.slice(0, length);
  1431. },
  1432.  
  1433. GetNonce: (window.BigInt != null) ? () => (BigInt(Date.now() - 14200704e5/*DISCORD_EPOCH*/) << BigInt(22)).toString() : () => Date.now().toString(),
  1434.  
  1435. FormatTime: (timestamp) => {
  1436. let timezoneOffset = new Date().getTimezoneOffset() * 60000;
  1437. let dateNow = new Date(Date.now() - timezoneOffset).toISOString().slice(0, 10);
  1438. let datetime = new Date(timestamp - timezoneOffset).toISOString();
  1439. let date = datetime.slice(0, 10);
  1440. let time = datetime.slice(11, 16);
  1441. return `${date === dateNow ? "Today at" : date} ${time}`;
  1442. },
  1443.  
  1444. Sha512: async (buffer) => await crypto.subtle.digest('SHA-512', buffer),
  1445. Sha512_128: async (buffer) => (await crypto.subtle.digest('SHA-512', buffer)).slice(0, 16),
  1446. Sha512_128str: async function(string) { return await this.Sha512_128(this.StringToUtf8Bytes(string)) },
  1447. Sha512_256: async (buffer) => (await crypto.subtle.digest('SHA-512', buffer)).slice(0, 32),
  1448. Sha512_256str: async function(string) { return await this.Sha512_256(this.StringToUtf8Bytes(string)) },
  1449.  
  1450. AesImportKey: async (buffer) => await crypto.subtle.importKey('raw', buffer, 'AES-CBC', false, ['encrypt','decrypt']),
  1451. AesEncrypt: async function(key, buffer) {
  1452. let initializationVector = this.GetRandomBytes(16);
  1453. let encryptedBuffer = await crypto.subtle.encrypt({name:'AES-CBC', iv:initializationVector}, key, buffer);
  1454. return this.ConcatBuffers([initializationVector, encryptedBuffer]);
  1455. },
  1456. AesDecrypt: async function(key, buffer) {
  1457. let initializationVector = buffer.slice(0, 16);
  1458. let encryptedBuffer = buffer.slice(16);
  1459. return await crypto.subtle.decrypt({name:'AES-CBC', iv:initializationVector}, key, encryptedBuffer);
  1460. },
  1461. AesEncryptString: async function(key, string) {
  1462. let bytes = this.StringToUtf8Bytes(string);
  1463. return await this.AesEncrypt(key, bytes);
  1464. },
  1465. AesDecryptString: async function(key, buffer) {
  1466. let bytes = await this.AesDecrypt(key, buffer);
  1467. return this.Utf8BytesToString(bytes);
  1468. },
  1469. AesEncryptCompressString: async function(key, string) {
  1470. let buffer = await this.TryCompress(this.StringToUtf8Bytes(string).buffer);
  1471. return await this.AesEncrypt(key, buffer);
  1472. },
  1473. AesDecryptDecompressString: async function(key, string) {
  1474. let buffer = await this.TryDecompress(await this.AesDecrypt(key, string));
  1475. return this.Utf8BytesToString(buffer);
  1476. },
  1477.  
  1478. DhGenerateKeys: async () => await crypto.subtle.generateKey({name:'ECDH', namedCurve:'P-521'}, true, ['deriveBits']),
  1479. DhImportPublicKey: async (buffer) => await crypto.subtle.importKey('raw', buffer, {name:'ECDH', namedCurve:'P-521'}, false, []),
  1480. DhImportPrivateKey: async (buffer) => await crypto.subtle.importKey('pkcs8', buffer, {name:'ECDH', namedCurve:'P-521'}, false, ['deriveBits']),
  1481. DhImportPrivateKeyFallback: async function(buffer) { return await crypto.subtle.importKey('jwk', JSON.parse(this.Utf8BytesToString(buffer)), {name:'ECDH', namedCurve:'P-521'}, false, ['deriveBits']) },
  1482. DhExportPublicKey: async (key) => await crypto.subtle.exportKey('raw', key),
  1483. DhExportPrivateKey: async (key) => await crypto.subtle.exportKey('pkcs8', key),
  1484. DhExportPrivateKeyFallback: async function(key) { return this.StringToUtf8Bytes(JSON.stringify(await crypto.subtle.exportKey('jwk', key))) },
  1485. DhGetSecret: async (privateKey, publicKey) => await crypto.subtle.deriveBits({name:'ECDH', namedCurve:'P-521', public:publicKey}, privateKey, 256),
  1486.  
  1487. utf8encoder: new TextEncoder(),
  1488. utf8decoder: new TextDecoder(),
  1489. StringToUtf8Bytes: function(string) { return this.utf8encoder.encode(string) },
  1490. StringToAsciiBytes: (string) => Uint8Array.from(string, c => c.charCodeAt(0)),
  1491. StringToUtf16Shorts: (string) => Uint16Array.from(string, c => c.charCodeAt(0)),
  1492. StringToUtf16Bytes: function(string) { return new Uint8Array(this.StringToUtf16Shorts(string).buffer) },
  1493. AsciiBytesToString: (buffer) => String.fromCharCode.apply(null, new Uint8Array(buffer)),
  1494. Utf16ShortsToString: (buffer) => String.fromCharCode.apply(null, new Uint16Array(buffer)),
  1495. Utf8BytesToString: function(buffer) { return this.utf8decoder.decode(buffer) },
  1496.  
  1497. BytesToBase64: (buffer) => btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))),
  1498. Base64ToBytes: (string) => Uint8Array.from(atob(string), c => c.charCodeAt(0)),
  1499.  
  1500. BytesToBase64url: function(buffer) { return this.BytesToBase64(buffer).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_") },
  1501. Base64urlToBytes: function(string) { return this.Base64ToBytes(string.replace(/\-/g, "+").replace(/_/g, "/")) },
  1502.  
  1503. GetRandomBytes: (n) => crypto.getRandomValues(new Uint8Array(n)),
  1504. GetRandomUints: (n) => crypto.getRandomValues(new Uint32Array(n)),
  1505.  
  1506. ConcatBuffers: (buffers) => {
  1507. let newLength = buffers.reduce((len, x) => len + x.byteLength, 0);
  1508. let newBuffer = new Uint8Array(newLength);
  1509.  
  1510. let currentOffset = 0;
  1511. for(let buffer of buffers) {
  1512. newBuffer.set(new Uint8Array(buffer), currentOffset);
  1513. currentOffset += buffer.byteLength;
  1514. }
  1515.  
  1516. return newBuffer;
  1517. },
  1518.  
  1519. PayloadEncode: (buffer) => String.fromCharCode.apply(null, Uint16Array.from(new Uint8Array(buffer), b => b + 0x2800)),
  1520. PayloadDecode: (string) => Uint8Array.from(string, c => c.charCodeAt(0) - 0x2800),
  1521.  
  1522. AttachEventToClass: (rootElement, className, eventName, callback) => {
  1523. for(let element of rootElement.getElementsByClassName(className))
  1524. element.addEventListener(eventName, callback);
  1525. },
  1526.  
  1527. trimKeyCache: () => {
  1528. let keyHashes = Object.keys(Cache.keys);
  1529. if(keyHashes.length === 200)
  1530. {
  1531. let lastseen = Number.MAX_SAFE_INTEGER;
  1532. let keyToTrim;
  1533. for(let hash of keyHashes) {
  1534. let key = DataBase.keys[hash];
  1535. if(key.l < lastseen)
  1536. {
  1537. keyToTrim = hash;
  1538. lastseen = key.l;
  1539. }
  1540. };
  1541. delete Cache[keyToTrim];
  1542. }
  1543. },
  1544. GetKeyByHash: async function(hashBase64) {
  1545. let keyObj = DataBase.keys[hashBase64];
  1546. if(keyObj == null) return null;
  1547. keyObj.l = Date.now(); //lastseen
  1548. this.dbChanged = true;
  1549.  
  1550. let cachedKey = Cache.keys[hashBase64];
  1551. if(cachedKey != null) return cachedKey;
  1552.  
  1553. let keyBase64 = keyObj.k;
  1554. let keyBytes = this.Base64ToBytes(keyBase64);
  1555.  
  1556. if(DataBase.isEncrypted)
  1557. keyBytes = await this.AesDecrypt(Cache.dbKey, keyBytes);
  1558.  
  1559. let key = await this.AesImportKey(keyBytes)
  1560. this.trimKeyCache();
  1561. Cache.keys[hashBase64] = key;
  1562. return key;
  1563. },
  1564. GetKeyBytesByHash: async function(hashBase64) {
  1565. let keyObj = DataBase.keys[hashBase64];
  1566. if(keyObj == null) return null;
  1567. keyObj.l = Date.now(); //lastseen
  1568. this.dbChanged = true;
  1569.  
  1570. let keyBase64 = keyObj.k;
  1571. let keyBytes = this.Base64ToBytes(keyBase64);
  1572.  
  1573. if(DataBase.isEncrypted)
  1574. keyBytes = await this.AesDecrypt(Cache.dbKey, keyBytes);
  1575.  
  1576. return keyBytes;
  1577. },
  1578. SaveKey: async function(keyBytes, type, descriptor, hidden) {
  1579. let keyHashBase64 = this.BytesToBase64(await this.Sha512_128(keyBytes));
  1580. if(DataBase.keys[keyHashBase64] != null) return keyHashBase64;
  1581. let keyObj = { t: type, d: descriptor, r/*registered*/: Date.now(), l/*lastseen*/: Date.now(), h/*hidden*/: (hidden != null) || ((type > 1) ? 1 : 0) };
  1582.  
  1583. if(DataBase.isEncrypted)
  1584. keyBytes = await this.AesEncrypt(Cache.dbKey, keyBytes);
  1585.  
  1586. keyObj.k = this.BytesToBase64(keyBytes);
  1587. DataBase.keys[keyHashBase64] = keyObj;
  1588. this.SaveDb();
  1589. return keyHashBase64;
  1590. },
  1591.  
  1592. dbChanged: false,
  1593. LoadDb: function(callback, failCallback, reload) { (async () => {
  1594. if(!reload) DataBase = await this.StorageLoad('⡒⠮⢑⠂⡐⣉⢄');
  1595. if(DataBase != null) {
  1596. Cache = { keys: {} };
  1597.  
  1598. if(DataBase.isEncrypted) {
  1599. const newdbCallback = () => { this.NewDb(callback) };
  1600. const passwordCallback = async (password) => {
  1601. if(this.BytesToBase64(await this.Sha512_128str(password + DataBase.dbPasswordSalt)) === DataBase.dbPasswordHash)
  1602. {
  1603. Cache.dbKey = await this.AesImportKey(await this.Sha512_256str(password + DataBase.dbKeySalt));
  1604. if(callback) callback();
  1605. }
  1606. else
  1607. UnlockWindow.Show(passwordCallback, newdbCallback);
  1608. };
  1609.  
  1610. UnlockWindow.Show(passwordCallback, newdbCallback, failCallback);
  1611. }
  1612. else {
  1613. if(callback) callback();
  1614. }
  1615. }
  1616. else {
  1617. this.NewDb(callback, failCallback);
  1618. }
  1619. })()},
  1620. SaveDb: async function() {
  1621. if(!this.dbChanged) return;
  1622. this.dbChanged = false;
  1623. await this.StorageSave('⡒⠮⢑⠂⡐⣉⢄', DataBase);
  1624. },
  1625. saveDbTimeout: null,
  1626. FastSaveDb: function() {
  1627. this.dbChanged = true;
  1628. clearTimeout(this.saveDbTimeout);
  1629. setTimeout(() => { this.SaveDb() }, 10);
  1630. },
  1631.  
  1632. DownloadDb: async function(uncompressed) {
  1633. let buffer = this.StringToUtf8Bytes(JSON.stringify(DataBase)).buffer;
  1634. if(!uncompressed) buffer = await this.TryCompress(buffer);
  1635. this.DownloadBlob(uncompressed ? "⡒⠮⢑⠂⡐⣉⢄.json" : "⡒⠮⢑⠂⡐⣉⢄.dat", new Blob([buffer]));
  1636. },
  1637. fileInput: (() => { //need reference to keep gc away (bug?)
  1638. let fileInput = document.createElement('input');
  1639. fileInput.type = 'file';
  1640. return fileInput;
  1641. })(),
  1642. ImportDb: function(callback, secondary) {
  1643. this.fileInput.accept = ".json,.dat";
  1644. this.fileInput.click();
  1645. this.fileInput.onchange = async () => {
  1646. let buffer = await this.ReadFile(this.fileInput.files[0]);
  1647. DataBase = JSON.parse(this.Utf8BytesToString(await this.TryDecompress(buffer)));
  1648. if(secondary) DataBase.isSecondary = true;
  1649. else delete DataBase.isSecondary;
  1650. this.FastSaveDb();
  1651. this.LoadDb(callback, null, true);
  1652. };
  1653. },
  1654.  
  1655. NewDb: function(callback, cancelCallback) {
  1656. NewdbWindow.Show(async (password) => {
  1657. DataBase = { isEncrypted: password !== "", keys: {}, channels: {}, autoKeyExchange: "DM+friends" };
  1658. Cache = { keys: {} };
  1659. if(DataBase.isEncrypted)
  1660. {
  1661. let salts = this.GetRandomUints(2);
  1662. DataBase.dbPasswordSalt = salts[0];
  1663. DataBase.dbKeySalt = salts[1];
  1664.  
  1665. DataBase.dbPasswordHash = await this.BytesToBase64(await this.Sha512_128str(password + DataBase.dbPasswordSalt));
  1666. Cache.dbKey = await this.AesImportKey(await this.Sha512_256str(password + DataBase.dbKeySalt));
  1667. }
  1668.  
  1669. await this.NewPersonalKey();
  1670. await this.NewDhKeys();
  1671. this.FastSaveDb();
  1672. if(callback) callback();
  1673. },
  1674. () => { this.ImportDb(() => { NewdbWindow.Remove(); if(callback) callback(); }) },
  1675. () => { this.ImportDb(() => { NewdbWindow.Remove(); if(callback) callback(); }, true) },
  1676. cancelCallback
  1677. );
  1678. },
  1679. NewDbPassword: function(callback) { //TODO: notifications
  1680. NewPasswordWindow.Show(async (password) => {
  1681. let newDataBase = Object.assign({}, DataBase);
  1682. let newDbKey = null;
  1683. let oldDbKey = Cache.dbKey;
  1684. newDataBase.isEncrypted = password !== "";
  1685. if(newDataBase.isEncrypted) {
  1686. let salts = this.GetRandomUints(2);
  1687. newDataBase.dbPasswordSalt = salts[0];
  1688. newDataBase.dbKeySalt = salts[1];
  1689.  
  1690. newDataBase.dbPasswordHash = await this.BytesToBase64(await this.Sha512_128str(password + newDataBase.dbPasswordSalt));
  1691. newDbKey = await this.AesImportKey(await this.Sha512_256str(password + newDataBase.dbKeySalt));
  1692.  
  1693. let keys = {};
  1694. let dhKeyBytes;
  1695. if(DataBase.isEncrypted) { //re-encrypt keys
  1696. for(let [keyHash, oldKey] of Object.entries(DataBase.keys)) {
  1697. let newKey = Object.assign({}, oldKey);
  1698. let keyBytes = await this.AesDecrypt(oldDbKey, this.Base64ToBytes(oldKey.k/*key*/));
  1699. newKey.k = this.BytesToBase64(await this.AesEncrypt(newDbKey, keyBytes));
  1700. keys[keyHash] = newKey;
  1701. }
  1702. dhKeyBytes = await this.AesDecrypt(oldDbKey, this.Base64ToBytes(DataBase.dhPrivateKey));
  1703. }
  1704. else { //encrypt keys
  1705. for(let [keyHash, oldKey] of Object.entries(DataBase.keys)) {
  1706. let newKey = Object.assign({}, oldKey);
  1707. let keyBytes = this.Base64ToBytes(oldKey.k/*key*/);
  1708. newKey.k = this.BytesToBase64(await this.AesEncrypt(newDbKey, keyBytes));
  1709. keys[keyHash] = newKey;
  1710. }
  1711. dhKeyBytes = this.Base64ToBytes(DataBase.dhPrivateKey);
  1712. }
  1713. newDataBase.dhPrivateKey = this.BytesToBase64(await this.AesEncrypt(newDbKey, dhKeyBytes));
  1714. newDataBase.keys = keys;
  1715. }
  1716. else if(DataBase.isEncrypted) { //decrypt keys
  1717. delete newDataBase.dbPasswordSalt;
  1718. delete newDataBase.dbKeySalt;
  1719. delete newDataBase.dbPasswordHash;
  1720. let keys = {};
  1721. for(let [keyHash, oldKey] of Object.entries(DataBase.keys)) {
  1722. let newKey = Object.assign({}, oldKey);
  1723. let keyBytes = await this.AesDecrypt(oldDbKey, this.Base64ToBytes(oldKey.k/*key*/));
  1724. newKey.k = this.BytesToBase64(keyBytes);
  1725. keys[keyHash] = newKey;
  1726. }
  1727. let dhKeyBytes = await this.AesDecrypt(oldDbKey, this.Base64ToBytes(DataBase.dhPrivateKey));
  1728. newDataBase.dhPrivateKey = this.BytesToBase64(dhKeyBytes);
  1729. newDataBase.keys = keys;
  1730. }
  1731.  
  1732. DataBase = newDataBase;
  1733. Cache.dbKey = newDbKey;
  1734. this.FastSaveDb();
  1735. if(callback) callback();
  1736. });
  1737. },
  1738. NewDhKeys: async function() {
  1739. let dhKeys = await this.DhGenerateKeys();
  1740. let dhPrivateKeyBytes;
  1741. let dhPrivateKeyFallback = false;
  1742. try {
  1743. dhPrivateKeyBytes = await this.DhExportPrivateKey(dhKeys.privateKey);
  1744. }
  1745. catch(e) {
  1746. dhPrivateKeyBytes = await this.DhExportPrivateKeyFallback(dhKeys.privateKey);
  1747. dhPrivateKeyFallback = true;
  1748. }
  1749.  
  1750. let dhPublicKeyBytes = await this.DhExportPublicKey(dhKeys.publicKey);
  1751.  
  1752. if(DataBase.isEncrypted)
  1753. dhPrivateKeyBytes = await this.AesEncrypt(Cache.dbKey, dhPrivateKeyBytes);
  1754.  
  1755. if(dhPrivateKeyFallback) DataBase.dhPrivateKeyFallback = true;
  1756. else delete DataBase.dhPrivateKeyFallback;
  1757. DataBase.dhPrivateKey = this.BytesToBase64(dhPrivateKeyBytes);
  1758. DataBase.dhPublicKey = this.BytesToBase64(dhPublicKeyBytes);
  1759. this.FastSaveDb();
  1760. },
  1761. ReadDhKey: async function() {
  1762. let dhPrivateKeyBytes = this.Base64ToBytes(DataBase.dhPrivateKey);
  1763. if(DataBase.isEncrypted)
  1764. dhPrivateKeyBytes = await this.AesDecrypt(Cache.dbKey, dhPrivateKeyBytes);
  1765.  
  1766. if(DataBase.dhPrivateKeyFallback) {
  1767. let dhPrivateKey = await this.DhImportPrivateKeyFallback(dhPrivateKeyBytes);
  1768. try {
  1769. dhPrivateKeyBytes = await this.DhExportPrivateKey(dhPrivateKey);
  1770. if(DataBase.isEncrypted)
  1771. dhPrivateKeyBytes = await this.AesEncrypt(Cache.dbKey, dhPrivateKeyBytes);
  1772.  
  1773. delete DataBase.dhPrivateKeyFallback;
  1774. DataBase.dhPrivateKey = this.BytesToBase64(dhPrivateKeyBytes);
  1775. }
  1776. catch(e) { }
  1777.  
  1778. return dhPrivateKey;
  1779. }
  1780. else return await this.DhImportPrivateKey(dhPrivateKeyBytes);
  1781. },
  1782. ChangeKeyDescriptor: function(hash, descriptor) { DataBase.keys[hash].d = descriptor.replace(/[`\r\n]/g, "").substr(0, 250); this.FastSaveDb() },
  1783. ChangeKeyHidden: function(hash, hidden) { DataBase.keys[hash].h = hidden; this.FastSaveDb() },
  1784. DeleteKey: async function(hash) {
  1785. if(hash === DataBase.personalKeyHash) {
  1786. await this.NewPersonalKey();
  1787. return;
  1788. }
  1789. Utils.ReplaceChannelKeys(hash, DataBase.personalKeyHash);
  1790. delete DataBase.keys[hash];
  1791. this.dbChanged = true;
  1792. },
  1793. ReplaceChannelKeys: function(oldHash, newHash) { Object.values(DataBase.channels).forEach(x => { if(x.k === oldHash) x.k = newHash } ); this.FastSaveDb() },
  1794. NewPersonalKey: async function() {
  1795. if(DataBase.personalKeyHash != null) this.ChangeKeyDescriptor(DataBase.personalKeyHash, "#Your old personal key#");
  1796. let newPersonalKeyHash = await this.SaveKey(this.GetRandomBytes(32), 3/*personal*/, "#Your personal key#");
  1797. this.ReplaceChannelKeys(DataBase.personalKeyHash, newPersonalKeyHash);
  1798. DataBase.personalKeyHash = newPersonalKeyHash;
  1799. this.FastSaveDb();
  1800. },
  1801.  
  1802. FormatDescriptor: function(descriptor) {
  1803. return descriptor.replace(/<@(\d{1,20})>/g, (m, x) => {
  1804. let user = Discord.getUser(x);
  1805. if(user != null) x = user.username;
  1806. return x;
  1807. }).replace(/<#(\d{1,20})>/g, (m, x) => {
  1808. let channel = Discord.getChannel(x);
  1809. if(channel == null) return m;
  1810. if(channel.guild_id == null) return channel.name;
  1811. let guild = Discord.getGuild(channel.guild_id);
  1812. return `${guild.name} #${channel.name}`;
  1813. });
  1814. },
  1815.  
  1816. GetChannelConfig: function(channelId) {
  1817. let channelConfig = DataBase.channels[channelId];
  1818. if(channelConfig != null)
  1819. {
  1820. channelConfig.l = Date.now();
  1821. this.dbChanged = true;
  1822. }
  1823. return channelConfig;
  1824. },
  1825. GetOrCreateChannelConfig: function(channelId) {
  1826. let channelConfig = DataBase.channels[channelId];
  1827. if(channelConfig != null)
  1828. {
  1829. channelConfig.l = Date.now();
  1830. this.dbChanged = true;
  1831. return channelConfig;
  1832. }
  1833. return this.NewChannelConfig(channelId);
  1834. },
  1835. NewChannelConfig: function(channelId, keyHash, descriptor, encrypt) {
  1836. let channelConfig = { k: keyHash || DataBase.personalKeyHash, e: encrypt ? 1 : 0, l: Date.now() };
  1837. if(descriptor != null) channelConfig.d = descriptor;
  1838. else
  1839. {
  1840. let channel = Discord.getChannel(channelId);
  1841. if(channel == null) channelConfig.d = `<#${channelId}>`;
  1842. else if(channel.type === 1) channelConfig.d = `DM with <@${channel.recipients[0]}>`;
  1843. else channelConfig.d = `<#${channelId}>`;
  1844. }
  1845. DataBase.channels[channelId] = channelConfig;
  1846. this.FastSaveDb();
  1847. return channelConfig;
  1848. },
  1849. DeleteChannelConfig: function(channelId) {
  1850. if(Cache.channelId === channelId) Cache.channelConfig = null;
  1851. delete DataBase.channels[channelId];
  1852. this.dbChanged = true;
  1853. },
  1854. GetCurrentChannelKeyHash: () => {
  1855. return (Cache.channelConfig != null) ? Cache.channelConfig.k : DataBase.personalKeyHash;
  1856. },
  1857. GetCurrentChannelEncrypt: () => {
  1858. return Cache.channelConfig != null && Cache.channelConfig.e && Cache.channelBlacklist !== 1;
  1859. },
  1860. ToggleCurrentChannelEncrypt: function() {
  1861. if(Cache.channelBlacklist === 1) return;
  1862.  
  1863. if(Cache.channelConfig == null)
  1864. Cache.channelConfig = this.NewChannelConfig(Cache.channelId, null, null, true);
  1865. else
  1866. {
  1867. Cache.channelConfig.e = Cache.channelConfig.e ? 0 : 1;
  1868. this.dbChanged = true;
  1869. }
  1870. },
  1871. SetCurrentChannelKey: function(hash) {
  1872. if(Cache.channelConfig == null)
  1873. Cache.channelConfig = this.NewChannelConfig(Cache.channelId, hash, null, false);
  1874. else {
  1875. let oldKeyHash = Cache.channelConfig.k;
  1876. if(hash == oldKeyHash) return;
  1877. //if(DataBase.keys[oldKeyHash].t/*type*/ === 2/*conversation*/) delete DataBase.keys[oldKeyHash];
  1878. Cache.channelConfig.k = hash;
  1879. this.dbChanged = true;
  1880. }
  1881. },
  1882. GetCurrentChannelIsDm: () => Discord.getChannel(Cache.channelId).type === 1,
  1883. GetCurrentDmUserId: () => Discord.getChannel(Cache.channelId).recipients[0],
  1884. RefreshCache: () => {
  1885. Cache.channelId = Discord.getChannelId();
  1886. Cache.channelConfig = DataBase.channels[Cache.channelId];
  1887. if(Cache.channelConfig != null) Cache.channelConfig.l = Date.now();
  1888. if(Blacklist != null) {
  1889. let channel = Discord.getChannel(Cache.channelId);
  1890. if(channel == null) return false;
  1891. let guildId = channel.guild_id;
  1892. Cache.channelBlacklist = (guildId == null) ? null : Blacklist[guildId];
  1893. }
  1894. return true;
  1895. },
  1896.  
  1897. SendSystemMessage: function(channelId, sysmsg) {
  1898. Discord.enqueue({
  1899. type: 'send',
  1900. message: {
  1901. channelId: channelId,
  1902. nonce: this.GetNonce(),
  1903. content: "",
  1904. embed: {
  1905. color: BaseColorInt,
  1906. author: {
  1907. name: "-----SYSTEM MESSAGE-----",
  1908. icon_url: "https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/logo.png",
  1909. },
  1910. description: sysmsg,
  1911. footer: {
  1912. text: "⡒⠮⢑⠂⡐⣉⢄", //"\u{1D61A}\u{1D62A}\u{1D62E}\u{1D631}\u{1D62D}\u{1D626}\u{1D60B}\u{1D62A}\u{1D634}\u{1D624}\u{1D630}\u{1D633}\u{1D625}\u{1D60A}\u{1D633}\u{1D63A}\u{1D631}\u{1D635}",
  1913. icon_url: "https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/logo.png",
  1914. }
  1915. }
  1916. }
  1917. }, () => { /*TODO*/ });
  1918. },
  1919. SendPersonalKey: async function(channelId) {
  1920. let channelConfig = this.GetChannelConfig(channelId);
  1921. let keyHash = channelConfig.k;
  1922. if(channelConfig == null || keyHash === DataBase.personalKeyHash) return;
  1923.  
  1924. let keyHashPayload = this.PayloadEncode(this.Base64ToBytes(keyHash));
  1925. let key = await this.GetKeyByHash(keyHash);
  1926. let personalKey = await this.GetKeyBytesByHash(DataBase.personalKeyHash);
  1927. let personalKeyPayload = this.PayloadEncode(await this.AesEncrypt(key, personalKey));
  1928.  
  1929. this.SendSystemMessage(channelId, `*type*: \`PERSONAL KEY\`\n*key*: \`${keyHashPayload}\`\n*personalKey*: \`${personalKeyPayload}\``);
  1930.  
  1931. delete channelConfig.w;
  1932. this.dbChanged = true;
  1933. },
  1934. ongoingKeyExchanges: {},
  1935. InitKeyExchange: async function(userId, auto) {
  1936. let currentUserId = Discord.getCurrentUser().id;
  1937. if(userId === currentUserId) return;
  1938.  
  1939. let channelId = Discord.getDMFromUserId(userId);
  1940. let channelConfig;
  1941. if(auto) {
  1942. channelConfig = this.GetChannelConfig(channelId);
  1943. if(channelConfig != null && (channelConfig.s/*systemMessageTime*/ > 0 || channelConfig.w/*waitingForSystemMessage*/))
  1944. return;
  1945.  
  1946. if(/friend/i.test(DataBase.autoKeyExchange) && !Discord.isFriend(userId)) {
  1947. if(this.ongoingKeyExchanges[userId]) return;
  1948. this.ongoingKeyExchanges[userId] = true;
  1949. let user = Discord.getUser(userId);
  1950. let force = await PopupManager.NewPromise(`Would you like to initiate key exchange with ${user.username}#${user.discriminator}?`);
  1951. //delete this.ongoingKeyExchanges[userId];
  1952. if(!force) return;
  1953. }
  1954. }
  1955. delete this.ongoingKeyExchanges[userId]; //this way once cancelled you either have to add them as friend or restart the plugin
  1956.  
  1957. keyExchangeWhitelist[userId] = true;
  1958.  
  1959. if(channelId == null) {
  1960. channelId = await Discord.ensurePrivateChannel(currentUserId, userId);
  1961. }
  1962.  
  1963. let dhPublicKeyPayload = this.PayloadEncode(this.Base64ToBytes(DataBase.dhPublicKey));
  1964.  
  1965. this.SendSystemMessage(channelId, `*type*: \`DH KEY\`\n*dhKey*: \`${dhPublicKeyPayload}\``);
  1966. channelConfig = channelConfig || this.GetOrCreateChannelConfig(channelId);
  1967. channelConfig.w = 1;
  1968. this.dbChanged = true;
  1969. },
  1970. ongoingKeyRequests: {},
  1971. RequestKey: async function(keyHash, userId, auto) {
  1972. if(DataBase.keys[keyHash] != null) return;
  1973.  
  1974. let channelId = Discord.getDMFromUserId(userId);
  1975. if(channelId == null) return;
  1976.  
  1977. let channelConfig;
  1978. let requestId = keyHash + userId;
  1979. if(auto) {
  1980. channelConfig = this.GetChannelConfig(channelId);
  1981. if(channelConfig != null && channelConfig.w/*waitingForSystemMessage*/)
  1982. return;
  1983.  
  1984. if(/friend/i.test(DataBase.autoKeyExchange) && !Discord.isFriend(userId)) {
  1985. if(this.ongoingKeyRequests[requestId]) return;
  1986. if(this.ongoingKeyExchanges[userId]) return;
  1987. this.ongoingKeyRequests[requestId] = true;
  1988. let user = Discord.getUser(userId);
  1989. if(!await PopupManager.NewPromise(`Would you like to request key from ${user.username}#${user.discriminator}`)) return;
  1990. }
  1991. }
  1992. delete this.ongoingKeyRequests[requestId];
  1993.  
  1994. keyExchangeWhitelist[userId] = true;
  1995.  
  1996. let requestedKeyPayload = this.PayloadEncode(this.Base64ToBytes(keyHash));
  1997.  
  1998. this.SendSystemMessage(channelId, `*type*: \`KEY REQUEST\`\n*requestedKey*: \`${requestedKeyPayload}\``);
  1999. channelConfig = channelConfig || this.GetOrCreateChannelConfig(channelId);
  2000. channelConfig.w = 1;
  2001. this.dbChanged = true;
  2002. },
  2003. ShareKey: async function(keyHash, channelId, nonForced, userId) {
  2004. let keyObj = DataBase.keys[keyHash];
  2005. if(keyObj == null) {
  2006. this.SendSystemMessage(channelId, `*type*: \`KEY SHARE\`\n*status*: \`NOT FOUND\``);
  2007. return;
  2008. }
  2009. if(nonForced != null && (nonForced || keyObj.h/*hidden*/)) {
  2010. let user = Discord.getUser(userId);
  2011. if(!await PopupManager.NewPromise(`Would you like to share key "${Utils.FormatDescriptor(keyObj.d)}" with ${user.username}#${user.discriminator}`)) {
  2012. this.SendSystemMessage(channelId, `*type*: \`KEY SHARE\`\n*status*: \`DENIED\``);
  2013. return;
  2014. }
  2015. }
  2016.  
  2017. let sharedKeyBase64 = keyObj.k;
  2018. let sharedKeyBytes = this.Base64ToBytes(sharedKeyBase64);
  2019. if(DataBase.isEncrypted)
  2020. sharedKeyBytes = await this.AesDecrypt(Cache.dbKey, sharedKeyBytes);
  2021.  
  2022. let channelConfig = this.GetOrCreateChannelConfig(channelId);
  2023. let key = await this.GetKeyByHash(channelConfig.k);
  2024. let keyHashPayload = this.PayloadEncode(this.Base64ToBytes(channelConfig.k));
  2025.  
  2026. let sharedKeyPayload = this.PayloadEncode(await Utils.AesEncrypt(key, sharedKeyBytes));
  2027.  
  2028. if(keyHash === DataBase.personalKeyHash) {
  2029. let keyDescriptor = `<@${Discord.getCurrentUser().id}>'s personal key`;
  2030. this.SendSystemMessage(channelId, `*type*: \`KEY SHARE\`\n*status*: \`OK\`\n*key*: \`${keyHashPayload}\`\n*sharedKey*: \`${sharedKeyPayload}\`\n*keyType*: \`PERSONAL\`\n*keyDescriptor*: \`${keyDescriptor}\``);
  2031. }
  2032. else {
  2033. const keyTypes = { 1:'GROUP', 2:'CONVERSATION', 3:'PERSONAL' };
  2034. let keyType = keyTypes[keyObj.t];
  2035. let keyDescriptor = keyObj.d;
  2036. let sharedChannels = [];
  2037. for(let [id, config] of Object.entries(DataBase.channels)) {
  2038. if(config.k === keyHash) {
  2039. let channel = Discord.getChannel(id);
  2040. if(channel == null || channel.type === 1/*DM*/) continue;
  2041.  
  2042. if(sharedChannels.push(id) === 20) break;
  2043. }
  2044. }
  2045. let sharedChannelsJson = JSON.stringify(sharedChannels);
  2046.  
  2047. this.SendSystemMessage(channelId, `*type*: \`KEY SHARE\`\n*status*: \`OK\`\n*key*: \`${keyHashPayload}\`\n*sharedKey*: \`${sharedKeyPayload}\`\n*keyType*: \`${keyType}\`\n*keyDescriptor*: \`${keyDescriptor}\`\n*sharedChannels*: \`${sharedChannelsJson}\``);
  2048. }
  2049.  
  2050. delete channelConfig.w;
  2051. this.dbChanged = true;
  2052. }
  2053. });
  2054. //Discord.window.SdcUtils = Utils;
  2055. //Discord.window.SdcDiscord = Discord;
  2056.  
  2057. if(!window.crypto || !crypto.subtle) { Utils.Error("Crypto API not found."); return -1; }
  2058.  
  2059. Discord.window.SdcDownloadUrl = (filename, url) => {
  2060. let a = document.createElement('a');
  2061. a.href = url;
  2062. a.download = filename;
  2063. a.click();
  2064. };
  2065. Discord.window.SdcDecryptDl = async (filename, keyHash, url) => {
  2066. let encryptedFileBuffer = await Utils.DownloadFile(url);
  2067. let fileBuffer = await Utils.AesDecrypt(await Utils.GetKeyByHash(keyHash), encryptedFileBuffer);
  2068. Utils.DownloadBlob(filename, new File([fileBuffer], filename));
  2069. };
  2070.  
  2071. const mirrorFunction = (moduleName, functionName) => {
  2072. Discord[`original_${functionName}`] = modules[moduleName][functionName];
  2073. Discord[functionName] = function() { return Discord[`original_${functionName}`].apply(Discord.modules[moduleName], arguments) };
  2074. };
  2075.  
  2076. if(modules.MessageQueue.enqueue == null) { Utils.Error("enqueue() not found."); return -1; }
  2077. mirrorFunction('MessageQueue', 'enqueue');
  2078.  
  2079. if(modules.MessageDispatcher.dispatch == null) { Utils.Error("dispatch() not found."); return -1; }
  2080. mirrorFunction('MessageDispatcher', 'dispatch');
  2081.  
  2082. if(modules.UserCache.getUser == null) { Utils.Error("getUser() not found."); return -1; }
  2083. mirrorFunction('UserCache', 'getUser');
  2084. if(modules.UserCache.getCurrentUser == null) { Utils.Error("getCurrentUser() not found."); return -1; }
  2085. mirrorFunction('UserCache', 'getCurrentUser');
  2086.  
  2087. if(modules.ChannelCache.getChannel == null) { Utils.Error("getChannel() not found."); return -1; }
  2088. mirrorFunction('ChannelCache', 'getChannel');
  2089. if(modules.ChannelCache.getDMFromUserId == null) { Utils.Error("getDMFromUserId() not found."); return -1; }
  2090. mirrorFunction('ChannelCache', 'getDMFromUserId');
  2091.  
  2092. if(modules.SelectedChannelStore.getChannelId == null) { Utils.Error("getChannelId() not found."); return -1; }
  2093. mirrorFunction('SelectedChannelStore', 'getChannelId');
  2094.  
  2095. if(modules.GuildCache.getGuild == null) { Utils.Error("getGuild() not found."); return -1; }
  2096. mirrorFunction('GuildCache', 'getGuild');
  2097.  
  2098. if(modules.FileUploader.upload == null) { Utils.Error("upload() not found."); return -1; }
  2099. mirrorFunction('FileUploader', 'upload');
  2100.  
  2101. if(modules.PermissionEvaluator.can == null) { Utils.Error("can() not found."); return -1; }
  2102. mirrorFunction('PermissionEvaluator', 'can');
  2103.  
  2104. if(modules.RelationshipStore.isFriend == null) { Utils.Error("isFriend() not found."); return -1; }
  2105. mirrorFunction('RelationshipStore', 'isFriend');
  2106.  
  2107. if(modules.PrivateChannelManager.ensurePrivateChannel == null) { Utils.Error("ensurePrivateChannel() not found."); return -1; }
  2108. mirrorFunction('PrivateChannelManager', 'ensurePrivateChannel');
  2109.  
  2110. //if(modules.MessageCache.prototype._merge == null) { Utils.Error("_merge not found."); return -1; }
  2111. //Discord.original__merge = modules.MessageCache.prototype._merge;
  2112.  
  2113. Style.Inject();
  2114.  
  2115. LockMessages(true);
  2116. Utils.LoadDb(() => { Load(); UnlockMessages(); }, UnlockMessages);
  2117.  
  2118. return 1;
  2119. }
  2120.  
  2121.  
  2122. async function handleMessage(event) {
  2123. await processMessage(event.message);
  2124. }
  2125. async function handleMessages(event) {
  2126. for(let message of event.messages.slice()) //in case they reverse the array
  2127. await processMessage(message);
  2128. }
  2129. async function handleSearch(event) {
  2130. for(let group of event.messages)
  2131. for(let message of group)
  2132. await processMessage(message);
  2133. }
  2134.  
  2135. const messageRegex = /^([\u{2800}-\u{28FF}]{16,}) `(?:⡒⠮⢑⠂⡐⣉⢄|\u{1D61A}\u{1D62A}\u{1D62E}\u{1D631}\u{1D62D}\u{1D626}\u{1D60B}\u{1D62A}\u{1D634}\u{1D624}\u{1D630}\u{1D633}\u{1D625}\u{1D60A}\u{1D633}\u{1D63A}\u{1D631}\u{1D635})`$/u;
  2136. const unknownKeyMessage = "```fix\n-----ENCRYPTED MESSAGE WITH UNKNOWN KEY-----\n```";
  2137. const invalidMessage = "```diff\n-\u2063----ENCRYPTED MESSAGE WITH UNKNOWN FORMAT-----\n```"; //invisible separator after the first '-'
  2138. async function processMessage(message) {
  2139.  
  2140. let match = messageRegex.exec(message.content);
  2141. if(match != null) { //simple messsage
  2142. let key = await decryptMessage(message, match[1]);
  2143.  
  2144. return;
  2145. }
  2146.  
  2147. await processEmbeds(message);
  2148. }
  2149.  
  2150. var mediaTypes = { 'png': 'img', 'jpg': 'img', 'jpeg': 'img', 'gif': 'img', 'webp': 'img' };
  2151. if(FixedCsp) mediaTypes['webm'] = mediaTypes['mp4'] = 'video';
  2152. const extensionRegex = /\.([^.]+)$/;
  2153. var downloadLocked = false;
  2154. var downloadLocks = [];
  2155. async function decryptAttachment(key, keyHash, message, attachment) {
  2156. let encryptedFilename = Utils.Base64urlToBytes(attachment.filename);
  2157. let filename;
  2158. try {
  2159. filename = await Utils.AesDecryptString(key, encryptedFilename);
  2160. }
  2161. catch(e) {
  2162. filename = "file";
  2163. }
  2164.  
  2165. attachment.filename = filename;
  2166. //attachment.size = fileBuffer.byteLength;
  2167. let encryptedUrl = attachment.url;
  2168.  
  2169. let match = extensionRegex.exec(filename);
  2170. let mediaType;
  2171. if(match != null) mediaType = mediaTypes[match[1].toLowerCase()];
  2172. if(mediaType == null) {
  2173. attachment.url = `javascript:SdcDecryptDl('${filename}','${keyHash}','${encryptedUrl}')`;
  2174. delete attachment.proxy_url;
  2175. message.attachments.push(attachment);
  2176. return;
  2177. }
  2178.  
  2179. let placeholder = {
  2180. type: 'image',
  2181. thumbnail: {
  2182. url: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",
  2183. width: 1,
  2184. height: 300
  2185. }
  2186. };
  2187. message.embeds.push(placeholder);
  2188.  
  2189. (async () => {
  2190. if(downloadLocked) {
  2191. await (new Promise((resolve) => downloadLocks.unshift(resolve)));
  2192. }
  2193. else downloadLocked = true;
  2194.  
  2195. let encryptedFileBuffer;
  2196. try {
  2197. encryptedFileBuffer = await Utils.DownloadFile(encryptedUrl);
  2198. }
  2199. finally {
  2200. if(downloadLocks.length !== 0) downloadLocks.pop()();
  2201. else downloadLocked = false;
  2202. }
  2203.  
  2204. let fileBuffer = await Utils.AesDecrypt(await Utils.GetKeyByHash(keyHash), encryptedFileBuffer);
  2205. let blob = new File([fileBuffer], filename);
  2206. let bloburl = `${URL.createObjectURL(new File([fileBuffer], filename))}#${filename}`;
  2207. let id = Utils.BytesToBase64(Utils.GetRandomBytes(16));
  2208. let url;
  2209. let downloadUrl = `javascript:SdcDownloadUrl('${filename}','${bloburl}')`;
  2210.  
  2211. let width;
  2212. let height;
  2213. if(FixedCsp) {
  2214. url = bloburl;
  2215. let tmpMedia = document.createElement(mediaType);
  2216. if(mediaType === 'video') {
  2217. await (new Promise((resolve) => {
  2218. //tmpMedia.onloadedmetadata = resolve;
  2219. tmpMedia.onloadeddata = resolve; //wait more so we can make a cover image
  2220. tmpMedia.src = url;
  2221. }));
  2222. width = tmpMedia.videoWidth;
  2223. height = tmpMedia.videoHeight;
  2224. let canvas = document.createElement('canvas');
  2225. canvas.width = width;
  2226. canvas.height = height;
  2227. let ctx = canvas.getContext('2d');
  2228. ctx.drawImage(tmpMedia, 0, 0);
  2229.  
  2230. Object.assign(placeholder, {
  2231. type: 'video',
  2232. //color: BaseColorInt,
  2233. url: downloadUrl,
  2234. title: "Download",
  2235. thumbnail: { url: URL.createObjectURL(await new Promise((resolve) => canvas.toBlob(resolve))) + "#", width, height },
  2236. video: { url: downloadUrl, proxy_url: url, width, height }
  2237. });
  2238. }
  2239. else {
  2240. await (new Promise((resolve) => {
  2241. tmpMedia.onload = resolve;
  2242. tmpMedia.src = url;
  2243. }));
  2244. width = tmpMedia.width;
  2245. height = tmpMedia.height;
  2246.  
  2247. Object.assign(placeholder, {
  2248. type: 'image',
  2249. url: downloadUrl,
  2250. thumbnail: {
  2251. url: downloadUrl,
  2252. proxy_url: url,
  2253. width,
  2254. height
  2255. }
  2256. });
  2257. }
  2258. }
  2259. else {
  2260. url = `https://media.discordapp.net/attachments/479272118538862592/479272171944804377/keylogo.png#${id}`;
  2261. let bitmap = await createImageBitmap(blob);
  2262. width = bitmap.width;
  2263. height = bitmap.height;
  2264.  
  2265. Patcher.Images[id] = blob;
  2266.  
  2267. Object.assign(placeholder, {
  2268. type: 'image',
  2269. url: downloadUrl,
  2270. thumbnail: {
  2271. url: downloadUrl,
  2272. proxy_url: url,
  2273. width,
  2274. height
  2275. }
  2276. });
  2277. }
  2278.  
  2279. Discord.dispatch({type: 'MESSAGE_UPDATE', message});
  2280.  
  2281. if(message.channel_id !== Cache.channelId) return;
  2282. let messageContainer = document.querySelector(`.scroller[class^="messages"]`);
  2283. if(messageContainer != null) {
  2284. if(messageContainer.scrollTop + 1 >= (messageContainer.scrollHeight - messageContainer.clientHeight)) return; //scrolled to bottom
  2285.  
  2286. let displayHeight = height;
  2287. if(width > 400 || height > 300) { //image will be resized
  2288. if(width / 400 > height / 300) { //scale by with
  2289. displayHeight = Math.round(height / (width / 400));
  2290. }
  2291. else { //scale by height
  2292. displayHeight = 300;
  2293. }
  2294. }
  2295.  
  2296. if(displayHeight != 300) messageContainer.scrollTop += 300 - displayHeight;
  2297. }
  2298. })();
  2299. }
  2300.  
  2301. function createYoutubeEmbed(id) {
  2302. return {
  2303. type: 'video',
  2304. url: `https://youtube.com/watch?v=${id}`,
  2305. thumbnail: { url: `https://i.ytimg.com/vi/${id}/maxresdefault.jpg`, width: 1280, height: 720 },
  2306. video: { url: `https://youtube.com/embed/${id}`, width: 1280, height: 720 }
  2307. }
  2308. }
  2309. const youtubeRegex = /[?&]v=([\w-]+)/;
  2310. function embedYoutube(message, url, queryString) {
  2311. let match = youtubeRegex.exec(queryString);
  2312. if(match != null) message.embeds.push(createYoutubeEmbed(match[1]));
  2313. }
  2314. const youtuRegex = /^[\w-]+/;
  2315. function embedYoutu(message, url, queryString) {
  2316. let match = youtuRegex.exec(queryString);
  2317. if(match != null) message.embeds.push(createYoutubeEmbed(match[0]));
  2318. }
  2319. const imageRegex = /\.(?:png|jpe?g|gif|webp)$/i;
  2320. function embedImage(message, url, queryString) {
  2321. if(!imageRegex.test(queryString)) return;
  2322.  
  2323. let tmpimg = document.createElement('img');
  2324. tmpimg.onload = () => {
  2325. message.embeds.push({
  2326. type: 'image',
  2327. url,
  2328. thumbnail: {
  2329. url,
  2330. width: tmpimg.width,
  2331. height: tmpimg.height
  2332. }
  2333. });
  2334. Discord.dispatch({type: 'MESSAGE_UPDATE', message});
  2335. };
  2336. tmpimg.src = url;
  2337. }
  2338. function embedEncrypted(message, url, queryString) {
  2339. message.embeds.push({
  2340. type: 'video',
  2341. url,
  2342. thumbnail: { url: "https://media.discordapp.net/attachments/449522590978146304/465783850144890890/key128.png", width: 128, height: 128 },
  2343. video: { url, width: 400, height: 300 }
  2344. });
  2345. }
  2346. function embedMega(message, url, queryString) {
  2347. if(queryString.startsWith("embed"))
  2348. embedEncrypted(message, url, queryString);
  2349. }
  2350. const linkEmbedders = {
  2351. "www.youtube.com": embedYoutube,
  2352. "youtu.be": embedYoutu,
  2353. "cdn.discordapp.com": embedImage,
  2354. "media.discordapp.net": embedImage,
  2355. "i.imgur.com": embedImage,
  2356. "i.redd.it": embedImage,
  2357. "share.riseup.net": embedEncrypted,
  2358. "mega.nz": embedMega
  2359. };
  2360. let urlRegex = /https?:\/\/((?:[^\s\/?\.#]+\.)+(?:[^\s\/?\.#]+))\/([^\s<>'"]+)/g
  2361. function postProcessMessage(message, content) {
  2362. let currentUser = Discord.getCurrentUser();
  2363. if(content.includes(`<@${currentUser.id}>`))
  2364. message.mentions = [currentUser];
  2365.  
  2366. let url;
  2367. while ((url = urlRegex.exec(content)) != null) {
  2368. let linkEmbedder = linkEmbedders[url[1]];
  2369. if(linkEmbedder != null) linkEmbedder(message, url[0], url[2]);
  2370. }
  2371. urlRegex.lastIndex = 0;
  2372. }
  2373.  
  2374. async function decryptMessage(message, payload) {
  2375. let payloadBuffer = Utils.PayloadDecode(payload).buffer;
  2376. let keyHashBytes = payloadBuffer.slice(0, 16);
  2377. let keyHashBase64 = Utils.BytesToBase64(keyHashBytes);
  2378. let key = await Utils.GetKeyByHash(keyHashBase64);
  2379.  
  2380. if(key == null) {
  2381. if(!DataBase.isSecondary) {
  2382. if(message.author != null) Utils.InitKeyExchange(message.author.id, true);
  2383.  
  2384. for(let i = 1; i <= 10; i++) {
  2385. await Utils.Sleep(i * 200);
  2386.  
  2387. key = await Utils.GetKeyByHash(keyHashBase64);
  2388. if(key != null) break;
  2389.  
  2390. if(message.author != null) Utils.RequestKey(keyHashBase64, message.author.id, true);
  2391. }
  2392. }
  2393. if(key == null) {
  2394. message.content = unknownKeyMessage;
  2395. message.embeds = [];
  2396. message.attachments = [];
  2397. return;
  2398. }
  2399. }
  2400.  
  2401. message.embeds = []; //remove embeds in case of edit and in case of the payload is from the embed
  2402.  
  2403. if(payloadBuffer.byteLength === 16) {
  2404. message.content = "\u2063:small_blue_diamond:"; //invisible separator at the end to make the emoji smaller
  2405. }
  2406. else {
  2407. let content;
  2408. try {
  2409. let encryptedMessage = payloadBuffer.slice(16);
  2410. content = await Utils.AesDecryptDecompressString(key, encryptedMessage);
  2411. }
  2412. catch(e) {
  2413. message.content = invalidMessage;
  2414. message.attachments = [];
  2415. return;
  2416. }
  2417. message.content = "\u2063:small_blue_diamond:" + content;
  2418. // message.content = content.replace(/^/gm, "\u2063:small_blue_diamond: "); //bad for code blocks
  2419. postProcessMessage(message, content);
  2420. }
  2421.  
  2422. if(message.attachments == null) return;
  2423.  
  2424. let attachments = message.attachments;
  2425. message.attachments = [];
  2426. for(let attachment of attachments) {
  2427. try {
  2428. await decryptAttachment(key, keyHashBase64, message, attachment);
  2429. }
  2430. catch(e) {
  2431. attachment.filename = "-----ENCRYPTED FILE FAILED TO DECRYPT-----";
  2432. }
  2433. }
  2434. }
  2435.  
  2436. function getSystemMessageProperty(propertyName, sysmsg) {
  2437. let match = new RegExp(`\\*${propertyName}\\*:\\s*\`(.*?)\``, "i").exec(sysmsg);
  2438. return (match == null) ? null : match[1];
  2439. }
  2440.  
  2441.  
  2442. const unknownKeySystemMessage = "```fix\n-----SYSTEM MESSAGE WITH UNKNOWN KEY-----\n```";
  2443. const invalidSystemMessage = "```diff\n-\u2063----SYSTEM MESSAGE WITH UNKNOWN FORMAT-----\n```";
  2444. const blockedSystemMessage = "```fix\n-----SYSTEM MESSAGE BLOCKED-----\n```";
  2445. var keyExchangeWhitelist = {};
  2446. async function processSystemMessage(message, sysmsg) {
  2447. let channel = Discord.getChannel(message.channel_id);
  2448. if(channel.type !== 1/*DM*/) return;
  2449.  
  2450. message.embeds = [];
  2451. let timestamp = new Date(message.timestamp).getTime();
  2452. let channelConfig = Utils.GetOrCreateChannelConfig(message.channel_id);
  2453. let oldMessage = true;
  2454. if(channelConfig.s/*systemMessageTime*/ == null || timestamp > channelConfig.s) {
  2455. channelConfig.s = timestamp;
  2456. Utils.dbChanged = true;
  2457. oldMessage = false;
  2458. }
  2459.  
  2460. let messageType = getSystemMessageProperty('type', sysmsg);
  2461. let userId;
  2462. if(message.author == null) oldMessage = true;
  2463. else {
  2464. userId = message.author.id;
  2465. if(userId === Discord.getCurrentUser().id) oldMessage = true;
  2466. }
  2467.  
  2468. if(DataBase.isSecondary && !keyExchangeWhitelist[userId]) oldMessage = true;
  2469.  
  2470. let nonForced = true;
  2471. if(!oldMessage) {
  2472. message.content = blockedSystemMessage;
  2473. if(/friend/i.test(DataBase.autoKeyExchange) && !Discord.isFriend(userId)) {
  2474. if(messageType === 'DH KEY' || messageType === 'DH RESPONSE' || messageType === 'PERSONAL KEY' || messageType === 'KEY SHARE') {
  2475. if(!keyExchangeWhitelist[userId]) {
  2476. let user = Discord.getUser(userId);
  2477. if(!await PopupManager.NewPromise(`Would you like to accept key exchange from ${user.username}#${user.discriminator}`)) return;
  2478. keyExchangeWhitelist[userId] = true;
  2479. }
  2480. }
  2481. }
  2482. else nonForced = false;
  2483. }
  2484.  
  2485. switch(messageType) {
  2486. case 'DH KEY': {
  2487. message.content = "\u{1F4BB} H-hi I would like to know you better";
  2488. if(oldMessage) return;
  2489.  
  2490. let dhKeyPayload = getSystemMessageProperty('dhKey', sysmsg);
  2491. if(dhKeyPayload == null) break;
  2492. try {
  2493. let dhRemoteKeyBytes = Utils.PayloadDecode(dhKeyPayload);
  2494. let dhRemoteKey = await Utils.DhImportPublicKey(dhRemoteKeyBytes);
  2495.  
  2496. let dhPrivateKey = await Utils.ReadDhKey();
  2497.  
  2498. let sharedSecret = await Utils.DhGetSecret(dhPrivateKey, dhRemoteKey);
  2499. let keyHash = await Utils.SaveKey(sharedSecret, 2/*conversation*/, `DM key with <@${message.author.id}>`);
  2500. channelConfig.k/*keyHash*/ = keyHash;
  2501.  
  2502. let dhPublicKeyPayload = Utils.PayloadEncode(Utils.Base64ToBytes(DataBase.dhPublicKey));
  2503.  
  2504. let key = await Utils.AesImportKey(sharedSecret);
  2505.  
  2506. let encryptedPersonalKey = await Utils.AesEncrypt(key, await Utils.GetKeyBytesByHash(DataBase.personalKeyHash));
  2507. let personalKeyPayload = Utils.PayloadEncode(encryptedPersonalKey);
  2508.  
  2509. Utils.SendSystemMessage(message.channel_id, `*type*: \`DH RESPONSE\`\n*dhKey*: \`${dhPublicKeyPayload}\`\n*personalKey*: \`${personalKeyPayload}\``);
  2510.  
  2511. channelConfig.w = 1; //waitingForSystemMessage
  2512. Utils.dbChanged = true;
  2513. }
  2514. catch(e) { break }
  2515. } return;
  2516. case 'DH RESPONSE': {
  2517. message.content = "\u{1F4BB} I like you :3, you can have my number";
  2518. if(oldMessage) return;
  2519.  
  2520. let dhKeyPayload = getSystemMessageProperty('dhKey', sysmsg);
  2521. if(dhKeyPayload == null) break;
  2522. let remotePersonalKeyPayload = getSystemMessageProperty('personalKey', sysmsg);
  2523. if(remotePersonalKeyPayload == null) break;
  2524. try {
  2525. let dhRemoteKeyBytes = Utils.PayloadDecode(dhKeyPayload);
  2526. let dhRemoteKey = await Utils.DhImportPublicKey(dhRemoteKeyBytes);
  2527.  
  2528. let dhPrivateKey = await Utils.ReadDhKey();
  2529.  
  2530. let sharedSecret = await Utils.DhGetSecret(dhPrivateKey, dhRemoteKey);
  2531. let keyHash = await Utils.SaveKey(sharedSecret, 2/*conversation*/, `DM key with <@${message.author.id}>`);
  2532. channelConfig.k/*keyHash*/ = keyHash;
  2533. Utils.dbChanged = true;
  2534. if(message.channel_id == Cache.channelId) {
  2535. Cache.channelConfig = channelConfig; //in case it's a new config
  2536. MenuBar.Update();
  2537. }
  2538.  
  2539. let key = await Utils.AesImportKey(sharedSecret);
  2540.  
  2541. let remotePersonalKey = await Utils.AesDecrypt(key, Utils.PayloadDecode(remotePersonalKeyPayload));
  2542. if(remotePersonalKey.byteLength !== 32) break;
  2543. await Utils.SaveKey(remotePersonalKey, 3/*personal*/, `<@${message.author.id}>'s personal key`);
  2544.  
  2545. await Utils.SendPersonalKey(message.channel_id);
  2546. }
  2547. catch(e) { break }
  2548. } return;
  2549. case 'PERSONAL KEY': {
  2550. message.content = "\u{1F4BB} Here is my number, now we can talk any time!!";
  2551. if(oldMessage) return;
  2552.  
  2553. let keyHashPayload = getSystemMessageProperty('key', sysmsg);
  2554. if(keyHashPayload == null) break;
  2555. let remotePersonalKeyPayload = getSystemMessageProperty('personalKey', sysmsg);
  2556. if(remotePersonalKeyPayload == null) break;
  2557. try {
  2558. let keyHash = Utils.BytesToBase64(Utils.PayloadDecode(keyHashPayload));
  2559. let key = await Utils.GetKeyByHash(keyHash);
  2560. if(key == null) {
  2561. message.content = unknownKeySystemMessage;
  2562. return;
  2563. }
  2564.  
  2565. let remotePersonalKey = await Utils.AesDecrypt(key, Utils.PayloadDecode(remotePersonalKeyPayload));
  2566. if(remotePersonalKey.byteLength !== 32) break;
  2567. await Utils.SaveKey(remotePersonalKey, 3/*personal*/, `<@${message.author.id}>'s personal key`);
  2568.  
  2569. delete channelConfig.w; //waitingForSystemMessage
  2570. Utils.dbChanged = true;
  2571. delete keyExchangeWhitelist[userId];
  2572. }
  2573. catch(e) { break }
  2574. } return;
  2575. case 'KEY REQUEST': {
  2576. message.content = "\u{1F4BB} Hey, can you tell me what this means?";
  2577. if(oldMessage) return;
  2578.  
  2579. let requestedKeyPayload = getSystemMessageProperty('requestedKey', sysmsg);
  2580. if(requestedKeyPayload == null) break;
  2581. try {
  2582. let keyHash = Utils.BytesToBase64(Utils.PayloadDecode(requestedKeyPayload));
  2583.  
  2584. Utils.ShareKey(keyHash, message.channel_id, nonForced, userId); //no need to wait
  2585. }
  2586. catch(e) { break }
  2587. } return;
  2588. case 'KEY SHARE': {
  2589. let status = getSystemMessageProperty('status', sysmsg);
  2590. const statusMsgs = {
  2591. 'OK': "\u{1F4BB} There you go, take good care of it!",
  2592. 'DENIED': "\u{1F4BB} That's a secret!!!",
  2593. 'NOT FOUND': "\u{1F4BB} Huh? I don't know"
  2594. };
  2595. let statusMsg = statusMsgs[status];
  2596. if(statusMsg == null) break;
  2597. message.content = statusMsg;
  2598. if(oldMessage || status !== 'OK') return;
  2599.  
  2600. let keyHashPayload = getSystemMessageProperty('key', sysmsg);
  2601. if(keyHashPayload == null) break;
  2602. let sharedKeyPayload = getSystemMessageProperty('sharedKey', sysmsg);
  2603. if(sharedKeyPayload == null) break;
  2604. let keyTypeName = getSystemMessageProperty('keyType', sysmsg);
  2605. if(keyTypeName == null) break;
  2606. let keyDescriptor = getSystemMessageProperty('keyDescriptor', sysmsg);
  2607. if(keyDescriptor == null) break;
  2608. try {
  2609. let keyHash = Utils.BytesToBase64(Utils.PayloadDecode(keyHashPayload));
  2610. let key = await Utils.GetKeyByHash(keyHash);
  2611. if(key == null) {
  2612. message.content = unknownKeySystemMessage;
  2613. return;
  2614. }
  2615.  
  2616. let sharedKey = await Utils.AesDecrypt(key, Utils.PayloadDecode(sharedKeyPayload));
  2617. if(sharedKey.byteLength !== 32) break;
  2618. const keyTypeNames = { 'GROUP':1, 'CONVERSATION':2, 'PERSONAL':3 }; //let's get personal :3
  2619. let keyType = keyTypeNames[keyTypeName];
  2620. if(keyType == null) break;
  2621.  
  2622. let sharedKeyHash = await Utils.SaveKey(sharedKey, keyType, keyDescriptor);
  2623.  
  2624. delete channelConfig.w; //waitingForSystemMessage
  2625. Utils.dbChanged = true;
  2626. delete keyExchangeWhitelist[userId];
  2627.  
  2628. let sharedChannelsJson = getSystemMessageProperty('sharedChannels', sysmsg);
  2629. if(sharedChannelsJson == null) return;
  2630. let sharedChannels = JSON.parse(sharedChannelsJson);
  2631. for(let channelId of sharedChannels) {
  2632. if(DataBase.channels[channelId] != null) continue;
  2633. Utils.NewChannelConfig(channelId, sharedKeyHash);
  2634. }
  2635. }
  2636. catch(e) { break }
  2637. } return;
  2638. }
  2639. message.content = invalidSystemMessage;
  2640. }
  2641.  
  2642. const descriptionRegex = /^[\u{2800}-\u{28FF}]{16,}$/u;
  2643. async function processEmbeds(message) {
  2644. if(message.embeds.length !== 1) return;
  2645. let embed = message.embeds[0];
  2646. if(embed.footer == null || (embed.footer.text !== "⡒⠮⢑⠂⡐⣉⢄" && embed.footer.text !== "\u{1D61A}\u{1D62A}\u{1D62E}\u{1D631}\u{1D62D}\u{1D626}\u{1D60B}\u{1D62A}\u{1D634}\u{1D624}\u{1D630}\u{1D633}\u{1D625}\u{1D60A}\u{1D633}\u{1D63A}\u{1D631}\u{1D635}")) return;
  2647.  
  2648. if(embed.author == null) return;
  2649.  
  2650. if(embed.author.name === "⣘⢅⡠⠾⡄⡉⢒⠮⢋⣭⣉⢰⢆⢚⠱⢭⣳⡐") {
  2651. if(!descriptionRegex.test(embed.description)) return;
  2652. await decryptMessage(message, embed.description);
  2653. }
  2654. else if(embed.author.name === "-----SYSTEM MESSAGE-----") {
  2655. processSystemMessage(message, embed.description);
  2656. }
  2657. }
  2658.  
  2659. /*async function handleMessageUpdate(event) {
  2660. if(event.message.edited_timestamp != null) return;
  2661.  
  2662. await processEmbeds(event.message);
  2663. }*/
  2664.  
  2665. async function handleChannelSelect(event) {
  2666. if(Blacklist != null) {
  2667. let guildId = event.guildId;
  2668. Cache.channelBlacklist = (guildId == null) ? null : Blacklist[guildId];
  2669. }
  2670. let channelId = event.channelId;
  2671. if(channelId != null) {
  2672. Cache.channelId = channelId;
  2673. Cache.channelConfig = Utils.GetChannelConfig(channelId);
  2674.  
  2675. MenuBar.Update();
  2676. PopupManager.Update();
  2677. }
  2678. }
  2679.  
  2680. const prefixRegex = /^(?::?ENC(?::|\b)|<:ENC:\d{1,20}>)\s*/;
  2681. const noencprefixRegex = /^(?::?NOENC:?|<:NOENC:\d{1,20}>)\s*/; //not really expecting an emoji
  2682. async function handleSend(channelId, message, forceSimple) {
  2683. let channelConfig = Utils.GetChannelConfig(channelId);
  2684. let content = message.content;
  2685. let prefixMatch = prefixRegex.exec(content);
  2686. if(channelConfig == null) {
  2687. if(prefixMatch != null) {
  2688. if(Cache.channelBlacklist !== 1)
  2689. channelConfig = Utils.NewChannelConfig(channelId);
  2690. }
  2691. else return null;
  2692. }
  2693. if(prefixMatch != null) content = content.substring(prefixMatch[0].length);
  2694. else if(!channelConfig.e) return null;
  2695.  
  2696. if(Cache.channelBlacklist === 1) {
  2697. if(prefixMatch != null) message.content = content;
  2698. return null;
  2699. }
  2700.  
  2701. let noencprefixMatch = noencprefixRegex.exec(content);
  2702. if(noencprefixMatch != null) {
  2703. message.content = content.substring(noencprefixMatch[0].length);
  2704. return null;
  2705. }
  2706.  
  2707.  
  2708. let key = await Utils.GetKeyByHash(channelConfig.k);
  2709. let keyHashBytes = Utils.Base64ToBytes(channelConfig.k);
  2710. let messageBytes;
  2711. if(content != "")
  2712. {
  2713. let encryptedMessage = await Utils.AesEncryptCompressString(key, content);
  2714. messageBytes = Utils.ConcatBuffers([keyHashBytes, encryptedMessage]);
  2715. }
  2716. else messageBytes = keyHashBytes;
  2717.  
  2718. let payload = Utils.PayloadEncode(messageBytes);
  2719.  
  2720. let channel = Discord.getChannel(channelId);
  2721. if(forceSimple || Cache.channelBlacklist === 2 || (channel.type === 0 && !Discord.can(0x4000/*EMBED_LINKS*/, Discord.getCurrentUser(), channel))) {
  2722. message.content = payload + " `⡒⠮⢑⠂⡐⣉⢄`"; //" `\u{1D61A}\u{1D62A}\u{1D62E}\u{1D631}\u{1D62D}\u{1D626}\u{1D60B}\u{1D62A}\u{1D634}\u{1D624}\u{1D630}\u{1D633}\u{1D625}\u{1D60A}\u{1D633}\u{1D63A}\u{1D631}\u{1D635}`";
  2723. }
  2724. else {
  2725. message.content = "";
  2726. message.embed = {
  2727. color: BaseColorInt,
  2728. author: {
  2729. name: "⣘⢅⡠⠾⡄⡉⢒⠮⢋⣭⣉⢰⢆⢚⠱⢭⣳⡐",
  2730. },
  2731. description: payload,
  2732. footer: {
  2733. text: "⡒⠮⢑⠂⡐⣉⢄", //"\u{1D61A}\u{1D62A}\u{1D62E}\u{1D631}\u{1D62D}\u{1D626}\u{1D60B}\u{1D62A}\u{1D634}\u{1D624}\u{1D630}\u{1D633}\u{1D625}\u{1D60A}\u{1D633}\u{1D63A}\u{1D631}\u{1D635}",
  2734. icon_url: "https://raw.githubusercontent.com/Ramewn/Deeskord-crypt/master/logo.png",
  2735. }
  2736. };
  2737. }
  2738. return key;
  2739. }
  2740.  
  2741. const filenameLimit = 47;
  2742. const filenameRegex = /^(.*?)((?:\.[^.]*)?)$/;
  2743. async function handleUpload(channelId, file, message) {
  2744. let key = await handleSend(channelId, message, true);
  2745. if(key == null) return file;
  2746.  
  2747. let filenameParts = filenameRegex.exec(file.name);
  2748. let filenameMax = filenameLimit - filenameParts[2].length;
  2749. let filename = filenameParts[1].substr(0, filenameMax) + filenameParts[2];
  2750.  
  2751. try {
  2752. let encryptedFilename;
  2753. let filenameBytes = Utils.StringToUtf8Bytes(filename);
  2754. if(filenameBytes.byteLength > filenameLimit) filenameBytes = Utils.StringToUtf8Bytes("file" + filenameParts[2]);
  2755. do {
  2756. encryptedFilename = Utils.BytesToBase64url(await Utils.AesEncrypt(key, filenameBytes));
  2757. } while(encryptedFilename.startsWith('_') || encryptedFilename.endsWith('_')); //this character is trimmed by discord (the solution assumes that the encryption looks fully random)
  2758. let fileBuffer = await Utils.ReadFile(file);
  2759. let encryptedBuffer = await Utils.AesEncrypt(key, fileBuffer);
  2760. return new File([encryptedBuffer], encryptedFilename);
  2761. }
  2762. catch(e) {
  2763. return null;
  2764. }
  2765. }
  2766.  
  2767. const eventHandlers = {
  2768. 'CHANNEL_SELECT': handleChannelSelect,
  2769. 'LOAD_MESSAGES_SUCCESS': handleMessages,
  2770. //'LOAD_MESSAGES_SUCCESS_CACHED': handleMessages,
  2771. 'LOAD_MESSAGES_AROUND_SUCCESS': handleMessages,
  2772. 'LOAD_PINNED_MESSAGES_SUCCESS': handleMessages,
  2773. 'LOAD_RECENT_MENTIONS_SUCCESS': handleMessages,
  2774. 'SEARCH_FINISH': handleSearch,
  2775. 'MESSAGE_CREATE': handleMessage,
  2776. 'MESSAGE_UPDATE': handleMessage
  2777. }
  2778.  
  2779. var messageLocks = [];
  2780. var UnlockMessages;
  2781. function LockMessages(initial) {
  2782. let messageDispatcher = Discord.modules.MessageDispatcher;
  2783. let dispatchHook = function(event){(async () => {
  2784. if(event.type === 'LOAD_MESSAGES_SUCCESS' || event.type === 'MESSAGE_CREATE' || event.type === 'MESSAGE_UPDATE') {
  2785. //if(initial && event.type === 'LOAD_MESSAGES_SUCCESS')
  2786. // event.messages.reverse(); //initial load has the messages reversed for some reason //(seems like not anymore)
  2787.  
  2788. await new Promise((resolve) => { messageLocks.push(resolve) });
  2789.  
  2790. messageDispatcher.dispatch.apply(this, arguments);
  2791. }
  2792.  
  2793. Discord.original_dispatch.apply(this, arguments);
  2794. })()};
  2795. messageDispatcher.dispatch = dispatchHook;
  2796.  
  2797. UnlockMessages = () => {
  2798. if(messageDispatcher.dispatch === dispatchHook)
  2799. messageDispatcher.dispatch = Discord.original_dispatch;
  2800. for(let unlockMessage of messageLocks)
  2801. unlockMessage();
  2802. messageLocks = [];
  2803. }
  2804. }
  2805.  
  2806. async function LoadBlacklist() {
  2807. let blacklistString = Utils.Utf8BytesToString(await Utils.DownloadFile(BlacklistUrl));
  2808. let blacklistRegex = /^\s*(\d{1,20})(E?)/gm;
  2809. Blacklist = {};
  2810. let record;
  2811. while ((record = blacklistRegex.exec(blacklistString)) != null) {
  2812. Blacklist[record[1]] = (record[2] === 'E') ? 2 : 1;
  2813. }
  2814.  
  2815. for(let i = 1;; i++) {
  2816. if(await Utils.RefreshCache() || i === 10) break;
  2817. await Utils.Sleep(i * 200);
  2818. }
  2819.  
  2820. if(Cache.channelBlacklist === 1) MenuBar.Update();
  2821. }
  2822.  
  2823. var dbSaveInterval;
  2824. function Load()
  2825. {
  2826. let modules = Discord.modules;
  2827.  
  2828. Utils.RefreshCache();
  2829.  
  2830. modules.MessageQueue.enqueue = function(packet){(async () => {
  2831.  
  2832. await handleSend(packet.message.channelId, packet.message, packet.type === 'edit');
  2833.  
  2834. Discord.original_enqueue.apply(this, arguments);
  2835. })()};
  2836.  
  2837. modules.MessageDispatcher.dispatch = function(event){(async () => {
  2838. let handler = eventHandlers[event.type];
  2839. if(handler) await handler(event);
  2840.  
  2841. Discord.original_dispatch.apply(this, arguments);
  2842. })()};
  2843.  
  2844. modules.FileUploader.upload = function(channelId, file, message){(async () => {
  2845.  
  2846. arguments[1] = await handleUpload(channelId, file, message);
  2847.  
  2848. Discord.original_upload.apply(this, arguments);
  2849. })()};
  2850.  
  2851. /*modules.MessageCache.prototype._merge = function(messages) {
  2852. console.log(messages);
  2853.  
  2854. messages.forEach((message) => {
  2855. if(message.state !== "SENT") return;
  2856. //message.contentParsed = null;
  2857. });
  2858.  
  2859. Discord.original__merge.apply(this, arguments);
  2860. };*/
  2861.  
  2862. MenuBar.Show(() => Utils.GetCurrentChannelEncrypt(),
  2863. () => { Utils.ToggleCurrentChannelEncrypt(); MenuBar.Update(); },
  2864. () => Utils.FormatDescriptor(DataBase.keys[Utils.GetCurrentChannelKeyHash()].d),
  2865. () => {
  2866. let keys = [];
  2867. let currentKeyHash;
  2868. if(Cache.channelConfig != null)
  2869. {
  2870. currentKeyHash = Cache.channelConfig.k;
  2871. let currentKeyObj = DataBase.keys[currentKeyHash];
  2872. if(currentKeyObj.t/*type*/ === 2/*conversation*/)
  2873. keys.push({ hash: currentKeyHash, descriptor: Utils.FormatDescriptor(currentKeyObj.d), selected: true });
  2874. }
  2875.  
  2876. keys.push({hash: DataBase.personalKeyHash,
  2877. descriptor: Utils.FormatDescriptor(DataBase.keys[DataBase.personalKeyHash].d),
  2878. selected: (currentKeyHash == null) || (DataBase.personalKeyHash === currentKeyHash)});
  2879.  
  2880. Object.entries(DataBase.keys)
  2881. .filter(([,x]) => x.t/*type*/ === 1/*group*/ && !x.h/*hidden*/)
  2882. .sort(([,a], [,b]) => b.l - a.l)
  2883. .forEach(([hash, keyObj]) => keys.push({ hash, descriptor: Utils.FormatDescriptor(keyObj.d), selected: hash === currentKeyHash }));
  2884.  
  2885. return keys;
  2886. },
  2887. (key) => { Utils.SetCurrentChannelKey(key.hash); MenuBar.Update(); },
  2888. () => Utils.GetCurrentChannelIsDm(),
  2889. () => Utils.DownloadDb(),
  2890. () => Utils.DownloadDb(true),
  2891. () => Utils.NewDb(() => { Utils.RefreshCache(); MenuBar.Update(); }),
  2892. () => Utils.NewDbPassword(),
  2893. () => Utils.InitKeyExchange(Utils.GetCurrentDmUserId()),
  2894. async () => { Utils.SetCurrentChannelKey(await Utils.SaveKey(Utils.GetRandomBytes(32), 1/*group*/, `Group <#${Cache.channelId}>`)); MenuBar.Update(); },
  2895. () => {
  2896. let personalKeyHash = DataBase.personalKeyHash;
  2897. let personalKey = DataBase.keys[personalKeyHash];
  2898. let keys = [{hash: personalKeyHash,
  2899. rawDescriptor: personalKey.d,
  2900. descriptor: Utils.FormatDescriptor(personalKey.d),
  2901. lastseen: personalKey.l,
  2902. hidden: personalKey.h,
  2903. type: 'PERSONAL',
  2904. protected: true}];
  2905. const keyTypes = { 1:'GROUP', 2:'CONVERSATION', 3:'PERSONAL' };
  2906. Object.entries(DataBase.keys).sort(([,a], [,b]) => b.l - a.l).forEach(([hash, keyObj]) => {
  2907. if(hash != personalKeyHash) keys.push({ hash, rawDescriptor: keyObj.d, lastseen: keyObj.l, descriptor: Utils.FormatDescriptor(keyObj.d), hidden: keyObj.h, type: keyTypes[keyObj.t] })
  2908. });
  2909. KeyManagerWindow.Show(keys,
  2910. (key, rawDescriptor) => {
  2911. Utils.ChangeKeyDescriptor(key.hash, rawDescriptor);
  2912. key.rawDescriptor = rawDescriptor;
  2913. key.descriptor = Utils.FormatDescriptor(rawDescriptor);
  2914. MenuBar.Update();
  2915. },
  2916. (key, hidden) => {
  2917. Utils.ChangeKeyHidden(key.hash, hidden);
  2918. key.hidden = hidden;
  2919. },
  2920. (key) => { Utils.DeleteKey(key.hash); MenuBar.Update(); }
  2921. );
  2922. },
  2923. () =>
  2924. ChannelManagerWindow.Show(Object.entries(DataBase.channels)
  2925. .sort(([,a], [,b]) => b.l - a.l)
  2926. .map(([id, channel]) => ({id, descriptor: Utils.FormatDescriptor(channel.d), lastseen: channel.l })),
  2927. (channel) => { Utils.DeleteChannelConfig(channel.id); if(channel.id === Cache.channelId) MenuBar.Update(); }
  2928. ),
  2929. () => KeyVisualizerWindow.Show(Utils.Base64ToBytes(Utils.GetCurrentChannelKeyHash()).buffer),
  2930. () => {
  2931. let personalKeyHash = DataBase.personalKeyHash;
  2932. let personalKey = DataBase.keys[personalKeyHash];
  2933. let keys = [{hash: personalKeyHash, descriptor: Utils.FormatDescriptor(personalKey.d), lastseen: personalKey.l}];
  2934. keys = keys.concat(Object.entries(DataBase.keys)
  2935. .filter(([,x]) => x.t/*type*/ === 1/*group*/)
  2936. .sort(([,a], [,b]) => b.l - a.l)
  2937. .map(([hash, keyObj]) => ({ hash, lastseen: keyObj.l, descriptor: Utils.FormatDescriptor(keyObj.d) })));
  2938.  
  2939. ShareKeyWindow.Show(keys, (key) => Utils.ShareKey(key.hash, Cache.channelId));
  2940. }
  2941. );
  2942.  
  2943. PopupManager.Inject();
  2944.  
  2945. if(!FixedCsp) {
  2946. const imgsrcIdRegex = /#([^?]+)/;
  2947. const tryReplaceImage = async (img) => {
  2948. let srcmatch = imgsrcIdRegex.exec(img.src);
  2949. if(srcmatch == null) return;
  2950. let blob = Patcher.Images[srcmatch[1]];
  2951. if(blob == null) return;
  2952. let bitmap = await createImageBitmap(blob);
  2953. let width = bitmap.width;
  2954. let height = bitmap.height;
  2955. let canvas = document.createElement('canvas');
  2956. canvas.width = width;
  2957. canvas.height = height;
  2958. let ctx = canvas.getContext('2d');
  2959. ctx.drawImage(bitmap, 0, 0);
  2960. canvas.style.cssText = img.style.cssText;
  2961. img.replaceWith(canvas);
  2962. };
  2963. const scriptLink = function(event) {
  2964. event.preventDefault();
  2965. return new Function(this.href.substr(11)).apply(this);
  2966. };
  2967. Patcher = {
  2968. observer: new MutationObserver((mutations) => {
  2969. for(let mutation of mutations) {
  2970. if(mutation.type === 'attributes') {
  2971. let tagName = mutation.target.tagName;
  2972. if(tagName === 'IMG') {
  2973. if(mutation.attributeName !== 'src') continue;
  2974. let img = mutation.target;
  2975. tryReplaceImage(img);
  2976. }
  2977. else if(tagName === 'A') {
  2978. if(!mutation.target.href.startsWith("javascript:")) continue;
  2979. mutation.target.addEventListener('auxclick', scriptLink);
  2980. }
  2981. }
  2982. else {
  2983. let addedNode = mutation.addedNodes[0];
  2984. if(addedNode == null || addedNode.getElementsByTagName == null) continue;
  2985.  
  2986. let replaceImages = Patcher.Images;
  2987. for(let img of addedNode.getElementsByTagName('img')) {
  2988. tryReplaceImage(img);
  2989. }
  2990.  
  2991. for(let a of addedNode.getElementsByTagName('a')) {
  2992. if(!a.href.startsWith("javascript:")) continue;
  2993. a.addEventListener('auxclick', scriptLink);
  2994. }
  2995. }
  2996. }
  2997. }),
  2998. Images: []
  2999. };
  3000. Patcher.observer.observe(document.documentElement, { attributes: true, childList: true, subtree: true });
  3001. }
  3002.  
  3003. dbSaveInterval = setInterval(() => { Utils.SaveDb() }, 10000);
  3004.  
  3005. Utils.Log("loaded");
  3006.  
  3007. LoadBlacklist();
  3008. }
  3009.  
  3010. function Unload()
  3011. {
  3012. const restoreFunction = (moduleName, functionName) => { let module = Discord.modules[moduleName]; module[functionName] = module[`original_${functionName}`]; };
  3013.  
  3014. restoreFunction('MessageQueue', 'enqueue');
  3015. restoreFunction('MessageDispatcher', 'dispatch');
  3016. restoreFunction('FileUploader', 'upload');
  3017.  
  3018. //Discord.MessageCache.prototype._merge = Discord.original__merge;
  3019.  
  3020. if(Patcher != null) Patcher.observer.disconnect();
  3021.  
  3022. Style.Remove();
  3023. UnlockWindow.Remove();
  3024. NewdbWindow.Remove();
  3025. NewPasswordWindow.Remove();
  3026. KeyManagerWindow.Remove();
  3027. ChannelManagerWindow.Remove();
  3028. MenuBar.Remove();
  3029. PopupManager.Remove();
  3030.  
  3031. clearInterval(dbSaveInterval);
  3032. }
  3033.  
  3034. function TryInit()
  3035. {
  3036. if(Init(true) !== 0) return;
  3037.  
  3038. window.setTimeout(TryInit, 100);
  3039. };
  3040.  
  3041.  
  3042. Utils.Log("injected");
  3043.  
  3044. //window.addEventListener('load', TryInit);
  3045. TryInit();
  3046.  
  3047. })();
Add Comment
Please, Sign In to add comment