thread_autoupdater.js: add
This commit is contained in:
parent
da8898624a
commit
accae507a6
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
* @file Thread auto updater.
|
||||
* @author jonsmy
|
||||
*/
|
||||
|
||||
$().ready(() => {
|
||||
|
||||
const kIsEnabled = LCNToggleSetting.build("enabled")
|
||||
//const kIsBellEnabled = LCNToggleSetting.build("bellEnabled")
|
||||
void LCNSettingsSubcategory.for("general", "threadUpdater")
|
||||
.setLabel("Thread Updater")
|
||||
.addSetting(kIsEnabled
|
||||
.setLabel(_("Fetch new replies in the background"))
|
||||
.setDefaultValue(true))
|
||||
/*.addSetting(kIsBellEnabled
|
||||
.setLabel(_("Play an audible chime when new replies are found"))
|
||||
.setDefaultValue(false))*/;
|
||||
|
||||
if (LCNSite.INSTANCE.isThreadPage()) {
|
||||
let threadUpdateStatus = null
|
||||
let secondsCounter = 0
|
||||
let threadState = null
|
||||
let threadStats = null
|
||||
let statReplies = null
|
||||
let statFiles = null
|
||||
let statPage = null
|
||||
let statUniqueIPs = null
|
||||
|
||||
const parser = new DOMParser()
|
||||
const abortable = LCNSite.createAbortable()
|
||||
const threadStatsItems = []
|
||||
const updateDOMStatus = () => {
|
||||
const text = threadState ?? (secondsCounter >= 0 ? `${secondsCounter}s` : "…")
|
||||
threadUpdateStatus.innerText = text
|
||||
}
|
||||
|
||||
const updateSecondsByTSLP = post_info => {
|
||||
secondsCounter = Math.floor(((Date.now() - post_info.getCreatedAt().getTime()) / 30000))
|
||||
secondsCounter = secondsCounter > 1000 ? 1000 : secondsCounter
|
||||
secondsCounter = secondsCounter < 11 ? 11 : secondsCounter
|
||||
}
|
||||
|
||||
const updateStatsFn = async thread => {
|
||||
// XXX: Using /%b/%d.json would be better however the page number isn't provided.
|
||||
const res = await fetch(`/${thread.getContent().getInfo().getBoardId()}/threads.json`, {
|
||||
"signal": abortable.signal
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const stats = LCNSite.getThreadFromPages(await res.json(), thread.getContent().getInfo().getThreadId())
|
||||
if (stats != null) {
|
||||
threadStats = stats
|
||||
} else {
|
||||
threadState = "Pruned"
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Server responded with non-OK status '${res.status}'`)
|
||||
}
|
||||
}
|
||||
|
||||
const updateThreadFn = async (thread) => {
|
||||
const threadPost = thread.getContent()
|
||||
const threadReplies = thread.getReplies()
|
||||
const lastPostC = threadReplies.at(-1).getParent()
|
||||
const lastPostTs = lastPostC.getContent().getInfo().getCreatedAt().getTime()
|
||||
|
||||
const res = await fetch(location.href, {
|
||||
"signal": abortable.signal
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const dom = parser.parseFromString(await res.text(), "text/html")
|
||||
const livePCList = Array.prototype.map.apply(dom.querySelectorAll(`#thread_${threadPost.getInfo().getThreadId()} > .postcontainer`), [ pc => LCNPostContainer.assign(pc) ])
|
||||
const documentPCList = threadReplies.map(p => p.getParent())
|
||||
const missingPCList = []
|
||||
|
||||
for (const pc of livePCList.reverse()) {
|
||||
if (pc.getContent().getInfo().getCreatedAt().getTime() > lastPostTs) {
|
||||
missingPCList.unshift(pc)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (missingPCList.length) {
|
||||
for (const pc of missingPCList) {
|
||||
documentPCList.at(-1).getElement().after(pc.getElement())
|
||||
documentPCList.push(pc)
|
||||
}
|
||||
|
||||
for (const pc of missingPCList) {
|
||||
$(document).trigger("new_post", [ pc.getContent().getElement() ])
|
||||
}
|
||||
|
||||
LCNSite.INSTANCE.setUnseen(LCNSite.INSTANCE.getUnseen() + missingPCList.length)
|
||||
secondsCounter = 11
|
||||
} else {
|
||||
updateSecondsByTSLP(lastPostC.getContent().getInfo())
|
||||
}
|
||||
} else if (res.status == 404) {
|
||||
threadState = "Pruned"
|
||||
} else {
|
||||
throw new Error(`Server responded with non-OK status '${res.status}'`)
|
||||
}
|
||||
}
|
||||
|
||||
const onTickClean = () => {
|
||||
if (onTickId != null) {
|
||||
clearTimeout(onTickId)
|
||||
onTickId = null
|
||||
}
|
||||
abortable.abort()
|
||||
}
|
||||
|
||||
let onTickId = null
|
||||
const onTickFn = async () => {
|
||||
void secondsCounter--;
|
||||
onTickClean()
|
||||
updateDOMStatus()
|
||||
|
||||
if (threadState == null) {
|
||||
if (secondsCounter < 0) {
|
||||
const thread = LCNThread.first()
|
||||
try {
|
||||
console.debug("fetch updates!")
|
||||
await updateStatsFn(thread)
|
||||
if (threadState == null && threadStats.last_modified > (thread.getReplies().at(-1).getInfo().getCreatedAt().getTime() / 1000)) {
|
||||
await updateThreadFn(thread)
|
||||
}
|
||||
|
||||
const threadEl = thread.getElement()
|
||||
|
||||
statUniqueIPs.innerText = threadStats.unique_ips
|
||||
statReplies.innerText = thread.getReplies().length
|
||||
statFiles.innerText = threadEl.querySelectorAll(".files .file").length - threadEl.querySelectorAll(".files .file .post-image.deleted").length
|
||||
statPage.innerText = threadStats.page + 1
|
||||
} catch (error) {
|
||||
console.error("threadAutoUpdater: Failed while processing update. Probably a network error", error)
|
||||
} finally {
|
||||
secondsCounter = secondsCounter > 0 ? secondsCounter : 60
|
||||
}
|
||||
}
|
||||
|
||||
onTickId = setTimeout(onTickFn, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
let floaterLinkBox = null
|
||||
const onStateChangeFn = v => {
|
||||
onTickClean()
|
||||
|
||||
if (v) {
|
||||
_domsetup_btn: {
|
||||
const container = LCNSite.INSTANCE.getFloaterLContainer()
|
||||
floaterLinkBox = document.createElement("span")
|
||||
const threadlink = document.createElement("span")
|
||||
const threadUpdateLink = document.createElement("a")
|
||||
threadUpdateStatus = document.createElement("span")
|
||||
|
||||
threadUpdateStatus.id = "thread-update-status"
|
||||
threadUpdateStatus.innerText = "…"
|
||||
threadUpdateLink.addEventListener("click", e => {
|
||||
e.preventDefault()
|
||||
if (secondsCounter >= 0) {
|
||||
secondsCounter = 0
|
||||
onTickFn()
|
||||
}
|
||||
})
|
||||
threadUpdateLink.href = "#"
|
||||
threadUpdateLink.appendChild(new Text("Refresh: "))
|
||||
threadUpdateLink.appendChild(threadUpdateStatus)
|
||||
threadlink.classList.add("threadlink")
|
||||
threadlink.appendChild(threadUpdateLink)
|
||||
floaterLinkBox.classList.add("threadlinks")
|
||||
floaterLinkBox.appendChild(threadlink)
|
||||
container.appendChild(floaterLinkBox)
|
||||
}
|
||||
|
||||
_domsetup_stats: {
|
||||
const container = LCNSite.INSTANCE.getThreadStatsRContainer()
|
||||
const span1 = document.createElement("span")
|
||||
const span2 = document.createElement("span")
|
||||
const span3 = document.createElement("span")
|
||||
statUniqueIPs = document.getElementById("lcn-uniqueips")
|
||||
statReplies = document.createElement("span")
|
||||
statFiles = document.createElement("span")
|
||||
statPage = document.createElement("span")
|
||||
|
||||
statReplies.id = "lcn_replies_n"
|
||||
statReplies.innerText = "…"
|
||||
|
||||
statFiles.id = "lcn_files_n"
|
||||
statReplies.innerText = "…"
|
||||
|
||||
statPage.id = "lcn_page_n"
|
||||
statPage.innerText = "…"
|
||||
|
||||
span1.appendChild(new Text("Replies: "))
|
||||
span1.appendChild(statReplies)
|
||||
span2.appendChild(new Text("Files: "))
|
||||
span2.appendChild(statFiles)
|
||||
span3.appendChild(new Text("Page: "))
|
||||
span3.appendChild(statPage)
|
||||
|
||||
for (const span of [ span1, span2, span3 ]) {
|
||||
threadStatsItems.push(span)
|
||||
container.appendChild(span)
|
||||
}
|
||||
}
|
||||
|
||||
secondsCounter = 0
|
||||
setTimeout(onTickFn, 1)
|
||||
} else {
|
||||
floaterLinkBox?.remove()
|
||||
floaterLinkBox = null
|
||||
statReplies = null
|
||||
statFiles = null
|
||||
statPage = null
|
||||
|
||||
while (threadStatsItems.length) {
|
||||
threadStatsItems.shift().remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kIsEnabled.onChange(onStateChangeFn)
|
||||
onStateChangeFn(kIsEnabled.getValue())
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue