mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-03-20 01:37:47 +01:00
bugs and types
This commit is contained in:
parent
759be29bd1
commit
6b81346b46
@ -89,9 +89,6 @@ var Get = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// First:
|
// First:
|
||||||
// In every posts,
|
|
||||||
// if it did quote this post,
|
|
||||||
// get all their backlinks.
|
|
||||||
posts.forEach(function (qPost) {
|
posts.forEach(function (qPost) {
|
||||||
if (qPost.quotes.includes(fullID)) {
|
if (qPost.quotes.includes(fullID)) {
|
||||||
return handleQuotes(qPost, 'quotelinks')
|
return handleQuotes(qPost, 'quotelinks')
|
||||||
@ -99,10 +96,6 @@ var Get = {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Second:
|
// Second:
|
||||||
// If we have quote backlinks:
|
|
||||||
// in all posts this post quoted
|
|
||||||
// and their clones,
|
|
||||||
// get all of their backlinks.
|
|
||||||
if (Conf['Quote Backlinks']) {
|
if (Conf['Quote Backlinks']) {
|
||||||
for (var quote of post.quotes) {
|
for (var quote of post.quotes) {
|
||||||
var qPost
|
var qPost
|
||||||
@ -113,7 +106,6 @@ var Get = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Third:
|
// Third:
|
||||||
// Filter out irrelevant quotelinks.
|
|
||||||
return quotelinks.filter(function (quotelink) {
|
return quotelinks.filter(function (quotelink) {
|
||||||
const { boardID, postID } = Get.postDataFromLink(quotelink)
|
const { boardID, postID } = Get.postDataFromLink(quotelink)
|
||||||
return boardID === post.board.ID && postID === post.ID
|
return boardID === post.board.ID && postID === post.ID
|
||||||
|
|||||||
@ -12,13 +12,7 @@ import Settings from './Settings'
|
|||||||
import UI from './UI'
|
import UI from './UI'
|
||||||
import meta from '../../package.json'
|
import meta from '../../package.json'
|
||||||
|
|
||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS101: Remove unnecessary use of Array.from
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* DS104: Avoid inline assignments
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
var Header = {
|
var Header = {
|
||||||
init() {
|
init() {
|
||||||
$.onExists(doc, 'body', () => {
|
$.onExists(doc, 'body', () => {
|
||||||
|
|||||||
@ -143,10 +143,8 @@ const Test = {
|
|||||||
testOne(post) {
|
testOne(post) {
|
||||||
Test.postsRemaining++
|
Test.postsRemaining++
|
||||||
return $.cache(
|
return $.cache(
|
||||||
g.SITE.urls.threadJSON({
|
g.SITE.urls.threadJSON(post.thread.ID, post.board),
|
||||||
boardID: post.boardID,
|
{ responseType: 'json' },
|
||||||
threadID: post.threadID,
|
|
||||||
}),
|
|
||||||
function () {
|
function () {
|
||||||
if (!this.response) {
|
if (!this.response) {
|
||||||
return
|
return
|
||||||
@ -179,7 +177,7 @@ const Test = {
|
|||||||
|
|
||||||
for (var key in Config.filter) {
|
for (var key in Config.filter) {
|
||||||
if (
|
if (
|
||||||
!key === 'General' &&
|
key !== 'MD5' ||
|
||||||
!(key === 'MD5' && post.board.ID === 'f')
|
!(key === 'MD5' && post.board.ID === 'f')
|
||||||
) {
|
) {
|
||||||
var val1 = Filter.values(key, obj)
|
var val1 = Filter.values(key, obj)
|
||||||
|
|||||||
@ -115,12 +115,12 @@ var ImageLoader = {
|
|||||||
if (!replace && !ImageLoader.prefetchEnabled) {
|
if (!replace && !ImageLoader.prefetchEnabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ($.hasClass(doc, 'catalog-mode')) {
|
if ($.hasClass(d, 'catalog-mode')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
![post, ...Array.from(post.clones)].some((clone) =>
|
![post, ...Array.from(post.clones)].some((clone) =>
|
||||||
doc.contains(clone.nodes.root),
|
d.contains(clone.nodes.root),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
@ -175,12 +175,13 @@ var ImageLoader = {
|
|||||||
return g.posts.forEach(function (post) {
|
return g.posts.forEach(function (post) {
|
||||||
for (post of [post, ...Array.from(post.clones)]) {
|
for (post of [post, ...Array.from(post.clones)]) {
|
||||||
for (var file of post.files) {
|
for (var file of post.files) {
|
||||||
if (file.videoThumb) {
|
if (file.isVideo && !file.isPrefetched) {
|
||||||
var { thumb } = file
|
const { thumb } = file
|
||||||
if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) {
|
if (qpClone === thumb) {
|
||||||
thumb.play()
|
continue
|
||||||
} else {
|
}
|
||||||
thumb.pause()
|
if (thumb.getBoundingClientRect().top < window.innerHeight) {
|
||||||
|
ImageLoader.prefetch(post, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,8 +130,10 @@ var Volume = {
|
|||||||
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
|
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!(el = $('video:not([data-md5])', this))) {
|
if (this instanceof HTMLAnchorElement) {
|
||||||
return
|
el = this.querySelector('video')
|
||||||
|
} else {
|
||||||
|
el = this.querySelector('video, audio')
|
||||||
}
|
}
|
||||||
if (el.muted || !$.hasAudio(el)) {
|
if (el.muted || !$.hasAudio(el)) {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export default class CatalogThreadNative {
|
|||||||
siteID: number
|
siteID: number
|
||||||
threadID: number
|
threadID: number
|
||||||
ID: string
|
ID: string
|
||||||
|
thread: any
|
||||||
toString() {
|
toString() {
|
||||||
return this.ID
|
return this.ID
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
function $$(selector: string, root: HTMLElement = document.body): HTMLElement[] {
|
function $$(selector: string, root: HTMLElement = document.body): HTMLElement[] | HTMLAnchorElement[] {
|
||||||
return Array.from(root.querySelectorAll(selector));
|
return Array.from(root.querySelectorAll(selector));
|
||||||
}
|
}
|
||||||
export default $$;
|
export default $$;
|
||||||
@ -1,21 +1,14 @@
|
|||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* DS205: Consider reworking code to avoid use of IIFEs
|
|
||||||
* DS207: Consider shorter variations of null checks
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
// loosely follows the jquery api:
|
|
||||||
// http://api.jquery.com/
|
|
||||||
|
|
||||||
import Notice from "../classes/Notice";
|
import Notice from "../classes/Notice";
|
||||||
import { c, Conf, d, doc, g } from "../globals/globals";
|
import { c, Conf, d, doc, g } from "../globals/globals";
|
||||||
import CrossOrigin from "./CrossOrigin";
|
import CrossOrigin from "./CrossOrigin";
|
||||||
import { debounce, dict, MINUTE, platform, SECOND } from "./helpers";
|
import { debounce, dict, MINUTE, platform, SECOND } from "./helpers";
|
||||||
|
import { AjaxPageOptions, ElementProperties } from "../types/$";
|
||||||
|
|
||||||
// not chainable
|
// not chainable
|
||||||
const $ = (selector, root = document.body) => root.querySelector(selector);
|
const $ = (selector, root = document.body) => root.querySelector(selector);
|
||||||
|
type AjaxPageRequest = XMLHttpRequest & {
|
||||||
|
abort: () => void;
|
||||||
|
}
|
||||||
$.id = id => d.getElementById(id);
|
$.id = id => d.getElementById(id);
|
||||||
$.cache = dict();
|
$.cache = dict();
|
||||||
$.ajaxPage = function (url, options) {
|
$.ajaxPage = function (url, options) {
|
||||||
@ -344,19 +337,9 @@ $.addStyle = function (css, id, test = 'head') {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$.addCSP = function (policy) {
|
$.addCSP = function (policy) {
|
||||||
const meta = $.el('meta', {
|
const meta = $.el('meta', { httpEquiv: 'Content-Security-Policy', content: policy }, { display: 'none' });
|
||||||
httpEquiv: 'Content-Security-Policy',
|
$.onExists(doc, 'head', () => $.add(d.head, meta));
|
||||||
content: policy
|
return meta;
|
||||||
}
|
|
||||||
);
|
|
||||||
if (d.head) {
|
|
||||||
$.add(d.head, meta);
|
|
||||||
return $.rm(meta);
|
|
||||||
} else {
|
|
||||||
const head = $.add((doc || d), $.el('head'));
|
|
||||||
$.add(head, meta);
|
|
||||||
return $.rm(head);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$.x = function (path, root) {
|
$.x = function (path, root) {
|
||||||
@ -413,11 +396,10 @@ $.before = (root, el) => root.parentNode.insertBefore($.nodes(el), root);
|
|||||||
|
|
||||||
$.replace = (root, el) => root.parentNode.replaceChild($.nodes(el), root);
|
$.replace = (root, el) => root.parentNode.replaceChild($.nodes(el), root);
|
||||||
|
|
||||||
$.el = function (tag, properties, properties2) {
|
$.el = function (tag: keyof HTMLElementTagNameMap, properties?: ElementProperties, properties2?: ElementProperties): HTMLElement {
|
||||||
const el = d.createElement(tag);
|
const element = document.createElement(tag);
|
||||||
if (properties) { $.extend(el, properties); }
|
Object.assign(element, properties, properties2);
|
||||||
if (properties2) { $.extend(el, properties2); }
|
return element;
|
||||||
return el;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$.on = function (el, events, handler) {
|
$.on = function (el, events, handler) {
|
||||||
@ -555,42 +537,41 @@ $.global = function (fn, data) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$.bytesToString = function (size) {
|
$.bytesToString = function (size) {
|
||||||
let unit = 0; // Bytes
|
if (size < 1024) {
|
||||||
while (size >= 1024) {
|
return `${size} B`;
|
||||||
size /= 1024;
|
} else if (size < 1048576) {
|
||||||
unit++;
|
return `${(size / 1024).toFixed(1)} KB`;
|
||||||
|
} else if (size < 1073741824) {
|
||||||
|
return `${(size / 1048576).toFixed(1)} MB`;
|
||||||
|
} else {
|
||||||
|
return `${(size / 1073741824).toFixed(1)} GB`;
|
||||||
}
|
}
|
||||||
// Remove trailing 0s.
|
|
||||||
size =
|
|
||||||
unit > 1 ?
|
|
||||||
// Keep the size as a float if the size is greater than 2^20 B.
|
|
||||||
// Round to hundredth.
|
|
||||||
Math.round(size * 100) / 100
|
|
||||||
:
|
|
||||||
// Round to an integer otherwise.
|
|
||||||
Math.round(size);
|
|
||||||
return `${size} ${['B', 'KB', 'MB', 'GB'][unit]}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$.minmax = (value, min, max) => value < min ?
|
$.minmax = (value, min, max) => Math.max(min, Math.min(max, value));
|
||||||
min
|
|
||||||
:
|
|
||||||
value > max ?
|
|
||||||
max
|
|
||||||
:
|
|
||||||
value;
|
|
||||||
|
|
||||||
$.hasAudio = video => video.mozHasAudio || !!video.webkitAudioDecodedByteCount;
|
$.hasAudio = function (el: HTMLVideoElement | HTMLAudioElement) {
|
||||||
|
if (el.tagName === 'VIDEO') {
|
||||||
|
return !el.muted;
|
||||||
|
} else if (el.tagName === 'AUDIO') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return el.querySelector('video:not([muted]), audio') != null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$.luma = rgb => (rgb[0] * 0.299) + (rgb[1] * 0.587) + (rgb[2] * 0.114);
|
$.luma = (rgb: number[]) => { // rgb: [r, g, b]
|
||||||
|
const [r, g, b] = rgb;
|
||||||
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||||
|
};
|
||||||
|
|
||||||
$.unescape = function (text) {
|
$.unescape = function (text) {
|
||||||
if (text == null) { return text; }
|
if (text == null) { return text; }
|
||||||
return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, c => ({ '&': '&', ''': "'", '"': '"', '<': '<', '>': '>', ',': ',' })[c]);
|
return text.replace(/<[^>]*>/g, '').replace(/&(amp|#039|quot|lt|gt|#44);/g, c => ({ '&': '&', ''': "'", '"': '"', '<': '<', '>': '>', ',': ',' })[c]);
|
||||||
};
|
};
|
||||||
|
|
||||||
$.isImage = url => /\.(jpe?g|jfif|png|gif|bmp|webp|avif|jxl)$/i.test(url);
|
$.isImage = url => /\.(jpe?g|png|gif|bmp|webp|svg|ico|tiff?)$/i.test(url);
|
||||||
$.isVideo = url => /\.(webm|mp4|ogv)$/i.test(url);
|
$.isVideo = url => /\.(webm|mp4|og[gv]|m4v|mov|avi|flv|wmv|mpg|mpeg|mkv|rm|rmvb|3gp|3g2|asf|swf|vob)$/i.test(url);
|
||||||
|
|
||||||
$.engine = (function () {
|
$.engine = (function () {
|
||||||
if (/Edge\//.test(navigator.userAgent)) { return 'edge'; }
|
if (/Edge\//.test(navigator.userAgent)) { return 'edge'; }
|
||||||
|
|||||||
@ -1,289 +0,0 @@
|
|||||||
import QR from '../Posting/QR'
|
|
||||||
import $ from './$'
|
|
||||||
import { dict, platform } from './helpers'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* decaffeinate suggestions:
|
|
||||||
* DS102: Remove unnecessary code created because of implicit returns
|
|
||||||
* DS205: Consider reworking code to avoid use of IIFEs
|
|
||||||
* DS206: Consider reworking classes to avoid initClass
|
|
||||||
* DS207: Consider shorter variations of null checks
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
let eventPageRequest
|
|
||||||
if (platform === 'crx') {
|
|
||||||
eventPageRequest = (function () {
|
|
||||||
const callbacks = []
|
|
||||||
chrome.runtime.onMessage.addListener(function (response) {
|
|
||||||
callbacks[response.id](response.data)
|
|
||||||
return delete callbacks[response.id]
|
|
||||||
})
|
|
||||||
return (params, cb) =>
|
|
||||||
chrome.runtime.sendMessage(params, (id) => (callbacks[id] = cb))
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
var CrossOrigin = {
|
|
||||||
binary(url, cb, headers = dict()) {
|
|
||||||
// XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
|
||||||
url = url.replace(
|
|
||||||
/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//,
|
|
||||||
'$1//adv/',
|
|
||||||
)
|
|
||||||
if (platform === 'crx') {
|
|
||||||
eventPageRequest(
|
|
||||||
{ type: 'ajax', url, headers, responseType: 'arraybuffer' },
|
|
||||||
function ({ response, responseHeaderString }) {
|
|
||||||
if (response) {
|
|
||||||
response = new Uint8Array(response)
|
|
||||||
}
|
|
||||||
return cb(response, responseHeaderString)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const fallback = function () {
|
|
||||||
return $.ajax(url, {
|
|
||||||
headers,
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
onloadend() {
|
|
||||||
if (this.status && this.response) {
|
|
||||||
return cb(
|
|
||||||
new Uint8Array(this.response),
|
|
||||||
this.getAllResponseHeaders(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return cb(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof window.GM_xmlhttpRequest === 'undefined' ||
|
|
||||||
window.GM_xmlhttpRequest === null
|
|
||||||
) {
|
|
||||||
fallback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const gmOptions = {
|
|
||||||
method: 'GET',
|
|
||||||
url,
|
|
||||||
headers,
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
overrideMimeType: 'text/plain; charset=x-user-defined',
|
|
||||||
onload(xhr) {
|
|
||||||
let data
|
|
||||||
if (xhr.response instanceof ArrayBuffer) {
|
|
||||||
data = new Uint8Array(xhr.response)
|
|
||||||
} else {
|
|
||||||
const r = xhr.responseText
|
|
||||||
data = new Uint8Array(r.length)
|
|
||||||
let i = 0
|
|
||||||
while (i < r.length) {
|
|
||||||
data[i] = r.charCodeAt(i)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cb(data, xhr.responseHeaders)
|
|
||||||
},
|
|
||||||
onerror() {
|
|
||||||
return cb(null)
|
|
||||||
},
|
|
||||||
onabort() {
|
|
||||||
return cb(null)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions)
|
|
||||||
} catch (error) {
|
|
||||||
return fallback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
file(url, cb) {
|
|
||||||
return CrossOrigin.binary(url, function (data, headers) {
|
|
||||||
if (data == null) {
|
|
||||||
return cb(null)
|
|
||||||
}
|
|
||||||
let name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?.[1]
|
|
||||||
const contentType = headers.match(/Content-Type:\s*(.*)/i)?.[1]
|
|
||||||
const contentDisposition = headers.match(
|
|
||||||
/Content-Disposition:\s*(.*)/i,
|
|
||||||
)?.[1]
|
|
||||||
let mime = contentType?.match(/[^;]*/)[0] || 'application/octet-stream'
|
|
||||||
const match =
|
|
||||||
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?.[1] ||
|
|
||||||
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?.[1]
|
|
||||||
if (match) {
|
|
||||||
name = match.replace(/\\"/g, '"')
|
|
||||||
}
|
|
||||||
if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) {
|
|
||||||
// In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead.
|
|
||||||
mime =
|
|
||||||
$.getOwn(
|
|
||||||
QR.typeFromExtension,
|
|
||||||
name.match(/[^.]*$/)[0].toLowerCase(),
|
|
||||||
) || 'application/octet-stream'
|
|
||||||
}
|
|
||||||
const blob = new Blob([data], { type: mime })
|
|
||||||
blob.name = name
|
|
||||||
return cb(blob)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
Request: (function () {
|
|
||||||
const Request = class Request {
|
|
||||||
static initClass() {
|
|
||||||
this.prototype.status = 0
|
|
||||||
this.prototype.statusText = ''
|
|
||||||
this.prototype.response = null
|
|
||||||
this.prototype.responseHeaderString = null
|
|
||||||
}
|
|
||||||
getResponseHeader(headerName) {
|
|
||||||
if (this.responseHeaders == null && this.responseHeaderString != null) {
|
|
||||||
this.responseHeaders = dict()
|
|
||||||
for (var header of this.responseHeaderString.split('\r\n')) {
|
|
||||||
var i
|
|
||||||
if ((i = header.indexOf(':')) >= 0) {
|
|
||||||
var key = header.slice(0, i).trim().toLowerCase()
|
|
||||||
var val = header.slice(i + 1).trim()
|
|
||||||
this.responseHeaders[key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.responseHeaders?.[headerName.toLowerCase()] ?? null
|
|
||||||
}
|
|
||||||
abort() {}
|
|
||||||
onloadend() {}
|
|
||||||
}
|
|
||||||
Request.initClass()
|
|
||||||
return Request
|
|
||||||
})(),
|
|
||||||
|
|
||||||
// Attempts to fetch `url` using cross-origin privileges, if available.
|
|
||||||
// Interface is a subset of that of $.ajax.
|
|
||||||
// Options:
|
|
||||||
// `onloadend` - called with the returned object as `this` on success or error/abort/timeout.
|
|
||||||
// `timeout` - time limit for request
|
|
||||||
// `responseType` - expected response type, 'json' by default; 'json' and 'text' supported
|
|
||||||
// `headers` - request headers
|
|
||||||
// Returned object properties:
|
|
||||||
// `status` - HTTP status (0 if connection not successful)
|
|
||||||
// `statusText` - HTTP status text
|
|
||||||
// `response` - decoded response body
|
|
||||||
// `abort` - function for aborting the request (silently fails on some platforms)
|
|
||||||
// `getResponseHeader` - function for reading response headers
|
|
||||||
ajax(url, options = {}) {
|
|
||||||
let gmReq
|
|
||||||
let { onloadend, timeout, responseType, headers } = options
|
|
||||||
if (responseType == null) {
|
|
||||||
responseType = 'json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
window.GM?.xmlHttpRequest == null &&
|
|
||||||
(typeof window.GM_xmlhttpRequest === 'undefined' ||
|
|
||||||
window.GM_xmlhttpRequest === null)
|
|
||||||
) {
|
|
||||||
return $.ajax(url, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
const req = new CrossOrigin.Request()
|
|
||||||
req.onloadend = onloadend
|
|
||||||
|
|
||||||
if (platform === 'userscript') {
|
|
||||||
const gmOptions = {
|
|
||||||
method: 'GET',
|
|
||||||
url,
|
|
||||||
headers,
|
|
||||||
timeout,
|
|
||||||
onload(xhr) {
|
|
||||||
try {
|
|
||||||
const response = (() => {
|
|
||||||
switch (responseType) {
|
|
||||||
case 'json':
|
|
||||||
if (xhr.responseText) {
|
|
||||||
return JSON.parse(xhr.responseText)
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return xhr.responseText
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
$.extend(req, {
|
|
||||||
response,
|
|
||||||
status: xhr.status,
|
|
||||||
statusText: xhr.statusText,
|
|
||||||
responseHeaderString: xhr.responseHeaders,
|
|
||||||
})
|
|
||||||
} catch (error) {}
|
|
||||||
return req.onloadend()
|
|
||||||
},
|
|
||||||
onerror() {
|
|
||||||
return req.onloadend()
|
|
||||||
},
|
|
||||||
onabort() {
|
|
||||||
return req.onloadend()
|
|
||||||
},
|
|
||||||
ontimeout() {
|
|
||||||
return req.onloadend()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
gmReq = (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions)
|
|
||||||
} catch (error) {
|
|
||||||
return $.ajax(url, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gmReq && typeof gmReq.abort === 'function') {
|
|
||||||
req.abort = function () {
|
|
||||||
try {
|
|
||||||
return gmReq.abort()
|
|
||||||
} catch (error1) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eventPageRequest(
|
|
||||||
{ type: 'ajax', url, responseType, headers, timeout },
|
|
||||||
function (result) {
|
|
||||||
if (result.status) {
|
|
||||||
$.extend(req, result)
|
|
||||||
}
|
|
||||||
return req.onloadend()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return req
|
|
||||||
},
|
|
||||||
|
|
||||||
cache(url, cb) {
|
|
||||||
if (platform === 'userscript') {
|
|
||||||
return CrossOrigin.file(url, cb)
|
|
||||||
}
|
|
||||||
return eventPageRequest({ type: 'cache', url }, function (result) {
|
|
||||||
if (result) {
|
|
||||||
return cb(result)
|
|
||||||
} else {
|
|
||||||
return cb(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
permission(cb, cbFail, origins) {
|
|
||||||
if (platform === 'crx') {
|
|
||||||
return eventPageRequest(
|
|
||||||
{ type: 'permission', origins },
|
|
||||||
function (result) {
|
|
||||||
if (result) {
|
|
||||||
return cb()
|
|
||||||
} else {
|
|
||||||
return cbFail()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return cb()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
export default CrossOrigin
|
|
||||||
264
src/platform/CrossOrigin.ts
Normal file
264
src/platform/CrossOrigin.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import { Options } from '../../node_modules/@vitejs/plugin-react/dist/index';
|
||||||
|
import QR from '../Posting/QR';
|
||||||
|
import $ from './$';
|
||||||
|
import { dict, platform } from './helpers';
|
||||||
|
|
||||||
|
type Callback = (response: any, responseHeaderString?: string) => void;
|
||||||
|
type GMXhrCallback = (xhr: XMLHttpRequestResponseType) => void;
|
||||||
|
|
||||||
|
let eventPageRequest: ((params: any, cb: Callback) => void) | undefined;
|
||||||
|
if (platform === 'crx') {
|
||||||
|
eventPageRequest = (function () {
|
||||||
|
const callbacks: { [id: string]: Callback } = {};
|
||||||
|
chrome.runtime.onMessage.addListener(function (response) {
|
||||||
|
callbacks[response.id](response.data);
|
||||||
|
return delete callbacks[response.id];
|
||||||
|
});
|
||||||
|
return (params: any, cb: Callback) =>
|
||||||
|
chrome.runtime.sendMessage(params, (id) => (callbacks[id] = cb));
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICrossOrigin {
|
||||||
|
binary(url: string, cb: Callback, headers?: typeof dict): void;
|
||||||
|
file(url: string, cb: Callback): void;
|
||||||
|
Request: any;
|
||||||
|
ajax(url: string, options?: any): any;
|
||||||
|
cache(url: string, cb: Callback): void;
|
||||||
|
permission(cb: () => void, cbFail: () => void, origins?: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CrossOrigin: ICrossOrigin = {
|
||||||
|
binary(url, cb, headers = dict()) {
|
||||||
|
url = url.replace(
|
||||||
|
/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//,
|
||||||
|
'$1//adv/',
|
||||||
|
);
|
||||||
|
if (platform === 'crx') {
|
||||||
|
eventPageRequest?.(
|
||||||
|
{ type: 'ajax', url, headers, responseType: 'arraybuffer' },
|
||||||
|
function ({ response, responseHeaderString }) {
|
||||||
|
if (response) {
|
||||||
|
response = new Uint8Array(response);
|
||||||
|
}
|
||||||
|
return cb(response, responseHeaderString);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const fallback = function () {
|
||||||
|
return $.ajax(url, {
|
||||||
|
headers,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
onloadend() {
|
||||||
|
if (this.status && this.response) {
|
||||||
|
return cb(
|
||||||
|
new Uint8Array(this.response),
|
||||||
|
this.getAllResponseHeaders(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
typeof window.GM_xmlhttpRequest === 'undefined' ||
|
||||||
|
window.GM_xmlhttpRequest === null
|
||||||
|
) {
|
||||||
|
fallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const gmOptions: any = {
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
overrideMimeType: 'text/plain; charset=x-user-defined',
|
||||||
|
onload: (xhr) => {
|
||||||
|
let data;
|
||||||
|
if (xhr.response instanceof ArrayBuffer) {
|
||||||
|
data = new Uint8Array(xhr.response);
|
||||||
|
} else {
|
||||||
|
const r = xhr.responseText;
|
||||||
|
data = new Uint8Array(r.length);
|
||||||
|
let i = 0;
|
||||||
|
while (i < r.length) {
|
||||||
|
data[i] = r.charCodeAt(i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cb(data, xhr.responseHeaders);
|
||||||
|
},
|
||||||
|
onerror: () => cb(null),
|
||||||
|
onabort: () => cb(null),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
return window.GM_xmlhttpRequest(gmOptions);
|
||||||
|
} catch (error) {
|
||||||
|
return fallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
file(url, cb) {
|
||||||
|
return CrossOrigin.binary(url, function (data, headers) {
|
||||||
|
if (data == null) {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
let name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?.[1];
|
||||||
|
const contentType = headers.match(/Content-Type:\s*(.*)/i)?.[1];
|
||||||
|
const contentDisposition = headers.match(
|
||||||
|
/Content-Disposition:\s*(.*)/i,
|
||||||
|
)?.[1];
|
||||||
|
let mime =
|
||||||
|
contentType?.match(/[^;]*/)[0] || 'application/octet-stream';
|
||||||
|
const match =
|
||||||
|
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?.[1] ||
|
||||||
|
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?.[1];
|
||||||
|
if (match) {
|
||||||
|
name = match.replace(/\\"/g, '"');
|
||||||
|
}
|
||||||
|
if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) {
|
||||||
|
mime =
|
||||||
|
$.getOwn(
|
||||||
|
QR.typeFromExtension,
|
||||||
|
name.match(/[^.]*$/)[0].toLowerCase(),
|
||||||
|
) || 'application/octet-stream';
|
||||||
|
}
|
||||||
|
const blob = new Blob([data], { type: mime });
|
||||||
|
return cb({ name, blob });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Request: (function () {
|
||||||
|
class Request {
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
response: any;
|
||||||
|
responseHeaders: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.status = 0;
|
||||||
|
this.statusText = '';
|
||||||
|
this.response = null;
|
||||||
|
this.responseHeaders = '';
|
||||||
|
}
|
||||||
|
getResponseHeader(headerName: string) {
|
||||||
|
const match = this.getResponseHeader
|
||||||
|
.toString()
|
||||||
|
.match(new RegExp(`^${headerName}: (.*)`, 'im'));
|
||||||
|
return match?.[1];
|
||||||
|
}
|
||||||
|
abort() {}
|
||||||
|
|
||||||
|
onloadend() {}
|
||||||
|
}
|
||||||
|
return Request;
|
||||||
|
})(),
|
||||||
|
ajax(url, options: any = {}) {
|
||||||
|
let gmReq: any;
|
||||||
|
let { onloadend, timeout, responseType, headers } = options;
|
||||||
|
if (responseType == null) {
|
||||||
|
responseType = 'json';
|
||||||
|
}
|
||||||
|
if (onloadend == null) {
|
||||||
|
onloadend = function () {};
|
||||||
|
} else {
|
||||||
|
onloadend = onloadend.bind(this);
|
||||||
|
}
|
||||||
|
const req = new CrossOrigin.Request();
|
||||||
|
req.onloadend = onloadend;
|
||||||
|
if (platform === 'userscript') {
|
||||||
|
const gmOptions: any = {
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
timeout,
|
||||||
|
onload: (xhr) => {
|
||||||
|
try {
|
||||||
|
const response = (() => {
|
||||||
|
switch (responseType) {
|
||||||
|
case 'json':
|
||||||
|
if (xhr.responseText) {
|
||||||
|
return JSON.parse(xhr.responseText);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return xhr.responseText;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
Object.assign(req, {
|
||||||
|
response,
|
||||||
|
status: xhr.status,
|
||||||
|
statusText: xhr.statusText,
|
||||||
|
responseHeaderString: xhr.responseHeaders,
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
return req.onloadend();
|
||||||
|
},
|
||||||
|
onerror: () => req.onloadend(),
|
||||||
|
onabort: () => req.onloadend(),
|
||||||
|
ontimeout: () => req.onloadend(),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
gmReq = (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions);
|
||||||
|
} catch (error) {
|
||||||
|
return $.ajax(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gmReq && typeof gmReq.abort === 'function') {
|
||||||
|
req.abort = function () {
|
||||||
|
try {
|
||||||
|
return gmReq.abort();
|
||||||
|
} catch (error1) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eventPageRequest?.(
|
||||||
|
{ type: 'ajax', url, headers, responseType, timeout },
|
||||||
|
function ({ response, responseHeaderString }) {
|
||||||
|
Object.assign(req, {
|
||||||
|
response,
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
responseHeaderString,
|
||||||
|
});
|
||||||
|
return req.onloadend();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
},
|
||||||
|
cache(url, cb) {
|
||||||
|
const cached = CrossOrigin.cache[url];
|
||||||
|
if (cached) {
|
||||||
|
return cb(cached);
|
||||||
|
} else {
|
||||||
|
return CrossOrigin.binary(url, function (data) {
|
||||||
|
if (data == null) {
|
||||||
|
return cb(null);
|
||||||
|
}
|
||||||
|
const blob = new Blob([data]);
|
||||||
|
CrossOrigin.cache[url] = blob;
|
||||||
|
return cb(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permission(cb, cbFail, origins) {
|
||||||
|
if (platform === 'crx') {
|
||||||
|
return eventPageRequest(
|
||||||
|
{ type: 'permission', origins },
|
||||||
|
function (result) {
|
||||||
|
if (result) {
|
||||||
|
return cb()
|
||||||
|
} else {
|
||||||
|
return cbFail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CrossOrigin;
|
||||||
@ -287,8 +287,8 @@ $\
|
|||||||
cleanComment(bq) {
|
cleanComment(bq) {
|
||||||
let abbr;
|
let abbr;
|
||||||
if (abbr = $('.abbr', bq)) { // 'Comment too long' or 'EXIF data available'
|
if (abbr = $('.abbr', bq)) { // 'Comment too long' or 'EXIF data available'
|
||||||
for (var node of $$('.abbr + br, .exif', bq)) {
|
for (let node of $$('.abbr, .abbr-exp', abbr)) {
|
||||||
$.rm(node);
|
$.replace(node, $.tn(node.textContent));
|
||||||
}
|
}
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
var br;
|
var br;
|
||||||
@ -337,7 +337,9 @@ $\
|
|||||||
|
|
||||||
testNativeExtension() {
|
testNativeExtension() {
|
||||||
return $.global(function() {
|
return $.global(function() {
|
||||||
if (window.Parser?.postMenuIcon) { return this.enabled = 'true'; }
|
if (window.File && window.FileReader && window.FileList && window.Blob) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -413,10 +415,15 @@ $\
|
|||||||
const o = {
|
const o = {
|
||||||
// id
|
// id
|
||||||
ID: data.no,
|
ID: data.no,
|
||||||
|
info: null,
|
||||||
|
capcodeHighlight: data.capcode === 'admin_highlight',
|
||||||
|
files: [],
|
||||||
|
file: null,
|
||||||
postID: data.no,
|
postID: data.no,
|
||||||
threadID: data.resto || data.no,
|
threadID: data.resto || data.no,
|
||||||
boardID,
|
boardID,
|
||||||
siteID,
|
siteID,
|
||||||
|
extra: {},
|
||||||
isReply: !!data.resto,
|
isReply: !!data.resto,
|
||||||
// thread status
|
// thread status
|
||||||
isSticky: !!data.sticky,
|
isSticky: !!data.sticky,
|
||||||
@ -471,6 +478,7 @@ $\
|
|||||||
name: ($.unescape(data.filename)) + data.ext,
|
name: ($.unescape(data.filename)) + data.ext,
|
||||||
url: site.urls.file({ siteID, boardID }, filename),
|
url: site.urls.file({ siteID, boardID }, filename),
|
||||||
height: data.h,
|
height: data.h,
|
||||||
|
dimensions: '',
|
||||||
width: data.w,
|
width: data.w,
|
||||||
MD5: data.md5,
|
MD5: data.md5,
|
||||||
size: $.bytesToString(data.fsize),
|
size: $.bytesToString(data.fsize),
|
||||||
@ -579,7 +587,7 @@ $\
|
|||||||
$.extend(container, wholePost);
|
$.extend(container, wholePost);
|
||||||
|
|
||||||
// Fix quotelinks
|
// Fix quotelinks
|
||||||
for (var quote of $$('.quotelink', container)) {
|
for (var quote of container.querySelectorAll('a.quoteLink')) {
|
||||||
var href = quote.getAttribute('href');
|
var href = quote.getAttribute('href');
|
||||||
if (href[0] === '#') {
|
if (href[0] === '#') {
|
||||||
if (!this.sameThread(boardID, threadID)) {
|
if (!this.sameThread(boardID, threadID)) {
|
||||||
|
|||||||
13
src/types/$.d.ts
vendored
Normal file
13
src/types/$.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export interface ElementProperties {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
interface AjaxPageOptions {
|
||||||
|
onloadend?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void;
|
||||||
|
timeout?: number;
|
||||||
|
responseType?: XMLHttpRequestResponseType;
|
||||||
|
withCredentials?: boolean;
|
||||||
|
type?: string;
|
||||||
|
onprogress?: (this: XMLHttpRequest, ev: ProgressEvent<EventTarget>) => void;
|
||||||
|
form?: FormData;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user