Skip to content

Instantly share code, notes, and snippets.

@clragon
Last active October 22, 2025 23:16
Show Gist options
  • Select an option

  • Save clragon/d833e65ef3d92b97d9ca7e741d6949fd to your computer and use it in GitHub Desktop.

Select an option

Save clragon/d833e65ef3d92b97d9ca7e741d6949fd to your computer and use it in GitHub Desktop.
Portable Areal Combustion Kit for e6: A weapon to surpass metal gear.
// ==UserScript==
// @name 6 P.A.C.K.
// @namespace http://tampermonkey.net/
// @version 3.1
// @description Portable Areal Combustion Kit for e6: A weapon to surpass metal gear.
// @author binaryfloof
// @icon https://em-content.zobj.net/source/microsoft-teams/400/firecracker_1f9e8.png
// @supportURL https://gist.github.com/clragon/d833e65ef3d92b97d9ca7e741d6949fd
// @updateURL https://gist.githubusercontent.com/clragon/d833e65ef3d92b97d9ca7e741d6949fd/raw/e621_comment_hammer_and_wrench.user.js
// @downloadURL https://gist.githubusercontent.com/clragon/d833e65ef3d92b97d9ca7e741d6949fd/raw/e621_comment_hammer_and_wrench.user.js
// @match https://e621.net/*
// @match https://e926.net/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
"use strict";
// #region html utils
function getControlsInsertionPoint() {
return (
document.querySelector("#searchform") ||
document.querySelector("#post-sections") ||
document.querySelector("article.thumbnail") ||
document.querySelector("#a-show > p.info") ||
document.querySelector("#a-show > h1") ||
document.querySelector(".a-new > h1")
);
}
function createElement(tag, options = {}) {
const element = document.createElement(tag);
Object.assign(element, options);
return element;
}
function updateNotice(message, show = true, reloadable = false) {
if (!notice) return;
const noticeInner = notice.querySelector("span");
if (noticeInner) {
noticeInner.textContent = message;
}
notice.style.display = show ? "block" : "none";
const reloadLink = notice.querySelector(".reload-link");
if (show && reloadable && !reloadLink) {
const link = createElement("a", {
href: "#",
textContent: "reload",
className: "reload-link",
style: "margin-left: 10px; float: right;",
});
link.addEventListener("click", (e) => {
e.preventDefault();
location.reload();
});
document
.querySelector("#close-notice-link")
?.insertAdjacentElement("afterend", link);
} else if (!show && reloadLink) {
reloadLink.remove();
}
}
function cleanupNotice() {
updateNotice("", false);
}
if (notice) {
const closeNoticeLink = document.querySelector("#close-notice-link");
if (closeNoticeLink) {
closeNoticeLink.addEventListener("click", () => cleanupNotice());
}
}
// #endregion
// #region http requests
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const getCsrfToken = () =>
document
.querySelector('meta[name="csrf-token"]')
?.getAttribute("content") ?? "";
async function postRequest(url, body) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": getCsrfToken(),
},
credentials: "same-origin",
body: JSON.stringify(body),
});
return response.ok;
} catch (error) {
console.error("Request failed:", error);
return false;
}
}
// #endregion
// #region burning items
async function incinerate(items, opts) {
const total = items.length;
let burnCount = 0;
const doMark = opts.markType !== "none";
const msg = `Incinerate ${total} items${
doMark ? ` for ${opts.markType}` : ""
}?`;
if (!confirm(msg)) return;
updateNotice(
`Burning ${total} items${doMark ? ` for ${opts.markType}` : ""}...`
);
for (const item of items) {
if (await burnItem(item, opts)) burnCount += 1;
await delay(500);
updateNotice(`Burning ${Math.floor(burnCount)} of ${total}...`);
}
updateNotice(`Turned ${Math.round(burnCount)} items to ash.`, true, true);
}
async function burnItem(item, opts = {}) {
let success = false;
switch (item.type) {
case "comment":
success = await postRequest(`/comments/${item.id}/hide.json`, {});
break;
case "forum_post":
success = await postRequest(`/forum_posts/${item.id}/hide.json`, {});
break;
case "user":
success = await postRequest(`/bans`, {
ban: {
user_id: item.id,
reason: opts.reason || "No reason provided.",
duration: opts.duration || "",
is_permaban: opts.isPermaban ? "1" : "0",
},
});
break;
default:
throw new Error(`Unsupported type for burn: ${item.type}`);
}
if (opts.markType && opts.markType !== "none") {
await delay(500);
await markItem(item, opts.markType);
}
return success;
}
async function markItem(item, markType) {
switch (item.type) {
case "comment":
return await postRequest(`/comments/${item.id}/warning.json`, {
record_type: markType,
});
case "forum_post":
return await postRequest(`/forum_posts/${item.id}/warning.json`, {
record_type: markType,
});
case "user":
// no-op
break;
default:
throw new Error(`Unsupported type for mark: ${item.type}`);
}
}
// #endregion
// #region selection logic
function getBurnables() {
const items = [];
const getTypeAndId = (el) => {
if (el.matches("article.comment[data-comment-id]")) {
return { type: "comment", id: el.getAttribute("data-comment-id") };
} else if (el.matches("article.forum-post[data-forum-post-id]")) {
return {
type: "forum_post",
id: el.getAttribute("data-forum-post-id"),
};
} else {
const userLink = el.querySelector('a[href^="/users/"]');
if (userLink) {
const match = userLink.getAttribute("href")?.match(/\/users\/(\d+)/);
if (match) {
return {
type: "user",
id: match[1],
};
}
}
}
return null;
};
const getChecked = (el) =>
el.querySelector(".burner-checkbox")?.checked ?? false;
const getBurnt = (el) =>
el.matches("tr")
? /\bBlocked\b/i.test(
el.querySelector("td:nth-last-child(3)")?.textContent ?? ""
)
: el.getAttribute("data-is-deleted") === "true" ||
el.getAttribute("data-is-hidden") === "true";
document
.querySelectorAll(
"article.comment[data-comment-id], article.forum-post[data-forum-post-id], div#c-users tbody tr"
)
.forEach((el) => {
const meta = getTypeAndId(el);
if (!meta) return;
items.push({
...meta,
selected: getChecked(el),
burnt: getBurnt(el),
el,
});
});
return items;
}
function getSelectionState() {
const items = getBurnables();
if (items.length === 0) return "none";
const selectedCount = items.filter((item) => item.selected).length;
if (selectedCount === 0) return "none";
if (selectedCount === items.length) return "all";
return "some";
}
function setSelectionState(state) {
const items = getBurnables();
const shouldSelect = state === "all";
for (const item of items) {
const checkbox = item.el.querySelector(".burner-checkbox");
if (checkbox) checkbox.checked = shouldSelect;
}
renderControls();
}
// #endregion
// #region selection ui
function BurnCheckbox(item) {
const checkbox = createElement("input", {
type: "checkbox",
className: "burner-checkbox",
style: "cursor: pointer;",
"data-type": item.type,
"data-id": item.id,
});
checkbox.addEventListener("change", () => {
renderControls();
});
return checkbox;
}
function BurnCheckboxContainer(item) {
const isTableRow = item.el.tagName === "TR";
const inner = createElement("div", {
className: "burn-checkbox-container",
style: [
"padding: 0.25rem 0.5rem;",
"cursor: pointer;",
isTableRow
? "display: flex; justify-content: flex-end; align-items: center;"
: "background-color: var(--color-section-lighten-5); grid-column: 3;",
].join(" "),
"data-id": item.id,
"data-type": item.type,
});
const checkbox = BurnCheckbox(item);
inner.appendChild(checkbox);
inner.addEventListener("click", (event) => {
if (event.target !== checkbox) {
checkbox.checked = !checkbox.checked;
checkbox.dispatchEvent(new Event("change"));
}
});
if (isTableRow) {
const wrapper = document.createElement("td");
wrapper.appendChild(inner);
return wrapper;
}
return inner;
}
function renderBurnCheckboxes() {
const items = getBurnables();
if (items.length === 0) return;
for (const item of items) {
if (item.el.querySelector(".burn-checkbox-container")) continue;
const container = BurnCheckboxContainer(item);
item.el.appendChild(container);
item.el.style.height = "100%";
}
}
renderBurnCheckboxes();
// #endregion
// #region mass bans storage
const MASS_BANS_KEY = "6pack.massbans";
function saveMassBan(ids) {
const existing = JSON.parse(localStorage.getItem(MASS_BANS_KEY) || "[]");
existing.push({ userIds: ids, timestamp: Date.now() });
localStorage.setItem(MASS_BANS_KEY, JSON.stringify(existing));
}
function loadMassBans() {
const data = localStorage.getItem(MASS_BANS_KEY);
if (!data) return [];
try {
return JSON.parse(data);
} catch (error) {
console.error("Failed to parse mass bans:", error);
return [];
}
}
// #endregion
// #region mass bans ui
function MassBanTemplateNotice() {
if (location.pathname !== "/bans/new") return null;
const params = new URLSearchParams(location.search);
const userId = params.get("ban[user_id]");
if (!userId) return null;
const bans = loadMassBans();
const entry = bans.find((e) => e.userIds.includes(userId));
if (!entry) return null;
const container = createElement("div", {
style:
"margin-top: 10px; margin-bottom: 10px; display: flex; gap: 10px; align-items: center;",
});
const label = createElement("div", {
textContent: `This ban can be used as a template for ${
entry.userIds.length - 1
} other users.`,
style: "font-weight: bold; color: var(--color-danger);",
});
const cancel = AbortMassBanButton(bans.indexOf(entry), container);
container.append(label, cancel);
return container;
}
function MassBanConfirmation() {
const banLinkMatch = location.pathname.match(/^\/bans\/(\d+)/);
if (!banLinkMatch) return null;
const banId = banLinkMatch[1];
const userLink = document.querySelector("#a-show a[href^='/users/']");
const userIdMatch = userLink?.href.match(/\/users\/(\d+)/);
if (!userIdMatch) return null;
const userId = userIdMatch[1];
const bans = loadMassBans();
const entryIndex = bans.findIndex((e) => e.userIds.includes(userId));
if (entryIndex === -1) return null;
const entry = bans[entryIndex];
const targets = entry.userIds.filter((id) => id !== userId);
if (targets.length === 0) return null;
const container = createElement("div", {
style: "margin-top: 10px; margin-bottom: 10px; display: flex; gap: 10px;",
});
const button = createElement("input", {
type: "button",
value: `Ban ${targets.length} other users with this template`,
className: "button btn-danger",
});
button.addEventListener("click", async () => {
button.disabled = true;
const ban = await fetch(`/bans/${banId}.json`).then((r) => r.json());
await incinerate(
targets.map((id) => ({
type: "user",
id,
el: null,
selected: true,
burnt: false,
})),
{
reason: ban.reason,
duration: ban.expires_at
? String(
Math.round(
(new Date(ban.expires_at) - new Date(ban.created_at)) /
(1000 * 60 * 60 * 24)
)
)
: "",
isPermaban: ban.expires_at == null,
markType: "none",
}
);
bans.splice(entryIndex, 1);
localStorage.setItem(MASS_BANS_KEY, JSON.stringify(bans));
button.remove();
});
const cancelButton = AbortMassBanButton(entryIndex, container);
container.append(button, cancelButton);
return container;
}
function AbortMassBanButton(entryIndex, container) {
const button = createElement("button", {
textContent: "Abort",
className: "button btn-neutral",
});
button.addEventListener("click", () => {
const bans = loadMassBans();
bans.splice(entryIndex, 1);
localStorage.setItem(MASS_BANS_KEY, JSON.stringify(bans));
container.remove();
});
return button;
}
function renderMassBans() {
const insertionPoint = getControlsInsertionPoint();
const notice = MassBanTemplateNotice();
if (notice) insertionPoint?.insertAdjacentElement("afterend", notice);
const confirm = MassBanConfirmation();
if (confirm) insertionPoint?.insertAdjacentElement("afterend", confirm);
}
renderMassBans();
// #endregion
// #region copy item links
function CopyButton() {
const button = createElement("button", {
innerText: "Copy",
className: "button btn-neutral",
style: "margin-right: 10px;",
});
button.addEventListener("click", () => {
const selectedItems = getBurnables().filter((item) => item.selected);
if (selectedItems.length === 0) return alert("No items selected.");
function buildUrl(type, ids) {
switch (type) {
case "comment":
return `https://e621.net/comments?group_by=comment&search[id]=${ids.join(
","
)}`;
case "forum_post":
return `https://e621.net/forum_posts?search[id]=${ids.join(",")}`;
case "user":
return `https://e621.net/users?search[id]=${ids.join(",")}`;
default:
throw new Error(`Unsupported item type: ${type}`);
}
}
const groupedByType = selectedItems.reduce((acc, item) => {
(acc[item.type] ??= []).push(item.id);
return acc;
}, {});
const output = Object.entries(groupedByType)
.map(([type, ids]) => buildUrl(type, ids))
.join("\n");
navigator.clipboard.writeText(output).then(() => {
alert("Copied item link to clipboard: " + output);
});
});
return button;
}
// #endregion
// #region visibility controls
let showBurntItems = true;
function VisibilityLabel() {
const burntCount = getBurnables().filter((item) => item.burnt).length;
return createElement("label", {
textContent: `Burnt (${burntCount})`,
style: "margin-right: 10px; font-weight: 700;",
});
}
function ToggleVisibilityLink() {
const link = createElement("a", {
href: "#",
textContent: showBurntItems ? "Hide" : "Show",
style: "color: var(--color-link); cursor: pointer;",
});
link.addEventListener("click", (event) => {
event.preventDefault();
showBurntItems = !showBurntItems;
getBurnables().forEach((item) => {
if (item.burnt) {
item.el.style.display = showBurntItems ? "" : "none";
}
});
renderControls();
});
return link;
}
function VisibilityControls() {
const container = createElement("div", {
style:
"display: flex; justify-content: flex-start; align-items: center; margin: 10px;",
});
const label = VisibilityLabel();
const toggleLink = ToggleVisibilityLink();
container.append(label, toggleLink);
return container;
}
// #endregion
// #region action controls
function ManageLabel() {
const items = getBurnables();
const selectedCount = items.filter((item) => item.selected).length;
const totalCount = items.length;
return createElement("label", {
textContent: `Manage (${selectedCount > 0 ? selectedCount : totalCount})`,
style: "margin-right: 10px; font-weight: 700;",
});
}
function MarkingSelect() {
const select = createElement("select", {
id: "sixpack-marking-select",
className: "button btn-neutral",
style: "margin-right: 10px;",
});
["none", "warning", "record", "ban"].forEach((option) => {
select.appendChild(
createElement("option", {
value: option,
textContent: option.charAt(0).toUpperCase() + option.slice(1),
})
);
});
return select;
}
function getMarking() {
return document.getElementById("sixpack-marking-select")?.value ?? "none";
}
function BurnButton() {
const button = createElement("button", {
innerText: "Burn",
className: "button btn-neutral",
style: "margin-right: 10px;",
});
button.addEventListener("click", async () => {
const items = getBurnables().filter((item) => item.selected);
if (items.length === 0) return alert("No items selected.");
const type = items[0]?.type;
if (!type || !items.every((i) => i.type === type)) {
alert("Cannot burn mixed item types.");
return;
}
if (type === "user") {
const ids = items.map((i) => i.id);
saveMassBan(ids);
location.href = `/bans/new?ban[user_id]=${items[0].id}`;
return;
}
const markType = getMarking();
await incinerate(items, { markType });
renderControls();
});
return button;
}
function LeftControls() {
const container = createElement("div", {
style: "display: flex; align-items: center;",
});
container.append(ManageLabel(), CopyButton(), BurnButton());
const path = window.location.pathname;
const isMarkableContext =
path.startsWith("/comments") ||
path.startsWith("/forum_posts") ||
path.startsWith("/forum_topics");
if (isMarkableContext) {
container.append(MarkingSelect());
}
return container;
}
function RightControls() {
const container = createElement("div", {
style: "display: flex; align-items: center;",
});
const checkbox = createElement("input", {
type: "checkbox",
id: "select-all-checkbox",
style: "margin-left: 10px;",
});
checkbox.addEventListener("change", () => {
setSelectionState(checkbox.checked ? "all" : "none");
});
const state = getSelectionState();
checkbox.checked = state === "all";
checkbox.indeterminate = state === "some";
container.appendChild(checkbox);
return container;
}
function ActionControls() {
const container = createElement("div", {
style:
"display: flex; justify-content: space-between; align-items: center; margin: 10px;",
});
container.append(LeftControls(), RightControls());
return container;
}
// #endregion
// #region controls
function renderControls() {
document.querySelector("#sixpack-control-region")?.remove();
const insertionPoint = getControlsInsertionPoint();
if (!insertionPoint) return;
const burnables = getBurnables();
if (burnables.length === 0) return;
const region = createElement("div", {
id: "sixpack-control-region",
style: "display: flex; flex-direction: column;",
});
const anyBurnt = burnables.some((item) => item.burnt);
if (anyBurnt) {
const vis = VisibilityControls();
vis.id = "sixpack-visibility-controls";
region.appendChild(vis);
}
const act = ActionControls();
act.id = "sixpack-controls";
region.appendChild(act);
insertionPoint.insertAdjacentElement("afterend", region);
}
renderControls();
// #endregion
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment