PTTOnYT

connect ptt pushes to youtube chatroom

当前为 2020-12-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name PTTOnYT
  3. // @name:zh-TW Youtube聊天室顯示PTT推文
  4. // @namespace https://github.com/zoosewu/PTTChatOnYoutube
  5. // @version 1.0.27
  6. // @description connect ptt pushes to youtube chatroom
  7. // @description:zh-tw 連結PTT推文到Youtube聊天室
  8. // @author Zoosewu
  9. // @match https://www.youtube.com/watch?v=*
  10. // @match https://youtu.be/*
  11. // @match https://term.ptt.cc/*
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_info
  14. // @grant unsafeWindow
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @run-at document-start
  18. // @require https://code.jquery.com/jquery-3.5.1.slim.min.js
  19. // @require https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js
  20. // @require https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js
  21. // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js
  22. // @require https://cdn.jsdelivr.net/npm/vue
  23. // @connect www.ptt.cc
  24. // @homepageURL https://github.com/zoosewu/PTTChatOnYoutube/tree/master/homepage
  25. // @license MIT
  26. // ==/UserScript==
  27. 'use strict';
  28. //user log
  29. const reportmode = false;
  30. //all log
  31. const showalllog = false;
  32. //dev log
  33. const showPTTscreen = (false || reportmode || showalllog);
  34. const showcommand = (false || reportmode || showalllog);
  35. const showPostMessage = (false || reportmode || showalllog);
  36. const showonMessage = (false || reportmode || showalllog);
  37. const showalertmsg = false || showalllog;
  38. //dev use
  39. let devmode = false;
  40. const defaultopen = false;
  41. const disablepttframe = false;
  42. const simulateisstreaming = false;
  43. // add listener to get msg
  44. let cryptkey;
  45. const msg = {
  46. targetorigin: "",
  47. ownorigin: "",
  48. targetWindow: null,
  49. PostMessage: function (msg, data) {
  50. if (this.targetWindow !== null) {
  51. const d = { m: msg, d: data };
  52. this.targetWindow.postMessage(d, this.targetorigin);
  53. if (showPostMessage) console.log(this.ownorigin + " message posted", d);
  54. }
  55. },
  56. onMessage: function (event) {
  57. // Check sender origin to be trusted
  58. if (event.origin !== msg.targetorigin) return;
  59. const data = event.data;
  60. if (showonMessage) console.log(msg.ownorigin + " onMessage", data);
  61. if (typeof (msg[data.m]) == "function") {
  62. msg[data.m].call(null, data.d);
  63. }
  64. },
  65. }
  66. if (window.addEventListener) {
  67. window.addEventListener("message", msg.onMessage, false);
  68. }
  69. else if (window.attachEvent) {
  70. window.attachEvent("onmessage", msg.onMessage, false);
  71. }
  72.  
  73.  
  74. let isTopframe = (window.top == window.self);
  75. if (/www\.youtube\.com\/watch\?v=/.exec(window.location.href) !== null) {
  76. //check script work in right frame
  77. if (!isTopframe) throw new Error("Script Stopped when Youtube is not top frame");
  78. //init postmessage
  79. msg.targetorigin = "https://term.ptt.cc";
  80. msg.ownorigin = "https://www.youtube.com";
  81. msg["test"] = data => { console.log("test parent onmessage", data); };
  82. //-----
  83. console.log("Script started at " + window.location.href);
  84. runYoutubeScript();
  85. console.log("Youtube Script initialize finish.");
  86. //-----
  87. }
  88. else if (/term\.ptt\.cc/.exec(window.location.href) !== null) {
  89. //check script work in right frame
  90. if (isTopframe) throw new Error("Script Stopped when PTT is top frame");
  91. //init postmessage
  92. msg.ownorigin = "https://term.ptt.cc";
  93. msg.targetorigin = "https://www.youtube.com";
  94. msg.targetWindow = top;
  95. msg["test"] = data => { console.log("test child onmessage", data); };
  96. //-----
  97. console.log("Script started at " + window.location.href);
  98. runPTTScript();
  99. console.log("PTT Script initialize finish.");
  100. //-----
  101. }
  102. //Youtube---------------------------------------------------------------------------------------------------------------------
  103. function runYoutubeScript() {
  104. //generate crypt key everytime;
  105. cryptkey = makeid(20 + Math.random() * 10);
  106. GM_setValue("cryptkey", cryptkey);
  107. function makeid(length) {
  108. var result = '';
  109. var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  110. var charactersLength = characters.length;
  111. for (var i = 0; i < length; i++) {
  112. result += characters.charAt(Math.floor(Math.random() * charactersLength));
  113. }
  114. return result;
  115. }
  116. const testPTTurl = "https://www.ptt.cc/bbs/C_Chat/M.1606557604.A.904.html";
  117.  
  118. let player;
  119. let isinitPTT = false;
  120. let ConnectAlertDiv;
  121. let AutoScrolling = true;
  122. let isstreaming;
  123. let streamtime = new Date();
  124. let streamtimeinput;
  125. //let urlPushData = {};
  126. let PTTpostdata = {};
  127. let gotomainchat = false;
  128. let autogetpush = false;
  129. let lastgetpushtime = Date.now();
  130. let isstreambeforepost = false;
  131. let pushdata = {
  132. AID: "",
  133. board: "",
  134. posttime: new Date(),
  135. lastpushtime: new Date(),
  136. lastendline: 0,
  137. pushes: [],
  138. pushcount: 0,
  139. nowpush: 0,
  140. };
  141. ChechChatInstanced();
  142. (function () {
  143. (function AddBootstrap(frame) {
  144. const frameHead = $("head", frame);
  145. const frameBody = $("body", frame);
  146. frameHead.append($(`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">`));
  147.  
  148. frameBody.append($(`<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>`));
  149. frameBody.append($(`<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>`));
  150. frameBody.append($(`<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>`));
  151.  
  152. })(document)
  153. })();
  154. function ChechChatInstanced() {
  155. const ChatContainer = $(`ytd-live-chat-frame`);
  156. const defaultChat = $(`iframe`, ChatContainer);
  157. if (defaultChat.length > 0) {
  158. console.log("chat frame instanced");
  159. ChatContainer.css({ "position": "relative" });
  160. player = document.getElementsByTagName("video")[0];
  161. if (simulateisstreaming) {
  162. isstreaming = true;
  163. updatelog("videotype", "實況");
  164. }
  165. InitChatApp(defaultChat);
  166. }
  167. else {
  168. setTimeout(ChechChatInstanced, 1000);
  169. }
  170. }
  171.  
  172. function InitChatApp(defaultChatApp) {
  173. //console.log(defaultChatApp);
  174. const PTTAppCollapse = $(`<pttdiv id="PTTChat" class="pttchat rounded-right rounded-bottom w-100 collapse" style="z-index: 301; position: absolute;"></pttdiv>`);
  175. const PTTApp = $(`<div id="PTTChat-app" class=" pttbg border rounded w-100 d-flex flex-column"></div>`);
  176. const PTTChatnavbar = $(`<ul id="PTTChat-navbar" class="nav nav-tabs justify-content-center" role="tablist"><li class="nav-item"><a class="nav-link ptttext bg-transparent" id="nav-item-Chat" data-toggle="tab" href="#PTTChat-contents-Chat" role="tab" aria-controls="PTTChat-contents-Chat" aria-selected="false">聊天室</a></li><li class="nav-item"><a class="nav-link ptttext bg-transparent active" id="nav-item-Connect" data-toggle="tab" href="#PTTChat-contents-Connect" role="tab" aria-controls="PTTChat-contents-Connect" aria-selected="true">連線設定</a></li><li class="nav-item"><a class="nav-link ptttext bg-transparent" id="nav-item-other" data-toggle="tab" href="#PTTChat-contents-other" role="tab" aria-controls="PTTChat-contents-other" aria-selected="false">說明</a></li><li class="nav-item"><a class="nav-link ptttext bg-transparent" id="nav-item-PTT" data-toggle="tab" href="#PTTChat-contents-PTT" role="tab" aria-controls="PTTChat-contents-PTT" aria-selected="false">PTT畫面</a></li><li class="nav-item"><a class="nav-link ptttext bg-transparent" id="nav-item-log" data-toggle="tab" href="#PTTChat-contents-log" role="tab" aria-controls="PTTChat-contents-log" aria-selected="false">log</a></li><li class="nav-item"><button class="nav-link ptttext bg-transparent d-none" id="nav-item-TimeSet" type="button" data-toggle="collapse" data-target="#PTTChat-Time" aria-controls="PTTChat-Time" aria-expanded="false">時間</button></li></ul>
  177. `);
  178. const PTTChatContents = $(`<div id="PTTChat-contents" class="tab-content container d-flex flex-column ptttext"><!-------- 聊天室 --------><div class="tab-pane mh-100 fade" id="PTTChat-contents-Chat" role="tabpanel" aria-labelledby="nav-item-Chat"><!-------- 開台時間 --------><div id="PTTChat-Time" class="ptttext pttbg p-2 position-absolute w-75 d-none" style="z-index:400"><div id="PTTChat-Time-Setting"><form class="form-inline d-flex justify-content-between w-100"><label for="dis" class="mr-1">實況重播時間微調:</label> <button id="minus-time" class="btn ptttext border btn-outline-secondary" type="button">-1分鐘</button> <button id="add-time" class="btn ptttext border btn-outline-secondary" type="button">+1分鐘</button></form></div></div><!-------- 聊天室 --------><div class="flex-grow-1 overflow-auto mh-100 row" id="PTTChat-contents-Chat-main" style="overscroll-behavior:contain"><ul id="PTTChat-contents-Chat-pushes" class="col mb-0"></ul><div id="PTTChat-contents-Chat-btn" class="position-absolute d-none" style="z-index:400;bottom:5%;left:50%;-ms-transform:translateX(-50%);transform:translateX(-50%)"><button id="AutoScroll" class="btn btn-primary" type="button">自動滾動</button></div></div></div><!-------- 連線設定 --------><div class="tab-pane h-100 row fade show active" id="PTTChat-contents-Connect" role="tabpanel" aria-labelledby="nav-item-Connect"><div id="PTTChat-contents-Connect-main" class="col overflow-auto h-100 mb-0 p-4" data-spy="scroll" data-offset="0"></div><div id="PTTChat-contents-Connect-alert" class="position-relative container" style="top:-100%;z-index:400"></div></div><!-------- 其他 --------><div class="tab-pane h-100 card bg-transparent overflow-auto row fade" id="PTTChat-contents-other" role="tabpanel" aria-labelledby="nav-item-other"><div id="PTTChat-contents-other-main" class="card-body"></div></div><!-------- PTT畫面 --------><div class="tab-pane h-100 row fade" id="PTTChat-contents-PTT" role="tabpanel" aria-labelledby="nav-item-PTT"><div id="PTTChat-contents-PTT-main" class="h-100 d-flex justify-content-center px-0"></div></div><!-------- Log --------><div class="tab-pane mh-100 fade" id="PTTChat-contents-log" role="tabpanel" aria-labelledby="nav-item-log" style="overscroll-behavior:contain"><div class="flex-grow-1 overflow-auto mh-100 row" id="PTTChat-contents-log-main" style="overscroll-behavior:contain"><!--<ul id="PTTChat-contents-log-table" class="col mb-0"> </ul>--></div></div></div>
  179. `);
  180. const MainBtn = $(`<a id="PTTMainBtn" class="btn btn-lg border" type="button" data-toggle="collapse" data-target="#PTTChat" aria-expanded="false" aria-controls="PTTChat">P</a>`)
  181.  
  182. PTTAppCollapse.insertBefore(defaultChatApp);
  183. PTTAppCollapse.append(PTTApp);
  184. MainBtn.insertBefore(defaultChatApp);
  185. MainBtn.css({ "z-index": "450", "position": "absolute" });
  186.  
  187. if (defaultopen) {
  188. $(`#PTTMainBtn`)[0].click();
  189. }
  190.  
  191. PTTApp.append(PTTChatnavbar);
  192. PTTApp.append(PTTChatContents);
  193.  
  194. //180 to 600
  195. //let PTTAppHeight = defaultChatApp[0].clientHeight * 0.6;
  196. ///
  197. let PTTAppHeight = 450;
  198. PTTChatContents.css({ "height": PTTAppHeight + "px" });
  199. player.addEventListener('timeupdate', PlayerUpdate);
  200.  
  201. //add globalcss
  202. setTimeout(() => {
  203. const YTbgcolor = getComputedStyle($('html')[0]).backgroundColor;
  204. let bdcolor, ptp, pid, ptm, pmsg, ptxt;
  205. const colorlight = "rgb(120, 120, 120)";
  206. const colordark = "rgb(24, 24, 24)"
  207. if (YTbgcolor === colordark) {
  208. updatelog("ytcolor", "深色");
  209. bdcolor = colorlight;
  210. ptp = "#fff"; pid = "#ff6"; ptm = "#bbb"; pmsg = "#990"; ptxt = "#f8f9fa";
  211. //PTTApp.addClass("border-white");
  212. MainBtn.addClass("btn-outline-light");
  213. }
  214. else {
  215. updatelog("ytcolor", "淺色");
  216. bdcolor = colordark;
  217. ptp = "#000"; pid = "#990"; ptm = "#bbb"; pmsg = "#550"; ptxt = "#343a40";
  218. //PTTApp.addClass("border-dark");
  219. MainBtn.addClass("btn-outline-dark");
  220. }
  221. const PTTcss =
  222. //PTTmaincss
  223. `.ptttext { color: ` + ptxt + `; }
  224. .pttbg {background-color: ` + YTbgcolor + `; }` +
  225. //border
  226. `.border{
  227. border-color: ` + bdcolor + `!important;
  228. border-top-color: `+ bdcolor + ` !important;
  229. border-right-color: `+ bdcolor + ` !important;
  230. border-bottom-color: `+ bdcolor + ` !important;
  231. border-left-color: `+ bdcolor + ` !important;}` +
  232. //PTTpushcss
  233. `.pid { color: ` + pid + `; }
  234. .ptime { color: ` + ptm + `; }
  235. .pmsg { color: `+ pmsg + `; }
  236. .ptype { color: ` + ptp + `}
  237. pttdiv{
  238. font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
  239. font-size: 1rem;
  240. font-weight: 400;
  241. line-height: 1.5;
  242. color: #212529;
  243. text-align: left;
  244. background-color: #fff;
  245. -webkit-tap-highlight-color: transparent;
  246. }
  247. body {
  248. font-family: Roboto, Arial, sans-serif;
  249. font-size: 1rem;
  250. font-weight: 400;
  251. line-height: normal;
  252. color: rgb(0, 0, 0);
  253. text-align: start;
  254. background-color: rgba(0, 0, 0, 0);
  255. }
  256. #primary,#secondary{ box-sizing: content-box;}
  257. html {
  258. -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18);
  259. }`;
  260. //unused css
  261. `*, ::after, ::before { box-sizing: content-box; }`;
  262. const style = document.createElement('style');
  263. if (style.styleSheet) {
  264. style.styleSheet.cssText = PTTcss;
  265. } else {
  266. style.appendChild(document.createTextNode(PTTcss));
  267. }
  268. $('head')[0].appendChild(style);
  269. }, 100);
  270.  
  271.  
  272. /*------------------------------------CHAT------------------------------------*/
  273.  
  274. PTTChat_Chat_Main = $(`#PTTChat-contents-Chat-pushes`, PTTChatContents);
  275. PTTChat_Chat = $(`#PTTChat-contents-Chat-main`, PTTChatContents);;
  276. const PTTChat_Chat_btn = $(`#PTTChat-contents-Chat-btn`, PTTChatContents);
  277.  
  278. const streamtimecollapse = $(`#PTTChat-Time`, PTTChatContents);
  279. const lbtn = $(`#minus-time`, streamtimecollapse);
  280. const rbtn = $(`#add-time`, streamtimecollapse);
  281.  
  282.  
  283. PTTChat_Chat[0].addEventListener("scroll", function () {
  284. const scrollnowpos = PTTChat_Chat[0].scrollTop;
  285. const t = Date.now() - scriptscrolltime;
  286. let scrolltype = "";
  287. scriptscrolltime = Date.now();
  288. //if (t < 0) {
  289. if ((scrolllastpos + 5 > scrollnowpos && scrollnowpos > scrolltargetpos - 5) || (scrolllastpos - 5 < scrollnowpos && scrollnowpos < scrolltargetpos + 5)) {
  290. scrolltype = "auto scrolling";
  291. //script scrolling
  292. }
  293. else {
  294. scrolltype = "user scrolling";
  295. //user scrolling
  296. AutoScrolling = false;
  297. //PTTChat_Chat_btn.css({ "z-index": "450" });
  298. // PTTChat_Chat_btn.css({ "z-index": "450" });
  299. PTTChat_Chat_btn.removeClass('d-none');
  300. if (!isstreaming) streamtimecollapse.removeClass('d-none');
  301. }
  302. updatelog("targetscroll", scrolltargetpos);
  303. updatelog("nowscroll", scrollnowpos);
  304. updatelog("lastscroll", scrolllastpos);
  305. scrolllastpos = scrollnowpos;
  306. if (reportmode) console.log(scrolltype + ", (targetpos, lastpos, nowpos): (" + scrolltargetpos + ", " + scrolllastpos + ", " + scrollnowpos + "), scroll time step:" + t + " ms.");
  307.  
  308. });
  309. const autoscrollbtn = $(`#AutoScroll`, PTTChatContents);
  310. autoscrollbtn[0].addEventListener("click", function (event) {
  311. event.preventDefault();
  312. AutoScrolling = true;
  313. ScrollToTime(true);
  314. // PTTChat_Chat_btn.css({ "z-index": "0" });
  315. // PTTChat_Chat_btn.css({ "z-index": "0" });
  316.  
  317. PTTChat_Chat_btn.addClass('d-none');
  318. streamtimecollapse.addClass('d-none');
  319. });
  320.  
  321. lbtn[0].addEventListener('click', () => {
  322. const result = /(\d\d)\:(\d\d)/.exec(streamtimeinput[0].value);
  323. var tmpdate = new Date();
  324. tmpdate.setHours(+result[1]);
  325. tmpdate.setMinutes(+result[2] - 1);
  326. const newtime = paddingLeft(tmpdate.getHours(), 2) + ":" + paddingLeft(tmpdate.getMinutes(), 2);
  327. console.log(newtime);
  328. streamtimeinput[0].value = newtime;
  329. autoscrollbtn[0].click();
  330. UpdateStreamTime();
  331. });
  332. rbtn[0].addEventListener('click', () => {
  333. const result = /(\d\d)\:(\d\d)/.exec(streamtimeinput[0].value);
  334. var tmpdate = new Date();
  335. tmpdate.setHours(+result[1]);
  336. tmpdate.setMinutes(+result[2] + 1);
  337. const newtime = paddingLeft(tmpdate.getHours(), 2) + ":" + paddingLeft(tmpdate.getMinutes(), 2);
  338. console.log(newtime);
  339. streamtimeinput[0].value = newtime;
  340. autoscrollbtn[0].click();
  341. UpdateStreamTime();
  342. });
  343. /*------------------------------------Connect------------------------------------*/
  344. const PTTChat_Connect = $(`#PTTChat-contents-Connect-main`, PTTChatContents);
  345. ConnectAlertDiv = $(`#PTTChat-contents-Connect-alert`, PTTChatContents);
  346. const PTTChat_ConnectContent = $(`<!-------- 連線 --------><!-- stream time input field--><div id="PTTConnect-Time-Setting" class="form-row mb-2 d-none"><div class="form-group col-7"><label for="appt-time">實況重播開台時間:</label> <input id="stream-time" type="time" name="stream-time"></div><div class="form-check col-4 pl-4"><input type="checkbox" class="form-check-input" id="streambeforepost"> <label class="form-check-label ml-2" for="streambeforepost">發文前已開台</label></div></div><!-- login input field--><div class="form-row mb-2"><div class="col-5"><label for="PTTid">PTT ID</label> <input id="PTTid" type="text" class="form-control" placeholder="PTT ID" autocomplete="off"></div><div class="col-5"><label for="PTTpw">PTT密碼</label> <input id="PTTpw" type="password" class="form-control" placeholder="PTT密碼" autocomplete="off"></div><div class="col-2"><label for="PTTlogin" class="col-2"> </label> <button id="PTTlogin" type="button" class="btn ptttext border btn-outline-secondary">登入</button></div></div><!-- Post AID input field --><div class="my-3 form-row"><label for="post0" class="col-3 col-form-label">輸入文章AID</label> <input id="post0" class="form-control col mr-3" type="text" placeholder="#1VobIvqC (C_Chat)" autocomplete="off"> <button id="post0btn" class="btn ptttext border btn-outline-secondary" type="button">讀取推文</button></div><!-- test push button --> <button id="fakebtn" class="btn ptttext border btn-outline-secondary m-2 d-none" type="button">讀取測試用假推文</button><!-- New version button --> <a id="updatebtn" class="btn ptttext border btn-outline-secondary m-2 d-none" href="https://greasyfork.org/zh-TW/scripts/418469-youtubechatonptt" target="_blank" rel="noopener noreferrer" role="button">檢測到新版本</a>
  347. `);
  348.  
  349. const fakedata = '{"board":"Test","AID":"1VpKTOfx","title":"","posttime":"2020-12-06T21:04:22.000Z","pushes":[{"type":"→ ","id":"ZooseWu","content":"推文1","date":"2020-12-06T21:04:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文2","date":"2020-12-06T21:05:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文3","date":"2020-12-06T21:05:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"","date":"2020-12-06T21:05:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文5","date":"2020-12-06T21:05:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文678","date":"2020-12-06T21:05:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文100","date":"2020-12-06T21:06:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文101","date":"2020-12-06T21:06:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"推文102Y","date":"2020-12-06T21:10:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"123","date":"2020-12-06T21:11:00.000Z"},{"type":"推 ","id":"hu7592","content":"☂","date":"2020-12-06T22:24:00.000Z"},{"type":"→ ","id":"ss15669659","content":"☂","date":"2020-12-06T23:56:00.000Z"},{"type":"→ ","id":"ZooseWu","content":"hey","date":"2020-12-07T00:31:00.000Z"}],"startline":"127","endline":"149","percent":"100"}';
  350. const fakedata1push = '{"board":"Test","AID":"1VpKTOfx","title":"","posttime":"2020-12-06T21:04:22.000Z","pushes":[{"type":"→ ","id":"ZooseWu","content":"hey","date":"2020-12-07T00:31:00.000Z"}],"startline":"127","endline":"149","percent":"100"}';
  351.  
  352. PTTChat_Connect.append(PTTChat_ConnectContent);
  353.  
  354. const loginbtn = $(`#PTTlogin`, PTTChat_Connect);
  355. const fakebtn = $(`#fakebtn`, PTTChat_Connect);
  356. const pptid = $(`#PTTid`, PTTChat_Connect);
  357. const pttpw = $(`#PTTpw`, PTTChat_Connect);
  358. const postinput = $(`#post0`, PTTChat_Connect);
  359. const postbtn = $(`#post0btn`, PTTChat_Connect);
  360. const streambeforepost = $(`#streambeforepost`, PTTChat_Connect);
  361.  
  362. streamtimeinput = $(`#stream-time`, PTTChatContents);
  363. streamtimeinput[0].addEventListener("input", function () {
  364. UpdateStreamTime();
  365. PlayerUpdate();
  366. }, false);
  367.  
  368. streambeforepost[0].addEventListener("click", () => {
  369. isstreambeforepost = streambeforepost[0].checked;
  370. UpdateStreamTime();
  371. });
  372.  
  373. loginbtn[0].addEventListener("click", function () {
  374. //const i = pptid[0].value;
  375. //const p = pttpw[0].value;
  376. const i = CryptoJS.AES.encrypt(pptid[0].value, cryptkey).toString();
  377. const p = CryptoJS.AES.encrypt(pttpw[0].value, cryptkey).toString();
  378. //console.log("login", pptid[0].value, pttpw[0].value, cryptkey);
  379. //console.log("login", i, p);
  380. msg.PostMessage("login", { id: i, pw: p });
  381. //GetChatData(posturl, AlertMsg, postindex);
  382. });
  383. pptid[0].addEventListener("keyup", loginenter);
  384. pttpw[0].addEventListener("keyup", loginenter);
  385. function loginenter(event) {
  386. if (event.keyCode === 13) {
  387. event.preventDefault();
  388. loginbtn[0].click();
  389. }
  390. }
  391.  
  392. postbtn[0].addEventListener("click", function () {
  393. const postAID = postinput[0].value;
  394. const result = /#(.+) \((.+)\)/.exec(postAID);
  395. if (!result || result.length <= 2) {
  396. AlertMsg(false, "文章AID格式錯誤,請重新輸入。");
  397. }
  398. else {
  399. gotomainchat = true;
  400. if (pushdata.AID === result[1] && pushdata.board === result[2]) {
  401. msg.PostMessage("getpost", { AID: pushdata.AID, board: pushdata.board, startline: pushdata.lastendline });
  402. }
  403. else {
  404. PTTChat_Chat_Main.html("");
  405. pushdata = {
  406. AID: "",
  407. board: "",
  408. posttime: new Date(),
  409. lastpushtime: new Date(),
  410. lastendline: 0,
  411. pushes: [],
  412. pushcount: 0,
  413. nowpush: 0,
  414. };
  415. msg.PostMessage("getpost", { AID: result[1], board: result[2], startline: 0 });
  416. }
  417. }
  418. });
  419.  
  420. postinput[0].addEventListener("keyup", e => {
  421. if (e.keyCode === 13) {
  422. e.preventDefault();
  423. postbtn[0].click();
  424. }
  425. });
  426.  
  427. fakebtn[0].addEventListener("click", getfakedata);
  428. function getfakedata(e, f) {
  429. f = f || fakedata;
  430. console.log("分析假推文", f);
  431. const obj = JSON.parse(f, dateReviver);
  432. ParsePostData(obj);
  433. if (simulateisstreaming) setTimeout(getfakedata, 5000, null, fakedata1push);
  434. }
  435.  
  436.  
  437. /*-------------------------------------Other-------------------------------------*/
  438. const other = `<div>使用教學:<p></p>1.設定紀錄檔開始的時間<p></p>(實況無須設定)<p></p>2.輸入帳號與密碼登入PTT<p></p>3.在你自己的PTT找到想要同步的文章<p></p>4.鍵入大寫Q複製文章完整AID<p></p>5.將複製的AID貼上並讀取文章<p></p> <p></p> <p></p></div><div>如果需要回報或有任何問題請打開除錯模式以檢視PTT畫面及Log<p></p>目前測試版運行中 除錯模式已開啟<p></p></div><button id="opendevmode" class="btn ptttext border btn-outline-secondary m-2" type="button">除錯模式</button><div> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p> <p></p>聲明:<p></p> <p></p>我的程式碼都公開在網路上了,如果覺得我會到帳號請不要使用。<p></p> <p></p>請保證瀏覽Youtube時沒有其他PTT腳本同時執行,這很重要。<p></p> <p></p>我盡量確保你的帳號不會因為我的插件被盜了。<p></p> <p></p>但是如果你被盜了我不負責。<p></p> <p></p>如果你用了插件導致被水桶或被退註或封IP與我無關。<p></p> <p></p>完整聲明請點網站說明進入<p></p> <p></p>Zoosewu<p></p> <p></p></div><a id="gfbtn" class="btn ptttext border btn-outline-secondary m-2" href="https://github.com/zoosewu/PTTChatOnYoutube/tree/master/homepage" target="_blank" rel="noopener noreferrer" role="button">腳本介紹</a> <a id="gfbtn" class="btn ptttext border btn-outline-secondary m-2" href="https://greasyfork.org/zh-TW/scripts/418469-youtubechatonptt" target="_blank" rel="noopener noreferrer" role="button">greasyfork</a> <a id="gfbtn" class="btn ptttext border btn-outline-secondary m-2" href="https://github.com/zoosewu/PTTChatOnYoutube/tree/master" target="_blank" rel="noopener noreferrer" role="button">github</a>
  439. `;
  440. $(`#PTTChat-contents-other-main`, PTTChatContents).html(other);
  441. $(`#opendevmode`, PTTChatContents)[0].addEventListener('click', () => {
  442. if (!devmode) {
  443. devmode = true;
  444. DevMode();
  445. }
  446. });
  447. /*------------------------------------PTT畫面------------------------------------*/
  448. MainBtn[0].addEventListener("click", () => {
  449. checkScriptEvent();
  450.  
  451. if (!isinitPTT && !disablepttframe) {
  452. isinitPTT = true;
  453. //PTTChat - contents - PTT
  454. const PTTChat_PTT = $(`#PTTChat-contents-PTT-main`, PTTChatContents);
  455. const PTTFrame = $(`<iframe id="PTTframe" src="//term.ptt.cc/" class="h-100 flex-grow-1" style="zoom: 1.65; z-index: 351; -moz-transform: scale(1);">你的瀏覽器不支援 iframe</iframe>`);
  456. $(window).on('beforeunload', function () {
  457. PTTFrame.remove();
  458. });
  459. PTTChat_PTT.append(PTTFrame);
  460. msg.targetWindow = PTTFrame[0].contentWindow;
  461. //PTTCHAT_PTTTab.css({ "display": "none" });
  462. }
  463. });
  464.  
  465. /*--------------------------------------Log--------------------------------------*/
  466.  
  467. PTTChat_Log = $(`<table class="table"><tbody class="ptttext"><tr><th scope="row">PTT狀態</th><td id="log-PTTstate">--</td><td colspan="2">更多的詳細資訊請參考PTT畫面</td></tr><th class="text-center bg-secondary text-white" colspan="4">文章資訊</th><tr><th scope="row">文章標題</th><td id="log-posttitle" colspan="3">--</td></tr><tr><th scope="row">文章看板</th><td id="log-postboard">--</td><th scope="row">文章代碼</th><td id="log-postaid">--</td></tr><tr><th scope="row">推文數</th><td id="log-postpushcount">--</td><th scope="row">結尾行數</th><td id="log-postendline">--</td></tr><tr><th scope="row">發文時間</th><td id="log-posttime" colspan="3">--</td></tr><tr><th scope="row">最後推文時間</th><td id="log-postlastpushtime" colspan="3">--</td></tr><th class="text-center bg-secondary text-white" colspan="4">詳細資訊</th><tr><th scope="row">影片類型</th><td id="log-videotype">--</td><th scope="row">自動獲得推文</th><td id="log-isautogetpush">--</td></tr><tr><th scope="row">YT主題顏色</th><td id="log-ytcolor">--</td><th scope="row"></th><td></td></tr><tr><th scope="row">預估開台時間</th><td id="log-streamstarttime" colspan="3">--</td></tr><tr><th scope="row">影片當下時間</th><td id="log-streamnowtime" colspan="3">--</td></tr><th class="text-center bg-secondary text-white" colspan="4">滾動狀態</th><tr><th scope="row">目標推文樓數</th><td id="log-pushindex">--</td><th scope="row">目標捲動高度</th><td id="log-targetscroll">--</td></tr><tr><th scope="row">現在捲動高度</th><td id="log-nowscroll">--</td><th scope="row">上次捲動高度</th><td id="log-lastscroll">--</td></tr></tbody></table>
  468. `);
  469. $(`#PTTChat-contents-log-main`, PTTChatContents).append(PTTChat_Log);
  470. /*--------------------------------------END--------------------------------------*/
  471. if (devmode) {
  472. DevMode();
  473. }
  474. function DevMode() {
  475. fakebtn.removeClass('d-none');
  476. $(`#nav-item-PTT`, PTTChatnavbar).removeClass('d-none');
  477. //$(`#nav-item-TimeSet`, PTTChatnavbar).removeClass('d-none');
  478. $(`#nav-item-log`, PTTChatnavbar).removeClass('d-none');
  479. }
  480. UpdateStreamTime();
  481. }
  482. /*------------------------------------Update Log------------------------------------*/
  483. let logs = {};
  484. function updatelog(logtype, msg) {
  485. if (!logs[logtype]) {
  486. logs[logtype] = $("#log-" + logtype, PTTChat_Log);
  487. }
  488. const log = logs[logtype];
  489. log.html(msg);
  490. }
  491. /*--------------------------------------Alert--------------------------------------*/
  492. function AlertMsg(type, msg) {
  493. if (showalertmsg) console.log("Alert,type: " + type + ", msg: " + msg);
  494.  
  495. const alerttype = type === true ? "alert-success" : "alert-danger";
  496. const Alart = $(`<div class="alert mt-3 fade show ` + alerttype + `" role="alert"> ` + msg + `</div>`);
  497. if (ConnectAlertDiv) {
  498. ConnectAlertDiv.append(Alart);
  499. setTimeout(() => { Alart.alert('close'); }, 2000);
  500. }
  501. }
  502. msg["alert"] = data => { AlertMsg(data.type, data.msg); };
  503. /*------------------------------------Chat Scroll------------------------------------*/
  504. let AotoScroller;
  505. let PTTChat_Chat_Main;
  506. let PTTChat_Chat;
  507. let PTTChat_Log;
  508. let scriptscrolltime = Date.now();
  509. let scrolltargetpos = 0;
  510. let scrolllastpos = 0;
  511. function PlayerUpdate(forceScroll) {
  512. /*console.log((scriptscrolltime + 100) + " + " + Date.now());
  513. console.log((scriptscrolltime - Date.now()));
  514. console.log((scriptscrolltime + 100 > Date.now()));*/
  515. if (isstreaming === undefined) {
  516. if ($('.ytp-live-badge.ytp-button')[0].getAttribute('disabled') === "") {
  517. console.log("This video is streaming.");
  518. isstreaming = true;
  519. //$(`#PTTConnect-Time-Setting`).addClass('d-none');
  520. updatelog("videotype", "實況");
  521. }
  522. else if ($('.ytp-live-badge.ytp-button[disabled=true]').length > 0) {
  523. console.log("This video is not streaming.");
  524. isstreaming = false;
  525. updatelog("videotype", "紀錄檔");
  526. $(`#PTTConnect-Time-Setting`).removeClass('d-none');
  527.  
  528. }
  529. }
  530. else if (isstreaming && autogetpush && (Date.now() > lastgetpushtime + 2500)) {
  531. console.log("PlayerUpdate autogetpush", autogetpush, lastgetpushtime, Date.now());
  532. autogetpush = false;
  533. lastgetpushtime = Date.now();
  534. msg.PostMessage("getpost", { AID: pushdata.AID, board: pushdata.board, startline: pushdata.lastendline });
  535. }
  536. const t = new Date(streamtime.getTime() + player.currentTime * 1000);
  537. updatelog("streamnowtime", t.toLocaleDateString() + " " + t.toLocaleTimeString());
  538. ScrollToTime(false);
  539. }
  540. let scrolloffset = 0;
  541. function _scroll() {
  542. const target = pushdata.pushes[pushdata.nowpush].div;
  543. if (scrolloffset === 0) scrolloffset = (PTTChat_Chat[0].clientHeight - target[0].clientHeight) / 2;
  544. let offset = target[0].offsetTop - scrolloffset;
  545.  
  546. const lastscreen = PTTChat_Chat_Main[0].clientHeight - PTTChat_Chat[0].clientHeight + 10
  547. //console.log("offset " + offset);
  548. //console.log("lastscreen: " + lastscreen);
  549. if (offset > lastscreen)
  550. offset = lastscreen;
  551. else if (offset < 0)
  552. offset = 0;
  553. //console.log("target: ", target);
  554. //console.log("PTTChat_Chat[0].scrollTop: " + PTTChat_Chat[0].scrollTop);
  555. //console.log("PTTChat_Chat[0].clientHeight: " + PTTChat_Chat[0].clientHeight);
  556. //console.log("PTTChat_Chat_Main[0].clientHeight: " + PTTChat_Chat_Main[0].clientHeight);
  557. //console.log("target[0].offsetTop: " + target[0].offsetTop);
  558. //console.log("offset: " + offset);
  559. //console.log("scrolloffset: " + scrolloffset);
  560. if (PTTChat_Chat[0].scrollTop - offset > 15 || PTTChat_Chat[0].scrollTop - offset < -15) {
  561. scriptscrolltime = Date.now();
  562. //scrolltargetpos = offset;
  563.  
  564. if (PTTChat_Chat[0].scrollTop - offset > 300) {
  565. //console.log("scroll to 1000 first");
  566. PTTChat_Chat[0].scrollTo({ top: offset + 1000, }); scrolllastpos = offset + 1001;
  567. }
  568. else if (offset - PTTChat_Chat[0].scrollTop > 3000) {
  569. //console.log("scroll to -1000 first");
  570. PTTChat_Chat[0].scrollTo({ top: offset - 1000, }); scrolllastpos = offset - 1001;
  571. }
  572. if (showalllog) console.log("go to push: " + pushdata.nowpush);
  573. console.log("go to push: " + pushdata.nowpush);
  574. updatelog("pushindex", pushdata.nowpush);
  575. //setTimeout(() => {
  576. scrolltargetpos = offset;
  577. PTTChat_Chat[0].scrollTo({
  578. top: offset,
  579. behavior: "smooth"
  580. });
  581. //}, 10);
  582. }
  583. }
  584. function ScrollToTime(forceScroll) {
  585. forceScroll = (typeof forceScroll !== 'undefined') ? forceScroll : true;
  586. if (pushdata.pushes.length < 1) return;
  587. if (scriptscrolltime + 100 > Date.now()) return;
  588. if (!forceScroll && !AutoScrolling) return;
  589. if (isstreaming) {
  590. pushdata.nowpush = pushdata.pushes.length - 1;
  591. }
  592. else {
  593. const playedtime = player.currentTime;
  594. let nowtime = new Date(streamtime.getTime());
  595. nowtime.setSeconds(nowtime.getSeconds() + playedtime);
  596. const prenowpush = pushdata.nowpush;
  597. /*console.log(nowtime + "-<-" + pushdata.posttime);
  598. console.log(nowtime.valueOf() + "-<-" + pushdata.posttime.valueOf());
  599. console.log(nowtime.valueOf() < pushdata.posttime.valueOf());
  600. console.log(nowtime + "->-" + pushdata.lastpushtime);
  601. console.log(nowtime.valueOf() + "->-" + pushdata.lastpushtime.valueOf());
  602. console.log(nowtime.valueOf() > pushdata.lastpushtime.valueOf());*/
  603.  
  604. if (nowtime.valueOf() < pushdata.posttime.valueOf()) {
  605. if (showalllog) console.log("before post:" + nowtime + "<" + pushdata.posttime);
  606. pushdata.nowpush = 0;
  607. }
  608. else if (nowtime.valueOf() > pushdata.lastpushtime.valueOf()) {
  609. if (showalllog) console.log("after post:" + nowtime + ">" + pushdata.lastpushtime);
  610. pushdata.nowpush = pushdata.pushcount - 1;
  611. }
  612. else {
  613. let newnewpush = pushdata.nowpush;
  614. while (pushdata.pushes[newnewpush].date.valueOf() > nowtime.valueOf() && newnewpush > 1) {
  615. //console.log(pushdata.pushes[newnewpush].date + "->-" + nowtime);
  616. newnewpush--;
  617. }
  618. while (pushdata.pushes[newnewpush].date.valueOf() < nowtime.valueOf() && newnewpush < pushdata.pushes.length - 1) {
  619. //console.log(pushdata.pushes[newnewpush].date + "-<-" + nowtime);
  620. newnewpush++;
  621. }
  622. pushdata.nowpush = newnewpush;
  623. }
  624. }
  625. if (pushdata.pushes[pushdata.nowpush]) {
  626. _scroll();
  627. }
  628. }
  629. /*--------------------------------------Parse Post Data--------------------------------------*/
  630. function PushGenerator(ID, pushtype, pushid, pushmsg, pushtimeH, pushtimem) {
  631. const ptype = `<h5 class="ptype mr-2 mb-0">` + pushtype + ` </h5>`;
  632. const pid = `<h5 class="pid mr-2 mb-0 flex-grow-1">` + pushid + `</h5>`;
  633. const ptime = `<h5 class="ptime mb-0">` + paddingLeft(pushtimeH, +2) + `:` + paddingLeft(pushtimem, +2) + `</h6>`;
  634.  
  635. //var pmsg = `<h4 class="f3 mb-0 ml-2 mr-2" style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">` + pushmsg + `</h4>`;
  636. const pmsg = `<h4 class="pmsg mb-0 ml-2 mr-2" style="word-break: break-all;">` + pushmsg + `</h4>`;
  637.  
  638. const firstline = `<div class="d-flex flex-row">` + ptype + pid + ptime + `</div>`;
  639. //var secondline = `<div class="d-flex flex-row">` + pmsg + `</div>`
  640. const secondline = `<div>` + pmsg + `</div>`
  641.  
  642. const mainpush = `<li id="` + ID + `"class="media mb-4"><div class="media-body mw-100">` + firstline + secondline + `</div></li>`;
  643. return $(mainpush);
  644. }
  645.  
  646.  
  647. msg["postdata"] = data => {
  648. ParsePostData(data);
  649. /*console.log(JSON.stringify(data));*/
  650. lastgetpushtime = Date.now();
  651. if (isstreaming) {
  652. autogetpush = true;
  653. updatelog("isautogetpush", "true");
  654. }
  655. if (gotomainchat) {
  656. gotomainchat = false;
  657. $(`#nav-item-Chat`)[0].click();
  658. ScrollToTime();
  659. }
  660. }
  661. function ParsePostData(data) {
  662. PTTpostdata = $.extend(true, {}, data);
  663. if (PTTpostdata.AID === pushdata.AID && PTTpostdata.board === pushdata.board) { }
  664. else {
  665. pushdata = {
  666. AID: PTTpostdata.AID,
  667. board: PTTpostdata.board,
  668. title: PTTpostdata.title,
  669. posttime: PTTpostdata.posttime,
  670. lastendline: PTTpostdata.endline,
  671. lastpushtime: new Date(),
  672. pushes: [],
  673. pushcount: 0,
  674. nowpush: 0,
  675. };
  676. updatelog("postaid", pushdata.AID);
  677. updatelog("postboard", pushdata.board);
  678. updatelog("posttitle", pushdata.title);
  679. const t = pushdata.posttime;
  680. updatelog("posttime", t.toLocaleDateString() + " " + t.toLocaleTimeString());
  681.  
  682. updatelog("postendline", pushdata.lastendline);
  683.  
  684. }
  685. console.log(pushdata);
  686. const pdata = PTTpostdata.pushes;
  687. let sametime = PTTpostdata.posttime;
  688. let sametimeIndex = pushdata.pushcount;
  689. for (let index = 0; index < pdata.length; index++) {
  690. let newpush = pdata[index];
  691. newpush.pushid = pushdata.pushcount;
  692. pushdata.pushcount++;
  693. if (newpush.date.valueOf() > sametime.valueOf()) {
  694. //補秒
  695. const sametimecount = index - sametimeIndex;
  696. for (let i = sametimeIndex; i < index; i++) {
  697. const thispart = i - sametimeIndex;
  698. pushdata.pushes[i].date.setSeconds(thispart * 60 / sametimecount);
  699. }
  700. sametime = newpush.date;
  701. sametimeIndex = index;
  702. }
  703. //if (isstream && AutoScrolling){
  704. const div = PushGenerator(`scroll-targer-` + newpush.pushid, newpush.type, newpush.id, newpush.content, newpush.date.getHours(), newpush.date.getMinutes());
  705. PTTChat_Chat_Main.append(div);
  706. newpush.div = div;
  707. //}
  708. pushdata.pushes.push(newpush);
  709. }
  710. if (pdata.length > 0) pushdata.lastpushtime = pdata[pdata.length - 1].date;
  711. console.log("pushdata", pushdata);
  712. updatelog("postpushcount", pushdata.pushcount);
  713. const t = pushdata.lastpushtime;
  714. updatelog("postlastpushtime", t.toLocaleDateString() + " " + t.toLocaleTimeString());
  715.  
  716. UpdateStreamTime();
  717.  
  718. /*console.log(PTTpostdata);
  719. for (let index = 0; index < 90; index++) {
  720. PTTChat_Chat.append(PushGenerator(`scroll-targer-` + index, "推", "Zoosewu", "太神啦太神啦太神啦太神啦太神啦太神啦太神啦太神啦太神", 0, 0));
  721. }*/
  722. };
  723. /*--------------------------------------Update Stream Time--------------------------------------*/
  724. function UpdateStreamTime() {
  725. let result = /(\d\d)\:(\d\d)/.exec(streamtimeinput[0].value);
  726. if (!result) result = ["0", "18", "00"];
  727. streamtime = new Date(pushdata.posttime.getTime());
  728. streamtime.setHours(+result[1]);
  729. streamtime.setMinutes(+result[2]);
  730. if (streamtime.valueOf() < pushdata.posttime.valueOf()) {
  731. streamtime.setHours(streamtime.getHours() + 24);
  732. if (isstreambeforepost) {
  733. streamtime.setHours(streamtime.getHours() - 24);
  734. }
  735. }
  736. const t = streamtime;
  737. updatelog("streamstarttime", t.toLocaleDateString() + " " + t.toLocaleTimeString());
  738. if (showalllog) console.log("UpdateStreamTime()");
  739. }
  740.  
  741. /*--------------------------------------New Version--------------------------------------*/
  742. function checkScriptEvent() {
  743. const oReq = new XMLHttpRequest();
  744. oReq.addEventListener("load", checkScriptVersion);
  745. oReq.open("GET", "https://greasyfork.org/zh-TW/scripts/418469-youtubechatonptt.json");
  746. oReq.send();
  747. }
  748. function checkScriptVersion() {
  749. const obj = JSON.parse(this.responseText);
  750. const webver = obj.version;
  751. const wv = /(\d+)\.(\d+)\.(\d+)/.exec(webver);
  752. const myver = GM_info.script.version;
  753. const mv = /(\d+)\.(\d+)\.(\d+)/.exec(myver);
  754. console.log("version check: web version " + webver + ", my version " + myver + ".", wv, mv);
  755. if (wv[1] > mv[1] || wv[2] > mv[2])
  756. $(`#updatebtn`).removeClass('d-none');
  757. }
  758. /*
  759. //page push parse
  760. function GetChatData(ChatUrl, alertfunc, loadpostindex, callback) {
  761. if (ChatUrl === "") {
  762. alertfunc(false, "推文讀取失敗:空網址。");
  763. callback();
  764. return;
  765. }
  766. if (isLoadingPost[loadpostindex] == true) {
  767. alertfunc(false, "推文讀取中。");
  768. return;
  769. } isLoadingPost[loadpostindex] = true;
  770. GM_xmlhttpRequest({
  771. method: "GET",
  772. url: ChatUrl,
  773. headers: {
  774. "User-Agent": "Mozilla/5.0", // If not specified, navigator.userAgent will be used.
  775. "Accept": "text/xml" // If not specified, browser defaults will be used.
  776. },
  777. onload: function (response) {
  778. //console.log(response.responseText);
  779. if ($(".push", response.responseText).length <= 1) {
  780. alertfunc(false, "推文讀取失敗:錯誤網址。");
  781. isLoadingPost[loadpostindex] = false;
  782. return;
  783. }
  784. alertfunc(true, "推文讀取成功。");
  785. isLoadingPost[loadpostindex] = false;
  786. GeturlPushData(loadpostindex, response.responseText);
  787. console.log("推文讀取 2");
  788. },
  789. abort: function (response) {
  790. alertfunc(false, "推文讀取失敗:無法連線。");
  791. isLoadingPost[loadpostindex] = false;
  792. console.log(response.responseText);
  793. } }); }
  794. function GeturlPushData(loadpostindex, responseText) {
  795. urlPushData[loadpostindex] = [];
  796. //urlPushData =
  797. console.log("GeturlPushData");
  798. //get post year
  799. const postMetadata = $(".article-metaline", responseText);
  800. const postdateText = $(".article-meta-value", postMetadata[2])[0].innerText;
  801. const postdate = new Date(postdateText);
  802. const yyyy = postdate.getFullYear();
  803. //get push
  804. const pushes = $(".push", responseText);
  805. console.log(pushes);
  806. setTimeout(parsePushData, 10, loadpostindex, yyyy, pushes, 0);
  807. }
  808. function parsePushData(loadpostindex, yyyy, pushes, Index) {
  809. let parseFinished = false;
  810. for (let index = 0; index < Index + 100 && index < pushes.length; index++) {
  811. const push = pushes[index];
  812. //get content
  813. const pcontent = $(".push-content", push)[0].innerText;
  814. const contentReg = /[^: ].* /;
  815. const content = contentReg.exec(pcontent);
  816. //get time
  817. const ptime = $(".push-ipdatetime", push)[0].innerText;
  818. const ptimeReg = / ?(\d+)\/?(\d+) ?(\d+):?(\d+)/;
  819. const ptimeResult = ptimeReg.exec(ptime);
  820. const MM = ptimeResult[1];
  821. const dd = ptimeResult[2];
  822. const hh = ptimeResult[3];
  823. const mm = ptimeResult[4];
  824. const date = new Date(yyyy, (MM - 1), dd, hh, mm);
  825. const pushdata = new Object();
  826. pushdata.type = $(".push-tag", push)[0].innerText;
  827. pushdata.id = $(".push-userid", push)[0].innerText;
  828. pushdata.content = content[0];
  829. pushdata.date = date;
  830. urlPushData[loadpostindex].push(pushdata);
  831. if (index == pushes.length - 1) parseFinished = true;
  832. PTTChat_Chat_Global.append(PushGenerator(`scroll-targer-` + index, pushdata.type, pushdata.id, pushdata.content, pushdata.date.getHours() + ":" + pushdata.date.getMinutes()));
  833. }
  834. if (parseFinished) {
  835. //console.log("parse finished, total " + pushes.length + " messages.");
  836. //console.log(urlPushData[loadpostindex]);
  837. }
  838. else {
  839. //console.log("parse " + Index + " to " + (Index + 100));
  840. setTimeout(parsePushData, 10, loadpostindex, yyyy, pushes, Index + 100);
  841. }
  842. }
  843. */
  844. }
  845. //PTT---------------------------------------------------------------------------------------------------------------------
  846. function runPTTScript() {
  847. //get crypt key;
  848. cryptkey = GM_getValue("cryptkey", Math.random());
  849. //start script
  850. 'use strict'
  851. let PTT = {
  852. connect: true,//自動 連線狀態
  853. login: false,//自動
  854. controlstate: 0,
  855. lastviewupdate: 0,
  856. lock: function () {
  857. PTT.controlstate = 1;
  858. },
  859. unlock: function () {
  860. PTT.controlstate = 0;
  861. PTT.commands.list = [];
  862. },
  863. //0 free,1 lock 手動更新 每次操作都要打開 用完關閉
  864. pagestate: 0,//自動 ptt的訊息 暫時沒什麼用
  865. screen: [],//自動 畫面資料
  866. screenstate: 0,//0 clear, 1 full 自動 畫面是否已更新
  867. wind: null,//自動
  868. screenHaveText: function (reg) {
  869. let result = null;
  870. //debug用
  871. if (this.screenstate === 0) {
  872. const sElement = $("[type='bbsrow']", this.wind.document);
  873. for (let i = 0; i < sElement.length; i++) {
  874. const txt = sElement[i].textContent;
  875. if (result == null) result = reg.exec(txt);
  876. this.screen.push(txt);
  877. }
  878. this.screenstate = 1;
  879. if (showalllog) console.log("screenHaveText", reg, result);
  880. return result;
  881. }
  882. else {
  883. for (let i = 0; i < this.screen.length; i++) {
  884. const txt = this.screen[i];
  885. result = reg.exec(txt);
  886. if (result != null) {
  887. if (showalllog) console.log("screenHaveText", reg, result);
  888. return result;
  889. }
  890. }
  891. if (showalllog) console.log("screenHaveText", reg, result);
  892. return null;
  893. }
  894. },
  895. screenclear: function () {
  896. this.screenstate = 0;
  897. this.screen = [];
  898. },
  899. commands: {
  900. list: [],
  901. add: function (reg, input, callback, ...args) {
  902. const com = { reg, input, callback, args };
  903. if (showcommand) console.log("Add command ", com);
  904. this.list.push(com);
  905. },
  906. getfirst: function () {
  907. return this.list[0];
  908. },
  909. removefirst: function () {
  910. this.list.shift();
  911. }
  912. },
  913. autocom: [
  914. { reg: /您想刪除其他重複登入的連線嗎|您要刪除以上錯誤嘗試的記錄嗎/, input: 'n\n' },
  915. { reg: /您要刪除以上錯誤嘗試的記錄嗎/, input: 'n\n' },
  916. { reg: /請按任意鍵繼續/, input: '\n' },
  917. {
  918. reg: /系統過載, 請稍後再來\.\.\./, input: '', callback: () => {
  919. serverfull = true;
  920. if (PTT.controlstate === 1) {
  921. PTT.unlock();
  922. msg.PostMessage("alert", { type: false, msg: "系統過載, 請稍後再來..." });
  923. PTT.unlock();
  924. }
  925. }, args: []
  926. },
  927. { reg: /大富翁 排行榜|發表次數排行榜/, input: 'q' },
  928. { reg: /本日十大熱門話題/, input: 'q' },
  929. { reg: /本週五十大熱門話題/, input: 'q' },
  930. { reg: /每小時上站人次統計/, input: 'q' },
  931. { reg: /本站歷史 \.\.\.\.\.\.\./, input: 'q' },
  932. { reg: /看 板 目錄數 檔案數 byte數 總 分 板 主/, input: 'q' },
  933. { reg: /看 板 目錄數 檔案數 byte數 總 分 板 主/, input: 'q' },
  934. { reg: /名次──────範本───────────次數/, input: 'q' },
  935.  
  936. ]
  937. }
  938. PTT.wind = window;
  939. let PTTPost = {
  940. board: "",
  941. AID: "",
  942. title: "",
  943. posttime: "",
  944. pushes: [],
  945. startline: 0,
  946. endline: 3,
  947. percent: 0,
  948. }
  949. let serverfull = false;
  950. const insertText = (() => {
  951. let t = PTT.wind.document.querySelector('#t')
  952. return str => {
  953. if (!t) t = PTT.wind.document.querySelector('#t')
  954. const e = new CustomEvent('paste')
  955. //debug用
  956. //console.log(`insertText : \"` + str + `\"`);
  957. e.clipboardData = { getData: () => str }
  958. t.dispatchEvent(e)
  959. }
  960. })()
  961. function ComLog(cmd) {
  962. if (showcommand) console.log("execute command:", [cmd]);
  963. }
  964.  
  965. function chechAutoCommand() {
  966. let commands = PTT.autocom;
  967. for (let autoi = 0; autoi < commands.length; autoi++) {
  968. const cmd = commands[autoi];
  969. const result = PTT.screenHaveText(cmd.reg);
  970. //if (showcommand) console.log("auto command", cmd, result);
  971. if (result != null) {
  972. ComLog(cmd);
  973. insertText(cmd.input);
  974. if (typeof cmd.callback !== "undefined") {
  975. cmd.callback(...cmd.args);
  976. }
  977. return true;
  978. }
  979. }
  980. return false;
  981. }
  982.  
  983. function command() {
  984. const cmd = PTT.commands.getfirst();
  985. if (typeof cmd !== 'undefined' && PTT.screenHaveText(cmd.reg) != null) {
  986. PTT.commands.removefirst();
  987. ComLog(cmd);
  988. insertText(cmd.input);
  989. if (typeof cmd.callback == "function") {
  990. cmd.callback(...cmd.args);
  991. }
  992. }
  993. }
  994. function OnUpdate() {
  995. if (showalllog) console.log("OnUpdate start");
  996. PTT.screenclear();
  997. if (showalllog) console.log("check autocommand.");
  998. if (!chechAutoCommand()) {
  999. if (showalllog) console.log("check command.");
  1000. command();
  1001. }
  1002. if (showPTTscreen) console.log("PTT screen shot:", PTT.screen);
  1003. let nextcom = PTT.commands.getfirst();
  1004. if (showcommand && typeof nextcom !== 'undefined') console.log("next command : reg:" + nextcom.reg + "input:" + nextcom.input, [nextcom.callback]);
  1005. else if (showcommand) console.log("next command : none.");
  1006. if (showalllog) console.log("OnUpdate end");
  1007. }
  1008. //hook start
  1009. function hook(obj, key, cb) {
  1010. const fn = obj[key].bind(obj)
  1011. obj[key] = function (...args) {
  1012. fn.apply(this, args)
  1013. cb.apply(this, args)
  1014. }
  1015. }
  1016. hook(unsafeWindow.console, 'log', t => {
  1017. if (typeof t === 'string') {
  1018. if (t.indexOf('page state:') >= 0) {
  1019. const newstate = /->(\d)/.exec(t)[1];
  1020. PTT.pagestate = newstate;
  1021. }
  1022. else if (t === 'view update') {
  1023. PTT.lastviewupdate = Date.now();
  1024. serverfull = false;
  1025. OnUpdate();
  1026. }
  1027. }
  1028. });
  1029. //hook end
  1030. function reconnect() {
  1031. const disbtn = $(`.btn.btn-danger[type=button]`);
  1032. if (disbtn && disbtn.length > 0) {
  1033. msg.PostMessage("alert", { type: false, msg: "PTT已斷線,重新嘗試連線。" });
  1034. PTT.login = false;
  1035. disbtn[0].click();
  1036. serverfull = false;
  1037. setTimeout(reconnect(), 2000);
  1038. }
  1039. }
  1040. function checkscreenupdate() {
  1041. if (PTT.controlstate === 0) return;
  1042. const now = Date.now();
  1043. if (now > PTT.lastviewupdate + 10000) {
  1044. msg.PostMessage("alert", { type: false, msg: "PTT無回應,請稍後再試,或重新整理頁面。" });
  1045. PTT.unlock();
  1046. }
  1047. else {
  1048. msg.PostMessage("alert", { type: true, msg: "指令執行中......" });
  1049. setTimeout(checkscreenupdate, 3500);
  1050. }
  1051. }
  1052. //-----------------tasks----------------------
  1053. function gotoBoard(boardname) {
  1054. const input = boardname + "\n";
  1055. PTT.commands.add(/輸入看板名稱\(按空白鍵自動搜尋\)\:/, input, () => {
  1056. PTTPost.board = boardname;
  1057. });
  1058. insertText("s");
  1059. }
  1060. function gotoPost(postcode) {
  1061. const gotopost = "#" + postcode + "\n\n";
  1062. PTT.commands.add(/文章選讀/, gotopost, () => {
  1063. PTTPost.AID = postcode;
  1064. });
  1065. PTT.commands.add(/.*/, "", () => {
  1066. let nopost = PTT.screenHaveText(/文章選讀/);
  1067. let posttitle = PTT.screenHaveText(/ 標題 +(.+)/);
  1068. if (nopost) {
  1069. msg.PostMessage("alert", { type: false, msg: "文章AID錯誤,文章已消失或是你找錯看板了。" });
  1070. PTT.unlock();
  1071. }
  1072. else if (posttitle) {
  1073. var reg = /\s+$/g;
  1074. let title = posttitle[1].replace(reg, "");
  1075. PTTPost.title = title;
  1076. _getpush();
  1077. }
  1078. });
  1079. }
  1080. function savepush(content, result) {
  1081. const pushdata = {};
  1082. pushdata.type = result[1];
  1083. pushdata.id = result[2];
  1084. pushdata.content = content;
  1085. pushdata.date = new Date(PTTPost.posttime.getFullYear(), result[4] - 1, result[5], result[6], result[7]);
  1086. PTTPost.pushes.push(pushdata);
  1087. //console.log(result);
  1088. }
  1089. function _getpush() {
  1090. const posttitle = PTT.screenHaveText(/ 標題 +(.+)/);
  1091. if (posttitle) {
  1092. const reg = /\s+$/g;
  1093. const title = posttitle[1].replace(reg, "");
  1094. if (PTTPost.title !== title) {
  1095. gotoPost(PTTPost.AID);
  1096. insertText("q");
  1097. return;
  1098. }
  1099. }
  1100. const lineresult = PTT.screenHaveText(/目前顯示: 第 (\d+)~(\d+) 行/);
  1101. const startline = lineresult[1];
  1102. const endline = lineresult[2];
  1103. let targetline = PTTPost.endline - startline + 1;
  1104. if (startline < 5) targetline += 1;
  1105. //console.log("(targetline,PTTPost.endline,startline)", targetline, PTTPost.endline, startline);
  1106. if (PTTPost.posttime === "") {
  1107. let result = PTT.screenHaveText(/時間 (\S{3} \S{3} ...\d{2}:\d{2}:\d{2} \d{4})/);
  1108. PTTPost.posttime = new Date(result[1]);
  1109. }
  1110. //console.log("targetline =" + targetline);
  1111. if (targetline < 1 || targetline > 23) {
  1112. //console.log("lastendline: " + PTTPost.endline + ", startline: " + startline + ", endline: " + endline);
  1113. const gotoline = PTTPost.endline + ".\n";
  1114. insertText(gotoline);
  1115. PTT.commands.add(/目前顯示: 第/, "", _getpush);
  1116. return;
  1117. }
  1118. for (let i = targetline; i < PTT.screen.length; i++) {
  1119. const line = PTT.screen[i];
  1120. //console.log(i + "," + line);
  1121. const result = /^(→ |推 |噓 )(.+): (.*)(\d\d)\/(\d\d) (\d\d):(\d\d)/.exec(line);
  1122. if (result != null) {
  1123. let content = result[3];
  1124. var reg = /\s+$/g;
  1125. content = content.replace(reg, "");
  1126. savepush(content, result);
  1127. }
  1128. }
  1129. let percentresult = PTT.screenHaveText(/瀏覽 第 .+ 頁 \( *(\d+)%\)/);
  1130. PTTPost.percent = percentresult[1];
  1131. PTTPost.startline = startline;
  1132. PTTPost.endline = endline;
  1133. if (PTT.screenHaveText(/頁 \(100%\)/) == null) {
  1134. PTT.commands.add(/目前顯示: 第/, "", _getpush);
  1135. insertText(' ');
  1136. }
  1137. else {
  1138. PTT.unlock();
  1139. msg.PostMessage("alert", { type: true, msg: "文章讀取完成。" });
  1140. msg.PostMessage("postdata", PTTPost);
  1141. if (showalllog)
  1142. console.log(PTTPost);
  1143. }
  1144. }
  1145. //------------------------tasks--------------------------------
  1146. function GetPostPush(pAID, bname, startline, forceget = false) {
  1147. if ((PTT.connect && PTT.login) || forceget) {
  1148. let searchboard = bname !== PTTPost.board;
  1149. let searchpost = pAID !== PTTPost.AID;
  1150. startline = startline | 3;
  1151. msg.PostMessage("alert", { type: true, msg: "文章讀取中。" });
  1152. if (searchpost) PTTPost = {
  1153. board: "",
  1154. AID: "",
  1155. title: "",
  1156. posttime: "",
  1157. pushes: [],
  1158. startline: 0,
  1159. endline: startline,
  1160. percent: 0,
  1161. }
  1162. PTT.endline = startline;
  1163. if (searchboard) {
  1164. if (showalllog) console.log("新看板 新文章");
  1165. gotoBoard(bname);
  1166. gotoPost(pAID);
  1167. }
  1168. else if (searchpost) {
  1169. if (showalllog) console.log("同看板 新文章");
  1170. gotoPost(pAID);
  1171. }
  1172. else {
  1173. if (showalllog) console.log("同看板 同文章");
  1174. PTTPost.pushes = [];
  1175. insertText("q");
  1176. PTT.commands.add(/文章選讀/, "\n");
  1177. PTT.commands.add(/目前顯示: 第/, "", _getpush);
  1178. }
  1179. }
  1180. else if (!PTT.connect) {
  1181. msg.PostMessage("alert", { type: false, msg: "PTT已斷線,請重新登入。" });
  1182. PTT.unlock();
  1183. }
  1184. else if (!PTT.login) {
  1185. msg.PostMessage("alert", { type: false, msg: "PTT尚未登入,請先登入。" });
  1186. PTT.unlock();
  1187. }
  1188. }
  1189. function login(id, pw) {
  1190. msg.PostMessage("alert", { type: true, msg: "登入中" });
  1191. if (!PTT.login) {
  1192. const logincheck = () => {
  1193. if (PTT.screenHaveText(/密碼不對或無此帳號。請檢查大小寫及有無輸入錯誤。|請重新輸入/)) {
  1194. msg.PostMessage("alert", { type: false, msg: "登入失敗,帳號或密碼有誤。" });
  1195. PTT.unlock();
  1196. }
  1197. else if (PTT.screenHaveText(/上方為使用者心情點播留言區|【 精華公佈欄 】/)) {
  1198. msg.PostMessage("alert", { type: true, msg: "登入成功。" });
  1199. PTT.login = true;
  1200. PTT.unlock();
  1201. //testcode
  1202. /*(() => {
  1203. PTTLockCheck(GetPostPush, `#1VobIvqM (C_Chat)`);
  1204. insertText("x");
  1205. })();*/
  1206. }
  1207. else if (PTT.screenHaveText(/登入中,請稍候\.\.\.|正在更新與同步線上使用者及好友名單,系統負荷量大時會需時較久.../)) {
  1208. PTT.commands.add(/.*/, "", logincheck);
  1209. }
  1210. else {
  1211. msg.PostMessage("alert", { type: false, msg: "發生了未知錯誤。" });
  1212. console.log(PTT.screen);
  1213. }
  1214. }
  1215.  
  1216. let result = PTT.screenHaveText(/請輸入代號,或以 guest 參觀,或以 new 註冊/);
  1217. if (result) {
  1218. insertText(id + "\n" + pw + "\n");
  1219. PTT.commands.add(/.*/, "", logincheck);
  1220. }
  1221. else {
  1222. PTT.commands.add(/.*/, "", login, id, pw);
  1223. }
  1224. }
  1225. else {
  1226. msg.PostMessage("alert", { type: false, msg: "已經登入,請勿重複登入。" });
  1227. PTT.unlock();
  1228. }
  1229. }
  1230. function PTTLockCheck(callback, ...args) {
  1231. const disbtn = $(`.btn.btn-danger[type=button]`);
  1232. if (disbtn.length > 0) reconnect();
  1233. if (PTT.controlstate === 1) {
  1234. msg.PostMessage("alert", { type: false, msg: "指令執行中,請稍後再試。" });
  1235. return;
  1236. }
  1237. else if (serverfull) {
  1238. msg.PostMessage("alert", { type: false, msg: "系統過載, 請稍後再來..." });
  1239. PTT.unlock();
  1240. }
  1241.  
  1242. if (!serverfull) {
  1243. PTT.lastviewupdate = Date.now();
  1244. PTT.lock();
  1245. callback(...args);
  1246. setTimeout(checkscreenupdate, 3500);
  1247. }
  1248. }
  1249. //end
  1250. msg["login"] = data => {
  1251. const i = CryptoJS.AES.decrypt(data.id, cryptkey).toString(CryptoJS.enc.Utf8);
  1252. const p = CryptoJS.AES.decrypt(data.pw, cryptkey).toString(CryptoJS.enc.Utf8);
  1253. //console.log(data );
  1254. //console.log([i, p],cryptkey);
  1255. PTTLockCheck(login, i, p);
  1256. };
  1257. msg["getpost"] = data => { PTTLockCheck(GetPostPush, data.AID, data.board, data.startline); };
  1258. }
  1259.  
  1260.  
  1261. //function
  1262. function paddingLeft(str, lenght) {
  1263. str = str + "";
  1264. if (str.length >= lenght)
  1265. return str;
  1266. else
  1267. return paddingLeft("0" + str, lenght);
  1268. }
  1269. function paddingRight(str, lenght) {
  1270. str = str + "";
  1271. if (str.length >= lenght)
  1272. return str;
  1273. else
  1274. return paddingRight(str + "0", lenght);
  1275. }
  1276.  
  1277. var dateReviver = function (key, value) {
  1278. if (typeof value === 'string') {
  1279. const a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  1280. if (a) {
  1281. return new Date(+a[1], +a[2] - 1, +a[3], +a[4] + 8, +a[5], +a[6]);
  1282. }
  1283. }
  1284. return value;
  1285. };