mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2025-10-07 07:22:37 +02:00
bugs and types
This commit is contained in:
parent
759be29bd1
commit
6b81346b46
@ -89,9 +89,6 @@ var Get = {
|
||||
}
|
||||
}
|
||||
// First:
|
||||
// In every posts,
|
||||
// if it did quote this post,
|
||||
// get all their backlinks.
|
||||
posts.forEach(function (qPost) {
|
||||
if (qPost.quotes.includes(fullID)) {
|
||||
return handleQuotes(qPost, 'quotelinks')
|
||||
@ -99,10 +96,6 @@ var Get = {
|
||||
})
|
||||
|
||||
// Second:
|
||||
// If we have quote backlinks:
|
||||
// in all posts this post quoted
|
||||
// and their clones,
|
||||
// get all of their backlinks.
|
||||
if (Conf['Quote Backlinks']) {
|
||||
for (var quote of post.quotes) {
|
||||
var qPost
|
||||
@ -113,7 +106,6 @@ var Get = {
|
||||
}
|
||||
|
||||
// Third:
|
||||
// Filter out irrelevant quotelinks.
|
||||
return quotelinks.filter(function (quotelink) {
|
||||
const { boardID, postID } = Get.postDataFromLink(quotelink)
|
||||
return boardID === post.board.ID && postID === post.ID
|
||||
|
||||
@ -12,13 +12,7 @@ import Settings from './Settings'
|
||||
import UI from './UI'
|
||||
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 = {
|
||||
init() {
|
||||
$.onExists(doc, 'body', () => {
|
||||
|
||||
@ -143,10 +143,8 @@ const Test = {
|
||||
testOne(post) {
|
||||
Test.postsRemaining++
|
||||
return $.cache(
|
||||
g.SITE.urls.threadJSON({
|
||||
boardID: post.boardID,
|
||||
threadID: post.threadID,
|
||||
}),
|
||||
g.SITE.urls.threadJSON(post.thread.ID, post.board),
|
||||
{ responseType: 'json' },
|
||||
function () {
|
||||
if (!this.response) {
|
||||
return
|
||||
@ -179,7 +177,7 @@ const Test = {
|
||||
|
||||
for (var key in Config.filter) {
|
||||
if (
|
||||
!key === 'General' &&
|
||||
key !== 'MD5' ||
|
||||
!(key === 'MD5' && post.board.ID === 'f')
|
||||
) {
|
||||
var val1 = Filter.values(key, obj)
|
||||
|
||||
@ -115,12 +115,12 @@ var ImageLoader = {
|
||||
if (!replace && !ImageLoader.prefetchEnabled) {
|
||||
return
|
||||
}
|
||||
if ($.hasClass(doc, 'catalog-mode')) {
|
||||
if ($.hasClass(d, 'catalog-mode')) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
![post, ...Array.from(post.clones)].some((clone) =>
|
||||
doc.contains(clone.nodes.root),
|
||||
d.contains(clone.nodes.root),
|
||||
)
|
||||
) {
|
||||
return
|
||||
@ -175,12 +175,13 @@ var ImageLoader = {
|
||||
return g.posts.forEach(function (post) {
|
||||
for (post of [post, ...Array.from(post.clones)]) {
|
||||
for (var file of post.files) {
|
||||
if (file.videoThumb) {
|
||||
var { thumb } = file
|
||||
if (Header.isNodeVisible(thumb) || post.nodes.root === qpClone) {
|
||||
thumb.play()
|
||||
} else {
|
||||
thumb.pause()
|
||||
if (file.isVideo && !file.isPrefetched) {
|
||||
const { thumb } = file
|
||||
if (qpClone === thumb) {
|
||||
continue
|
||||
}
|
||||
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) {
|
||||
return
|
||||
}
|
||||
if (!(el = $('video:not([data-md5])', this))) {
|
||||
return
|
||||
if (this instanceof HTMLAnchorElement) {
|
||||
el = this.querySelector('video')
|
||||
} else {
|
||||
el = this.querySelector('video, audio')
|
||||
}
|
||||
if (el.muted || !$.hasAudio(el)) {
|
||||
return
|
||||
|
||||
@ -9,6 +9,7 @@ export default class CatalogThreadNative {
|
||||
siteID: number
|
||||
threadID: number
|
||||
ID: string
|
||||
thread: any
|
||||
toString() {
|
||||
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));
|
||||
}
|
||||
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 { c, Conf, d, doc, g } from "../globals/globals";
|
||||
import CrossOrigin from "./CrossOrigin";
|
||||
import { debounce, dict, MINUTE, platform, SECOND } from "./helpers";
|
||||
import { AjaxPageOptions, ElementProperties } from "../types/$";
|
||||
|
||||
// not chainable
|
||||
const $ = (selector, root = document.body) => root.querySelector(selector);
|
||||
|
||||
type AjaxPageRequest = XMLHttpRequest & {
|
||||
abort: () => void;
|
||||
}
|
||||
$.id = id => d.getElementById(id);
|
||||
$.cache = dict();
|
||||
$.ajaxPage = function (url, options) {
|
||||
@ -344,19 +337,9 @@ $.addStyle = function (css, id, test = 'head') {
|
||||
};
|
||||
|
||||
$.addCSP = function (policy) {
|
||||
const meta = $.el('meta', {
|
||||
httpEquiv: 'Content-Security-Policy',
|
||||
content: policy
|
||||
}
|
||||
);
|
||||
if (d.head) {
|
||||
$.add(d.head, meta);
|
||||
return $.rm(meta);
|
||||
} else {
|
||||
const head = $.add((doc || d), $.el('head'));
|
||||
$.add(head, meta);
|
||||
return $.rm(head);
|
||||
}
|
||||
const meta = $.el('meta', { httpEquiv: 'Content-Security-Policy', content: policy }, { display: 'none' });
|
||||
$.onExists(doc, 'head', () => $.add(d.head, meta));
|
||||
return meta;
|
||||
};
|
||||
|
||||
$.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);
|
||||
|
||||
$.el = function (tag, properties, properties2) {
|
||||
const el = d.createElement(tag);
|
||||
if (properties) { $.extend(el, properties); }
|
||||
if (properties2) { $.extend(el, properties2); }
|
||||
return el;
|
||||
$.el = function (tag: keyof HTMLElementTagNameMap, properties?: ElementProperties, properties2?: ElementProperties): HTMLElement {
|
||||
const element = document.createElement(tag);
|
||||
Object.assign(element, properties, properties2);
|
||||
return element;
|
||||
};
|
||||
|
||||
$.on = function (el, events, handler) {
|
||||
@ -555,42 +537,41 @@ $.global = function (fn, data) {
|
||||
};
|
||||
|
||||
$.bytesToString = function (size) {
|
||||
let unit = 0; // Bytes
|
||||
while (size >= 1024) {
|
||||
size /= 1024;
|
||||
unit++;
|
||||
if (size < 1024) {
|
||||
return `${size} B`;
|
||||
} else if (size < 1048576) {
|
||||
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 ?
|
||||
min
|
||||
:
|
||||
value > max ?
|
||||
max
|
||||
:
|
||||
value;
|
||||
$.minmax = (value, min, max) => Math.max(min, Math.min(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) {
|
||||
if (text == null) { return text; }
|
||||
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);
|
||||
$.isVideo = url => /\.(webm|mp4|ogv)$/i.test(url);
|
||||
$.isImage = url => /\.(jpe?g|png|gif|bmp|webp|svg|ico|tiff?)$/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 () {
|
||||
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) {
|
||||
let abbr;
|
||||
if (abbr = $('.abbr', bq)) { // 'Comment too long' or 'EXIF data available'
|
||||
for (var node of $$('.abbr + br, .exif', bq)) {
|
||||
$.rm(node);
|
||||
for (let node of $$('.abbr, .abbr-exp', abbr)) {
|
||||
$.replace(node, $.tn(node.textContent));
|
||||
}
|
||||
for (let i = 0; i < 2; i++) {
|
||||
var br;
|
||||
@ -337,7 +337,9 @@ $\
|
||||
|
||||
testNativeExtension() {
|
||||
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 = {
|
||||
// id
|
||||
ID: data.no,
|
||||
info: null,
|
||||
capcodeHighlight: data.capcode === 'admin_highlight',
|
||||
files: [],
|
||||
file: null,
|
||||
postID: data.no,
|
||||
threadID: data.resto || data.no,
|
||||
boardID,
|
||||
siteID,
|
||||
extra: {},
|
||||
isReply: !!data.resto,
|
||||
// thread status
|
||||
isSticky: !!data.sticky,
|
||||
@ -471,6 +478,7 @@ $\
|
||||
name: ($.unescape(data.filename)) + data.ext,
|
||||
url: site.urls.file({ siteID, boardID }, filename),
|
||||
height: data.h,
|
||||
dimensions: '',
|
||||
width: data.w,
|
||||
MD5: data.md5,
|
||||
size: $.bytesToString(data.fsize),
|
||||
@ -579,7 +587,7 @@ $\
|
||||
$.extend(container, wholePost);
|
||||
|
||||
// Fix quotelinks
|
||||
for (var quote of $$('.quotelink', container)) {
|
||||
for (var quote of container.querySelectorAll('a.quoteLink')) {
|
||||
var href = quote.getAttribute('href');
|
||||
if (href[0] === '#') {
|
||||
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