1
0
forked from w0bm/f0bm

6 Commits

Author SHA1 Message Date
x
224064d0ca fixing background visibility and states 2026-01-23 18:37:44 +01:00
x
3ee28fd0b7 Merge branch 'f0bm' into eins-f0bm 2026-01-23 17:03:13 +01:00
x
9a03d5f697 adding generic tag cards 2026-01-23 16:53:19 +01:00
4bc8b8f436 Merge pull request 'fixed issues with the random button and hotkeys' (#2) from eins/f0bm:f0bm into f0bm
Reviewed-on: w0bm/f0bm#2
2026-01-23 14:53:20 +00:00
007cf3189c Merge pull request 'added AJAX loading for videos' (#1) from eins/f0bm:eins-patch-1 into f0bm
Reviewed-on: w0bm/f0bm#1
Reviewed-by: Kibi Kelburton <schrumpel@noreply.DOMAIN>
2026-01-23 13:33:59 +00:00
4a2925b141 revert a4f9c48e13
revert various fixes to get it working for myself
2026-01-23 13:24:43 +00:00
12 changed files with 432 additions and 192 deletions

View File

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

View File

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

View File

@@ -1585,14 +1585,10 @@ span.placeholder {
}
@media (max-width: 1056px) {
.navbar {
display: grid;
grid-template-rows: 1fr 1fr;
grid-template-areas: 'f0ck f0ck f0ck';
}
/* Navbar grid layout removed for modern-navbar compatibility */
.navbar-brand {
grid-area: f0ck;
/* maintained for potential other uses or reset */
}
.pagination-container-fluid {
@@ -2947,7 +2943,7 @@ div.favs div.posts {
filter: blur(100px);
transform: translate3d(0, 0, 0);
z-index: 0;
transition: 2s ease;
transition: opacity 1.5s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0.2;
}
@@ -3000,11 +2996,11 @@ button#togglebg {
}
.fader-in {
animation: fadeIn .8s steps(100) forwards;
opacity: 0.4 !important;
}
.fader-out {
animation: fadeOut .8s steps(100) forwards
opacity: 0 !important;
}
.settings {
@@ -3052,10 +3048,76 @@ input#s_avatar {
0%,
100% {
opacity: 0.4;
opacity: 0.1;
}
50% {
opacity: 1;
}
/* Modern Tags Layout */
.tags-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
padding: 20px 0;
}
.tag-card {
display: flex;
flex-direction: column;
background: var(--badge-bg, #171717);
border-radius: 12px;
overflow: hidden;
text-decoration: none !important;
transition: transform 0.2s, box-shadow 0.2s;
border: 1px solid var(--nav-border-color, rgba(255, 255, 255, 0.1));
position: relative;
}
.tag-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
background: var(--dropdown-bg, #232323);
border-color: var(--accent, #9f0);
}
.tag-card-image {
width: 100%;
height: 100px;
overflow: hidden;
position: relative;
background: #000;
}
.tag-card-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s;
opacity: 0.8;
}
.tag-card:hover .tag-card-image img {
transform: scale(1.1);
opacity: 1;
}
.tag-card-content {
padding: 15px;
display: flex;
flex-direction: column;
gap: 5px;
}
.tag-name {
color: var(--white, #fff);
font-weight: bold;
font-size: 1.1em;
font-family: var(--font, monospace);
}
.tag-count {
color: #888;
font-size: 0.9em;
}

View File

@@ -8,6 +8,25 @@ window.requestAnimFrame = (function () {
(() => {
let video;
// Initialize background preference
if (localStorage.getItem('background') == undefined) {
localStorage.setItem('background', 'true');
}
var background = localStorage.getItem('background') === 'true';
// Apply initial visual state
var initialCanvas = document.getElementById('bg');
if (initialCanvas) {
if (background) {
initialCanvas.classList.add('fader-in');
initialCanvas.classList.remove('fader-out');
} else {
initialCanvas.classList.add('fader-out');
initialCanvas.classList.remove('fader-in');
}
}
if (elem = document.querySelector("#my-video")) {
video = new v0ck(elem);
document.addEventListener("keydown", e => {
@@ -17,13 +36,27 @@ window.requestAnimFrame = (function () {
}
});
const toggleBg = document.getElementById('togglebg');
if (toggleBg) {
toggleBg.addEventListener('click', function (e) {
e.preventDefault();
background = !background;
localStorage.setItem('background', background.toString());
var canvas = document.getElementById('bg');
if (elem !== null) {
// ... existing code ...
}
}
// Export init function for dynamic calls
window.initBackground = () => {
// Re-fetch elements as they might have been replaced
const elem = document.querySelector("#my-video");
const canvas = document.getElementById('bg');
if (elem) {
// Initialize video wrapper if needed or just get instance
// Assuming v0ck handles re-init or we just use raw element for events
// But video variable is local.
// We need to re-bind 'play' event if it's a new element.
if (canvas) {
// Restore visual state on re-init
if (background) {
canvas.classList.add('fader-in');
canvas.classList.remove('fader-out');
@@ -31,33 +64,102 @@ window.requestAnimFrame = (function () {
canvas.classList.add('fader-out');
canvas.classList.remove('fader-in');
}
animationLoop();
});
}
if (elem !== null) {
if (localStorage.getItem('background') == undefined) {
localStorage.setItem('background', 'true');
}
const context = canvas.getContext('2d');
const cw = canvas.width = canvas.clientWidth | 0;
const ch = canvas.height = canvas.clientHeight | 0;
var background = localStorage.getItem('background') === 'true';
var canvas = document.getElementById('bg');
if (canvas) {
var context = canvas.getContext('2d');
var cw = canvas.width = canvas.clientWidth | 0;
var ch = canvas.height = canvas.clientHeight | 0;
function animationLoop() {
if (video.paused || video.ended || !background)
const animationLoop = () => {
if (elem.paused || elem.ended || !background)
return;
context.drawImage(video, 0, 0, cw, ch);
context.drawImage(elem, 0, 0, cw, ch);
window.requestAnimFrame(animationLoop);
}
elem.addEventListener('play', animationLoop);
if (!elem.paused) {
animationLoop();
}
}
}
};
// Initial call
window.initBackground();
const loadPageAjax = async (url) => {
// Show loading indicator
const navbar = document.querySelector("nav.navbar");
if (navbar) navbar.classList.add("pbwork");
try {
// Extract page number, user, tag, etc.
let page = 1;
const pMatch = url.match(/\/p\/(\d+)/);
if (pMatch) page = pMatch[1];
// Extract context
let tag = null, user = null, mime = null;
const tagMatch = url.match(/\/tag\/([^/]+)/);
if (tagMatch) tag = decodeURIComponent(tagMatch[1]);
const userMatch = url.match(/\/user\/([^/]+)/);
if (userMatch) user = decodeURIComponent(userMatch[1]);
const mimeMatch = url.match(/\/(image|audio|video)/);
if (mimeMatch) mime = mimeMatch[1];
let ajaxUrl = `/ajax/items/?page=${page}`;
if (tag) ajaxUrl += `&tag=${encodeURIComponent(tag)}`;
if (user) ajaxUrl += `&user=${encodeURIComponent(user)}`;
if (mime) ajaxUrl += `&mime=${encodeURIComponent(mime)}`;
console.log("Fetching Page:", ajaxUrl);
const response = await fetch(ajaxUrl, { credentials: 'include' });
const data = await response.json();
if (data.success) {
// Replace grid content
// If "infinite scroll" we might append, but pagination implies jumping properly?
// User said "resembled in pagination", which implies staying in sync.
// If I click Next Page, I expect to SEE page 2.
// But infinite scroll usually appends.
// Let's implement REPLACE for explicit page navigation to be safe/standard.
// Wait, the "infinite scroll" feature usually implies APPEND.
// If the user wants infinite scroll, they shouldn't click pagination?
// But if they scroll, `changePage` is called which clicks `.next`.
// So if I replace content, it breaks infinite scroll flow (items disappear).
// So I should APPEND if it's "next page" and we are already on the page?
// But `changePage` is triggered by scroll.
// Let's APPEND.
const posts = document.querySelector('.posts');
if (posts) {
// Check if we are appending (next page) or jumping
// For simple "infinite scroll", we append.
posts.insertAdjacentHTML('beforeend', data.html);
}
// Update pagination
if (data.pagination) {
document.querySelectorAll('.pagination-wrapper').forEach(el => el.innerHTML = data.pagination);
}
// Update History
history.pushState({}, '', url);
}
} catch (err) {
console.error(err);
window.location.href = url; // Fallback
} finally {
if (navbar) navbar.classList.remove("pbwork");
// Restore pagination visibility for Grid View
const navPag = document.querySelector('.pagination-container-fluid');
if (navPag) navPag.style.display = '';
}
};
let tt = false;
const stimeout = 500;
@@ -89,6 +191,10 @@ window.requestAnimFrame = (function () {
// Extract item ID from URL. Regex now handles query params, hashes, and trailing slashes.
const match = url.match(/\/(\d+)(?:\/|#|\?|$)/);
// Hide navbar pagination for Item View (matches SSR)
const navPag = document.querySelector('.pagination-container-fluid');
if (navPag) navPag.style.display = 'none';
if (!match) {
console.warn("loadItemAjax: No ID match found in URL", url);
// fallback for weird/external links
@@ -99,12 +205,15 @@ window.requestAnimFrame = (function () {
// <context-preservation>
// Extract context from Target URL first
let tag = null, user = null;
let tag = null, user = null, isFavs = false;
const tagMatch = url.match(/\/tag\/([^/]+)/);
if (tagMatch) tag = decodeURIComponent(tagMatch[1]);
const userMatch = url.match(/\/user\/([^/]+)/);
if (userMatch) user = decodeURIComponent(userMatch[1]); // Note: "user" variable shadowed? No, block scope or different name? let user defined above.
if (userMatch) {
user = decodeURIComponent(userMatch[1]);
if (url.includes(`/user/${userMatch[1]}/favs`)) isFavs = true;
}
// If missing and inheritContext is true, check Window Location
if (inheritContext) {
@@ -114,7 +223,10 @@ window.requestAnimFrame = (function () {
}
if (!user) {
const wUserMatch = window.location.href.match(/\/user\/([^/]+)/);
if (wUserMatch) user = decodeURIComponent(wUserMatch[1]);
if (wUserMatch) {
user = decodeURIComponent(wUserMatch[1]);
if (window.location.href.includes(`/user/${wUserMatch[1]}/favs`)) isFavs = true;
}
}
}
// </context-preservation>
@@ -126,13 +238,14 @@ window.requestAnimFrame = (function () {
const params = new URLSearchParams();
if (tag) params.append('tag', tag);
if (user) params.append('user', user);
if (isFavs) params.append('fav', 'true');
if ([...params].length > 0) {
ajaxUrl += '?' + params.toString();
}
console.log("Fetching:", ajaxUrl);
const response = await fetch(ajaxUrl);
const response = await fetch(ajaxUrl, { credentials: 'include' });
if (!response.ok) throw new Error(`Network response was not ok: ${response.status}`);
const rawText = await response.text();
@@ -187,13 +300,17 @@ window.requestAnimFrame = (function () {
// If we inherited context, we should reflect it in the URL
let pushUrl = `/${itemid}`;
// Logic from ajax.mjs context reconstruction:
if (user) pushUrl = `/user/${user}/${itemid}`; // User takes precedence usually? Or strictly mutually exclusive in UI
if (user) {
pushUrl = `/user/${user}/${itemid}`;
if (isFavs) pushUrl = `/user/${user}/favs/${itemid}`;
}
else if (tag) pushUrl = `/tag/${tag}/${itemid}`;
// We overwrite proper URL even if the link clicked was "naked"
history.pushState({}, '', pushUrl);
setupMedia();
if (window.initBackground) window.initBackground();
// Try to extract ID from response if possible or just use itemid
document.title = `f0bm - ${itemid}`;
if (navbar) navbar.classList.remove("pbwork");
@@ -266,12 +383,50 @@ window.requestAnimFrame = (function () {
// Standard item links
e.preventDefault();
if (link.href.match(/\/p\/\d+/) || link.href.match(/[?&]page=\d+/)) {
loadPageAjax(link.href);
} else {
loadItemAjax(link.href, true);
}
} else if (e.target.closest('#togglebg')) {
e.preventDefault();
background = !background;
localStorage.setItem('background', background.toString());
var canvas = document.getElementById('bg');
if (canvas) {
if (background) {
canvas.classList.remove('fader-out');
canvas.classList.add('fader-in');
// Re-trigger loop if started completely fresh or paused
if (video && !video.paused) {
// We need to access animationLoop from closure?
// Accessing it via window.initBackground might be cleaner or just restart it.
// But initBackground defines it locally.
// We can just rely on initBackground being called or canvas update.
// Actually, if we just change opacity, the loop doesn't need to stop/start technically,
// but for performance we stopped it if !background.
// So we should restart it.
window.initBackground();
}
} else {
canvas.classList.remove('fader-in');
canvas.classList.add('fader-out');
}
}
}
});
window.addEventListener('popstate', (e) => {
if (window.location.href.match(/\/p\/\d+/) || window.location.href.match(/[?&]page=\d+/) || window.location.pathname === '/') {
// Ideally we should reload page or call loadPageAjax(currentUrl) if it supports it
// But if we are going BACK to index from item, we expect grid.
// loadItemAjax fails on index.
// loadPageAjax handles /p/N logic.
// If just slash, loadPageAjax might default to page 1.
loadPageAjax(window.location.href);
} else {
loadItemAjax(window.location.href, true);
}
});
// <keybindings>
@@ -593,6 +748,7 @@ window.requestAnimFrame = (function () {
// </scroller>
})();
// disable default scroll event when mouse is on content div
// this is useful for items that have a lot of tags for example: 12536
const targetSelector = '.content';

View File

@@ -23,7 +23,8 @@ export default (router, tpl) => {
url: contextUrl,
user: query.user,
tag: query.tag,
mime: query.mime
mime: query.mime,
fav: query.fav === 'true'
});
if (!data.success) {
@@ -38,10 +39,8 @@ export default (router, tpl) => {
if (req.session) {
data.session = { ...req.session };
// data.user comes from f0cklib (uploader). req.session.user is logged-in user string.
// If template engine confuses them, removing session.user from this context might help.
// item-partial doesn't use session.user.
// Note: If anything fails, it prints literal code, so we ensure no collision.
if (data.session.user) delete data.session.user;
// Templates use session.user for matching favorites. We must preserve it.
// if (data.session.user) delete data.session.user; // REMOVED THIS
} else {
data.session = false;
}
@@ -103,6 +102,12 @@ export default (router, tpl) => {
link: data.link
});
// Render pagination
const paginationHtml = tpl.render('snippets/pagination', {
pagination: data.pagination,
link: data.link
});
const hasMore = data.pagination.next !== null;
return res.reply({
@@ -110,6 +115,7 @@ export default (router, tpl) => {
body: JSON.stringify({
success: true,
html: itemsHtml,
pagination: paginationHtml,
hasMore: hasMore,
nextPage: data.pagination.next,
currentPage: data.pagination.page

View File

@@ -0,0 +1,41 @@
import crypto from 'crypto';
export default (router, tpl) => {
router.get(/^\/tag_image\/(?<tag>.+)$/, async (req, res) => {
const tag = req.params.tag;
// Create a deterministic hash from the tag
const hash = crypto.createHash('md5').update(tag).digest('hex');
// Generate colors from hash
const c1 = '#' + hash.substring(0, 6);
const c2 = '#' + hash.substring(6, 12);
const c3 = '#' + hash.substring(12, 18);
// Generate some deterministic numbers for shapes
const n1 = parseInt(hash.substring(18, 20), 16);
const n2 = parseInt(hash.substring(20, 22), 16);
const svg = `
<svg width="300" height="150" viewBox="0 0 300 150" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:${c1};stop-opacity:1" />
<stop offset="100%" style="stop-color:${c2};stop-opacity:1" />
</linearGradient>
</defs>
<rect width="300" height="150" fill="url(#grad)" />
<circle cx="${n1}%" cy="${n2}%" r="${(n1 + n2) / 4}" fill="${c3}" fill-opacity="0.3" />
<circle cx="${100 - n1}%" cy="${100 - n2}%" r="${(n1 + n2) / 3}" fill="${c3}" fill-opacity="0.2" />
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="sans-serif" font-size="24" fill="#fff" fill-opacity="0.9" font-weight="bold">${tag}</text>
</svg>
`.trim();
res.writeHead(200, {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'public, max-age=86400'
});
res.end(svg);
});
return router;
};

View File

@@ -23,7 +23,7 @@ export default async bot => {
name: "parser",
call: regex.all,
active: true,
f: async e => {
f: e => {
const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || [];
let repost;
if(e.media)
@@ -51,7 +51,7 @@ export default async bot => {
console.log(`parsing ${links.length} link${links.length > 1 ? "s" : ""}...`);
for (const link of links) {
links.forEach(async link => {
//if(regex.imgur.test(link))
// return await e.reply(`fuck imgur... seriously`);
@@ -73,12 +73,11 @@ export default async bot => {
// read metadata
let ext;
const proxyArgs = cfg.main.socks ? `--proxy ${cfg.main.socks}` : '';
if(link.match(regex.instagram)) {
// is instagram
try {
// @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 ${proxyArgs} -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 --proxy ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext;
} catch(err) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
@@ -93,10 +92,9 @@ export default async bot => {
else if(link.match(regex.yt)) {
//yt - fuck anti bot protection
try {
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);
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);
ext = meta.ext;
} catch(err) {
console.error("YT-DLP Error:", err);
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
ext = cfg.mimes[tmphead];
@@ -105,7 +103,7 @@ export default async bot => {
else if(link.match(regex.fourchan)) {
//4chan - fuck cloudflare :)
try {
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);
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);
ext = meta.ext;
} catch(err) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
@@ -120,10 +118,9 @@ 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);
ext = meta.ext;
} catch(err) {
console.error('err:', err);
if (e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, err);
return await e.reply('something went wrong lol / check maxfilesize?');
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
ext = cfg.mimes[tmphead];
}
}
@@ -142,7 +139,7 @@ export default async bot => {
if(link.match(regex.instagram)) {
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
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();
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();
} catch(err) {
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "instagram dl error");
@@ -164,7 +161,7 @@ export default async bot => {
else if(link.match(regex.yt)) {
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
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();
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();
} catch(err) {
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error");
@@ -174,7 +171,7 @@ export default async bot => {
else if(link.match(regex.fourchan)) {
// 4chan via proxy - fuck cloudflare
try {
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();
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();
} catch(err) {
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "yt dl error");
@@ -276,7 +273,8 @@ export default async bot => {
}
await db`
insert into items ${db({
insert into items ${
db({
src: e.media ? "" : link,
dest: filename,
mime: mime,
@@ -293,16 +291,6 @@ export default async bot => {
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
try {
await queue.genThumbnail(filename, mime, itemid, link);
@@ -351,7 +339,7 @@ export default async bot => {
else {
await e.reply(outputmsgirc);
}
}
});
}
}];
};

View File

@@ -1,5 +1,5 @@
@include(snippets/header)
<canvas class="hidden-xs" id="bg"></canvas>
<div class="wrapper">
<div id="main">

View File

@@ -1,14 +1,19 @@
<!doctype html>
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif" res="@if(typeof fullscreen !== "undefined"){{ fullscreen == 1 ? 'fullscreen' : '' }}@endif">
<html lang="en" theme="@if(typeof theme !== 'undefined'){{ theme }}@endif"
res="@if(typeof fullscreen !== 'undefined'){{ fullscreen == 1 ? 'fullscreen' : '' }}@endif">
<head>
@if(typeof item !== "undefined")<title>f0bm - {{ item.id }}</title>@else<title>f0bm</title>@endif
@if(typeof item !== 'undefined')<title>f0bm - {{ item.id }}</title>@else<title>f0bm</title>@endif
<link rel="icon" type="image/gif" href="/s/img/favicon.png" />
<link rel="stylesheet" href="/s/css/f0ck.css?v=@mtime(/public/s/css/f0ck.css)">
<link rel="stylesheet" href="/s/css/w0bm.css?v=@mtime(/public/s/css/w0bm.css)">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
@if(typeof item !== "undefined")<link rel="canonical" href="https://w0bm.com/{{ item.id }}" />@endif
@if(typeof item !== 'undefined')
<link rel="canonical" href="https://w0bm.com/{{ item.id }}" />@endif
</head>
<body>
<!-- hier splitting betreiben -->
<canvas class="hidden-xs" id="bg"></canvas>
@include(snippets/navbar)

View File

@@ -2,7 +2,6 @@
<!-- logged in -->
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">w0bm.com</span></a>
<div class="navigation-links-guest">
<ol>
<a href="/tags">tags</a>
@@ -13,7 +12,7 @@
</ol>
</div>
<!-- show pagination only for tags and main page -->
@if(!/^\/\d$/.test(url.pathname))
@if(!/^\/\d+$/.test(url.pathname))
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
<div class="pagination-container-fluid">
<div class="pagination-wrapper">
@@ -27,7 +26,6 @@
<!-- not logged in -->
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">w0bm.com</span></a>
<div class="navigation-links-guest">
<ol>
<a href="/tags">tags</a>
@@ -38,7 +36,7 @@
</ol>
</div>
<!-- show pagination only for tags and main page -->
@if(!/^\/\d$/.test(url.pathname))
@if(!/^\/\d+$/.test(url.pathname))
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
<div class="pagination-container-fluid">
<div class="pagination-wrapper">

View File

@@ -2,24 +2,30 @@
<div id="main">
<div class="container">
<h3 style="text-align: center;"></h3>
<div class="tags">
<div class="tags-grid">
@if(session)
@each(toptags_regged as toptag)
<div class="tag badge badge-light mr-2">
<div class="tagbox-body">
<span class="toptag_id">{!! toptag.tag !!}</span>
<span class="toptag_tag"><a href="/tag/{!! toptag.tag !!}">{{ toptag.total_items }}</a></span>
<a href="/tag/{!! toptag.tag !!}" class="tag-card">
<div class="tag-card-image">
<img src="/tag_image/{!! toptag.tag !!}" loading="lazy" alt="{!! toptag.tag !!}">
</div>
<div class="tag-card-content">
<span class="tag-name">#{!! toptag.tag !!}</span>
<span class="tag-count">{{ toptag.total_items }} posts</span>
</div>
</a>
@endeach
@else
@each(toptags as toptag)
<div class="tag badge badge-light mr-2">
<div class="tagbox-body">
<span class="toptag_id">{!! toptag.tag !!}</span>
<span class="toptag_tag"><a href="/tag/{!! toptag.tag !!}">{{ toptag.total_items }}</a></span>
<a href="/tag/{!! toptag.tag !!}" class="tag-card">
<div class="tag-card-image">
<img src="/tag_image/{!! toptag.tag !!}" loading="lazy" alt="{!! toptag.tag !!}">
</div>
<div class="tag-card-content">
<span class="tag-name">#{!! toptag.tag !!}</span>
<span class="tag-count">{{ toptag.total_items }} posts</span>
</div>
</a>
@endeach
@endif
</div>