// ==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()