// ==UserScript== // @name 4-ch.net Refresher // @namespace Violentmonkey Scripts // @match https://4-ch.net/* // @version 1.0 // @author - // @description Shows how many new posts there are // @grant GM_setValue // @grant GM_getValue // ==/UserScript== //If there are some boards you aren't interested in, change fields in refresh_list1 from 1 to 0, you will not get updates from those var boards_list = ["general", "hobby", "personal", "req", "tv", "japan", "games", "music", "book", "ascii", "dqn", "tech", "iaa", "language", "nihongo", "current"] var board_is_shift_jis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0] var refresh_list1 = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] //var refresh_list1 = [ 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0] //var refresh_list1 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] //var refresh_list2 = [ 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1] var fetching = 0 var fetching_el = null var board_new = new Array(boards_list.length) var katalog_all = new Array(boards_list.length) var katalog_all_old = new Array(boards_list.length) var board_el_all = new Array(boards_list.length) var refreshbox_peek //data description: // //katalog elements are [thread_id, wasnt_deleted_yet, amount_of_replies, thread_title] // //katalog_new elements are element_type //if element_type == 0, this is a "new thread" element, and the rest are [thread_id, amount_of_replies, thread_title] //if element_type == 1, this is a "updated thread", and the rest are [index_in_katalog, new_amount_of_replies] //if element_type == 2, this is a "deleted thread", and the rest are [index_in_katalog] //TODO // *) The results may be weird if you merged the data in the other tab, but still have the old data in memory on this one // *) Maybe display new posts right in the tab, instead of making users go to that page. // *) Add ability to hide some threads, and to get some threads more priority. // *) And maybe keep track of last updated threads, I sometimes click merge too fast and regret doing it. function start() { var stylebox = document.getElementById("stylebox") if (!stylebox) { return } stylebox.style.display="none" var l_board_new = GM_getValue("4c--board_new") if (l_board_new) board_new = JSON.parse(l_board_new) var titlebox = document.getElementById("stylebox") if (!titlebox) { error() return } var refreshboxouter = document.createElement("div") refreshboxouter.id = "refreshbox" refreshboxouter.classList.add("outerbox") titlebox.parentNode.insertBefore(refreshboxouter, titlebox) var refreshbox_base = document.createElement("div") refreshbox_base.id = "refreshbox_base" refreshbox_base.classList.add("innerbox") refreshboxouter.appendChild(refreshbox_base) var refreshbox = document.createElement("div") //refreshbox.innerHTML = "[styles:-]" //refreshbox.style.display = "flex" refreshbox_base.appendChild(refreshbox) refreshbox_peek = document.createElement("div") refreshbox_base.appendChild(refreshbox_peek) var style_show = document.createElement("a") style_show.href = "#" style_show.textContent = "[st-]" refreshbox.appendChild(style_show) style_show.onclick = function(evt) { //GM_setValue("/games/", "HAHA\nHAH") //alert("HI") if (stylebox.style.display == "") { stylebox.style.display = "none" style_show.textContent = "[st-]" } else { stylebox.style.display = "" style_show.textContent = "[st+]" } evt.stopPropagation();evt.preventDefault();return false; } var refresh = document.createElement("a") refresh.href = "#" refresh.textContent = "[refresh]" refreshbox.appendChild(refresh) refresh.onclick = function(evt) { var l_fetching = 0 for (var board of boards_list) { l_fetching += refresh_list1[board] } fetching = l_fetching fetching_el.style.display="" //katalog_element_get(6) //"games" for (var board = 0; board < boards_list.length; board++) { if (refresh_list1[board]) { katalog_element_get(board) } } evt.stopPropagation();evt.preventDefault();return false; } /*refresh = document.createElement("a") refresh.href = "#" refresh.textContent = "[rf2]" refreshbox.appendChild(refresh) refresh.onclick = function(evt) { var l_fetching = 0 for (var board of boards_list) { l_fetching += refresh_list2[board] } fetching = l_fetching fetching_el.style.display="" //katalog_element_get(6) //"games" for (var board = 0; board < boards_list.length; board++) { if (refresh_list2[board]) { katalog_element_get(board) } } evt.stopPropagation();evt.preventDefault();return false; }*/ /* var all_show = document.createElement("a") all_show.href = "#" all_show.textContent = "[peek]" refreshbox.appendChild(all_show) all_show.onclick = function(evt) { //alert(JSON.stringify(board_new)) board_peek() evt.stopPropagation();evt.preventDefault();return false; }*/ all_show = document.createElement("a") all_show.href = "#" all_show.textContent = "[merge]" refreshbox.appendChild(all_show) all_show.onclick = function(evt) { katalog_merge() evt.stopPropagation();evt.preventDefault();return false; } for (var i = 0; i < boards_list.length; i++) { var board = boards_list[i] var space = document.createTextNode(" ") //var space = document.createTextNode("\u200B") //var space = document.createElement("wbr") refreshbox.appendChild(space) var boardel = document.createElement("a") boardel.href = "/" + board + "/" //boardel.textContent = board var b = board_new[i] var p = 0 var m = 0 if (b) for (var j = 0; j < b.length; j++) { if (b[j][0] == 2) { m++ } else { p++ } } var name = boards_list[i] if (p) name += "(" + p + ")" if (m) name += "(-" + m + ")" boardel.textContent = name //games.style.whiteSpace = "nowrap" refreshbox.appendChild(boardel) //boardel.onclick = click_board board_el_all[i] = boardel } fetching_el = document.createElement("span") fetching_el.textContent = "..." fetching_el.style.display="none" refreshbox.appendChild(fetching_el) //var games = document.createElement("a") //games.href = "/games/" //games.textContent = "/games/" //refreshbox.appendChild(games) ////games.onclick = parse_katalog //games.onclick = function(evt) { // evt.stopPropagation();evt.preventDefault();return false; //} board_peek() } function board_peek() { refreshbox_peek.innerHTML = "" for (var board = 0; board < board_new.length; board++ ) { var b = board_new[board] if (!b) continue var katalog_old = katalog_get_old(board) if (!katalog_old) continue for (var entry of b) { if (entry[0] == 0) { //alert("NOT DONE YET") var thread_id = entry[1] var title = entry[3] var how_many_posts = entry[2] var div = document.createElement("div") refreshbox_peek.appendChild(div) var a = document.createElement("a") div.appendChild(a) a.href = "/" + boards_list[board] + "/kareha.pl/" + thread_id + "/l50" //a.href = "#" a.textContent = "/" + boards_list[board] + "/" + title + "(NEW:" + how_many_posts + ")" } else if (entry[0] == 1) { var katalog_entry = katalog_old[entry[1]] var thread_id = katalog_entry[0] var title = katalog_entry[3] var how_many_posts = entry[2] - katalog_entry[2] var div = document.createElement("div") refreshbox_peek.appendChild(div) var a = document.createElement("a") div.appendChild(a) a.href = "/" + boards_list[board] + "/kareha.pl/" + thread_id + "/l50" a.textContent = "/" + boards_list[board] + "/" + title + "(" + how_many_posts + ")" } if (entry[0] == 2) { //alert("NOT DONE YET (deleted)") var katalog_entry = katalog_old[entry[1]] var thread_id = katalog_entry[0] var title = katalog_entry[3] var how_many_posts = katalog_entry[2] var div = document.createElement("div") refreshbox_peek.appendChild(div) var a = document.createElement("a") div.appendChild(a) a.href = "/" + boards_list[board] + "/kareha.pl/" + thread_id + "/l50" a.textContent = "/" + boards_list[board] + "/" + title + "(DEL:" + how_many_posts + ")" } } //a.href } } function board_redraw() { GM_setValue("4c--board_new", JSON.stringify(board_new)) for (var i = 0; i < boards_list.length; i++ ) { var b = board_new[i] var p = 0 var m = 0 if (b) for (var j = 0; j < b.length; j++) { if (b[j][0] == 2) { m++ } else { p++ } } var name = boards_list[i] if (p) name += "(" + p + ")" if (m) name += "(-" + m + ")" board_el_all[i].textContent = name } if (fetching) { fetching_el.style.display = "" } else { fetching_el.style.display = "none" } if (!fetching) { board_peek() } } function katalog_merge() { for (var board = 0; board < board_new.length; board++ ) { var b = board_new[board] if (!b) continue var katalog_old = katalog_get_old(board) if (!katalog_old) continue for (var entry of b) { if (entry[0] == 0) { //new thread if (katalog_old[katalog_old.length - 1][0] >= entry[1]) { //I assume this will only ever happen if some thread will be moved from some other board //if it is already in the old_katalog, this is not an issue in the slightest //overwise, yeah, ring the bell //LATER: I should walk through the entire old katalog and check if it is present or not, but I'm lazy right now //this will break only if there are two threads added at once, and you merge them from an other tab. Quite unlikely if (katalog_old[katalog_old.length - 1][0] == entry[1]) { continue; } else { alert("two or more threads at once, oh no") return } } var new_entry = [entry[1], 1, entry[2], entry[3]] katalog_old.push(new_entry) } else if (entry[0] == 1) { //new replies in an existing thread katalog_old[entry[1]][2] = entry[2] } else if (entry[0] == 2) { //deleted trhead katalog_old[entry[1]][1] = 0 } } board_new[board] = null katalog_set_old(board) } board_redraw() } function katalog_element_get(board) { var xmlhttp = new XMLHttpRequest(); var address_full = "https://4-ch.net/" + boards_list[board] + "/subback.html" xmlhttp.open("GET", address_full, true) if (board_is_shift_jis[board]) { xmlhttp.responseType = 'arraybuffer' } xmlhttp.onload = function(e) { var response = "" if (board_is_shift_jis[board]) { var uInt8Array = new Uint8Array(this.response) var codepage = "shift_jis" var shift_jis = new TextDecoder(codepage, {fatal: true}) response = shift_jis.decode(uInt8Array) } else { response = this.responseText } response = response.split(/]*>((?:.|\n|\r)*)<\/body>/i)[1] var page = document.createElement('div') page.innerHTML = response page = document.evaluate('//*[@id="oldthreadlist"]', page, null, 9, null).singleNodeValue katalog_parse(board, page) } xmlhttp.send(); } function katalog_parse(board, tl) { //var katalog_element = get_katalog_element("games") //var tl = katalog_element.getElementById("oldthreadlist") //var tl = katalog_element_get("games") var tr_all = tl.getElementsByTagName("tr") var katalog = new Array(tr_all.length-1) var ii = 0 for (var i = tr_all.length - 1; i != 0; i--) { var tr = tr_all[i] var a_all = tr.getElementsByTagName("a") var a0 = a_all[0] var thread_url = a0.getAttribute('href') var thread_num_str = thread_url.split("/")[3] var thread_num = parseInt(thread_num_str) var thread_title = a0.textContent var a1 = a_all[1] var thread_posts = parseInt(a1.textContent) katalog[ii] = [thread_num, 1, thread_posts, thread_title] var j = ii; if (j > 0) do { //from oldest to newest if (katalog[j][0] > katalog[j-1][0]) break var temp = katalog[j] katalog[j] = katalog[j-1] katalog[j-1] = temp } while (--j > 0) ii++ } katalog_all[board] = katalog //alert(katalog[0][3]) //alert(JSON.stringify(katalog)) katalog_compare(board) } function katalog_compare(board) { //alert("got here") var katalog_old = katalog_get_old(board) var katalog = katalog_all[board] if (!katalog_old) { katalog_all_old[board] = katalog katalog_set_old(board) return } var changed = [] if (!katalog || !katalog_old || katalog.length == 0 || katalog_old.length == 0) { alert("something went wrong in katalog_compare") return } var i = 0 var o = 0 while (true) { var ii = katalog[i] var oo = katalog_old[o] if (ii[0] == oo[0]) { if (ii[2] != oo[2]) { changed.push([1, o, ii[2]]) } i++ o++ } else if (ii[0] > oo[0]) { if (oo[1]) { changed.push([2, o]) } o++ } else { changed.push([0, ii[0], ii[2], ii[3]]) i++ } if (i >= katalog.length) { while (o < katalog_old.length) { oo = katalog_old[o] if (oo[1]) { changed.push([2, o]) } o++ } break } if (o >= katalog_old.length) { while (i < katalog.length) { ii = katalog[i] changed.push([0, ii[0], ii[2], ii[3]]) i++ } break } } if (changed.length) { board_new[board] = changed } //alert("hi") fetching-- board_redraw() //alert("hi") //alert(katalog[0][3]) //katalog_all_old[board] = katalog //katalog_set_old(board) } function katalog_set_old(board) { var katalog = katalog_all_old[board] var res = "" var i = 0 for (ka of katalog) { res += katalog[i][0] + "," + katalog[i][1] + "," + katalog[i][2] + "," + katalog[i][3] + "\n" i++ } GM_setValue(boards_list[board], res) } function katalog_get_old(board) { var res = katalog_all_old[board] if (res !== undefined) return res res = GM_getValue(boards_list[board]) if (res === undefined) return res var res2 = [] res = res.split("\n") for (var line of res) { if (line) { var parsed_line = [] var j = line.indexOf(",") parsed_line.push(parseInt(line.substring(0,j))) j++ var k = line.indexOf(",",j) parsed_line.push(parseInt(line.substring(j,k))) j = k+1 k = line.indexOf(",",j) parsed_line.push(parseInt(line.substring(j,k))) parsed_line.push(line.substring(k+1)) res2.push(parsed_line) } } katalog_all_old[board] = res2 return res2 } function error() { alert("4-ch.net userscript broke") } start()