1
0
forked from w0bm/f0bm

Compare commits

3 Commits

Author SHA1 Message Date
eins
45f9345e9c fix r key on tag overview 2026-01-23 14:46:39 +00:00
eins
c74e5a7402 fixed scrolling in overview 2026-01-23 14:39:42 +00:00
eins
6799ec1567 fixed issues with the random button and hotkeys 2026-01-23 14:07:52 +00:00
10 changed files with 227 additions and 178 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ deleted/b
deleted/ca deleted/ca
deleted/t deleted/t
tmp/* tmp/*
tools

15
debug/check_last_item.mjs Normal file
View File

@@ -0,0 +1,15 @@
import db from "../src/inc/sql.mjs";
(async () => {
try {
const item = (await db`select * from items order by id desc limit 1`)?.[0];
console.log("Last Item:", item);
if (item) {
const tags = await db`select * from tags_assign where item_id = ${item.id}`;
console.log("Tags:", tags);
}
} catch (err) {
console.error(err);
}
process.exit(0);
})();

View File

@@ -6,13 +6,14 @@ import { promises as fs } from "fs";
network: "console", network: "console",
message: _args.join(" "), message: _args.join(" "),
args: _args.slice(1), args: _args.slice(1),
channel: "console", channel: "#w0bm",
user: { user: {
prefix: "console!console@console", prefix: "console!console@console",
nick: "console", nick: "console",
username: "console", username: "console",
account: "console" account: "console"
}, },
raw: {},
reply: (...args) => console.log(args), reply: (...args) => console.log(args),
replyAction: (...args) => console.log(args), replyAction: (...args) => console.log(args),
replyNotice: (...args) => console.log(args) replyNotice: (...args) => console.log(args)
@@ -26,9 +27,12 @@ import { promises as fs } from "fs";
try { try {
if (trigger.length === 0) if (trigger.length === 0)
return console.error("no matches"); return console.error("no matches");
console.log(`triggered > ${trigger[0].name} (${_e.message})`); for (const t of trigger) {
await trigger[0].f(_e); console.log(`triggered > ${t.name} (${_e.message})`);
await t.f(_e);
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
process.exit(0);
})(); })();

3
init.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
npm i
npm start

View File

@@ -130,12 +130,12 @@
return false; return false;
} }
renderTags(res.tags); renderTags(res.tags);
span.parentElement.removeChild(span); if (span.parentElement) span.parentElement.removeChild(span);
testList.innerText = ""; testList.innerText = "";
addtagClick(); addtagClick();
} }
else if (e.key === "Escape") { else if (e.key === "Escape") {
span.parentElement.removeChild(span); if (span.parentElement) span.parentElement.removeChild(span);
testList.innerText = ""; testList.innerText = "";
} }
else { else {

View File

@@ -140,7 +140,7 @@ window.requestAnimFrame = (function () {
html = rawText; html = rawText;
} }
let container = document.querySelector('.container'); let container = document.querySelector('#main .container');
if (!container && document.querySelector('.index-container')) { if (!container && document.querySelector('.index-container')) {
// Transition from Index to Item View // Transition from Index to Item View
@@ -148,6 +148,11 @@ window.requestAnimFrame = (function () {
main.innerHTML = '<div class="container"></div>'; main.innerHTML = '<div class="container"></div>';
container = main.querySelector('.container'); container = main.querySelector('.container');
} else if (container) { } else if (container) {
// Check if we are on Tags Overview logic (which reuses .container)
const tagsOverview = container.querySelector('.tags');
if (tagsOverview) {
container.innerHTML = '';
} else {
// Already in Item View, clear usage // Already in Item View, clear usage
const oldContent = container.querySelector('.content'); const oldContent = container.querySelector('.content');
const oldMetadata = container.querySelector('.metadata'); const oldMetadata = container.querySelector('.metadata');
@@ -156,6 +161,7 @@ window.requestAnimFrame = (function () {
if (oldContent) oldContent.remove(); if (oldContent) oldContent.remove();
if (oldMetadata) oldMetadata.remove(); if (oldMetadata) oldMetadata.remove();
} }
}
container.insertAdjacentHTML('beforeend', html); container.insertAdjacentHTML('beforeend', html);
@@ -187,13 +193,12 @@ window.requestAnimFrame = (function () {
}; };
const changePage = (e, pbwork = true) => { const changePage = (e, pbwork = true) => {
if (e.tagName === 'A') { if (pbwork) {
e.preventDefault(); const nav = document.querySelector("nav.navbar");
loadItemAjax(e.href); if (nav) nav.classList.add("pbwork");
} else {
pbwork && document.querySelector("nav.navbar").classList.add("pbwork");
!tt && (tt = setTimeout(() => e.click(), stimeout));
} }
// Trigger native click for navigation
e.click();
}; };
// Intercept clicks // Intercept clicks
@@ -208,10 +213,10 @@ window.requestAnimFrame = (function () {
return; return;
} }
const link = e.target.closest('#next, #prev, #random, .id-link, .nav-next, .nav-prev'); const link = e.target.closest('#next, #prev, #random, #nav-random, .id-link, .nav-next, .nav-prev');
if (link && link.href && link.hostname === window.location.hostname && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { if (link && link.href && link.hostname === window.location.hostname && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
// Special check for random // Special check for random
if (link.id === 'random') { if (link.id === 'random' || link.id === 'nav-random') {
e.preventDefault(); e.preventDefault();
const nav = document.querySelector("nav.navbar"); const nav = document.querySelector("nav.navbar");
if (nav) nav.classList.add("pbwork"); if (nav) nav.classList.add("pbwork");
@@ -240,7 +245,10 @@ window.requestAnimFrame = (function () {
window.location.href = link.href; window.location.href = link.href;
} }
}) })
.catch(() => window.location.href = link.href); .catch((err) => {
console.error("Random fetch failed:", err);
window.location.href = link.href;
});
return; return;
} }
@@ -261,7 +269,7 @@ window.requestAnimFrame = (function () {
"a": clickOnElementBinding("#next"), "a": clickOnElementBinding("#next"),
"ArrowRight": clickOnElementBinding("#prev"), "ArrowRight": clickOnElementBinding("#prev"),
"d": clickOnElementBinding("#prev"), "d": clickOnElementBinding("#prev"),
"r": clickOnElementBinding("#random"), "r": clickOnElementBinding("#random, #nav-random"),
" ": clickOnElementBinding("#f0ck-image") " ": clickOnElementBinding("#f0ck-image")
}; };
document.addEventListener("keydown", e => { document.addEventListener("keydown", e => {
@@ -541,10 +549,13 @@ function init() {
window.addEventListener('load', init); window.addEventListener('load', init);
document.getElementById('sbtForm').addEventListener('submit', (e) => { const sbtForm = document.getElementById('sbtForm');
if (sbtForm) {
sbtForm.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
const input = document.getElementById('sbtInput').value.trim(); const input = document.getElementById('sbtInput').value.trim();
if (input) { if (input) {
window.location.href = `/tag/${encodeURIComponent(input)}`; window.location.href = `/tag/${encodeURIComponent(input)}`;
} }
}); });
}

View File

@@ -17,6 +17,9 @@ const Cookie = {
(() => { (() => {
const acttheme = Cookie.get('theme') ?? "w0bm"; const acttheme = Cookie.get('theme') ?? "w0bm";
const themecontainer = document.querySelector("li#themes > ul.dropdown-menu"); const themecontainer = document.querySelector("li#themes > ul.dropdown-menu");
if (!themecontainer) return; // Theme menu not present on this page
const themes = [...themecontainer.querySelectorAll("li > a")].map(t => t.innerText.toLowerCase()); const themes = [...themecontainer.querySelectorAll("li > a")].map(t => t.innerText.toLowerCase());
if (acttheme !== document.documentElement.getAttribute("theme") && themes.includes(acttheme)) if (acttheme !== document.documentElement.getAttribute("theme") && themes.includes(acttheme))
document.documentElement.setAttribute("theme", acttheme); document.documentElement.setAttribute("theme", acttheme);

View File

@@ -13,14 +13,18 @@ export default router => {
group.get(/\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => { group.get(/\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => {
const user = req.url.split[3] === "user" ? req.url.split[4] : "%"; const user = req.url.split[3] === "user" ? req.url.split[4] : "%";
const mime = (allowedMimes.filter(n => req.url.split[3]?.startsWith(n))[0] ? req.url.split[3] : "") + "%"; const mime = (allowedMimes.filter(n => req.url.split[3]?.startsWith(n))[0] ? req.url.split[3] : "") + "%";
const tag = req.url.qs.tag || null;
const rows = await db` const rows = await db`
select * select "items".*
from "items" from "items"
left join tags_assign on tags_assign.item_id = items.id
left join tags on tags.id = tags_assign.tag_id
where where
mime ilike ${mime} and mime ilike ${mime} and
username ilike ${user} and username ilike ${user} and
active = 'true' active = 'true'
${tag ? db`and tags.normalized ilike ${'%' + tag + '%'}` : db``}
order by random() order by random()
limit 1 limit 1
`; `;
@@ -51,15 +55,13 @@ export default router => {
where where
${db.unsafe(modequery)} and ${db.unsafe(modequery)} and
active = 'true' active = 'true'
${ ${opt.older
opt.older
? db`and id <= ${opt.older}` ? db`and id <= ${opt.older}`
: opt.newer : opt.newer
? db`and id >= ${opt.newer}` ? db`and id >= ${opt.newer}`
: db`` : db``
} }
order by id ${ order by id ${opt.newer
opt.newer
? db`asc` ? db`asc`
: db`desc` : db`desc`
} }
@@ -175,8 +177,7 @@ export default router => {
} }
const q = (await db` const q = (await db`
update "tags" set ${ update "tags" set ${db({
db({
tag: newtag tag: newtag
}, 'tag') }, 'tag')
} }
@@ -287,8 +288,7 @@ export default router => {
else { else {
// add fav // add fav
await db` await db`
insert into "favorites" ${ insert into "favorites" ${db({
db({
item_id: +postid, item_id: +postid,
user_id: +req.session.id user_id: +req.session.id
}, 'item_id', 'user_id') }, 'item_id', 'user_id')

View File

@@ -23,7 +23,7 @@ export default async bot => {
name: "parser", name: "parser",
call: regex.all, call: regex.all,
active: true, active: true,
f: e => { f: async e => {
const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || []; const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || [];
let repost; let repost;
if (e.media) if (e.media)
@@ -51,7 +51,7 @@ export default async bot => {
console.log(`parsing ${links.length} link${links.length > 1 ? "s" : ""}...`); console.log(`parsing ${links.length} link${links.length > 1 ? "s" : ""}...`);
links.forEach(async link => { for (const link of links) {
//if(regex.imgur.test(link)) //if(regex.imgur.test(link))
// return await e.reply(`fuck imgur... seriously`); // return await e.reply(`fuck imgur... seriously`);
@@ -73,11 +73,12 @@ export default async bot => {
// read metadata // read metadata
let ext; let ext;
const proxyArgs = cfg.main.socks ? `--proxy ${cfg.main.socks}` : '';
if (link.match(regex.instagram)) { if (link.match(regex.instagram)) {
// is instagram // is instagram
try { try {
// @flummi -> is there a variable for the actual work directory so it doesn't have to be hardcoded? // @flummi -> is there a variable for the actual work directory so it doesn't have to be hardcoded?
const meta = JSON.parse((await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout); const meta = JSON.parse((await queue.exec(`yt-dlp ${proxyArgs} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext; ext = meta.ext;
} catch (err) { } catch (err) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"]; const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
@@ -92,9 +93,10 @@ export default async bot => {
else if (link.match(regex.yt)) { else if (link.match(regex.yt)) {
//yt - fuck anti bot protection //yt - fuck anti bot protection
try { try {
const meta = JSON.parse((await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' -I 1 --skip-download --dump-json "${link}"`)).stdout); const meta = JSON.parse((await queue.exec(`yt-dlp ${proxyArgs} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' -I 1 --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext; ext = meta.ext;
} catch (err) { } catch (err) {
console.error("YT-DLP Error:", err);
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"]; const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
// this can be undefined for unsupported mime types, but will be caught in the general mime check below // this can be undefined for unsupported mime types, but will be caught in the general mime check below
ext = cfg.mimes[tmphead]; ext = cfg.mimes[tmphead];
@@ -103,7 +105,7 @@ export default async bot => {
else if (link.match(regex.fourchan)) { else if (link.match(regex.fourchan)) {
//4chan - fuck cloudflare :) //4chan - fuck cloudflare :)
try { try {
const meta = JSON.parse((await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout); const meta = JSON.parse((await queue.exec(`yt-dlp ${proxyArgs} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext; ext = meta.ext;
} catch (err) { } catch (err) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"]; const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
@@ -118,9 +120,10 @@ export default async bot => {
const meta = JSON.parse((await queue.exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout); const meta = JSON.parse((await queue.exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext; ext = meta.ext;
} catch (err) { } catch (err) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"]; console.error('err:', err);
// this can be undefined for unsupported mime types, but will be caught in the general mime check below if (e.type == 'tg')
ext = cfg.mimes[tmphead]; return await e.editMessageText(msg.result.chat.id, msg.result.message_id, err);
return await e.reply('something went wrong lol / check maxfilesize?');
} }
} }
@@ -139,7 +142,7 @@ export default async bot => {
if (link.match(regex.instagram)) { if (link.match(regex.instagram)) {
try { try {
// add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited or optionally use a socks proxy from a network that is not being detected as a public network // add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited or optionally use a socks proxy from a network that is not being detected as a public network
source = (await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=1080]+ba/b[height<=1080] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim(); source = (await queue.exec(`yt-dlp ${proxyArgs} -f 'bv*[height<=1080]+ba/b[height<=1080] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
} catch (err) { } catch (err) {
if (e.type == 'tg') if (e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "instagram dl error"); return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "instagram dl error");
@@ -161,7 +164,7 @@ export default async bot => {
else if (link.match(regex.yt)) { else if (link.match(regex.yt)) {
try { try {
// add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited or optionally use a socks proxy from a network that is not being detected as a public network // add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited or optionally use a socks proxy from a network that is not being detected as a public network
source = (await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" -I 1 --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim(); source = (await queue.exec(`yt-dlp ${proxyArgs} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" -I 1 --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
} catch (err) { } catch (err) {
if (e.type == 'tg') if (e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error"); return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error");
@@ -171,7 +174,7 @@ export default async bot => {
else if (link.match(regex.fourchan)) { else if (link.match(regex.fourchan)) {
// 4chan via proxy - fuck cloudflare // 4chan via proxy - fuck cloudflare
try { try {
source = (await queue.exec(`yt-dlp --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim(); source = (await queue.exec(`yt-dlp ${proxyArgs} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
} catch (err) { } catch (err) {
if (e.type == 'tg') if (e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error"); return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error");
@@ -273,8 +276,7 @@ export default async bot => {
} }
await db` await db`
insert into items ${ insert into items ${db({
db({
src: e.media ? "" : link, src: e.media ? "" : link,
dest: filename, dest: filename,
mime: mime, mime: mime,
@@ -291,6 +293,16 @@ export default async bot => {
const itemid = await queue.getItemID(filename); const itemid = await queue.getItemID(filename);
// auto-tag sfw
await db`
insert into tags_assign ${db({
item_id: itemid,
tag_id: 1, // sfw
user_id: 2 // f0ck
}, 'item_id', 'tag_id', 'user_id')
}
`;
// generate thumbnail // generate thumbnail
try { try {
await queue.genThumbnail(filename, mime, itemid, link); await queue.genThumbnail(filename, mime, itemid, link);
@@ -339,7 +351,7 @@ export default async bot => {
else { else {
await e.reply(outputmsgirc); await e.reply(outputmsgirc);
} }
}); }
} }
}]; }];
}; };

View File

@@ -8,7 +8,7 @@
<a href="/tags">tags</a> <a href="/tags">tags</a>
<a href="/about">about</a> <a href="/about">about</a>
@if(!/^\/\d$/.test(url.pathname)) @if(!/^\/\d$/.test(url.pathname))
<a href="/random">rand</a> <a href="/random" id="nav-random">rand</a>
@endif @endif
</ol> </ol>
</div> </div>
@@ -33,7 +33,7 @@
<a href="/tags">tags</a> <a href="/tags">tags</a>
<a href="/about">about</a> <a href="/about">about</a>
@if(!/^\/\d$/.test(url.pathname)) @if(!/^\/\d$/.test(url.pathname))
<a href="/random">rand</a> <a href="/random" id="nav-random">rand</a>
@endif @endif
</ol> </ol>
</div> </div>