// ==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== "use strict"; const autofill_sage = true const 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 // if fetching > 0, we are currently grabbing katalog data for some boards var fetch = null // this is the [fetch] button var board_selected = -1 var katalog_all = new Array(boards_list.length) //katalog_all is recently fetched and parsed katalog. Array of array of thread data. var katalog_all_old = new Array(boards_list.length) //katalog_all_old is previous version of katalog, we keep it to compare old amount of replies to new one. //katalog elements are [thread_id, wasnt_deleted_yet, amount_of_replies, thread_title] const kat_thread_id = 0, kat_wasnt_deleted_yet = 1, kat_position = 1, kat_amount_of_replies = 2, kat_thread_title = 3 var changes_all = new Array(boards_list.length) //changes_all is //changes elements are [changes_type, the rest] //if changes_type == 0, this is a "new thread" element, and the rest are [thread_id, amount_of_replies, thread_title] //if changes_type == 1, this is a "updated thread", and the rest are [index_in_katalog, new_amount_of_replies] //if changes_type == 2, this is a "deleted thread", and the rest are [index_in_katalog] const changes_type_new = 0, changes_type_updated = 1, changes_type_deleted = 2 const changes_type = 0, changes_thread_id = 1, changes_amount_of_replies = 2, changes_thread_title = 3, changes_index_in_katalog = 1, changes_new_amount_of_replies = 2 var board_el_all = new Array(boards_list.length) var refreshbox_peek = null //it shows updated threads var katalog_div = null //it shows katalog on page var settings //stores all settings var sync // sync is array of numbers from 0 to 255. // first one indicates if 4-c--changes was updated in another tab // the rest indicates if any of the katalog was updated in another tab. const just_hardcode_peeked_post_background_color = false // it's ok to set this to true if you don't use any styles over than pseud0ch //TODO // *) The most important one is faving and hiding threads. The rest is feature creep. // *) And maybe keep track of last updated threads, I sometimes click merge too fast and regret doing it. // *) Maybe move [merge] button somewhere. And add merge to each thread. Add [mark all as read] // *) Add [options-]. Add list of: List board / Follow board / Ignore unfaved threads // *) Display thread as /dqn/1:Thread Name, show its position so that I can bump it in time. // This will require changing the way threads are currently stored, will need to store their position too. // Oh, I know, I can reuse kat_wasn't deleted yet, when 1 is first, 2 is second, etc, and 3 is deleted. // *) Options screen is a bunch of /board/ [_][X][_] checkboxes. Top row is description and will set all boards. // *) Option to show current thread position on board. // *) Option to autodelete unused threads. // *) A way to open katalog without actually going to the board of interest. // *) Save options as json, and all faved/hidden threads as json, so that the user can paste all of it into a single text field for the user to copypaste. // *) Get rid of refresh_list1 // *) Add a way to show faved threads even if there are no new posts. // *) Add the date to the sync, and compare the last fetch date. I need to stop old fetch from overwriting new fetch. // *) Get rid of kat_thread_title and turn it into just objects. // I just need to add buttons [refresh][by new-][fav first-][show hidden-][board list-] // [mark all as read] // and [thread list style:1] //I feel like I need to rewrite a lot in this thing. //My number one priority is ability to favorite threads, //I need to display favorited threads above all other threads, //and I need a lot of switches like //[sort by new][sort by bump][sort by length][show faved threads only] //[show unhidden threads only][show all threads][show hidden threads only][only show threads with new replies+]. //I want people to be able to forget about this site for a few months, visit it again, //and to read the threads that interest them first. //The other thing is, I use list of lists when I could've used list of objects. //I do "change[change_type]" (where changes_type = 0) instead of "change.type", //had some brainfart about wanting to use variable length objects, //but I now think that it's better to just have some unused fields. /*var cleaner_threads = document.createElement("style"); cleaner_threads.setAttribute("id", "cleaner_threads"); cleaner_threads.innerHTML = " \n \ #threadlist { \n \ overflow-y: hidden; \n \ max-height: 194px; \n \ } \n \ #threadlist span { \n \ display:block; \n \ }"; document.head.appendChild(cleaner_threads);*/ //start -- entry point function start() { var stylebox = document.getElementById("stylebox") if (!stylebox) { start_thread() return } stylebox.style.display="none" settings_load() sync = sync_load() var l_changes_all = GM_getValue("4-c--changes") if (l_changes_all) { changes_all = JSON.parse(l_changes_all) } 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_base.appendChild(refreshbox) refreshbox_peek = document.createElement("div") refreshbox_base.appendChild(refreshbox_peek) katalog_div = document.createElement("div") katalog_div.id = "katalog_div" refreshbox_base.appendChild(katalog_div) katalog_redraw() var style_show = document.createElement("a") style_show.href = "#" style_show.textContent = "[st-]" refreshbox.appendChild(style_show) style_show.onclick = function(evt) { if (stylebox.style.display == "") { stylebox.style.display = "none" style_show.textContent = "[st-]" } else { stylebox.style.display = "" style_show.textContent = "[st+]" } return element_onclick_end(evt) } fetch = document.createElement("a") fetch.href = "#" fetch.textContent = "[fetch]" refreshbox.appendChild(fetch) fetch.onclick = function(evt) { if (fetching != 0) { return element_onclick_end(evt) } var l_fetching = 0 for (var is_refreshed of refresh_list1) { l_fetching += is_refreshed } fetching = l_fetching fetch.textContent = "[fetching(0/" + boards_list.length + ")]" for (var board = 0; board < boards_list.length; board++) { if (refresh_list1[board]) { katalog_request(board) } } return element_onclick_end(evt) } var merge = document.createElement("a") merge.href = "#" merge.textContent = "[merge]" refreshbox.appendChild(merge) merge.onclick = function(evt) { katalog_merge() return element_onclick_end(evt) } for (let i = 0; i < boards_list.length; i++) { var board = boards_list[i] refreshbox.appendChild(document.createTextNode(" ")) var boardel = document.createElement("a") boardel.href = "/" + board + "/" refreshbox.appendChild(boardel) board_el_all[i] = boardel boardel.onclick = function(evt) { if (board_selected == i) { board_selected = -1 } else { board_selected = i } board_redraw() katalog_redraw() return element_onclick_end(evt) } } board_redraw_2() { refreshbox.appendChild(document.createTextNode(" ")) var boardel = document.createElement("a") boardel.href = "/img/" boardel.textContent = "img" refreshbox.appendChild(boardel) } { refreshbox.appendChild(document.createTextNode(" ")) var boardel = document.createElement("a") boardel.href = "/img/res/2664.html" boardel.textContent = "ihost" refreshbox.appendChild(boardel) } { refreshbox.appendChild(document.createTextNode(" ")) var boardel = document.createElement("a") boardel.href = "/hades/" boardel.textContent = "hades" refreshbox.appendChild(boardel) } document.addEventListener("focus", function(evt) { var new_sync = sync_load() for (var board = 0; board < boards_list.length; board++ ) { if (sync[board+1] != new_sync[board+1]) { katalog_all_old[board] = null katalog_get_old(board) } } if (sync[0] != new_sync[0]) { changes_all = JSON.parse(GM_getValue("4-c--changes")) board_redraw() } sync = new_sync }) inject() board_peek() textarea_change() } //start_thread -- like start, but for thread. function start_thread() { textarea_change() } //textarea_change -- resizes textarea as you type function textarea_change() { // https://stackoverflow.com/questions/7745741/auto-expanding-textarea for (const textarea of document.getElementsByTagName("textarea")) { textarea.onfocus = "" textarea.onblur = "" textarea.oninput = function() { //textarea.style.height = "" textarea.style.height = Math.max(60, textarea.scrollHeight - textarea_get_border_height(textarea)) + "px"; //window.scrollTo(0, document.body.scrollHeight) } if (autofill_sage) textarea_autofill_sage(textarea) } } function textarea_get_border_height(textarea) { let topText = getComputedStyle(textarea).getPropertyValue('border-top-width') let top = 0 if (topText.endsWith('px')) { topText = topText.substring(0, topText.length-2) top = parseInt(topText) //just in case if (top < 0) alert("what") if (top > 16) alert("what") } let bottomText = getComputedStyle(textarea).getPropertyValue('border-bottom-width') let bottom = 0 if (bottomText.endsWith('px')) { bottomText = bottomText.substring(0, bottomText.length-2) bottom = parseInt(bottomText) //just in case if (bottom < 0) alert("what") if (bottom > 16) alert("what") } return top + bottom } //function textarea_change() { // // https://stackoverflow.com/questions/7745741/auto-expanding-textarea // for (const textarea of document.getElementsByTagName("textarea")) { // textarea.onfocus = "" // textarea.onblur = "" // textarea.setAttribute("charcount", textarea.value.length); /* textarea.setAttribute("linecount", textarea.value.split(/\r\n|\r|\n/).length); textarea.style.height = Math.max(80, textarea.scrollHeight) + "px"; textarea.oninput = function() { var textlen = textarea.value.length, charcount = textarea.getAttribute("charcount"), chardiff = charcount - textlen, linelen = textarea.value.split(/\r\n|\r|\n/).length, linecount = textarea.getAttribute("linecount"), linediff = linecount - linelen, charsize = 0.3, linesize = 16, minimum = 80; // reset if zero if (textlen == 0) { textarea.style.height = minimum + "px"; return; } if (linelen != linecount) { textarea.style.height = Math.max(minimum, parseFloat(textarea.style.height).toFixed(2) - (linesize*linediff) ) + "px"; } if (textlen != charcount) { textarea.style.height = Math.max(minimum, parseFloat(textarea.style.height).toFixed(2) - (charsize*chardiff)) + "px"; } textarea.setAttribute("linecount", linelen); textarea.setAttribute("charcount", textlen); } if (autofill_sage) textarea_autofill_sage(textarea) } }*/ function textarea_autofill_sage(textarea) { //could've just walked every input field instead, but it's good enough too var form = textarea.parentElement while (form.tagName != "FORM") form = form.parentElement let inputs = form.getElementsByTagName("input") for (let i = 0; i < inputs.length; i++) { let input = inputs[i] if (input.name == "field_b") { input.value = "sage" } } } function settings_save() { GM_setValue("4-c--settings", JSON.stringify(settings)) } function settings_load() { var l_settings = GM_getValue("4-c--settings") if (l_settings) { settings = JSON.parse(l_settings) } else { settings = { by_bump : 0, fave : new Array(boards_list.length), hide : new Array(boards_list.length) } } } //GM_setValue("4-c--changes", JSON.stringify(changes_all)) function katalog_redraw() { katalog_div.innerHTML = "" if (board_selected == -1) return var katalog_first = katalog_get_old(board_selected) if (!katalog_first) return var katalog_div_settings = document.createElement("div") element_add(katalog_div, katalog_div_settings) var open = document.createElement("a") open.href = "/" + boards_list[board_selected] + "/" open.textContent = "[open]" element_add(katalog_div_settings, open) var by_bump = document.createElement("a") by_bump.href = "#" if (settings.by_bump == 0) { by_bump.textContent = "[by bump]" } else { by_bump.textContent = "[by new]" } element_add(katalog_div_settings, by_bump) by_bump.onclick = function(evt) { if (settings.by_bump == 0) { settings.by_bump = 1 } else { settings.by_bump = 0 } if (settings.by_bump == 0) { by_bump.textContent = "[by bump]" } else { by_bump.textContent = "[by new]" } settings_save() katalog_redraw() return element_onclick_end(evt) } if (settings.by_bump == 0) { for (var i = 0; i < katalog_first.length; i++) for (var kat_i = 0; kat_i < katalog_first.length; kat_i++) { var thread = katalog_first[kat_i] if (thread[kat_position] == i + 1) { let el_a = document.createElement("a") el_a.href = "/" + boards_list[board_selected] + "/kareha.pl/" + thread[kat_thread_id] + "/l50" el_a.textContent = thread[kat_position] + ":" + thread[kat_thread_title] + "(" + thread[kat_amount_of_replies] + ")" //el_a.innerHTML = "hellohellohello
ddddiiiivvv
line2" //el_a.innerHTML = "[hello hello hello hello hello hello hello]" element_add(katalog_div, el_a) element_add_space(katalog_div) el_a.onclick = function(evt) { if (el_a.nextElementSibling) { let a2 = el_a.nextElementSibling let span = document.createElement("span") span.textContent = ":" span.style.float = "left" span.style.display = "inline-block" let offset = a2.offsetLeft - a2.parentElement.offsetLeft span.style.width = offset + "px" element_add_before(a2, document.createElement("br")) element_add_before(a2, span) } return element_onclick_end(evt) } break } } } else { for (var kat_i = katalog_first.length - 1; kat_i >= 0; kat_i--) { var thread = katalog_first[kat_i] var el_a = document.createElement("a") el_a.href = "/" + boards_list[board_selected] + "/kareha.pl/" + thread[kat_thread_id] + "/l50" el_a.textContent = thread[kat_position] + ":" + thread[kat_thread_title] + "(" + thread[kat_amount_of_replies] + ")" element_add(katalog_div, el_a) element_add(katalog_div, document.createTextNode(" ")) } } } //sync_load -- load settings //do that every time another tab did something function sync_load() { var new_sync var l_sync = GM_getValue("4-c--sync") if (l_sync) { new_sync = JSON.parse(l_sync) if (new_sync.length != (boards_list.length+1)) new_sync = null } if (!new_sync) { new_sync = new Array(boards_list.length+1) for (var i = 0; i < (boards_list.length+1); i++ ) { new_sync[i] = 0 } GM_setValue("4-c--sync", JSON.stringify(new_sync)) } return new_sync } //sync_save -- save setting //save it for other tab to load function sync_save() { GM_setValue("4-c--sync", JSON.stringify(sync)) } //sync_add -- call this after every sync_save function sync_add(index) { sync[index]++ if (sync[index] > 255) sync[index] = 0 } function inject() { var threads = document.getElementsByClassName("thread") for (var thread_i = 0; thread_i < threads.length; thread_i++) { let thread = threads[thread_i] var threadlinks = thread.getElementsByClassName("threadlinks") if (threadlinks.length == 0) continue threadlinks = threadlinks[0] if (threadlinks.getElementsByTagName("a").length == 0) continue let favbottom = document.createElement("a") favbottom.innerText = "[Fav]" favbottom.href = "#" let hidebottom = document.createElement("a") hidebottom.innerText = "[Hide]" hidebottom.href = "#" threadlinks.appendChild(document.createTextNode(" ")) threadlinks.appendChild(favbottom) threadlinks.appendChild(document.createTextNode(" ")) threadlinks.appendChild(hidebottom) var threadnavigation = thread.getElementsByClassName("threadnavigation") if (threadnavigation.length == 0) continue threadnavigation = threadnavigation[0] let favtop = document.createElement("a") favtop.innerText = "[Fav]" favtop.href = "#" let hidetop = document.createElement("a") hidetop.innerText = "[Hide]" hidetop.href = "#" hidetop.onclick = hidebottom.onclick = function(evt) { if (hidetop.textContent == "[Hide]") { thread.style.height = "1.25em" thread.style.overflow = "hidden" hidetop.textContent = "[Unhide]" } else { thread.style.height = "" thread.style.overflow = "" hidetop.textContent = "[Hide]" } return element_onclick_end(evt) } element_add_before(threadnavigation.firstChild, hidetop) element_add_before(threadnavigation.firstChild, document.createTextNode(" ")) element_add_before(threadnavigation.firstChild, favtop) element_add_before(threadnavigation.firstChild, document.createTextNode(" ")) } } // board_peek -- show the list of updated threads function board_peek() { refreshbox_peek.innerHTML = "" for (var board = 0; board < boards_list.length; board++ ) { var changes = changes_all[board] if (!changes) continue var katalog_old = katalog_get_old(board) if (!katalog_old) continue for (var change of changes) { var thread_id var title var how_many_posts var indicator var posts_before var posts_now var element_type = change[0] if (element_type == changes_type_new) { thread_id = change[changes_thread_id] title = change[changes_thread_title] how_many_posts = change[changes_amount_of_replies] indicator = "NEW:" posts_before = 0 posts_now = how_many_posts } else if (element_type == changes_type_updated) { var katalog_entry = katalog_old[change[changes_index_in_katalog]] thread_id = katalog_entry[kat_thread_id] title = katalog_entry[kat_thread_title] how_many_posts = change[changes_amount_of_replies] - katalog_entry[kat_amount_of_replies] indicator = "" posts_before = katalog_entry[kat_amount_of_replies] posts_now = change[changes_amount_of_replies] } else if (element_type == changes_type_deleted) { var katalog_entry = katalog_old[change[changes_index_in_katalog]] thread_id = katalog_entry[kat_thread_id] title = katalog_entry[kat_thread_title] how_many_posts = katalog_entry[kat_amount_of_replies] indicator = "DEL:" posts_before = katalog_entry[kat_amount_of_replies] posts_now = change[kat_amount_of_replies] } 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 + "(" + indicator + how_many_posts + ")" var space = document.createTextNode(" ") div.appendChild(space) var a_newtab = document.createElement("a") div.appendChild(a_newtab) a_newtab.href = "/" + boards_list[board] + "/kareha.pl/" + thread_id + "/l50" a_newtab.setAttribute("target", "_blank") a_newtab.textContent = "[newtab]" if (element_type == changes_type_deleted) continue div.appendChild(document.createTextNode(" ")) let a_peek = document.createElement("a") div.appendChild(a_peek) a_peek.href = "#" a_peek.textContent = "[peek-]" let a_peek_div let a_peek_div_top if (just_hardcode_peeked_post_background_color) { a_peek_div = document.createElement("div") a_peek_div.style.backgroundColor="#EFEFEF" div.appendChild(a_peek_div) a_peek_div_top = a_peek_div a_peek_div_top.style.display = "none" } else { var temp1 = document.createElement("div") temp1.id = "posts" element_add(div, temp1) var temp2 = document.createElement("div") temp2.className = "thread" temp2.style.border = "none" temp2.style.margin = "0px" temp2.style.padding = "0px" element_add(temp1, temp2) a_peek_div = document.createElement("div") a_peek_div.className = "allreplies" element_add(temp2, a_peek_div) a_peek_div_top = temp1 a_peek_div_top.style.display = "none" } let a_peek_board = board let a_peek_url if (how_many_posts == 1) { a_peek_url = "https://4-ch.net/" + boards_list[board] + "/kareha.pl/" + thread_id + "/" + posts_now } else { a_peek_url = "https://4-ch.net/" + boards_list[board] + "/kareha.pl/" + thread_id + "/n" + (posts_before+1) + "-" + posts_now } a_peek.onclick = function(evt) { if (a_peek.textContent == "[peek-]") { a_peek.textContent = "[peek+]" thread_request(a_peek, a_peek_div, a_peek_url, a_peek_div_top) } else { a_peek.textContent = "[peek-]" //a_peek_div.innerHTML = "" a_peek_div_top.style.display = "none" } return element_onclick_end(evt) } } } } // thread_request -- get some posts from the thread function thread_request(a_peek, a_peek_div, url, a_peek_div_top) { var xmlhttp = new XMLHttpRequest(); var address_full = url xmlhttp.open("GET", address_full, true) var board = 0 //LATER: need to do something about shift-jis being badly supported by TextDecoder 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('//*[@class="allreplies"]', page, null, 9, null).singleNodeValue a_peek_div.innerHTML = page.innerHTML a_peek_div_top.style.display = "" } xmlhttp.send(); } function board_redraw_2() { for (var i = 0; i < boards_list.length; i++ ) { var b = changes_all[i] var updated = 0 var deleted = 0 if (b) for (var j = 0; j < b.length; j++) { if (b[j][changes_type] == changes_type_deleted) { deleted++ } else { updated++ } } var name = boards_list[i] if (board_selected == i) name = "+" + name + "+" if (updated) name += "(" + updated + ")" if (deleted) name += "(-" + deleted + ")" board_el_all[i].textContent = name } } function board_redraw() { board_redraw_2() if (fetching == 0) { fetch.textContent = "[fetch]" GM_setValue("4-c--changes", JSON.stringify(changes_all)) sync_add(0) sync_save() } else { fetch.textContent = "[fetching(" + fetching + "/" + boards_list.length + ")]" } board_peek() } function katalog_merge() { for (var board = 0; board < boards_list.length; board++ ) { //LATER var changes = changes_all[board] if (!changes) continue var katalog_old = katalog_get_old(board) if (!katalog_old) continue for (var change of changes) { if (change[changes_type] == changes_type_new) { if (katalog_old[katalog_old.length - 1][kat_thread_id] >= change[changes_thread_id]) { //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][kat_thread_id] == change[changes_thread_id]) { continue; } else { alert("two or more threads at once, oh no") return } } var new_entry = [change[changes_thread_id], 1, change[changes_amount_of_replies], change[changes_thread_title]] katalog_old.push(new_entry) } else if (change[changes_type] == changes_type_updated) { katalog_old[change[changes_thread_id]][kat_amount_of_replies] = change[changes_amount_of_replies] } else if (change[changes_type] == changes_type_deleted) { katalog_old[change[changes_thread_id]][kat_wasnt_deleted_yet] = 0 } } changes_all[board] = null sync_add(board+1) katalog_set_old(board) } GM_setValue("4-c--changes", JSON.stringify(changes_all)) sync_add(0) //sync_add(1) sync_save() board_redraw() } function katalog_request(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 tr_all = tl.getElementsByTagName("tr") var katalog = new Array(tr_all.length-1) var ii = 0 for (var i = 1; i < tr_all.length; 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, ii+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[kat_thread_id] == oo[kat_thread_id]) { if (ii[kat_amount_of_replies] != oo[kat_amount_of_replies]) { changed.push([changes_type_updated, o, ii[kat_amount_of_replies]]) } i++ o++ } else if (ii[kat_thread_id] > oo[kat_thread_id]) { if (oo[kat_wasnt_deleted_yet]) { changed.push([changes_type_deleted, o]) } o++ } else { changed.push([changes_type_new, ii[kat_thread_id], ii[kat_amount_of_replies], ii[kat_thread_title]]) i++ } if (i >= katalog.length) { while (o < katalog_old.length) { oo = katalog_old[o] if (oo[1]) { changed.push([changes_type_deleted, o]) } o++ } break } if (o >= katalog_old.length) { while (i < katalog.length) { ii = katalog[i] changed.push([changes_type_new, ii[kat_thread_id], ii[kat_amount_of_replies], ii[kat_thread_title]]) i++ } break } } if (changed.length) { changes_all[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 (var ka of katalog) { res += katalog[i][kat_thread_id] + "," + katalog[i][kat_wasnt_deleted_yet] + "," + katalog[i][kat_amount_of_replies] + "," + katalog[i][kat_thread_title] + "\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") } function element_add_after(referenceNode, newNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling) } function element_add_before(referenceNode, newNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode) } function element_add(referenceNode, newNode) { referenceNode.appendChild(newNode) } function element_add_space(referenceNode) { referenceNode.appendChild(document.createTextNode(" ")) } function element_delete(element) { element.parentNode.removeChild(element) } function element_hide(element) { element.style.display = "none" } function element_show(element) { element.style.display = "" } function element_onclick_end(evt) { evt.stopPropagation();evt.preventDefault();return false; } start()