Get Watcher 2

Estimates time until the next get on 4chan

  1. // ==UserScript==
  2. // @author iralakaelah
  3. // @name Get Watcher 2
  4. // @version 2022.05.07
  5. // @license WTFPL
  6. // @namespace https://greatest.deepsurf.us/en/scripts/419176-get-watcher-2
  7. // @homepage https://greatest.deepsurf.us/en/scripts/
  8. // @description Estimates time until the next get on 4chan
  9. // @match http://boards.4channel.org/*
  10. // @match https://boards.4channel.org/*
  11. // @match http://boards.4chan.org/*
  12. // @match https://boards.4chan.org/*
  13. // @grant GM_xmlhttpRequest
  14. // @grant GM_openInTab
  15. // @connect a.4cdn.org
  16. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiI+PHBhdGggZD0iTTAgMzJIMTIuMkwxMS42IDMwLjMgMTMuMiAyMCA0LjYgMTYuMSAwIDIwLjciIGZpbGw9IiM3NmUiLz48cGF0aCBkPSJNNC42IDE2LjFDNC43IDE1LjcgNS42IDE0LjQgNS44IDExLjUgNS45IDEwLjEgNy4zIDkuMiA3LjggOCA4LjUgNi40IDguNCA0LjYgOC45IDIuOSA5LjIgMS45IDkgLjEgMTAuMSAwIDExLjctLjEgMTIuMyAyLjcgMTIuNSA0LjMgMTIuOCA2LjEgMTEuNiA3LjggMTEuMyA5LjVjLS4zIDEuNSAuMyAxLjkgMS40IDEuNCAyLjMtMS4yIDQuNC0xLjggNi4yLTMuMSAyLjctMS43IDQuOS00LjEgNy44LTUuNSAxLjYtLjggNC4xLTMgNS4xLTEuNSAxLjMgMi4xLTMuMiAzLjgtNC45IDUuNS0xLjcgMS43LTUuNiAzLjYtNS4xIDQuOCAuMyAuNyAxIDEuMyAxLjMgMi4xIC4zIDEgLjEgMi4xIC4zIDMuMSAuMiAxIDEgMS44IC45IDIuOC0uMSAyLjEtMS4yIDQuMy0yLjUgNS45LTEgMS4yLTIuMyAyLjQtMy44IDIuNi0yIC4yLTUuOSAyLjUtNi40IDIuN0MxMC4zIDI2LjkgOC40IDIyLjMgNy44IDIxIDcgMTkuNCA1LjYgMTcuNSA0LjYgMTYuMSIgZmlsbD0iI2NiZSIvPjwvc3ZnPg==
  17. // ==/UserScript==
  18. //<!doctype html><html><head><meta http-equiv="x-ua-compatible" content="ie=edge"></head><body id="hta"><script>
  19. 'use strict';
  20.  
  21. var version='2022.05.07'
  22.  
  23. var boards='3 a aco adv an asp b bant biz c cgl ck cm co d diy e f fa fit g gd gif \
  24. h hc his hm hr i ic int jp k lgbt lit m mlp mu n news o out p po pol pw qa qst r r9k \
  25. s s4s sci soc sp t tg toy trash trv tv u v vg vip vm vmg vp vr vrpg vst vt w wg wsg wsr x xs y'.split(' ')
  26.  
  27. function noDubsBoard(board){
  28. return /^(v|vg|vr)$/.test(board)
  29. }
  30.  
  31. function fastBoard(board){
  32. return /^(f)$/.test(board)
  33. }
  34.  
  35. function boardTitle(board){
  36. return board=='s4s'?'[s4s]':'/'+board+'/'
  37. }
  38.  
  39. if(document.body.id=='hta'){
  40. var zoom=screen.deviceXDPI/96
  41. resizeTo(700*zoom,420*zoom)
  42. var info=document.body.firstChild
  43. document.body.removeChild(info)
  44. var hta=1
  45. var boardName='s4s'
  46. var protocol='http:'
  47. document.title='Get Watcher'
  48. oncontextmenu=function(event){
  49. event.preventDefault()
  50. }
  51. window.GM_xmlhttpRequest=function(opts){
  52. var xhr=new XMLHttpRequest()
  53. xhr.onload=function(){
  54. opts.onload({
  55. responseText:xhr.responseText,
  56. status:xhr.status,
  57. responseHeaders:xhr.getAllResponseHeaders()
  58. })
  59. }
  60. xhr.onerror=function(){
  61. opts.onerror()
  62. }
  63. xhr.open(opts.method,opts.url)
  64. for(var i in opts.headers){
  65. xhr.setRequestHeader(i,opts.headers[i])
  66. }
  67. xhr.send()
  68. }
  69. }else{
  70. var zoom=1
  71. var hta=0
  72. var lp=location.pathname
  73. var boardName='s4s'
  74. var lpmatch=lp.match(/\/([^\/]+)\//)
  75. if(lpmatch&&(boards.indexOf(lpmatch[1])+1)){
  76. var boardName=lpmatch[1]
  77. }
  78. var protocol=location.protocol
  79. }
  80.  
  81. var website='greatest.deepsurf.us'
  82. var userjsurl=protocol+'//greatest.deepsurf.us/scripts/16671-get-watcher/code/Get%20Watcher.user.js'
  83. var metajsurl=protocol+'//greatest.deepsurf.us/scripts/16671-get-watcher/code/Get%20Watcher.meta.js'
  84.  
  85. var verage=toRelativeTime((new Date().getTime()-new Date(version.replace(/\./g,'/')).getTime())/1000,0,1)
  86. var delayValue=[1,5,30]
  87.  
  88. var localElements={}
  89.  
  90. var includedCss='#getWatcherBody{background:#fff;z-index:2;color:#000;font:14px sans-serif;margin:0;cursor:default;text-align:left}\
  91. input[type="text"],input[type="number"]{font-size:14px}\
  92. #windowTitle{height:25px;background:#bbb;padding:5px 0 0 5px;color:#555;cursor:default;box-sizing:border-box}\
  93. #windowClose{background:#d77;width:19px;height:19px;position:absolute;top:3px;right:3px}\
  94. #getWatcherBody a{font-weight:bold;color:#00f;text-decoration:none}\
  95. #getWatcherBody a:hover{text-decoration:underline}\
  96. .board-menu{position:absolute;background:#fff;border:1px solid #7f9db9;z-index:2}\
  97. .board-menu div{float:left;width:40px;padding:1px 3px;white-space:nowrap;overflow:hidden}\
  98. #tab1-board-menu div:hover,#tab3-board-menu div:hover,.board-menu .selected{background:#39f;color:#fff}\
  99. .board-menu br{clear:left}\
  100. #board-menu-bg{position:absolute;top:0;bottom:0;left:0;right:0}\
  101. .board-menu .tab2-long-option{width:132px;text-align:center}\
  102. .board-menu input{width:46px;height:100%;box-sizing:border-box;margin:0!important;padding:0 1px!important}\
  103. #getWatcherBody label{display:block;margin:15px 0}\
  104. #getWatcherBody .left-side{float:left;background:#eee;border-right:1px solid #999;padding:20px;width:140px;height:100%}\
  105. #getWatcherBody .left-side input[type="checkbox"],.left-side input[type="text"],.left-side input[type="number"],.left-side select{margin:0 7px}\
  106. #getWatcherBody .center{text-align:center}\
  107. #tab1,#tab2,#tab3,#tab4,#tab5{overflow:hidden;box-sizing:border-box}\
  108. #tab1-delay,#tab2-delay,#tab3-delay{width:50px}\
  109. #tab1-digits,#tab1-custompost,#tab2-board,#tab2-digits,#tab3-custompost{width:90%;box-sizing:border-box}\
  110. .tab1-customshown{margin-bottom:0!important}\
  111. #tab1-customlabel{margin-top:0!important}\
  112. #tab1 input[type="button"],#tab2 input[type="button"],#tab3 input[type="button"]{width:120px;height:30px;font-size:14px}\
  113. #tabs{white-space:nowrap}\
  114. #tabs>div{display:inline-block;width:25%;text-align:center;border-bottom:1px solid #999;height:50px;line-height:50px;background:#eee;box-sizing:border-box}\
  115. #tabs>div:hover{background:#ddd}\
  116. #tabs>.tab-selected{background:#ccc!important}\
  117. #tab1-content,#tab3-content,#tab4{padding:10px}\
  118. #tab1-content,#tab2-content,#tab3-content,#tab4{overflow:auto;height:100%;box-sizing:border-box}\
  119. #tab1-highlighted,#tab3-highlighted{width:100%;background:#eee;border-bottom:1px solid #999;text-align:center;padding:15px;font-size:20px}\
  120. #tab1-board,#tab3-board{width:70px}\
  121. .board-title{font-size:30px;padding-bottom:10px}\
  122. #tab2-content table{border-collapse:collapse;width:100%}\
  123. #tab2-content th,#tab2-content tr:hover{background:#ddd!important}\
  124. #tab2-content td,#tab2-content th{padding:3px;cursor:pointer}\
  125. #tab2-content td:first-child{text-align:center}\
  126. #tab2-content td:nth-child(2),#tab2-content td:nth-child(3){text-align:right}\
  127. #tab2-content th.sortup,#tab2-content th.sortdown{background:#ddd}\
  128. #tab2-content th.sortup::after{content:\'\u25BC\';color:#aaa}\
  129. #tab2-content th.sortdown::after{content:\'\u25B2\';color:#aaa}\
  130. #tab2-content tr:nth-child(2n+1){background:#f5f5f5}\
  131. #tab3-progress{position:absolute;bottom:0;height:10px}\
  132. #tab5{padding-top:80px}\
  133. #tab5-save,#tab5-load{width:60%;height:60px;display:block;margin:0 auto 50px auto;font-size:25px}\
  134. #saveload-input{display:none;width:1px;height:1px;background:none;border:0}'
  135. var windowTitle=[]
  136. if(hta){
  137. includedCss+='html,#getWatcherBody{height:100%}\
  138. body{margin:0;width:'+100/zoom+'%;height:'+100/zoom+'%}\
  139. body,.board-menu{zoom:'+zoom+'}\
  140. input[type="button"]{border:1px solid #adadad;background:#e1e1e1}\
  141. input[type="button"]:hover{border-color:#0078d7;background:#e5f1fb}\
  142. input[type="button"]:active{border-color:#005499;background:#cce4f7}\
  143. input[type="text"]{border:1px solid #abadb3;font-size:inherit;margin:2px 0;padding:2px}\
  144. input[type="text"]:focus{border-color:#55a2d8}\
  145. select{font-size:14px;border:1px solid #abadb3}\
  146. #tab1,#tab2,#tab3,#tab4,#tab5{height:calc(100% - 50px);box-sizing:border-box}\
  147. #tab3-progress{width:calc(100% - 181px * '+zoom+');border:0;background:#fff;right:0}'
  148. }else{
  149. includedCss+='#getWatcherBody{position:fixed;border:1px solid #333;width:700px;height:400px}\
  150. #tab3-progress{width:calc(100% - 194px);right:13px}\
  151. #windowResize{position:absolute;bottom:0;right:0;background:url(data:image/gif;base64,R0lGODlhCwALAPMIAJaWlq2srLm5ucXFxdHR0d3d3ejn5/Hx8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUAAAkALAAAAAALAAsAAAQhMMkpkRmUHhLyLALgjdWVWNi0dSsFii85oaeJtjgX72EEADs=) no-repeat;cursor:se-resize;width:13px;height:13px}\
  152. #tab1,#tab2,#tab3,#tab4,#tab5{height:calc(100% - 75px)}\
  153. #getWatcherBody .tab-selected{font-weight:normal!important}'
  154. windowTitle=[
  155. ['div',{
  156. id:'windowTitle',
  157. onmousedown:function(){
  158. move=1
  159. moveHandler(event)
  160. }
  161. },'Get Watcher'],
  162. ['div',{
  163. id:'windowClose',
  164. onclick:function(){
  165. getWatcherBody.style.display='none'
  166. }
  167. }],
  168. ['div',{
  169. id:'windowResize',
  170. onmousedown:function(){
  171. resize=1
  172. moveHandler(event)
  173. }
  174. }]
  175. ]
  176. }
  177.  
  178. element(
  179. document.body,
  180. ['div',{
  181. id:'getWatcherBody',
  182. style:'display:none'
  183. },
  184. windowTitle[0],
  185. windowTitle[1],
  186. windowTitle[2],
  187. ['div',{
  188. id:'board-menu-bg',
  189. style:'display:none',
  190. onmousedown:function(){
  191. id('board-menu-bg').style.display=
  192. id('tab1-board-menu').style.display=
  193. id('tab2-board-menu').style.display=
  194. id('tab3-board-menu').style.display='none'
  195. }
  196. }],
  197. ['div',{
  198. id:'tabs'
  199. },
  200. ['div',{
  201. id:'tab1-button',
  202. class:'tab-selected',
  203. onclick:function(){
  204. changeTab(1)
  205. }
  206. },'One board'],
  207. ['div',{
  208. id:'tab2-button',
  209. onclick:function(){
  210. changeTab(2)
  211. }
  212. },'Multiple boards'],
  213. ['div',{
  214. id:'tab3-button',
  215. onclick:function(){
  216. changeTab(3)
  217. }
  218. },'Loop'],
  219. ['div',{
  220. id:'tab4-button',
  221. onclick:function(){
  222. changeTab(4)
  223. }
  224. },'Help'],
  225. ['div',{
  226. id:'tab5-button',
  227. style:'display:none'
  228. }]
  229. ],
  230. ['div',{
  231. id:'tab1'
  232. },
  233. ['div',{
  234. class:'left-side'
  235. },
  236. ['label',{
  237. style:'display:inline'
  238. },
  239. 'Board:',
  240. ['select',{
  241. id:'tab1-board',
  242. onmousedown:function(){
  243. showChooseBoard(1)
  244. }
  245. },
  246. ['option',{
  247. value:boardName
  248. },boardTitle(boardName)]
  249. ]
  250. ],
  251. ['div',{
  252. id:'tab1-board-menu',
  253. class:'board-menu',
  254. style:'display:none'
  255. }],
  256. ['label',{
  257. id:'tab1-digitslabel'
  258. },
  259. ['select',{
  260. id:'tab1-digits'
  261. },
  262. ['option',{
  263. value:2
  264. },'Dubs (2)'],
  265. ['option',{
  266. value:3
  267. },'Trips (3)'],
  268. ['option',{
  269. value:4
  270. },'Quads (4)'],
  271. ['option',{
  272. value:5,
  273. selected:1
  274. },'Quints (5)'],
  275. ['option',{
  276. value:6
  277. },'Sexts (6)'],
  278. ['option',{
  279. value:7
  280. },'Septs (7)'],
  281. ['option',{
  282. value:8
  283. },'Octs (8)'],
  284. ['option',{
  285. value:9
  286. },'Nons (9)'],
  287. ['option',{
  288. value:0
  289. },'Custom post']
  290. ]
  291. ],
  292. ['label',{
  293. id:'tab1-customlabel',
  294. style:'display:none'
  295. },
  296. ['input',{
  297. id:'tab1-custompost',
  298. type:'text',
  299. maxlength:15,
  300. onkeydown:changeCustom,
  301. onkeyup:changeCustom
  302. }]
  303. ],
  304. ['label',{
  305. class:'tab1-notcustom',
  306. },
  307. ['input',{
  308. id:'tab1-clear',
  309. type:'checkbox'
  310. }],
  311. 'Clear'
  312. ],
  313. ['label',{
  314. class:'tab1-notcustom',
  315. },
  316. ['input',{
  317. id:'tab1-palindrome',
  318. type:'checkbox'
  319. }],
  320. 'Palindrome'
  321. ],
  322. ['label',
  323. 'Delay:',
  324. ['input',{
  325. id:'tab1-delay',
  326. type:'text',
  327. value:delayValue[0],
  328. min:1,
  329. max:99,
  330. maxlength:2
  331. }]
  332. ],
  333. ['label',
  334. ['input',{
  335. id:'tab1-fast',
  336. type:'checkbox'
  337. }],
  338. 'Fast check'
  339. ],
  340. ['label',{
  341. class:'center'
  342. },
  343. ['input',{
  344. id:'tab1-check',
  345. type:'button',
  346. value:'Check',
  347. onclick:oneBoard
  348. }]
  349. ],
  350. ['label',{
  351. class:'center'
  352. },
  353. ['input',{
  354. type:'button',
  355. value:'Save/Load',
  356. onclick:function(){
  357. saveLoadTab(1)
  358. }
  359. }]
  360. ]
  361. ],
  362. ['div',{
  363. id:'tab1-highlighted',
  364. style:'display:none'
  365. }],
  366. ['div',{
  367. id:'tab1-content'
  368. }]
  369. ],
  370. ['div',{
  371. id:'tab2',
  372. style:'display:none'
  373. },
  374. ['div',{
  375. class:'left-side'
  376. },
  377. ['label',{
  378. style:'display:inline'
  379. },
  380. ['select',{
  381. id:'tab2-board',
  382. onmousedown:function(){
  383. showChooseBoard(2)
  384. }
  385. },
  386. ['option','Boards...']
  387. ]
  388. ],
  389. ['div',{
  390. id:'tab2-board-menu',
  391. class:'board-menu',
  392. style:'display:none'
  393. }],
  394. ['label',
  395. ['select',{
  396. id:'tab2-digits'
  397. },
  398. ['option',{
  399. value:2
  400. },'Dubs (2)'],
  401. ['option',{
  402. value:3
  403. },'Trips (3)'],
  404. ['option',{
  405. value:4
  406. },'Quads (4)'],
  407. ['option',{
  408. value:5,
  409. selected:1
  410. },'Quints (5)'],
  411. ['option',{
  412. value:6
  413. },'Sexts (6)'],
  414. ['option',{
  415. value:7
  416. },'Septs (7)'],
  417. ['option',{
  418. value:8
  419. },'Octs (8)'],
  420. ['option',{
  421. value:9
  422. },'Nons (9)']
  423. ]
  424. ],
  425. ['label',
  426. ['input',{
  427. id:'tab2-clear',
  428. type:'checkbox'
  429. }],
  430. 'Clear'
  431. ],
  432. ['label',
  433. ['input',{
  434. id:'tab2-palindrome',
  435. type:'checkbox'
  436. }],
  437. 'Palindrome'
  438. ],
  439. ['label',
  440. 'Delay:',
  441. ['input',{
  442. id:'tab2-delay',
  443. type:'text',
  444. value:delayValue[1],
  445. min:1,
  446. max:99,
  447. maxlength:2
  448. }]
  449. ],
  450. ['label',
  451. ['input',{
  452. id:'tab2-fast',
  453. type:'checkbox',
  454. checked:1
  455. }],
  456. 'Fast check'
  457. ],
  458. ['label',{
  459. class:'center'
  460. },
  461. ['input',{
  462. id:'tab2-check',
  463. type:'button',
  464. value:'Check',
  465. onclick:multipleBoards
  466. }]
  467. ],
  468. ['label',{
  469. class:'center'
  470. },
  471. ['input',{
  472. type:'button',
  473. value:'Save/Load',
  474. onclick:function(){
  475. saveLoadTab(2)
  476. }
  477. }]
  478. ]
  479. ],
  480. ['div',{
  481. id:'tab2-content'
  482. }]
  483. ],
  484. ['div',{
  485. id:'tab3',
  486. style:'display:none'
  487. },
  488. ['div',{
  489. class:'left-side'
  490. },
  491. ['label',{
  492. style:'display:inline'
  493. },
  494. 'Board:',
  495. ['select',{
  496. id:'tab3-board',
  497. onmousedown:function(){
  498. showChooseBoard(3)
  499. }
  500. },
  501. ['option',{
  502. value:boardName
  503. },boardTitle(boardName)]
  504. ]
  505. ],
  506. ['div',{
  507. id:'tab3-board-menu',
  508. class:'board-menu',
  509. style:'display:none'
  510. }],
  511. ['label',
  512. 'Post number:',
  513. ['input',{
  514. id:'tab3-custompost',
  515. type:'text',
  516. maxlength:15,
  517. onkeydown:changeCustom,
  518. onkeyup:changeCustom
  519. }]
  520. ],
  521. ['label',
  522. 'Delay:',
  523. ['input',{
  524. type:'text',
  525. value:delayValue[2],
  526. min:1,
  527. max:99,
  528. maxlength:2,
  529. id:'tab3-delay'
  530. }]
  531. ],
  532. ['label',{
  533. class:'center'
  534. },
  535. ['input',{
  536. id:'tab3-check',
  537. type:'button',
  538. value:'Check',
  539. onclick:loopBoard
  540. }]
  541. ]
  542. ],
  543. ['div',{
  544. id:'tab3-highlighted',
  545. style:'display:none'
  546. }],
  547. ['div',{
  548. id:'tab3-content'
  549. }],
  550. ['progress',{
  551. value:0,
  552. id:'tab3-progress'
  553. }]
  554. ],
  555. ['div',{
  556. id:'tab4',
  557. style:'display:none'
  558. },
  559. ['div',{
  560. class:'center'
  561. },
  562. ['h3',
  563. 'Get Watcher'
  564. ],
  565. '(',
  566. ['a',{
  567. onclick:updateScript,
  568. style:'color:#00f;cursor:pointer'
  569. },'Version '+version],
  570. ', '+verage+' ago)',
  571. ['br'],
  572. ['br']
  573. ],
  574. 'Get Watcher uses 4chan API to predict the time until the next get. You can choose between three options: One board, Multiple boards, and Loop. Get Watcher is open source and is released under public domain.',
  575. ['br'],
  576. ['br'],
  577. ['b','One board'],
  578. ': Get stats from only one board, like time until next get, posts per second, etc.',
  579. ['br'],
  580. ['br'],
  581. ['b','Multiple boards'],
  582. ': It lists every board\'s stats in a table that you can sort. The table\'s contents are: the board, next get, time until that next get, and how many posts are left until it. To sort the list, click on one of the headers.',
  583. ['br'],
  584. ['br'],
  585. ['b','Loop'],
  586. ': Constantly checks the first page for latest post and posts per second value until stopped.',
  587. ['br'],
  588. ['br'],
  589. ['b','Board'],
  590. ': The board you want to get stats from.',
  591. ['br'],
  592. ['br'],
  593. ['b','Digits dropdown'],
  594. ': Recurring digits at the end of a post number.',
  595. ['br'],
  596. 'Example: 123444 has three recurring numbers at the end, 133333 has five.',
  597. ['br'],
  598. ['br'],
  599. ['b','Clear'],
  600. ': Last digits to be always zero, locked on for boards that don\'t have dubs (/b/, /v/, /vg/, /vr/).',
  601. ['br'],
  602. 'Example: 130000.',
  603. ['br'],
  604. ['br'],
  605. ['b','Palindrome'],
  606. ': Post IDs that read the same forwards and backwards.',
  607. ['br'],
  608. 'Example: 123321, 4132314.',
  609. ['br'],
  610. ['br'],
  611. ['b','Delay'],
  612. ': How much delay in seconds there should be between calls to 4chan API, the documentation suggests no less than 10 seconds. With each call Get Watcher downloads about 25-30KB of JSON data.',
  613. ['br'],
  614. ['br'],
  615. ['b','Fast check'],
  616. ': Instead of making two calls for both the first post and for an old post from page 10, it makes only one. Has different results, recommended on for multiple boards.'
  617. ],
  618. ['div',{
  619. id:'tab5',
  620. style:'display:none'
  621. },
  622. ['input',{
  623. type:'button',
  624. id:'tab5-save',
  625. value:'Save',
  626. onclick:saveToFile
  627. }],
  628. ['input',{
  629. id:'tab5-load',
  630. type:'button',
  631. value:'Load',
  632. onclick:function(){
  633. id('saveload-input').style.display='block'
  634. id('saveload-input').click()
  635. id('saveload-input').style.display='none'
  636. }
  637. }],
  638. ['form',{
  639. id:'saveload-form'
  640. },
  641. ['input',{
  642. id:'saveload-input',
  643. type:'file',
  644. onchange:loadFromFile
  645. }]
  646. ]
  647. ],
  648. ['style',includedCss]
  649. ]
  650. )
  651.  
  652. var initializeCompleted=0
  653. if(hta){
  654. initialize()
  655. }else{
  656. element(
  657. document.body,
  658. ['input',{
  659. id:'open-get-watcher',
  660. type:'button',
  661. value:'Open Get Watcher',
  662. style:'position:absolute;top:100px;left:10px',
  663. onclick:initialize
  664. }]
  665. )
  666. }
  667.  
  668. var oneBoardCache={}
  669. var multipleBoardsCache={}
  670. var loopCache={}
  671. var sortCache=[2,0]
  672. var saveloadFromTab=1
  673. var multipleCurrentBoards=[]
  674. var currentBoards=[]
  675. var boardIndex=0
  676. var move=0
  677. var resize=0
  678. var tab2BoardOptions
  679. var oneBoardTimer
  680. var multipleBoardsTimer
  681. var loopBoardTimer
  682. var mouseXlast=null
  683. var mouseYlast=null
  684.  
  685. var getWatcherBody
  686.  
  687. function initialize(){
  688. getWatcherBody=id('getWatcherBody')
  689. if(!hta){
  690. getWatcherBody.style.top='100px'
  691. getWatcherBody.style.left='100px'
  692. }
  693. getWatcherBody.style.display='block'
  694. if(!initializeCompleted){
  695. initializeCompleted=1
  696.  
  697. for(var i=1;i<4;i++){
  698. changeTab(i)
  699. id('tab'+i+'-board-menu').style.top=(id('tab'+i+'-board').offsetTop+id('tab'+i+'-board').offsetHeight-1)*zoom+'px'
  700. id('tab'+i+'-board-menu').style.left=id('tab'+i+'-board').offsetLeft*zoom+'px'
  701. }
  702. changeTab(1)
  703.  
  704. var tab1Elements=document.querySelectorAll('#tab1 [id^="tab1-"]')
  705. for(var i=0;i<tab1Elements.length;i++){
  706. tab1Elements[i].onchange=tab1Changed
  707. }
  708. var tab2Elements=document.querySelectorAll('#tab2 [id^="tab2-"]')
  709. for(var i=0;i<tab2Elements.length;i++){
  710. tab2Elements[i].onchange=tab2Changed
  711. }
  712.  
  713. id('tab1-custompost').addEventListener('focus',focusCustom)
  714. id('tab3-custompost').addEventListener('focus',focusCustom)
  715. id('tab1-custompost').addEventListener('blur',changeCustom)
  716. id('tab3-custompost').addEventListener('blur',changeCustom)
  717.  
  718. var boardList=[]
  719. for(var i=0;i<boards.length;i++){
  720. if(i&&i%6==0){
  721. boardList.push(['br'])
  722. }
  723. boardList.push(
  724. ['div',{
  725. board:boards[i]
  726. },boardTitle(boards[i])]
  727. )
  728. }
  729. element(
  730. id('tab1-board-menu'),
  731. boardList,
  732. i%6==0?['br']:null,
  733. ['div',{
  734. onclick:plusBoard
  735. },'+'],
  736. id('tab3-board-menu'),
  737. boardList,
  738. i%6==0?['br']:null,
  739. ['div',{
  740. onclick:plusBoard
  741. },'+'],
  742. id('tab2-board-menu'),
  743. boardList,
  744. ['br'],
  745. ['div',{
  746. class:'tab2-long-option',
  747. onclick:function(){
  748. tab2SelectAllBoards()
  749. }
  750. },'Select all'],
  751. ['div',{
  752. class:'tab2-long-option',
  753. onclick:function(){
  754. tab2SelectAllBoards(1)
  755. }
  756. },'Unselect all']
  757. )
  758.  
  759. var tab1BoardOptions=document.querySelectorAll('#tab1-board-menu div')
  760. tab2BoardOptions=document.querySelectorAll('#tab2-board-menu div:not(.tab2-long-option)')
  761. var tab3BoardOptions=document.querySelectorAll('#tab3-board-menu div')
  762. for(var i=0;i<tab2BoardOptions.length;i++){
  763. if(tab1BoardOptions[i].getAttribute('board')==boardName){
  764. tab1BoardOptions[i].classList.add('selected')
  765. tab3BoardOptions[i].classList.add('selected')
  766. }
  767. tab2BoardOptions[i].classList.add('selected')
  768. tab1BoardOptions[i].onclick=tab1or3ChooseBoard
  769. tab2BoardOptions[i].onclick=tab2ChooseBoard
  770. tab3BoardOptions[i].onclick=tab1or3ChooseBoard
  771. }
  772.  
  773. id('tab1-digits').addEventListener('change',function(event){
  774. if(event.currentTarget.value*1){
  775. id('tab1-customlabel').style.display='none'
  776. id('tab1-digitslabel').classList.remove('tab1-customshown')
  777. }else{
  778. id('tab1-customlabel').style.display='block'
  779. id('tab1-digitslabel').classList.add('tab1-customshown')
  780. }
  781. var notcustom=document.getElementsByClassName('tab1-notcustom')
  782. for(var i=0;i<notcustom.length;i++){
  783. notcustom[i].style.display=event.currentTarget.value*1?'block':'none'
  784. }
  785. })
  786.  
  787. for(var i=1;i<4;i++){
  788. id('tab'+i+'-delay').addEventListener('change',changeDelay)
  789. id('tab'+i+'-delay').addEventListener('blur',changeDelay)
  790. id('tab'+i+'-delay').onkeydown=changeDelay
  791. id('tab'+i+'-delay').onkeyup=changeDelay
  792. }
  793.  
  794. if(!hta){
  795. addEventListener('mousemove',moveHandler)
  796. addEventListener('mouseup',function(){
  797. move=0
  798. resize=0
  799. mouseXlast=null
  800. mouseYlast=null
  801. })
  802. }
  803. }
  804. }
  805.  
  806. function id(a){
  807. return localElements[a]
  808. }
  809.  
  810. function toRelativeTime(difference,originaldate,mode){
  811. var timd=Math.abs(difference)
  812. if(mode>1&&timd<10){
  813. var sec=(timd%60*10|0)/10
  814. }else{
  815. var sec=timd%60|0
  816. }
  817. timd/=60
  818. var min=timd%60|0
  819. timd/=60
  820. var hrs=timd%24|0
  821. timd/=24
  822. var day=timd%30.4375|0
  823. timd/=30.4375
  824. var mon=timd%12|0
  825. var yer=Math.floor(timd/12)
  826. if(mode==1){
  827. var till=[
  828. yer?yer+' year'+(yer>1?'s ':' '):'',
  829. mon&&yer<2?mon+' month'+(mon>1?'s ':' '):'',
  830. (day&&mon<2)||!mon?day+' day'+(day==1?'':'s'):''
  831. ]
  832. }else if(mode==3){
  833. var till=[
  834. yer?formatNumber(yer)+'y':'',
  835. mon&&yer<2?mon+'mo':'',
  836. day&&mon<2?day+'d':'',
  837. hrs&&day<2?hrs+'h':'',
  838. min&&hrs<3?min+'m':'',
  839. (sec&&min<10)||!min?sec+'s':''
  840. ]
  841. }else{
  842. var till=[
  843. yer?formatNumber(yer)+'\xA0year'+(yer>1?'s ':' '):'',
  844. mon&&yer<2?mon+'\xA0month'+(mon>1?'s ':' '):'',
  845. day&&mon<2?day+'\xA0day'+(day>1?'s ':' '):'',
  846. hrs&&day<2?hrs+'\xA0hour'+(hrs>1?'s ':' '):'',
  847. min&&hrs<3?min+'\xA0minute'+(min>1?'s ':' '):'',
  848. (sec&&min<10)||!min?sec+'\xA0second'+(sec==1?'':'s'):''
  849. ]
  850. }
  851. for(var g=0;g<till.length&&!till[g];g++){}
  852. if(mode==3){
  853. var tout=till[g]+(till[g+1]||'')
  854. }else{
  855. var tout=(till[g]+(till[g+1]?'and '+till[g+1]:'')).trim()
  856. }
  857. if(originaldate){
  858. var newdt=new Date((originaldate*1+difference*1)*1000)
  859. if(newdt*1){
  860. try{
  861. var parseddate=newdt.toLocaleTimeString()+' ('+newdt.toDateString()+')'
  862. }catch(e){
  863. var parseddate=newdt
  864. }
  865. }else{
  866. var parseddate='Year\xA0'+formatNumber(new Date(originaldate*1000).getFullYear()+yer)
  867. }
  868. return [tout,parseddate]
  869. }else{
  870. return tout
  871. }
  872. }
  873.  
  874. function element(){
  875. var parent
  876. var lasttag
  877. var createdtag
  878. var toreturn={}
  879. for(var i=0;i<arguments.length;i++){
  880. var current=arguments[i]
  881. if(current){
  882. if(current.nodeType){
  883. parent=lasttag=current
  884. }else if(Array.isArray(current)){
  885. for(var j=0;j<current.length;j++){
  886. if(current[j]){
  887. if(!j&&typeof current[j]=='string'){
  888. var tagname=current[0].split('#')
  889. lasttag=createdtag=document.createElement(tagname[0])
  890. if(tagname[1]){
  891. toreturn[tagname[1]]=createdtag
  892. }
  893. }else if(current[j].constructor==Object){
  894. if(lasttag){
  895. for(var value in current[j]){
  896. if(value=='id'){
  897. localElements[current[j][value]]=lasttag
  898. }
  899. if(value!='style'&&value in lasttag){
  900. lasttag[value]=current[j][value]
  901. }else{
  902. lasttag.setAttribute(value,current[j][value])
  903. }
  904. }
  905. }
  906. }else{
  907. var returned=element(lasttag,current[j])
  908. for(var k in returned){
  909. toreturn[k]=returned[k]
  910. }
  911. }
  912. }
  913. }
  914. }else if(current){
  915. createdtag=document.createTextNode(current)
  916. }
  917. if(parent&&createdtag){
  918. parent.appendChild(createdtag)
  919. }
  920. createdtag=0
  921. }
  922. }
  923. return toreturn
  924. }
  925.  
  926. function formatNumber(num){
  927. num=num+''
  928. while(/\d{4}/.test(num)){
  929. num=num.replace(/\d+(?=\d{3})/,'$&\u202F')
  930. }
  931. return num
  932. }
  933.  
  934. function changeTab(tabN){
  935. var tabselected=document.getElementsByClassName('tab-selected')[0]
  936. if(tabselected){
  937. tabselected.classList.remove('tab-selected')
  938. }
  939. var tabs=id('tabs').childNodes
  940. for(var i=0;i<tabs.length;i++){
  941. id('tab'+(i+1)).style.display='none'
  942. }
  943. tabs[tabN-1].classList.add('tab-selected')
  944. id('tab'+tabN).style.display='block'
  945. }
  946.  
  947. function tab2SelectAllBoards(unselect){
  948. for(var i=0;i<tab2BoardOptions.length;i++){
  949. if(unselect){
  950. tab2BoardOptions[i].className=''
  951. }else{
  952. tab2BoardOptions[i].className='selected'
  953. }
  954. }
  955. }
  956.  
  957. function showChooseBoard(tabNumber){
  958. var boardMenu=id('tab'+tabNumber+'-board-menu')
  959. id('board-menu-bg').style.display=boardMenu.style.display='block'
  960.  
  961. setTimeout(function(){
  962. id('tab'+tabNumber+'-board').blur()
  963. },10)
  964. }
  965.  
  966. function tab1or3ChooseBoard(event,target){
  967. if(!target){
  968. target=event.currentTarget
  969. }
  970. var boardSelect=target.parentNode.parentNode
  971. var boardMenu=boardSelect.getElementsByClassName('board-menu')[0]
  972. var optionElement=boardSelect.getElementsByTagName('option')[0]
  973. var selected=boardMenu.getElementsByClassName('selected')[0]
  974. if(selected){
  975. selected.classList.remove('selected')
  976. }
  977. boardMenu.style.display=id('board-menu-bg').style.display='none'
  978. target.classList.add('selected')
  979. optionElement.value=target.getAttribute('board')
  980. optionElement.innerHTML=target.innerHTML
  981. }
  982.  
  983. function tab2ChooseBoard(event){
  984. var sourceElement=event.currentTarget
  985. if(sourceElement.className){
  986. sourceElement.className=''
  987. }else{
  988. sourceElement.className='selected'
  989. }
  990. }
  991.  
  992. function oneBoard(){
  993. var checkButton=id('tab1-check')
  994. if(checkButton.value=='Check'){
  995. var currentBoard=id('tab1-board').value
  996. oneBoardCache={
  997. currentBoard:currentBoard
  998. }
  999. getLastPost(currentBoard,oneBoard2)
  1000. }else{
  1001. clearTimeout(oneBoardTimer)
  1002. checkButton.value='Check'
  1003. }
  1004. }
  1005.  
  1006. function oneBoard2(lastStats){
  1007. var checkButton=id('tab1-check')
  1008. var currentBoard=oneBoardCache.currentBoard
  1009. if(lastStats.post){
  1010. oneBoardCache={
  1011. currentBoard:currentBoard,
  1012. lastStats:lastStats
  1013. }
  1014. if(!id('tab1-fast').checked&&!fastBoard(currentBoard)){
  1015. checkButton.value='Stop'
  1016. oneBoardTimer=setTimeout(function(){
  1017. getOldPost(currentBoard,0,oneBoard3)
  1018. },delayValue[0]*1000)
  1019. }
  1020. }else{
  1021. oneBoardCache={}
  1022. }
  1023. tab1Changed()
  1024. }
  1025.  
  1026. function oneBoard3(oldStats){
  1027. if(oldStats.post){
  1028. oneBoardCache.oldStats=oldStats
  1029. tab1Changed()
  1030. }
  1031. id('tab1-check').value='Check'
  1032. }
  1033.  
  1034. function multipleBoards(){
  1035. var checkButton=id('tab2-check')
  1036. if(checkButton.value=='Check'){
  1037. currentBoards=id('tab2-board-menu').getElementsByClassName('selected')
  1038. if(currentBoards.length){
  1039. checkButton.value='Stop'
  1040. multipleBoardsCache={}
  1041. boardIndex=0
  1042. multipleCurrentBoards=[]
  1043. for(var i=0;i<currentBoards.length;i++){
  1044. multipleCurrentBoards[i]=currentBoards[i].getAttribute('board')
  1045. }
  1046. multipleBoardsLoop()
  1047. }
  1048. }else{
  1049. clearTimeout(multipleBoardsTimer)
  1050. checkButton.value='Check'
  1051. }
  1052. }
  1053.  
  1054. function loopBoard(){
  1055. var checkButton=id('tab3-check')
  1056. if(checkButton.value=='Check'){
  1057. checkButton.value='Stop'
  1058. var currentBoard=id('tab3-board').value
  1059. loopBoardLoop(currentBoard)
  1060. }else{
  1061. clearTimeout(loopBoardTimer)
  1062. id('tab3-progress').value=0
  1063. checkButton.value='Check'
  1064. }
  1065. }
  1066.  
  1067. function tab1Changed(){
  1068. id('tab1-highlighted').innerHTML=id('tab1-content').innerHTML=''
  1069. var lastStats=oneBoardCache.lastStats
  1070. if(lastStats){
  1071. id('tab1-custompost').placeholder=lastStats.post
  1072. if(oneBoardCache.oldStats){
  1073. var oldStats=oneBoardCache.oldStats
  1074. }else{
  1075. var oldStats=getOldPost(0,lastStats.cache)
  1076. oneBoardCache.oldStats=oldStats
  1077. delete oneBoardCache.lastStats.cache
  1078. }
  1079. var postsInbetween=lastStats.post-oldStats.post
  1080. var timeInbetween=lastStats.time-oldStats.time
  1081. var postsPerSecond=postsInbetween/timeInbetween
  1082.  
  1083. var currentBoard=oneBoardCache.currentBoard
  1084. var noDubs=noDubsBoard(currentBoard)
  1085. var getDigits=id('tab1-digits').value*1
  1086. var customPost=id('tab1-custompost').value
  1087. if(!getDigits){
  1088. if(/[-+]/.test(customPost[0])){
  1089. customPost-=-lastStats.post
  1090. }
  1091. if(customPost>0&&customPost<1e15){
  1092. getStats={next:Math.floor(customPost)}
  1093. }else{
  1094. getStats={next:lastStats.post}
  1095. }
  1096. }else{
  1097. var getClear=id('tab1-clear').checked
  1098. var getPalindrome=id('tab1-palindrome').checked
  1099. var getStats=getNextGet(lastStats.post,getDigits,getClear,noDubs,getPalindrome)
  1100. }
  1101. var lastGetAgo=lastStats.post-getStats.last
  1102. var postsUntilGet=getStats.next-lastStats.post
  1103. var relativeTime=toRelativeTime(postsUntilGet/postsPerSecond,lastStats.time)
  1104. var lastDate=new Date(lastStats.time*1000)
  1105. var postPast=postsUntilGet<0
  1106. if(noDubs){
  1107. postsPerSecond*=0.901
  1108. // You can get this number by filling an array with numbers from 0 to 999, not including the numbers that have repeating digits at the end. The resulting array should have 901 numbers, just divide that by 1000
  1109. }
  1110. postsPerSecond=toRelativeTime(1/postsPerSecond,0,2)
  1111. element(
  1112. id('tab1-highlighted'),[{
  1113. style:'display:block'
  1114. }],
  1115. ['div',{
  1116. class:'board-title'
  1117. },boardTitle(currentBoard)],
  1118. ['b',relativeTime[0]],
  1119. ' '+(postPast?'after':'until')+' the ',
  1120. ['b',formatNumber(getStats.next)],
  1121. ' get',
  1122. id('tab1-content'),
  1123. 'Posts ',
  1124. (postPast?['b','since']:'left'),
  1125. ': ',
  1126. ['b',formatNumber(Math.abs(postsUntilGet))],
  1127. ['br'],
  1128. 'Board speed: 1 post per ',
  1129. ['b',postsPerSecond],
  1130. ['br'],
  1131. 'Estimated date: ',
  1132. relativeTime[1],
  1133. ['br'],
  1134. ['br'],
  1135. 'Current post: ',
  1136. ['a',{
  1137. href:protocol+'//sys.4chan.org/'+currentBoard+'/imgboard.php?res='+lastStats.post
  1138. },formatNumber(lastStats.post)],
  1139. ' ('+lastDate.toLocaleTimeString()+', '+lastDate.toDateString()+')'
  1140. )
  1141. if(getStats.last&&getDigits){
  1142. element(
  1143. id('tab1-content'),
  1144. ['br'],
  1145. 'Last get (',
  1146. ['a',{
  1147. href:protocol+'//sys.4chan.org/'+currentBoard+'/imgboard.php?res='+getStats.last
  1148. },getStats.last],
  1149. ') happened ',
  1150. ['b',lastGetAgo],
  1151. ' post'+(lastGetAgo==1?'':'s')+' ago'
  1152. )
  1153. }
  1154. }else{
  1155. id('tab1-highlighted').style.display='none'
  1156. }
  1157. }
  1158.  
  1159. function multipleBoardsLoop(){
  1160. var currentBoard=multipleCurrentBoards[boardIndex]
  1161. getLastPost(currentBoard,multipleBoardsLoop2)
  1162. }
  1163. function multipleBoardsLoop2(lastStats){
  1164. var currentBoard=multipleCurrentBoards[boardIndex]
  1165. if(lastStats.post){
  1166. multipleBoardsCache[currentBoard]={
  1167. lastStats:lastStats
  1168. }
  1169. tab2Changed()
  1170. }
  1171.  
  1172. if(id('tab2-fast').checked||fastBoard(currentBoard)||!lastStats.post){
  1173. boardIndex++
  1174. if(boardIndex<currentBoards.length){
  1175. multipleBoardsTimer=setTimeout(function(){
  1176. multipleBoardsLoop(currentBoards,boardIndex)
  1177. },delayValue[1]*1000)
  1178. }else{
  1179. id('tab2-check').value='Check'
  1180. }
  1181. }else{
  1182. multipleBoardsTimer=setTimeout(function(){
  1183. getOldPost(currentBoard,0,multipleBoardsLoop3)
  1184. },delayValue[1]*1000)
  1185. }
  1186. }
  1187. function multipleBoardsLoop3(oldStats){
  1188. var currentBoard=multipleCurrentBoards[boardIndex]
  1189. if(oldStats.post){
  1190. multipleBoardsCache[new String(currentBoard)].oldStats=oldStats
  1191. tab2Changed()
  1192. }
  1193. boardIndex++
  1194. if(boardIndex<currentBoards.length){
  1195. multipleBoardsTimer=setTimeout(function(){
  1196. multipleBoardsLoop()
  1197. },delayValue[1]*1000)
  1198. }else{
  1199. id('tab2-check').value='Check'
  1200. }
  1201. }
  1202.  
  1203. function tab2Changed(){
  1204. if(Object.keys(multipleBoardsCache).length){
  1205. var getDigits=id('tab2-digits').value
  1206. var getClear=id('tab2-clear').checked
  1207. var getPalindrome=id('tab2-palindrome').checked
  1208.  
  1209. var sortWhich=sortCache[0]
  1210. var sortBackwards=sortCache[1]
  1211.  
  1212. var rows=['Board','Next get','Happens in','Posts left','Board speed']
  1213.  
  1214. var sorttablerow=['tr']
  1215. for(var i in rows){
  1216. var newth=['th',{
  1217. id:'sortTableRow'+i,
  1218. class:sortWhich==i?sortBackwards?'sortup':'sortdown':''
  1219. },rows[i]]
  1220. if(i!=1){
  1221. newth[1].onclick=sortTableRow
  1222. }
  1223. sorttablerow.push(newth)
  1224. }
  1225.  
  1226. var multipleBoardsStats=[]
  1227.  
  1228. for(var currentBoard in multipleBoardsCache){
  1229. var boardCache=multipleBoardsCache[currentBoard]
  1230. var noDubs=noDubsBoard(currentBoard)
  1231. var lastStats=boardCache.lastStats
  1232.  
  1233. if(boardCache.oldStats){
  1234. var oldStats=boardCache.oldStats
  1235. }else{
  1236. var oldStats=getOldPost(0,lastStats.cache)
  1237. boardCache.oldStats=oldStats
  1238. delete lastStats.cache
  1239. }
  1240. var postsInbetween=lastStats.post-oldStats.post
  1241. var timeInbetween=lastStats.time-oldStats.time
  1242. var postsPerSecond=postsInbetween/timeInbetween
  1243.  
  1244. var getStats=getNextGet(lastStats.post,getDigits,getClear,noDubs,getPalindrome)
  1245. var postsUntilGet=getStats.next-lastStats.post
  1246. var relativeTimestamp=postsUntilGet/postsPerSecond
  1247.  
  1248. if(noDubs){
  1249. postsPerSecond*=0.901
  1250. }
  1251.  
  1252. multipleBoardsStats.push([
  1253. currentBoard,
  1254. getStats.next,
  1255. relativeTimestamp,
  1256. postsUntilGet,
  1257. 1/postsPerSecond
  1258. ])
  1259. }
  1260.  
  1261. if(sortBackwards){
  1262. var sortedStats=multipleBoardsStats.sort(function(a,b){
  1263. return a[sortWhich]<b[sortWhich]?1:-1
  1264. })
  1265. }else{
  1266. var sortedStats=multipleBoardsStats.sort(function(a,b){
  1267. return a[sortWhich]>b[sortWhich]?1:-1
  1268. })
  1269. }
  1270.  
  1271. var statstable=[]
  1272. for(var i in sortedStats){
  1273. statstable.push(
  1274. ['tr',{
  1275. id:'table-board-'+sortedStats[i][0],
  1276. onclick:multipleToSingle
  1277. },
  1278. ['td',boardTitle(sortedStats[i][0])],
  1279. ['td',formatNumber(sortedStats[i][1])],
  1280. ['td','(in '+toRelativeTime(sortedStats[i][2])+','],
  1281. ['td',formatNumber(sortedStats[i][3])+' post'+(sortedStats[i][3]==1?'':'s')+')'],
  1282. ['td','1 post/\u200B'+toRelativeTime(sortedStats[i][4],0,3)]
  1283. ]
  1284. )
  1285. }
  1286.  
  1287. var tab2Content=id('tab2-content')
  1288. tab2Content.innerHTML=''
  1289. element(
  1290. tab2Content,
  1291. ['table',
  1292. sorttablerow,
  1293. statstable
  1294. ]
  1295. )
  1296. }
  1297. }
  1298.  
  1299. function sortTableRow(event){
  1300. var sortWhich=event.currentTarget.id.replace('sortTableRow','')*1
  1301. if(sortWhich==sortCache[0]&&!sortCache[1]){
  1302. sortCache=[sortWhich,1]
  1303. }else{
  1304. sortCache=[sortWhich,0]
  1305. }
  1306. tab2Changed()
  1307. }
  1308.  
  1309. function multipleToSingle(event){
  1310. var target=event.currentTarget
  1311. for(;target;){
  1312. if(target.id){
  1313. var currentBoard=target.id.replace('table-board-','')
  1314. break
  1315. }
  1316. target=target.parentNode
  1317. }
  1318. oneBoardCache=multipleBoardsCache[currentBoard]
  1319. oneBoardCache.currentBoard=currentBoard
  1320. tab1Changed()
  1321. changeTab(1)
  1322. }
  1323.  
  1324. function loopBoardLoop(){
  1325. var currentBoard=id('tab3-board').value
  1326. var lastCall=''
  1327. if(loopCache.currentBoard==currentBoard){
  1328. lastCall=loopCache.lastCall
  1329. }
  1330. loopCache={
  1331. currentBoard:currentBoard
  1332. }
  1333. getLastPost(currentBoard,loopBoardLoop2,lastCall)
  1334. }
  1335.  
  1336. function loopBoardLoop2(lastStats){
  1337. id('tab3-progress').value=0
  1338. if(lastStats){
  1339. loopCache={
  1340. currentBoard:loopCache.currentBoard,
  1341. lastCall:lastStats.lastCall,
  1342. lastStats:lastStats
  1343. }
  1344. }
  1345. tab3Changed()
  1346. loopBoardTimer=setInterval(function(){
  1347. var progress=id('tab3-progress')
  1348. var maxProgress=delayValue[2]
  1349. progress.setAttribute('max',maxProgress)
  1350. var progressValue=progress.value
  1351. if(progressValue>=maxProgress){
  1352. clearTimeout(loopBoardTimer)
  1353. loopBoardLoop()
  1354. }else{
  1355. progress.value+=1
  1356. }
  1357. },1000)
  1358. }
  1359.  
  1360. function tab3Changed(){
  1361. id('tab3-highlighted').innerHTML=id('tab3-content').innerHTML=''
  1362. var lastStats=loopCache.lastStats
  1363. if(lastStats){
  1364. id('tab3-custompost').placeholder=lastStats.post
  1365. var oldStats=getOldPost(0,lastStats.cache)
  1366. var postsInbetween=lastStats.post-oldStats.post
  1367. var timeInbetween=lastStats.time-oldStats.time
  1368. var lastDate=new Date(lastStats.time*1000)
  1369. var postsPerSecond=postsInbetween/timeInbetween
  1370. var customPost=Math.floor(id('tab3-custompost').value)
  1371. if(customPost==lastStats.post){
  1372. customPost=0
  1373. }
  1374. if(customPost>0){
  1375. var postsUntilGet=customPost-lastStats.post
  1376. var relativeTime=toRelativeTime(postsUntilGet/postsPerSecond,lastStats.time)
  1377. var postPast=postsUntilGet<0
  1378. }
  1379. if(noDubsBoard(loopCache.currentBoard)){
  1380. postsPerSecond*=0.901
  1381. }
  1382. postsPerSecond=toRelativeTime(1/postsPerSecond,0,2)
  1383. element(
  1384. id('tab3-highlighted'),[{
  1385. style:'display:block'
  1386. }],
  1387. ['div',{
  1388. class:'board-title'
  1389. },boardTitle(loopCache.currentBoard)],
  1390. 'Current post: ',
  1391. ['a',{
  1392. href:protocol+'//sys.4chan.org/'+loopCache.currentBoard+'/imgboard.php?res='+lastStats.post,
  1393. style:'font-weight:bold'
  1394. },formatNumber(lastStats.post)],
  1395. ['br'],
  1396. 'Board speed: 1 post per ',
  1397. ['b',postsPerSecond]
  1398. )
  1399. if(customPost>0){
  1400. element(
  1401. id('tab3-content'),
  1402. ['b',relativeTime[0]],
  1403. ' '+(postPast?'after':'until')+' the ',
  1404. ['b',formatNumber(customPost)],
  1405. ' get',
  1406. ['br'],
  1407. 'Posts ',
  1408. (postPast?['b',{
  1409. style:'color:red'
  1410. },'since']:'left'),
  1411. ': ',
  1412. ['b',formatNumber(Math.abs(postsUntilGet))],
  1413. ['br'],
  1414. 'Estimated date: ',
  1415. relativeTime[1],
  1416. ['br'],
  1417. ['br']
  1418. )
  1419. }
  1420. element(
  1421. id('tab3-content'),
  1422. 'Current post date: '+lastDate.toLocaleTimeString()+', '+lastDate.toDateString()
  1423. )
  1424. }else{
  1425. id('tab3-highlighted').style.display='none'
  1426. }
  1427. }
  1428.  
  1429. function requestPage(currentBoard,page,realNextStep,nextStep,lastCall){
  1430. var jsonobj
  1431. GM_xmlhttpRequest({
  1432. method:'get',
  1433. url:protocol+'//a.4cdn.org/'+currentBoard+'/'+page+'.json',
  1434. headers:{'If-Modified-Since':lastCall},
  1435. onload:function(response){
  1436. if(lastCall){
  1437. var headers=response.responseHeaders.split('\n')
  1438. for(var i in headers){
  1439. var header=headers[i].split(': ')
  1440. if(header[0]=='Last-Modified'){
  1441. lastCall=header[1]
  1442. break
  1443. }
  1444. }
  1445. }
  1446. if(response.status==200){
  1447. jsonobj=JSON.parse(response.responseText)
  1448. }else if(response.status==304){
  1449. nextStep()
  1450. return
  1451. }else{
  1452. connectionError('4chan',response.status,protocol+'//a.4cdn.org/'+currentBoard+'/'+page+'.json')
  1453. }
  1454. realNextStep(jsonobj,nextStep,lastCall)
  1455. },
  1456. onerror:function(){
  1457. connectionError('4chan',0,protocol+'//a.4cdn.org/'+currentBoard+'/'+page+'.json')
  1458. realNextStep(jsonobj,nextStep)
  1459. },
  1460. onabort:function(){
  1461. for(var i=1;i<4;i++){
  1462. id('tab'+i+'-check').value='Check'
  1463. }
  1464. }
  1465. })
  1466. }
  1467.  
  1468. function getLastPost(currentBoard,nextStep,lastCall){
  1469. requestPage(currentBoard,1,getLastPost2,nextStep,lastCall)
  1470. }
  1471.  
  1472. function getLastPost2(jsonobj,nextStep,lastCall){
  1473. if(!jsonobj){
  1474. nextStep({
  1475. post:0,
  1476. time:0,
  1477. cache:[[0],[0]]
  1478. })
  1479. return
  1480. }
  1481. var postNumbers=[]
  1482. var postTimes=[]
  1483. var allsticky=jsonobj.threads[jsonobj.threads.length-1].posts[0].sticky
  1484. for(var i=0;i<jsonobj.threads.length;i++){
  1485. if(!jsonobj.threads[i].posts[0].sticky||allsticky){
  1486. for(var j=0;j<jsonobj.threads[i].posts.length;j++){
  1487. postNumbers.push(jsonobj.threads[i].posts[j].no)
  1488. postTimes.push(jsonobj.threads[i].posts[j].time)
  1489. }
  1490. }
  1491. }
  1492. var result={
  1493. post:Math.max.apply(Math,postNumbers),
  1494. time:Math.max.apply(Math,postTimes),
  1495. cache:[postNumbers,postTimes]
  1496. }
  1497. if(lastCall){
  1498. result.lastCall=lastCall
  1499. }
  1500. nextStep(result)
  1501. }
  1502.  
  1503. function getOldPost(currentBoard,cache,nextStep){
  1504. if(cache){
  1505. var postNumbers=cache[0]
  1506. var postTimes=cache[1]
  1507. return {
  1508. post:Math.min.apply(Math,postNumbers),
  1509. time:Math.min.apply(Math,postTimes)
  1510. }
  1511. }else{
  1512. requestPage(currentBoard,10,getOldPost2,nextStep)
  1513. }
  1514. }
  1515.  
  1516. function getOldPost2(jsonobj,nextStep){
  1517. if(!jsonobj){
  1518. nextStep({
  1519. post:0,
  1520. time:0
  1521. })
  1522. }
  1523. var postNumbers=[]
  1524. var postTimes=[]
  1525. for(var i=0;i<jsonobj.threads.length;i++){
  1526. for(var j=0;j<jsonobj.threads[i].posts.length;j++){
  1527. postNumbers.push(jsonobj.threads[i].posts[j].no)
  1528. postTimes.push(jsonobj.threads[i].posts[j].time)
  1529. }
  1530. }
  1531. nextStep({
  1532. post:Math.min.apply(Math,postNumbers),
  1533. time:Math.min.apply(Math,postTimes)
  1534. })
  1535. }
  1536.  
  1537. function getNextGet(lastPost,getDigits,getClear,noDubs,getPalindrome){
  1538. if(getPalindrome)
  1539. return {
  1540. next:nextPalindrome(lastPost,0,noDubs),
  1541. last:nextPalindrome(lastPost,1,noDubs)
  1542. }
  1543. var getClear=noDubs||getClear
  1544. if(noDubs&&getDigits<3)
  1545. getDigits=3
  1546. var tpow=Math.pow(10,getDigits)
  1547. var allones=tpow/(getClear?1:9)|0
  1548. if(allones>lastPost)
  1549. return {
  1550. next:allones,
  1551. last:0
  1552. }
  1553. var unchanged=(lastPost/tpow|0)*tpow
  1554. if(getClear)
  1555. return {
  1556. next:unchanged+tpow,
  1557. last:unchanged
  1558. }
  1559. var array=[]
  1560. for(var i=getDigits-1;i>=0&&lastPost;i--){
  1561. array[i]=lastPost%10
  1562. lastPost=lastPost/10|0
  1563. }
  1564. var repdigit=array[0]
  1565. for(i=0;i<getDigits;i++){
  1566. if(array[0]<array[i])
  1567. repdigit=(repdigit+1)%10
  1568. if(array[0]!=array[i])
  1569. break
  1570. }
  1571. if(i==getDigits&&array[0]==array[i-1]){
  1572. repdigit=(repdigit+1)%10
  1573. if(array[0]==9){
  1574. unchanged=unchanged+tpow
  1575. allones=1
  1576. }
  1577. }
  1578. var alldigits=unchanged+((1/9-1e-16)*repdigit*tpow|0)
  1579. return {
  1580. next:alldigits,
  1581. last:alldigits-allones
  1582. }
  1583. }
  1584.  
  1585. function nextPalindrome(lastPost,prev,noDubs){
  1586. if(noDubs&&lastPost<101&&lastPost>8)
  1587. return prev?9:101
  1588. lastPost++
  1589. var midf=(Math.log(lastPost)/Math.LN10+1|0)/2
  1590. var mid=Math.ceil(midf)
  1591. var nomid=mid==midf?0:1
  1592. var midp=Math.pow(10,mid-nomid)
  1593. var leftnum=lastPost/midp|0
  1594. var mulprev=prev?-1:1
  1595. for(;;){
  1596. var nextPalin=leftnum*midp
  1597. for(var i=nomid;i<mid;i++)
  1598. nextPalin+=((leftnum/Math.pow(10,i)|0)%10)*Math.pow(10,mid-i-1)
  1599. if(!prev&&leftnum>midp*(nomid?10:1))
  1600. return midp*midp*(nomid?10:1)+1
  1601. else if(!(nextPalin%10))
  1602. return nextPalindrome(lastPost+mulprev-1,prev,noDubs)
  1603. else if(noDubs&&(nextPalin%10)==(nextPalin/10%10|0))
  1604. leftnum=((leftnum/(midp/(nomid?10:100))|0)+(mulprev+1)/2)*(midp/(nomid?10:100))-prev
  1605. else if(nextPalin*mulprev<lastPost*mulprev+prev)
  1606. leftnum+=mulprev
  1607. else
  1608. break
  1609. }
  1610. return nextPalin
  1611. }
  1612.  
  1613. function saveLoadTab(tabnum){
  1614. saveloadFromTab=tabnum
  1615. changeTab(5)
  1616. }
  1617.  
  1618. function saveToFile(overwrite){
  1619. if(saveloadFromTab==1){
  1620. var fileName='oneboard.json'
  1621. var fileContents=oneBoardCache
  1622. }else{
  1623. var fileName='multipleboards.json'
  1624. var fileContents=multipleBoardsCache
  1625. }
  1626. if(hta){
  1627. var fileSystem=new ActiveXObject('Scripting.FileSystemObject')
  1628. if(fileSystem.FileExists(fileName)){
  1629. if(overwrite==1){
  1630. var openedFile=fileSystem.OpenTextFile(fileName,2,0)
  1631. }else{
  1632. if(confirm(fileName+' exists in the current directory. Overwrite it?')){
  1633. saveToFile(1)
  1634. }
  1635. return
  1636. }
  1637. }else{
  1638. var openedFile=fileSystem.CreateTextFile(fileName)
  1639. }
  1640. openedFile.WriteLine(JSON.stringify(fileContents))
  1641. openedFile.Close()
  1642. if(!overwrite){
  1643. alert('Saved as '+fileName)
  1644. }
  1645. }else{
  1646. var saveLink=document.createElement('a')
  1647. saveLink.innerHTML='link'
  1648. saveLink.href='data:application/json;base64,'+btoa(JSON.stringify(fileContents))
  1649. saveLink.setAttribute('download',fileName)
  1650. saveLink.setAttribute('target','_blank')
  1651. document.body.appendChild(saveLink)
  1652. saveLink.click()
  1653. document.body.removeChild(saveLink)
  1654. }
  1655. }
  1656.  
  1657. function loadFromFile(){
  1658. if(window.multipleBoardsTimer){
  1659. clearTimeout(multipleBoardsTimer)
  1660. }
  1661. if(window.oneBoardTimer){
  1662. clearTimeout(oneBoardTimer)
  1663. }
  1664. id('tab1-check').value=id('tab2-check').value='Check'
  1665.  
  1666. var filesInput=id('saveload-input')
  1667. if(filesInput.files.length){
  1668. var openedFile=filesInput.files[0]
  1669. var reader=new FileReader()
  1670. reader.onload=function(event){
  1671. fileContents=event.currentTarget.result
  1672. id('saveload-form').reset()
  1673. try{
  1674. var fileContents=JSON.parse(fileContents)
  1675. }catch(e){
  1676. alert('This is not a JSON file!')
  1677. return
  1678. }
  1679. if(fileContents.currentBoard&&fileContents.lastStats){
  1680. oneBoardCache=fileContents
  1681. tab1Changed()
  1682. changeTab(1)
  1683. }else{
  1684. var error=1
  1685. for(var i in fileContents){
  1686. if(fileContents[i].lastStats){
  1687. error=0
  1688. multipleBoardsCache=fileContents
  1689. tab2Changed()
  1690. changeTab(2)
  1691. }else{
  1692. alert('The JSON file is corrupt!')
  1693. }
  1694. break
  1695. }
  1696. if(error){
  1697. alert('The JSON file is empty!')
  1698. }
  1699. }
  1700. }
  1701. }
  1702. reader.readAsText(filesInput.files[0])
  1703. }
  1704.  
  1705. function focusCustom(event){
  1706. var target=event.currentTarget
  1707. if(target.value==''){
  1708. target.value=target.placeholder
  1709. target.setSelectionRange(target.value.length,target.value.length)
  1710. }
  1711. }
  1712.  
  1713. function changeCustom(event){
  1714. var tabNumber=event.currentTarget.id.match(/tab(\d)-custompost/)[1]
  1715. changeNumber(event,tabNumber==1?1-1e15:1,1e15-1,tabNumber==1)
  1716. if(tabNumber==1){
  1717. tab1Changed()
  1718. }else{
  1719. tab3Changed()
  1720. }
  1721. }
  1722.  
  1723. function changeDelay(event){
  1724. var tabNumber=event.currentTarget.id.match(/tab(\d)-delay/)[1]
  1725. changeNumber(event,1,99)
  1726. if(event.type=='blur'||event.keyCode==38||event.keyCode==40){
  1727. delayValue[tabNumber-1]=event.currentTarget.value
  1728. }
  1729. }
  1730.  
  1731. function changeNumber(event,min,max,mp){
  1732. var pressed=event.keyCode
  1733. var target=event.currentTarget
  1734. var result=target.value
  1735. var oldValue=result
  1736. var cursorPos=target.selectionStart
  1737. if(event.type=='keydown'){
  1738. if(!event.shiftKey&&(
  1739. pressed>36&&pressed<41||//Arrow keys
  1740. pressed>41&&pressed<59||//Number row
  1741. pressed>95&&pressed<106//Numpad digits
  1742. )){
  1743. if(pressed==38||pressed==40){
  1744. event.preventDefault()
  1745. var plus=''
  1746. if(mp&&result==''||/[-+]/.test(result)){
  1747. var plus='+'
  1748. }
  1749. result=(result*1+(pressed==38?1:-1))+''
  1750. if(plus&&result[0]!='-'){
  1751. result=plus+result
  1752. }
  1753. }
  1754. }else if(mp&&(
  1755. pressed==107||//Numpad plus
  1756. pressed==109||//Numpad minus
  1757. event.shiftKey&&pressed==187||//Equals
  1758. pressed==189//Minus
  1759. )){
  1760. event.preventDefault()
  1761. result=(pressed==109||pressed==189?'-':'+')+result.replace(/[-+]/,'')
  1762. }else if(
  1763. event.ctrlKey||
  1764. event.altKey||
  1765. pressed==8||//Backspace
  1766. pressed==9||//Tab
  1767. pressed==27||//Esc
  1768. pressed>32&&pressed<37||//Home, End, Page Up/Down
  1769. event.shiftKey&&pressed>36&&pressed<41||//Arrow keys
  1770. pressed==46||//Delete
  1771. pressed>111&&pressed<124//F1-F12 keys
  1772. ){}else{
  1773. event.preventDefault()
  1774. }
  1775. if(!/^[-+]$/.test(result)&&(
  1776. !(result*0+1)||
  1777. result<min||
  1778. result>max||
  1779. target.maxLength!=-1&&(result+'').length>target.maxLength
  1780. )){
  1781. result=target.value
  1782. }
  1783. }
  1784. if(result){
  1785. result=result[0].replace(mp?/[^+-\d]/:/\D/,'')+result.slice(1).replace(/\D+/g,'')
  1786. }
  1787. if(event.type=='blur'&&result==target.placeholder){
  1788. target.value=''
  1789. }else if(oldValue!=result){
  1790. target.value=result
  1791. if(pressed==38||pressed==40){
  1792. target.setSelectionRange(cursorPos,cursorPos)
  1793. }
  1794. }
  1795. }
  1796.  
  1797. function plusBoard(event){
  1798. var input=element(['input#input',{
  1799. type:'text',
  1800. value:event.currentTarget.innerHTML=='+'?'':event.currentTarget.innerHTML.slice(1,-1),
  1801. onblur:selectPlusBoard,
  1802. onkeydown:selectPlusBoard
  1803. }]).input
  1804. event.currentTarget.parentNode.replaceChild(input,event.currentTarget)
  1805. input.setSelectionRange(input.value.length,input.value.length)
  1806. }
  1807.  
  1808. function selectPlusBoard(event){
  1809. if(event.type=='keydown'){
  1810. if(event.keyCode==13){
  1811. event.currentTarget.blur()
  1812. }
  1813. return
  1814. }
  1815. var value=event.currentTarget.value.replace(/^\/+|\/+$/g,'')
  1816. var div=element(['div#div',{
  1817. onclick:plusBoard,
  1818. board:value
  1819. },value?'/'+value+'/':'+']).div
  1820. event.currentTarget.parentNode.replaceChild(div,event.currentTarget)
  1821. if(value){
  1822. tab1or3ChooseBoard(0,div)
  1823. }else{
  1824. if(!div.parentNode.getElementsByClassName('selected')[0]){
  1825. tab1or3ChooseBoard(0,div.parentNode.querySelector('[board="'+boardName+'"]'))
  1826. }
  1827. }
  1828. }
  1829.  
  1830. function moveHandler(event){
  1831. if(move||resize){
  1832. event.preventDefault()
  1833. getSelection().removeAllRanges()
  1834. var startX=event.clientX*1
  1835. var startY=event.clientY*1
  1836. if(mouseXlast!=null){
  1837. if(move){
  1838. var boxLeft=getWatcherBody.offsetLeft-(mouseXlast-startX)
  1839. var xmax=document.body.clientWidth-getWatcherBody.offsetWidth
  1840. var nox=0
  1841. if(boxLeft<0){
  1842. boxLeft=0
  1843. nox=1
  1844. }
  1845. if(boxLeft>xmax){
  1846. boxLeft=xmax
  1847. nox=1
  1848. }
  1849. if(!nox){
  1850. mouseXlast=startX
  1851. }
  1852. var boxTop=getWatcherBody.offsetTop-(mouseYlast-startY)
  1853. var ymax=document.body.clientHeight-getWatcherBody.offsetHeight
  1854. var noy=0
  1855. if(boxTop<0){
  1856. boxTop=0
  1857. noy=1
  1858. }
  1859. if(boxTop>ymax){
  1860. boxTop=ymax
  1861. noy=1
  1862. }
  1863. if(!noy){
  1864. mouseYlast=startY
  1865. }
  1866. getWatcherBody.style.left=boxLeft+'px'
  1867. getWatcherBody.style.top=boxTop+'px'
  1868. }else{
  1869. var width=getWatcherBody.style.width.slice(0,-2)
  1870. var height=getWatcherBody.style.height.slice(0,-2)
  1871. var boxWidth=width-(mouseXlast-startX)
  1872. if(boxWidth>700){
  1873. mouseXlast=startX
  1874. }else{
  1875. boxWidth=700
  1876. }
  1877. var boxHeight=height-(mouseYlast-startY)
  1878. if(boxHeight>400){
  1879. mouseYlast=startY
  1880. }else{
  1881. boxHeight=400
  1882. }
  1883. getWatcherBody.style.width=boxWidth+'px'
  1884. getWatcherBody.style.height=boxHeight+'px'
  1885. }
  1886. }else{
  1887. mouseXlast=startX
  1888. mouseYlast=startY
  1889. }
  1890. }
  1891. }
  1892.  
  1893. function updateScript(){
  1894. GM_xmlhttpRequest({
  1895. method:'get',
  1896. url:metajsurl,
  1897. onload:function(response){
  1898. if(response.status==200){
  1899. var newversion=response.responseText.match(/@version\s+([^\r\n]+)\s*\n/)[1]
  1900. if(version!=newversion){
  1901. var versionTime=new Date(version.replace(/\./g,'/')).getTime()
  1902. var newVersionTime=new Date(newversion.replace(/\./g,'/')).getTime()
  1903. if(newVersionTime>versionTime){
  1904. var downloadconfirm=confirm('New version ('+newversion+') is available! Download it now?')
  1905. if(downloadconfirm){
  1906. if(hta){
  1907. updateHta()
  1908. }else{
  1909. GM_openInTab(userjsurl)
  1910. }
  1911. }
  1912. }else{
  1913. alert('You\'re using the latest version')
  1914. }
  1915. }else{
  1916. alert('You\'re using the latest version')
  1917. }
  1918. }else{
  1919. connectionError(website,response.status,metajsurl)
  1920. }
  1921. },
  1922. onerror:function(){
  1923. connectionError(website,0,metajsurl)
  1924. }
  1925. })
  1926. }
  1927.  
  1928. function updateHta(){
  1929. GM_xmlhttpRequest({
  1930. method:'get',
  1931. url:userjsurl,
  1932. onload:function(response){
  1933. if(response.status==200){
  1934. var htafile=response.responseText
  1935. if(htafile.length){
  1936. var fileSystem=new ActiveXObject('Scripting.FileSystemObject')
  1937. var fileName=unescape(location.href.match(/\/([^\/]+)$/)[1])
  1938. if(fileName){
  1939. if(fileSystem.FileExists(fileName)){
  1940. var openedFile=fileSystem.OpenTextFile(fileName,2,0)
  1941. }else{
  1942. var openedFile=fileSystem.CreateTextFile(fileName)
  1943. }
  1944. openedFile.WriteLine(htafile)
  1945. openedFile.Close()
  1946. location.reload()
  1947. }
  1948. }
  1949. }else{
  1950. connectionError(website,response.status,userjsurl)
  1951. }
  1952. },
  1953. onerror:function(){
  1954. connectionError(website,0,userjsurl)
  1955. }
  1956. })
  1957. }
  1958.  
  1959. function connectionError(title,status,url){
  1960. if(status){
  1961. var statusParsed='HTTP '+status
  1962. }else{
  1963. var statusParsed='Connection error'
  1964. }
  1965. alert('Couldn\'t connect to '+title+(status==404?'':', it might be offline at the moment')+' ('+statusParsed+')\n\nURL: '+url)
  1966. }
  1967. //</script></body></html>