mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-01-30 09:48:12 +01:00
247 lines
7.3 KiB
TypeScript
247 lines
7.3 KiB
TypeScript
import Notice from '../classes/Notice'
|
|
import { Conf } from '../globals/globals'
|
|
import $ from '../platform/$'
|
|
import CrossOrigin from '../platform/CrossOrigin'
|
|
import { DAY, dict } from '../platform/helpers'
|
|
import archives from './archives.json'
|
|
/*
|
|
* decaffeinate suggestions:
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
|
*/
|
|
|
|
const Redirect = {
|
|
archives,
|
|
|
|
init() {
|
|
this.selectArchives()
|
|
if (Conf['archiveAutoUpdate']) {
|
|
const now = Date.now()
|
|
if (now - (2 * DAY) >= Conf['lastarchivecheck'] || Conf['lastarchivecheck'] > now) { return this.update() }
|
|
}
|
|
},
|
|
|
|
selectArchives() {
|
|
let boardID, boards, data, files
|
|
const o = {
|
|
thread: dict(),
|
|
post: dict(),
|
|
file: dict()
|
|
}
|
|
|
|
const archives = dict()
|
|
for (data of Conf['archives']) {
|
|
let name, software, uid
|
|
for (const key of ['boards', 'files']) {
|
|
if (!(data[key] instanceof Array)) { data[key] = [] }
|
|
}
|
|
({ uid, name, boards, files, software } = data)
|
|
if (!['fuuka', 'foolfuuka'].includes(software)) { continue }
|
|
archives[JSON.stringify(uid ?? name)] = data
|
|
for (boardID of boards) {
|
|
if (!(boardID in o.thread)) { o.thread[boardID] = data }
|
|
if (!(boardID in o.post) && (software === 'foolfuuka')) { o.post[boardID] = data }
|
|
if (!(boardID in o.file) && files.includes(boardID)) { o.file[boardID] = data }
|
|
}
|
|
}
|
|
|
|
for (boardID in Conf['selectedArchives']) {
|
|
const record = Conf['selectedArchives'][boardID]
|
|
for (const type in record) {
|
|
let archive
|
|
const id = record[type]
|
|
if ((archive = archives[JSON.stringify(id)]) && $.hasOwn(o, type)) {
|
|
boards = type === 'file' ? archive.files : archive.boards
|
|
if (boards.includes(boardID)) { o[type][boardID] = archive }
|
|
}
|
|
}
|
|
}
|
|
|
|
return Redirect.data = o
|
|
},
|
|
|
|
update(cb) {
|
|
let url
|
|
const urls = []
|
|
const responses = []
|
|
let nloaded = 0
|
|
for (url of Conf['archiveLists'].split('\n')) {
|
|
if (url[0] !== '#') {
|
|
url = url.trim()
|
|
if (url) { urls.push(url) }
|
|
}
|
|
}
|
|
|
|
const fail = (url, action, msg) => new Notice('warning', `Error ${action} archive data from\n${url}\n${msg}`, 20)
|
|
|
|
const load = i => (function () {
|
|
if (this.status !== 200) { return fail(urls[i], 'fetching', (this.status ? `Error ${this.statusText} (${this.status})` : 'Connection Error')) }
|
|
let { response } = this
|
|
if (!(response instanceof Array)) { response = [response] }
|
|
responses[i] = response
|
|
nloaded++
|
|
if (nloaded === urls.length) {
|
|
return Redirect.parse(responses, cb)
|
|
}
|
|
})
|
|
|
|
if (urls.length) {
|
|
for (let i = 0; i < urls.length; i++) {
|
|
url = urls[i]
|
|
if (['[', '{'].includes(url[0])) {
|
|
let response
|
|
try {
|
|
response = JSON.parse(url)
|
|
} catch (err) {
|
|
fail(url, 'parsing', err.message)
|
|
continue
|
|
}
|
|
load(i).call({ status: 200, response })
|
|
} else {
|
|
CrossOrigin.ajax(url,
|
|
{ onloadend: load(i) })
|
|
}
|
|
}
|
|
} else {
|
|
Redirect.parse([], cb)
|
|
}
|
|
},
|
|
|
|
parse(responses, cb) {
|
|
const archives = []
|
|
const archiveUIDs = dict()
|
|
for (const response of responses) {
|
|
for (const data of response) {
|
|
const uid = JSON.stringify(data.uid ?? data.name)
|
|
if (uid in archiveUIDs) {
|
|
$.extend(archiveUIDs[uid], data)
|
|
} else {
|
|
archiveUIDs[uid] = dict.clone(data)
|
|
archives.push(data)
|
|
}
|
|
}
|
|
}
|
|
const items = { archives, lastarchivecheck: Date.now() }
|
|
$.set(items)
|
|
$.extend(Conf, items)
|
|
Redirect.selectArchives()
|
|
return cb
|
|
},
|
|
|
|
to(dest, data) {
|
|
const archive = (['search', 'board'].includes(dest) ? Redirect.data.thread : Redirect.data[dest])[data.boardID]
|
|
if (!archive) { return '' }
|
|
return Redirect[dest](archive, data)
|
|
},
|
|
|
|
protocol(archive) {
|
|
let {
|
|
protocol
|
|
} = location
|
|
if (!$.getOwn(archive, protocol.slice(0, -1))) {
|
|
protocol = protocol === 'https:' ? 'http:' : 'https:'
|
|
}
|
|
return `${protocol}//`
|
|
},
|
|
|
|
thread(archive, { boardID, threadID, postID }) {
|
|
// Keep the post number only if the location.hash was sent f.e.
|
|
let path = threadID ?
|
|
`${boardID}/thread/${threadID}`
|
|
:
|
|
`${boardID}/post/${postID}`
|
|
if (archive.software === 'foolfuuka') {
|
|
path += '/'
|
|
}
|
|
if (threadID && postID) {
|
|
path += archive.software === 'foolfuuka' ?
|
|
`#${postID}`
|
|
:
|
|
`#p${postID}`
|
|
}
|
|
return `${Redirect.protocol(archive)}${archive.domain}/${path}`
|
|
},
|
|
|
|
post(archive, { boardID, postID }) {
|
|
// For fuuka-based archives:
|
|
// https://github.com/eksopl/fuuka/issues/27
|
|
const protocol = Redirect.protocol(archive)
|
|
const url = `${protocol}${archive.domain}/_/api/chan/post/?board=${boardID}&num=${postID}`
|
|
if (!Redirect.securityCheck(url)) { return '' }
|
|
|
|
return url
|
|
},
|
|
|
|
file(archive, { boardID, filename }) {
|
|
if (!filename) { return '' }
|
|
if (boardID === 'f') {
|
|
filename = encodeURIComponent($.unescape(decodeURIComponent(filename)))
|
|
} else {
|
|
if (/[sm]\.jpg$/.test(filename)) { return '' }
|
|
}
|
|
return `${Redirect.protocol(archive)}${archive.domain}/${boardID}/full_image/${filename}`
|
|
},
|
|
|
|
board(archive, { boardID }) {
|
|
return `${Redirect.protocol(archive)}${archive.domain}/${boardID}/`
|
|
},
|
|
|
|
search(archive, { boardID, type, value }) {
|
|
type = type === 'name' ?
|
|
'username'
|
|
: type === 'MD5' ?
|
|
'image'
|
|
:
|
|
type
|
|
if (type === 'capcode') {
|
|
// https://github.com/pleebe/FoolFuuka/blob/bf4224eed04637a4d0bd4411c2bf5f9945dfec0b/src/Model/Search.php#L363
|
|
value = $.getOwn({
|
|
'Developer': 'dev',
|
|
'Verified': 'ver'
|
|
}, value) || value.toLowerCase()
|
|
} else if (type === 'image') {
|
|
value = value.replace(/[+/=]/g, c => ({ '+': '-', '/': '_', '=': '' })[c])
|
|
}
|
|
value = encodeURIComponent(value)
|
|
const path = archive.software === 'foolfuuka' ?
|
|
`${boardID}/search/${type}/${value}/`
|
|
: type === 'image' ?
|
|
`${boardID}/image/${value}`
|
|
:
|
|
`${boardID}/?task=search2&search_${type}=${value}`
|
|
return `${Redirect.protocol(archive)}${archive.domain}/${path}`
|
|
},
|
|
|
|
report(boardID) {
|
|
const urls = []
|
|
for (const archive of Conf['archives']) {
|
|
const { software, https, reports, boards, name, domain } = archive
|
|
if ((software === 'foolfuuka') && https && reports && boards instanceof Array && boards.includes(boardID)) {
|
|
urls.push([name, `https://${domain}/_/api/chan/offsite_report/`])
|
|
}
|
|
}
|
|
return urls
|
|
},
|
|
|
|
securityCheck(url) {
|
|
return /^https:\/\//.test(url) ||
|
|
(location.protocol === 'http:') ||
|
|
Conf['Exempt Archives from Encryption']
|
|
},
|
|
|
|
navigate(dest, data, alternative) {
|
|
if (!Redirect.data) { Redirect.init() }
|
|
const url = Redirect.to(dest, data)
|
|
if (url && (
|
|
Redirect.securityCheck(url) ||
|
|
confirm(`Redirect to ${url}?\n\nYour connection will not be encrypted.`)
|
|
)) {
|
|
return location.replace(url)
|
|
} else if (alternative) {
|
|
return location.replace(alternative)
|
|
}
|
|
}
|
|
}
|
|
|
|
export default Redirect
|