Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"dmail",
"dmails",
"dnpcache",
"downvotes",
"downvoting",
"Dragonfruit",
"dtext",
"Earlopain",
Expand Down Expand Up @@ -55,6 +57,7 @@
"onshift",
"paysite",
"Pixiv",
"postface",
"postid",
"postinghacks",
"postingunreleasedcontent",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/snippets.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// "description": "Log output to console"
// }
"New Component File": {
"description": "A new re621.ModTools component",
"description": "A new Mod Tools Component",
"isFileTemplate": true,
"prefix": ["component"],
"body": [
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
A collection of utilities for using and moderating the booru site e621.

## Downstream projects
Take a careful look at `package.json`, `bin/userscript-header.js`, & `src/js/models/data/Script.ts`; these 3 points are the main source of project-specific configuration. If done properly, modifying these 3 files for your project's needs should minimize merge conflicts with this project.
84 changes: 73 additions & 11 deletions bin/userscript-header.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,69 @@
const pkg = require('../package.json');
const pkgLock = (() => {
try {
return require('../package-lock.json');
} catch (error) {
return undefined;
}
})();
const fs = require('node:fs');
const bunLock = (() => {
try {
const contents = fs.readFileSync("../bun.lock", { encoding: "utf-8" });
return JSON.parse(contents.replace(/,$(?=\s+[}\]])/gm, ""));
} catch (error) {
return undefined;
}
})();
// It's an indirect dependency, but it's not required.
const chalk = (() => {
try {
return require("chalk");
} catch (error) {
return undefined;
}
})();
function tryChalk(funcName, text) {
return chalk?.[funcName]?.(text) ?? text;
}

const fallbacks = {
"jquery": "3.6.0",
"jquery-ui": "1.12.1",
"@re621/zestyapi": "latest",
"jquery.hotkeys": "0.2.0",
}
/**
* Attempts to retrieve the version of the dependency directly from the package
* manager's lockfile.
*
* Ensures the dependency referenced by the script is exactly the same as the
* one used in development; prior to this, both the `jquery-ui` &
* `jquery.hotkeys` dependencies were actually referencing a newer & older
* version (respectively) in the development environment than the version
* requested by the userscript with the previously static `require` strings.
*
* Attempts to pull from both an NPM `package-lock.json` file and a Bun
* `bun.lock` file to make a potential future package manager change smoother.
* @param {string} key
* @returns {string}
*/
function getDependencyVersion(key) {
console.info("Attempting to retrieve version info for %s; starting with 'package-lock.json'...", key);
const fromPkgLock = pkgLock?.packages?.[`node_modules/${key}`]?.version;
const fromBunLock = bunLock?.packages?.[key]?.[0]?.match(new RegExp(`^${RegExp.escape(key)}@([0-9.]+)$`))?.[1];
if (fromPkgLock) {
if (fromBunLock && fromPkgLock !== fromBunLock) console.warn(tryChalk("yellowBright", "[WARNING]") + ": version from 'package-lock.json' (%s) & 'bun.lock' (%s) are not the same; if you're using Bun for package installation, be sure to update 'package-lock.json' with NPM before submitting a PR.", fromPkgLock, fromBunLock);
return fromPkgLock;
}
console.warn("Failed to find version from 'package-lock.json'...");

if (fromBunLock) return fromBunLock;
console.warn("Failed to find version from 'bun.lock'...");
const fromFallbacks = fallbacks[key];
if (fromFallbacks) return fromFallbacks;
throw new Error(`Failed to find version for ${key}`);
}

module.exports = {
name: pkg.displayName,
Expand All @@ -10,22 +75,19 @@ module.exports = {

homepageURL: pkg.homepage,
supportURL: pkg.homepage + "/issues",
icon: "https://cdn.jsdelivr.net/gh/re621/re621@master/assets/icon64.png",
icon64: "https://cdn.jsdelivr.net/gh/re621/re621@master/assets/icon64.png",
icon: pkg.config.iconUri ?? pkg.config.icon64Uri,
icon64: pkg.config.icon64Uri ?? pkg.config.iconUri,

updateURL: pkg.homepage + "/releases/latest/download/script.meta.js",
downloadURL: pkg.homepage + "/releases/latest/download/script.user.js",

match: [
"https://e621.net/*",
"https://e926.net/*",
],
match: pkg.config.domains.map(e => `https://${e}/*`),

require: [
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js",
"https://cdn.jsdelivr.net/npm/@re621/zestyapi@latest/dist/ZestyAPI.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/jquery.hotkeys/0.2.0/jquery.hotkeys.min.js",
`https://cdnjs.cloudflare.com/ajax/libs/jquery/${getDependencyVersion("jquery")}/jquery.min.js`,
`https://cdnjs.cloudflare.com/ajax/libs/jqueryui/${getDependencyVersion("jquery-ui")}/jquery-ui.min.js`,
`https://cdn.jsdelivr.net/npm/@re621/zestyapi@${getDependencyVersion("@re621/zestyapi")}/dist/ZestyAPI.min.js`,
`https://cdnjs.cloudflare.com/ajax/libs/jquery.hotkeys/${getDependencyVersion("jquery.hotkeys")}/jquery.hotkeys.min.js`,
],

grant: [
Expand All @@ -44,7 +106,7 @@ module.exports = {
],

connect: [
"static1.e621.net",
...(pkg.config.staticDomains ?? pkg.config.domains.map(e => `static1.${e}`)),
"*",
],

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "mod621",
"displayName": "RE621 Mod Tools",
"namespace": "mod621.bitwolfy.com",
"version": "6.4.3",
"version": "6.5.0",
"description": "Moderation toolkit for e621.net",
"author": "bitWolfy",
"contributors": [
Expand All @@ -14,6 +14,12 @@
"homepage": "https://github.com/re621/re621.ModTools",
"funding": "https://ko-fi.com/bitwolfy",
"repository": "github:re621/re621.ModTools",
"config": {
"iconUri": "https://cdn.jsdelivr.net/gh/re621/re621@master/assets/icon64.png",
"icon64Uri": "https://cdn.jsdelivr.net/gh/re621/re621@master/assets/icon64.png",
"domains": ["e621.net", "e926.net"],
"staticDomains": ["static1.e621.net", "static1.e926.net"]
},
"scripts": {
"push": "npm version patch && git push --follow-tags",
"push-minor": "npm version minor && git push --follow-tags",
Expand Down
22 changes: 11 additions & 11 deletions src/REMT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ export default class REMT {

if (Page.matches(IgnoredPages)) return;

console.log("%c[RE621.ModTools]%c v." + Script.version, "color: maroon", "color: unset");
Debug.logPrefix("v." + Script.versionObj.full);

// Set up the API connection
// TODO: Temporary instantiation method
REMT.API = window["ZestyAPI"].connect({
userAgent: Script.userAgent,
userAgent: Util.Network.userAgent,
debug: Debug.Connect,
});

Expand All @@ -77,7 +77,7 @@ export default class REMT {

bodyLoaded = PageObserver.watch("body").then(() => {
Debug.log("+ BODY is ready");
$("body").attr("remt", Script.version);
$("body").attr(Script.htmlPrefix, Script.versionObj.full);
Util.DOM.setupDialogContainer(); // TODO Move to the dialog class
User.init();
});
Expand Down Expand Up @@ -105,16 +105,16 @@ export default class REMT {
REMT._Registry[instance.getName()] = instance;
await instance.bootstrapSettings();
}
Util.Events.trigger("re621-mt:bootstrap");
Util.Events.trigger(`${Script.eventPrefixLegacy}:bootstrap`);

// Load modules (asynchronous)
const promises: Promise<void>[] = [];
for (const instance of Object.values(REMT._Registry))
promises.push(instance?.load() ?? Promise.resolve());
Promise.all(promises).then(() => {
console.log("%c[RE621.ModTools]%c loaded.", "color: maroon", "color: unset");
Debug.logPrefix("Loaded.");
this.buildSettingsButton(true);
console.log("%c[RE621.ModTools]%c fully loaded.", "color: maroon", "color: unset");
Debug.logPrefix("Fully loaded.");
});
}

Expand All @@ -126,7 +126,7 @@ export default class REMT {
*/
private buildSettingsButton(addListeners = false) {
this.settingsButton?.remove();
console.log("%c[RE621.ModTools]%c checking for settings...", "color: maroon", "color: unset");
Debug.logPrefix("Checking for settings...");
const configs = Object.values(REMT._Registry).reduce((p, e) => {
if (!e) return p;
if (addListeners) e.on("settingsConfigured", () => this.buildSettingsButton());
Expand All @@ -135,17 +135,17 @@ export default class REMT {
return p;
}, [] as SettingsDialogConfig[]);
if (configs.length <= 0) {
console.log("%c[RE621.ModTools]%c no settings registered", "color: maroon", "color: unset");
Debug.logPrefix("No settings registered");
return;
}
console.log("%c[RE621.ModTools]%c loading %s settings...", "color: maroon", "color: unset", configs.length);
Debug.logPrefix("Loading %s settings...", configs.length);
this.settingsButton = Util.DOM.addSettingsButton({
id: "remt-component-settings",
id: `${Script.htmlPrefix}-component-settings`,
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" name="settings"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path><circle cx="12" cy="12" r="3"></circle></svg>',
name: "REMT",
onClick: () => MultiDialogForm.getRequestedInput(configs),
});
console.log("%c[RE621.ModTools]%c Settings button built.", "color: maroon", "color: unset");
Debug.logPrefix("Settings button built.");
}

}
Expand Down
5 changes: 4 additions & 1 deletion src/js/components/AutoClickPosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { html } from "../utilities/HtmlTemplate";
import Component from "./Component";

/**
* @todo What does this do?
* Automates clicking posts on the post search page; combined with the vanilla
* `Mode` selector, allows automating batch actions.
*
* @todo Add post selection mode.
*/
export default class AutoClickPosts extends Component {
private static readonly modeBoxSelector = "#mode-box";
Expand Down
9 changes: 5 additions & 4 deletions src/js/components/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import REMT from "../../REMT";
import XM from "../models/api/XM";
import KeybindManager, { Keybind, ResponseFunction } from "../models/data/Keybinds";
import Page from "../models/data/Page";
import Script from "../models/data/Script";
import Debug from "../models/Debug";
import { JsonStrictObject } from "../models/JsonTypes";
import { DialogConfig } from "../models/structure/DialogForm";
Expand Down Expand Up @@ -285,7 +286,7 @@ export default class Component {

/** Execute all handlers for the specified component event */
public trigger(event: string, data?: PrimitiveType | PrimitiveType[] | PrimitiveMap): void {
$(document).trigger(`remt.${this.name}.${event}`, data);
$(document).trigger(`${Script.eventPrefix}.${this.name}.${event}`, data);
}

/**
Expand All @@ -295,18 +296,18 @@ export default class Component {
public on(event: string, handler: (event: JQuery.TriggeredEvent, data?: PrimitiveType | PrimitiveType[] | PrimitiveMap) => void): number {
const eventList = event.split(" ");
for (const one of eventList)
$(document).on(`remt.${this.name}.${one}.${this.eventIndex}`, handler);
$(document).on(`${Script.eventPrefix}.${this.name}.${one}.${this.eventIndex}`, handler);
return this.eventIndex++;
}

/** Executes a handler function exactly once whe encountering a specified event */
public one(event: string, handler: (event: JQuery.TriggeredEvent, data?: PrimitiveType | PrimitiveType[] | PrimitiveMap) => void): void {
$(document).one(`remt.${this.name}.${event}`, handler);
$(document).one(`${Script.eventPrefix}.${this.name}.${event}`, handler);
}

/** Detaches handlers from the specified component event */
public off(event: string, eventID?: number): void {
$(document).off(`remt.${this.name}.${event}` + (eventID ? `.${eventID}` : ""));
$(document).off(`${Script.eventPrefix}.${this.name}.${event}` + (eventID ? `.${eventID}` : ""));
}

public getName(): string { return this.name; }
Expand Down
Loading
Loading