2 Commits

Author SHA1 Message Date
d951a072ee dev progress 2022-10-28 04:08:35 +02:00
a64a4da8cd comment mockup 2022-10-27 22:19:27 +02:00
55 changed files with 2740 additions and 3626 deletions

View File

@ -1,43 +0,0 @@
name: fetch npm modules
run-name: fetch npm modules
on: [push]
jobs:
f0ck the f0cker:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15.2
env:
POSTGRES_USER: f0ck
POSTGRES_PASSWORD: f0ck
POSTGRES_DB: f0ck
POSTGRES_PORT: 5432
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 19
- run: npm ci
- name: Install PostgreSQL client
run: |
apt-get update
apt-get install --yes postgresql-client
- name: import f0ck database
run: psql -h postgres -d f0ck -U f0ck < f0ck.sql
env:
PGPASSWORD: f0ck
- name: create directories
run: mkdir -p tmp public/ca deleted/{ca,b,t}
- name: copy config
run: cp config_example.json config.json

View File

@ -1,7 +1,7 @@
# how to install: # how to install:
## dependencies ## dependencies
```bash ```bash
sudo pacman -S nodejs npm ffmpeg yt-dlp ffmpegthumbnailer postgresql imagemagick git mime-types sudo pacman -S nodejs npm ffmpeg yt-dlp ffmpegthumbnailer postgresql python python-pip imagemagick git mime-types
``` ```
## postgres ## postgres
```bash ```bash
@ -26,5 +26,7 @@ mkdir -p public/ca deleted/{ca,b,t}
cp config_example.json config.json cp config_example.json config.json
- edit config.json - edit config.json
- set up clients, as described here: https://git.lat/keinBot/cuffeo - set up clients, as described here: https://git.lat/keinBot/cuffeo
pip install nsfw_detector
#if this fails make sure to have enough dedicated ram or swap space, alternatively try with pip install nsfw_detector --no-cache-dir
npm start npm start
``` ```

View File

@ -22,33 +22,20 @@
"thumbnails": "/t", "thumbnails": "/t",
"coverarts": "/ca" "coverarts": "/ca"
}, },
"themes": [ "f0ck", "p1nk", "orange", "atmos", "amoled", "paper", "term", "iced", "f0ck95", "f0ck95d" ], "themes": [ "f0ck", "p1nk", "orange", "atmos", "amoled", "paper", "term", "iced" ],
"eps": 300, "eps": 294,
"cache": false, "cache": false,
"phrases": [ "phrases": [
"<img src=\"/s/img/crap/nyancat.gif\" style=\"margin-top: 5px\" />" "<img src=\"/s/img/crap/nyancat.gif\" style=\"margin-top: 5px\" />"
] ]
}, },
"clients": [{ "clients": [
"type": "irc",
"enabled": true, ],
"network": "n0xy",
"host": "irc.n0xy.net",
"port": 6697,
"ssl": true,
"selfSigned": false,
"sasl": false,
"nickname": "f0ckci",
"username": "f0ckci",
"realname": "f0ckci",
"channels": [
"#f0ck-dev"
]
}],
"sql": { "sql": {
"host": "localhost", "host": "localhost",
"user": "f0ck", "user": "f0ck",
"password": "f0ck", "password": "",
"database": "f0ck", "database": "f0ck",
"schema": "public", "schema": "public",
"multipleStatements": true "multipleStatements": true
@ -67,10 +54,8 @@
"audio/mpeg": "mpg", "audio/mpeg": "mpg",
"audio/mp3": "mp3", "audio/mp3": "mp3",
"audio/ogg": "ogg", "audio/ogg": "ogg",
"audio/opus": "opus",
"audio/flac": "flac", "audio/flac": "flac",
"audio/x-flac": "flac", "audio/x-flac": "flac",
"video/x-m4v": "mp4", "video/x-m4v": "mp4"
"video/x-matroska": "mkv"
} }
} }

32
debug/adduser.mjs Normal file
View File

@ -0,0 +1,32 @@
import db from '../src/inc/sql.mjs';
import lib from '../src/inc/lib.mjs';
import readline from 'node:readline/promises';
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const newuser = process.argv[2]?.length ? process.argv[2] : await rl.question('username: ');
const password = await rl.question('password: ');
const level = +(await rl.question('level (0-100): '));
rl.close();
if(!newuser.length || !password.length) {
console.log('nope lol');
process.exit();
}
const id = (await db`
insert into "user" ${
db({
login: newuser.toLowerCase(),
user: newuser,
password: await lib.hash(password),
level: level >= 0 && level <= 100 ? level : 0
})
}
returning id
`)[0]?.id;
console.log(`created new user ${newuser} with ID ${id}`);
process.exit();

View File

@ -1,95 +1,33 @@
import cfg from "../src/inc/config.mjs"; import sql from "../src/inc/sql.mjs";
import db from "../src/inc/sql.mjs"; import { promises as fs } from "fs";
import fs from "node:fs";
import readline from 'node:readline/promises';
const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const opts = {
b: "public/b",
// npm run clean -- --dry-run t: "public/t",
const dry = !!process.argv.filter(a => a == '--dry-run').length; tmp: "tmp"
console.log(`dry run? ${dry}`);
const dirs = {
b: "./public/b",
t: "./public/t",
tmp: "./tmp"
}; };
const files = {
b: (await fs.promises.readdir(dirs.b)) .filter(f => f !== '.empty'),
t: (await fs.promises.readdir(dirs.t)) .filter(f => f !== '.empty'),
tmp: (await fs.promises.readdir(dirs.tmp)).filter(f => f !== '.empty')
};
const extensions = [ ...Object.values(cfg.mimes), 'mov' ];
const count = { const count = {
missing: { b: [], t: [] }, b: 0, t: 0, tmp: 0
invalid: { b: [], t: [] },
spare: { b: [], t: [] },
tmp: files.tmp
};
const rows = await db`select id, dest from items where active = true`;
const f0cks = {
b: rows.flatMap(f => f.dest),
t: rows.flatMap(f => `${f.id}.webp`)
}; };
// missing const rows = await sql('items').select('id', 'dest');
for(const row of rows) { const ids = rows.map(r => r.id.toString() + ".webp");
if(!fs.existsSync(`${dirs.b}/${row.dest}`)) const dests = rows.map(r => r.dest);
count.missing.b.push(row.id); const files = {
if(!fs.existsSync(`${dirs.t}/${row.id}.webp`)) b: (await fs.readdir(opts.b)).filter(f => f !== '.empty'),
count.missing.t.push(row.id); t: await fs.readdir(opts.t)
};
const unused = {
b: files.b.filter(f => !dests.includes(f)),
t: files.t.filter(f => !ids.includes(f))
} }
// invalid count.b = (await Promise.all(unused.b.map(f => fs.rm(`${opts.b}/${f}`)))).length;
count.invalid.b = files.b.filter(f => !extensions.includes(f.toLowerCase().split('.')[1])); count.t = (await Promise.all(unused.t.map(f => fs.rm(`${opts.t}/${f}`)))).length;
count.invalid.t = files.t.filter(f => !f.endsWith('.webp'));
// spare // clear tmp
for(const file of files.b) const tmp = (await fs.readdir(opts.tmp)).filter(f => f !== '.empty');
if(!f0cks.b.includes(file)) count.tmp = (await Promise.all(tmp.map(f => fs.rm(`${opts.tmp}/${f}`)))).length;
count.spare.b.push(`${dirs.b}/${file}`);
for(const file of files.t) console.log(count, unused);
if(!f0cks.t.includes(file))
count.spare.t.push(`${dirs.t}/${file}`);
// show confusing summary
console.log(count);
// delete spare if --dry-run
if(!dry) {
let q;
if(count.spare.b.length > 0) {
q = (await rl.question(`delete ${count.spare.b.length} unnecessary files in ${dirs.b}? [y/N] `)) == 'y';
if(q) {
await Promise.all(count.spare.b.map(f => fs.promises.unlink(f)));
console.log(`deleted ${count.spare.b.length} files`);
}
else
console.log('abort...');
}
if(count.spare.t.length > 0) {
q = (await rl.question(`delete ${count.spare.t.length} unnecessary files in ${dirs.t}? [y/N] `)) == 'y';
if(q) {
await Promise.all(count.spare.t.map(f => fs.promises.unlink(f)));
console.log(`deleted ${count.spare.t.length} files`);
}
else
console.log('abort...');
}
if(files.tmp.length > 0) {
q = (await rl.question(`delete ${files.tmp.length} files in ${dirs.tmp}? [y/N] `)) == 'y';
if(q) {
await Promise.all(files.tmp.map(f => fs.promises.unlink(`${dirs.tmp}/${f}`)));
console.log(`deleted ${files.tmp.length} files`);
}
else
console.log('abort...');
}
}
// close connection
await db.end();
process.exit();

View File

@ -140,8 +140,7 @@ CREATE TABLE public.user_options (
user_id integer NOT NULL, user_id integer NOT NULL,
mode integer NOT NULL, mode integer NOT NULL,
theme character varying(50) NOT NULL, theme character varying(50) NOT NULL,
avatar integer DEFAULT 1 NOT NULL, avatar integer DEFAULT 1 NOT NULL
fullscreen integer DEFAULT 0 NOT NULL
); );
ALTER TABLE public.user_options OWNER TO f0ck; ALTER TABLE public.user_options OWNER TO f0ck;

BIN
nsfw_model.h5 Normal file

Binary file not shown.

32
package-lock.json generated
View File

@ -1,24 +1,24 @@
{ {
"name": "f0ckv2", "name": "f0ckv2",
"version": "2.2.1", "version": "2.2.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "f0ckv2", "name": "f0ckv2",
"version": "2.2.1", "version": "2.2.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cuffeo": "^1.2.2", "cuffeo": "^1.1.0",
"flumm-fetch": "^1.0.1", "flumm-fetch": "^1.0.1",
"flummpress": "^2.0.5", "flummpress": "^2.0.5",
"postgres": "^3.3.4" "postgres": "^3.0.1"
} }
}, },
"node_modules/cuffeo": { "node_modules/cuffeo": {
"version": "1.2.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz", "resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.1.0.tgz",
"integrity": "sha512-Pd2AL/Zp5RCAbaSbT7rLUOyxSEVCFovihaIaje7uYzpQMzIwPbFapQ/mn2d2iz+Tbkjs9LF+FD5JzAvANh9Wzw==", "integrity": "sha512-7lmx2dvREqCYwy8oUzk3Q0EkyLZkKQTYTBLEjNqKVbinzdEL45Oimi54lmBDFPlrrvTLzQkFvzKmiJ7Zcegi2w==",
"dependencies": { "dependencies": {
"flumm-fetch": "^1.0.1" "flumm-fetch": "^1.0.1"
} }
@ -34,9 +34,9 @@
"integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ==" "integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ=="
}, },
"node_modules/postgres": { "node_modules/postgres": {
"version": "3.3.4", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.1.0.tgz",
"integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==", "integrity": "sha512-yQbJtA7z6SsQuFiF01rmHlkG2gU5IEv9D/Pn2fu8Tz6x3/suNWZs4fIPd59rkL+dfVfhDqYQ7DU3YnUqzEDB5Q==",
"funding": { "funding": {
"type": "individual", "type": "individual",
"url": "https://github.com/sponsors/porsager" "url": "https://github.com/sponsors/porsager"
@ -45,9 +45,9 @@
}, },
"dependencies": { "dependencies": {
"cuffeo": { "cuffeo": {
"version": "1.2.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz", "resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.1.0.tgz",
"integrity": "sha512-Pd2AL/Zp5RCAbaSbT7rLUOyxSEVCFovihaIaje7uYzpQMzIwPbFapQ/mn2d2iz+Tbkjs9LF+FD5JzAvANh9Wzw==", "integrity": "sha512-7lmx2dvREqCYwy8oUzk3Q0EkyLZkKQTYTBLEjNqKVbinzdEL45Oimi54lmBDFPlrrvTLzQkFvzKmiJ7Zcegi2w==",
"requires": { "requires": {
"flumm-fetch": "^1.0.1" "flumm-fetch": "^1.0.1"
} }
@ -63,9 +63,9 @@
"integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ==" "integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ=="
}, },
"postgres": { "postgres": {
"version": "3.3.4", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.1.0.tgz",
"integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==" "integrity": "sha512-yQbJtA7z6SsQuFiF01rmHlkG2gU5IEv9D/Pn2fu8Tz6x3/suNWZs4fIPd59rkL+dfVfhDqYQ7DU3YnUqzEDB5Q=="
} }
} }
} }

View File

@ -3,21 +3,21 @@
"version": "2.2.1", "version": "2.2.1",
"description": "f0ck, kennste?", "description": "f0ck, kennste?",
"main": "index.mjs", "main": "index.mjs",
"type": "module",
"scripts": { "scripts": {
"start": "node --trace-uncaught src/index.mjs", "start": "node --experimental-json-modules src/index.mjs",
"trigger": "node debug/trigger.mjs", "trigger": "node --experimental-json-modules debug/trigger.mjs",
"autotagger": "node debug/autotagger.mjs", "autotagger": "node --experimental-json-modules debug/autotagger.mjs",
"thumbnailer": "node debug/thumbnailer.mjs", "thumbnailer": "node --experimental-json-modules debug/thumbnailer.mjs",
"test": "node debug/test.mjs", "test": "node --experimental-json-modules debug/test.mjs",
"clean": "node debug/clean.mjs" "clean": "node --experimental-json-modules debug/clean.mjs",
"adduser": "node --experimental-json-modules debug/adduser.mjs"
}, },
"author": "Flummi", "author": "Flummi",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cuffeo": "^1.2.2", "cuffeo": "^1.1.0",
"flumm-fetch": "^1.0.1", "flumm-fetch": "^1.0.1",
"flummpress": "^2.0.5", "flummpress": "^2.0.5",
"postgres": "^3.3.4" "postgres": "^3.0.1"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,7 @@
display: none; display: none;
} }
.v0ck_player_controls svg { svg {
width: 20px; width: 20px;
height: 20px; height: 20px;
fill: #fff; fill: #fff;

View File

@ -6,7 +6,5 @@
<symbol id="heart_regular" viewBox="0 0 512 512"><path d="M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"/></symbol> <symbol id="heart_regular" viewBox="0 0 512 512"><path d="M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"/></symbol>
<symbol id="heart_solid" viewBox="0 0 512 512"><path d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z"/></symbol> <symbol id="heart_solid" viewBox="0 0 512 512"><path d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z"/></symbol>
<symbol id="cross" viewBox="0 0 512 512"><path d="M53.6,62.3 L458.4,458.4 M458.4,62.3 L53.6,458.4" style="stroke-linecap: round;stroke-width: 60;"/></symbol> <symbol id="cross" viewBox="0 0 512 512"><path d="M53.6,62.3 L458.4,458.4 M458.4,62.3 L53.6,458.4" style="stroke-linecap: round;stroke-width: 60;"/></symbol>
<symbol id="window-maximize" viewBox="0 0 512 512"><path d="M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 394c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V192h416v234z" style="fill: var(--maximize_button);"/></symbol>
<symbol id="window-minimize" viewBox="0 0 512 512"><path d="M464 0H144c-26.5 0-48 21.5-48 48v48H48c-26.5 0-48 21.5-48 48v320c0 26.5 21.5 48 48 48h320c26.5 0 48-21.5 48-48v-48h48c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zm-96 464H48V256h320v208zm96-96h-48V144c0-26.5-21.5-48-48-48H144V48h320v320z" style="fill: var(--maximize_button);"/></symbol>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -85,7 +85,7 @@ const flash = ({ type, msg }) => {
[...document.querySelectorAll("#tags > .badge")].forEach(tag => tag.parentElement.removeChild(tag)); [...document.querySelectorAll("#tags > .badge")].forEach(tag => tag.parentElement.removeChild(tag));
_tags.reverse().forEach(tag => { _tags.reverse().forEach(tag => {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = `/tag/${tag.normalized}`; a.href = `/tag/${tag.tag}`;
a.style = "color: inherit !important"; a.style = "color: inherit !important";
a.innerHTML = tag.tag; a.innerHTML = tag.tag;
a.addEventListener("click", editTagEvent); // tmp a.addEventListener("click", editTagEvent); // tmp

View File

@ -1,11 +1,3 @@
window.requestAnimFrame = (function(){
return window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| function(callback) { window.setTimeout(callback, 1000 / 60);};
})();
(() => { (() => {
let video; let video;
if(elem = document.querySelector("#my-video")) { if(elem = document.querySelector("#my-video")) {
@ -16,42 +8,6 @@ window.requestAnimFrame = (function(){
document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden'); document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden');
} }
}); });
document.getElementById('togglebg').addEventListener('click', function (e) {
e.preventDefault();
background = !background;
localStorage.setItem('background', background.toString());
var canvas = document.getElementById('bg');
if (background) {
canvas.classList.add('fader-in');
canvas.classList.remove('fader-out');
} else {
canvas.classList.add('fader-out');
canvas.classList.remove('fader-in');
}
animationLoop();
});
if(elem !== null) {
if(localStorage.getItem('background') == undefined) {
localStorage.setItem('background', 'true');
}
var background = localStorage.getItem('background') === 'true';
var canvas = document.getElementById('bg');
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)
return;
context.drawImage(video, 0, 0, cw, ch);
window.requestAnimFrame(animationLoop);
}
elem.addEventListener('play', animationLoop);
}
} }
let tt = false; let tt = false;
@ -90,44 +46,19 @@ window.requestAnimFrame = (function(){
i.src = e.src; i.src = e.src;
}); });
// <wheeler>
const wheelEventListener = function(event) {
if (event.target.closest('.media-object')) {
if (event.deltaY < 0) {
document.getElementById('next').click();
} else if (event.deltaY > 0) {
document.getElementById('prev').click();
}
}
};
window.addEventListener('wheel', wheelEventListener);
// </wheeler>
if(f0ckimage = document.querySelector("img#f0ck-image")) { if(f0ckimage = document.querySelector("img#f0ck-image")) {
const f0ckimagescroll = document.querySelector("#image-scroll"); const f0ckimagescroll = document.querySelector("#image-scroll");
let isImageExpanded = false;
console.log("entry point - image unclicked")
f0ckimage.addEventListener("click", async e => { f0ckimage.addEventListener("click", async e => {
e.preventDefault(); e.preventDefault();
const img = await imgSize(f0ckimage); const img = await imgSize(f0ckimage);
console.log("img clicked"); if(img.width > img.height)
if (isImageExpanded) { return;
isImageExpanded = false; f0ckimagescroll.hasAttribute("style")
f0ckimagescroll.removeAttribute("style"); ? f0ckimagescroll.removeAttribute("style")
f0ckimage.removeAttribute("style"); : f0ckimagescroll.setAttribute("style", "overflow-y: scroll");
console.log("image is not expanded") f0ckimage.hasAttribute("style")
window.addEventListener('wheel', wheelEventListener); ? f0ckimage.removeAttribute("style")
} else { : f0ckimage.setAttribute("style", "max-height: none; height: auto; width: 100%; position: absolute; left: 0; border: var(--img-border-width) solid var(--img-border-color); border-top: none; border-bottom: none;");
if (img.width > img.height) return;
isImageExpanded = true;
window.removeEventListener('wheel', wheelEventListener);
f0ckimagescroll.setAttribute("style", "overflow-y: scroll");
f0ckimage.setAttribute("style", "max-height: none; height: auto; width: 100%; position: absolute; left: 0; border: var(--img-border-width) solid var(--img-border-color); border-top: none; border-bottom: none;");
}
}); });
} }
// </image-responsive> // </image-responsive>
@ -137,7 +68,7 @@ window.requestAnimFrame = (function(){
const scroll_treshold = 1; const scroll_treshold = 1;
if([...document.querySelectorAll("div.posts")].length === 1) { if([...document.querySelectorAll("div.posts")].length === 1) {
document.addEventListener("wheel", e => { document.addEventListener("wheel", e => {
if(Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down if(Math.ceil(window.innerHeight + window.scrollY) >= document.body.offsetHeight && e.deltaY > 0) { // down
if(elem = document.querySelector(".pagination > .next:not(.disabled)")) { if(elem = document.querySelector(".pagination > .next:not(.disabled)")) {
if(tts < scroll_treshold) { if(tts < scroll_treshold) {
document.querySelector("div#footbar").style.boxShadow = "inset 0px 4px 0px var(--footbar-color)"; document.querySelector("div#footbar").style.boxShadow = "inset 0px 4px 0px var(--footbar-color)";
@ -303,8 +234,4 @@ window.requestAnimFrame = (function(){
}); });
} }
// </mediakeys> // </mediakeys>
})();
// <scroller>
// </scroller>
})();

View File

@ -8,7 +8,6 @@ const Cookie = {
opts['max-age'] = opts.days * 60 * 60 * 24; opts['max-age'] = opts.days * 60 * 60 * 24;
delete opts.days; delete opts.days;
} }
opts.SameSite = 'Strict';
opts = Object.entries(opts).reduce((accumulatedStr, [k, v]) => `${accumulatedStr}; ${k}=${v}`, ''); opts = Object.entries(opts).reduce((accumulatedStr, [k, v]) => `${accumulatedStr}; ${k}=${v}`, '');
document.cookie = name + '=' + encodeURIComponent(value) + opts document.cookie = name + '=' + encodeURIComponent(value) + opts
} }
@ -45,21 +44,4 @@ const Cookie = {
Cookie.set("theme", themes[i], { path: "/", days: 360 }); Cookie.set("theme", themes[i], { path: "/", days: 360 });
} }
}); });
if(tbuttonfull = document.querySelector('svg#a_tfull')) {
tbuttonfull.addEventListener('click', e => {
let f = Cookie.get('fullscreen');
if(f == 1) {
Cookie.set('fullscreen', 0);
document.querySelector('html').setAttribute('res', '');
tbuttonfull.innerHTML = `<use href="/s/img/iconset.svg#window-maximize"></use>`;
}
else {
Cookie.set('fullscreen', 1);
document.querySelector('html').setAttribute('res', 'fullscreen');
tbuttonfull.innerHTML = `<use href="/s/img/iconset.svg#window-minimize"></use>`;
}
return true;
});
}
})(); })();

View File

@ -18,7 +18,6 @@ const tpl_player = svg => `<div class="v0ck_player_controls">
<input type="range" name="volume" min="0" max="1" step="0.01" value="1" /> <input type="range" name="volume" min="0" max="1" step="0.01" value="1" />
<button class="v0ck_player_button v0ck_playtime">00:00 / 00:00</button> <button class="v0ck_player_button v0ck_playtime">00:00 / 00:00</button>
<span style="flex: 30"></span> <span style="flex: 30"></span>
<button id="togglebg">&#128161;</button>
<button data-skip="-10" class="v0ck_player_button"> <button data-skip="-10" class="v0ck_player_button">
<svg style="width: 20px; height: 20px;"><use id="v0ck_svg_backward" href="${svg}#backward"></use></svg> <svg style="width: 20px; height: 20px;"><use id="v0ck_svg_backward" href="${svg}#backward"></use></svg>
</button> </button>

View File

@ -1,32 +0,0 @@
import fetch from 'flumm-fetch';
import cfg from './config.mjs';
export default new class autotagger {
async isNSFW(filename, filesize) {
let opts = {
method: 'POST',
};
let apiurl;
if(filesize < 4194304) {
apiurl = cfg.apis.nsfw1.url;
opts.headers = cfg.apis.nsfw1.headers;
opts.body = JSON.stringify({
DataRepresentation: "URL",
Value: `${cfg.main.url.full}/b/${filename}`
});
}
else {
apiurl = cfg.apis.nsfw2.url;
opts.headers = cfg.apis.nsfw2.headers;
opts.body = JSON.stringify({
url: `${cfg.main.url.full}/b/${filename}`
})
}
const res = await (await fetch(apiurl, opts)).json();
if(filesize < 4194304)
return res.IsImageAdultClassified || res.RacyClassificationScore > 0.6;
else
return res.unsafe;
};
};

View File

@ -1,318 +0,0 @@
import logger from "../log.mjs";
import { getLevel } from "../../inc/admin.mjs";
import { promises as fs } from "fs";
import cfg from "../config.mjs";
import db from "../sql.mjs";
import lib from "../lib.mjs";
const tagkeyboard = id => {
const tags = [{
tag: 'music',
id: 115
}, {
tag: 'german',
id: 329
}, {
tag: 'cat',
id: 217
}, {
tag: 'doggo',
id: 5
}];
return Promise.all(tags.map(async t => ({ text: `${await lib.hasTag(id, t.id) ? '✓ ' : ''}${t.tag}`, callback_data: `b_settag_${t.id}:${id}` })));
};
export default async bot => {
return [{
name: "callback_query",
listener: "callback_query",
f: async e => {
logger.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${e.message}`);
let [ cmd, id ] = e.opt.data.split(':');
let f0ck;
id = +id;
if(cmd.startsWith('b_settag_')) {
const tagid = +cmd.replace('b_settag_', '');
if(!(await lib.getTags(id)).filter(tag => tag.id == tagid).length) {
// insert
await db`
insert into "tags_assign" ${
db({
item_id: id,
tag_id: tagid,
user_id: 1
})
}
`;
}
else {
// delete
await db`
delete from "tags_assign"
where tag_id = ${tagid}
and item_id = ${id}
`;
}
const keyboard = await tagkeyboard(id);
return await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
...keyboard
], [
{ text: 'back', callback_data: `b_back:${id}` }
]]
})
});
}
switch(cmd) {
case "b_tags":
if(!id)
return;
const keyboard = await tagkeyboard(id);
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
...keyboard
], [
{ text: 'back', callback_data: `b_back:${id}` }
]]
})
});
break;
case "b_back":
if(!id)
return;
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(id, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${id}` },
{ text: (await lib.hasTag(id, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${id}` },
{ text: 'tags', callback_data: `b_tags:${id}` },
{ text: '❌ delete', callback_data: `b_delete:${id}` }
], [
{ text: `open f0ck #${id}`, url: `${cfg.main.url.full}/${id}` }
]]
})
});
break;
case "b_sfw":
if(!id)
return;
if(!await lib.hasTag(id, 1)) {
// insert
await db`
insert into "tags_assign" ${
db({
item_id: id,
tag_id: 1, // sfw
user_id: 1
})
}
`;
if(await lib.hasTag(id, 2)) {
await db`
delete from "tags_assign"
where tag_id = 2
and item_id = ${id}
`;
}
}
else {
// delete
await db`
delete from "tags_assign"
where tag_id = 1
and item_id = ${id}
`;
}
return await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(id, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${id}` },
{ text: (await lib.hasTag(id, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${id}` },
{ text: 'tags', callback_data: `b_tags:${id}` },
{ text: '❌ delete', callback_data: `b_delete:${id}` }
], [
{ text: `open f0ck #${id}`, url: `${cfg.main.url.full}/${id}` }
]]
})
});
break;
case "b_nsfw":
if(!id)
return;
if(!await lib.hasTag(id, 2)) {
// insert
await db`
insert into "tags_assign" ${
db({
item_id: id,
tag_id: 2, // nsfw
user_id: 1
})
}
`;
if(await lib.hasTag(id, 1)) {
await db`
delete from "tags_assign"
where tag_id = 1
and item_id = ${id}
`;
}
}
else {
// delete
await db`
delete from "tags_assign"
where tag_id = 2
and item_id = ${id}
`;
}
return await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(id, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${id}` },
{ text: (await lib.hasTag(id, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${id}` },
{ text: 'tags', callback_data: `b_tags:${id}` },
{ text: '❌ delete', callback_data: `b_delete:${id}` }
], [
{ text: `open f0ck #${id}`, url: `${cfg.main.url.full}/${id}` }
]]
})
});
break;
case "b_delete":
if(id <= 1)
return;
e.user = {
prefix: `${e.raw.reply_to_message.from.username}!${e.raw.reply_to_message.from.id}@${e.network}`,
nick: e.raw.reply_to_message.from.first_name,
username: e.raw.reply_to_message.from.username,
account: e.raw.reply_to_message.from.id.toString()
};
f0ck = await db`
select dest, mime, username, userchannel, usernetwork
from "items"
where
id = ${id} and
active = 'true'
limit 1
`;
const level = getLevel(e.user).level;
if(f0ck.length === 0) {
return await e.reply(`f0ck ${id}: f0ck not found`);
}
if(
(f0ck[0].username !== (e.user.nick || e.user.username) ||
f0ck[0].userchannel !== e.channel ||
f0ck[0].usernetwork !== e.network) &&
level < 100
) {
return await e.reply(`f0ck ${id}: insufficient permissions`);
}
if(~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
return await e.reply(`f0ck ${id}: too late lol`);
}
await db`update "items" set active = 'false' where id = ${id}`;
await fs.copyFile(`./public/b/${f0ck[0].dest}`, `./deleted/b/${f0ck[0].dest}`).catch(_=>{});
await fs.copyFile(`./public/t/${id}.webp`, `./deleted/t/${id}.webp`).catch(_=>{});
await fs.unlink(`./public/b/${f0ck[0].dest}`).catch(_=>{});
await fs.unlink(`./public/t/${id}.webp`).catch(_=>{});
if(f0ck[0].mime.startsWith('audio')) {
await fs.copyFile(`./public/ca/${id}.webp`, `./deleted/ca/${id}.webp`).catch(_=>{});
await fs.unlink(`./public/ca/${id}.webp`).catch(_=>{});
}
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(id, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${id}` },
{ text: (await lib.hasTag(id, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${id}` },
{ text: 'tags', callback_data: `b_tags:${id}` },
{ text: 'recover', callback_data: `b_recover:${id}` }
], [
{ text: `open f0ck #${id}`, url: `${cfg.main.url.full}/${id}` }
]]
})
});
break;
case "b_recover":
if(id <= 1)
return;
e.user = {
prefix: `${e.raw.reply_to_message.from.username}!${e.raw.reply_to_message.from.id}@${e.network}`,
nick: e.raw.reply_to_message.from.first_name,
username: e.raw.reply_to_message.from.username,
account: e.raw.reply_to_message.from.id.toString()
};
f0ck = await db`
select dest, mime
from "items"
where
id = ${id} and
active = 'false'
limit 1
`;
if(f0ck.length === 0) {
return await e.reply(`f0ck ${id}: f0ck not found`);
}
await fs.copyFile(`./deleted/b/${f0ck[0].dest}`, `./public/b/${f0ck[0].dest}`).catch(_=>{});
await fs.copyFile(`./deleted/t/${id}.webp`, `./public/t/${id}.webp`).catch(_=>{});
await fs.unlink(`./deleted/b/${f0ck[0].dest}`).catch(_=>{});
await fs.unlink(`./deleted/t/${id}.webp`).catch(_=>{});
if(f0ck[0].mime.startsWith('audio')) {
await fs.copyFile(`./deleted/ca/${id}.webp`, `./public/ca/${id}.webp`).catch(_=>{});
await fs.unlink(`./deleted/ca/${id}.webp`).catch(_=>{});
}
await db`update "items" set active = 'true' where id = ${id}`;
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(id, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${id}` },
{ text: (await lib.hasTag(id, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${id}` },
{ text: 'tags', callback_data: `b_tags:${id}` },
{ text: '❌ delete', callback_data: `b_delete:${id}` }
], [
{ text: `open f0ck #${id}`, url: `${cfg.main.url.full}/${id}` }
]]
})
});
break;
default:
await e.reply('lol');
}
}
}];
};

View File

@ -40,7 +40,7 @@ export default async bot => {
} }
catch(err) { catch(err) {
console.error(err); console.error(err);
await e.reply(`${t[0]}: An error occured.`); e.reply(`${t[0]}: An error occured.`);
logger.error(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${err.toString ? err : JSON.stringify(err)}`); logger.error(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${err.toString ? err : JSON.stringify(err)}`);
} }
}); });

View File

@ -84,43 +84,32 @@ export default new class {
// async funcs // async funcs
async countf0cks() { async countf0cks() {
const tagged = +(await db` const tagged = (await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign group by item_id) and active = true where id in (select item_id from tags_assign group by item_id)
`)[0].total; `)[0].total;
const untagged = +(await db` const untagged = (await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id not in (select item_id from tags_assign group by item_id) and active = true where id not in (select item_id from tags_assign group by item_id)
`)[0].total; `)[0].total;
const sfw = +(await db` const sfw = (await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign where tag_id = 1 group by item_id) and active = true where id in (select item_id from tags_assign where tag_id = 1 group by item_id)
`)[0].total; `)[0].total;
const nsfw = +(await db` const nsfw = (await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign where tag_id = 2 group by item_id) and active = true where id in (select item_id from tags_assign where tag_id = 2 group by item_id)
`)[0].total; `)[0].total;
const deleted = +(await db`
select count(*) as total
from "items"
where active = false
`)[0].total;
const lastf0ck = +(await db`
select max(id) as id
from "items"
`)[0].id;
return { return {
tagged, tagged,
untagged, untagged,
total: tagged + untagged, total: +tagged + +untagged,
deleted,
untracked: lastf0ck - (tagged + untagged + deleted),
sfw, sfw,
nsfw, nsfw
}; };
}; };
async hash(str) { async hash(str) {
@ -172,17 +161,6 @@ export default new class {
} }
return tags; return tags;
}; };
async hasTag(itemid, tagid) {
const tag = (await db`
select *
from "tags_assign"
where
item_id = ${+itemid} and
tag_id = ${+tagid}
limit 1
`).length;
return !!tag;
};
async detectNSFW(dest) { async detectNSFW(dest) {
return false; return false;
const { stdout, stderr } = await exec( const { stdout, stderr } = await exec(

View File

@ -1,122 +0,0 @@
import fetch from "flumm-fetch";
import { exec as _exec } from "child_process";
import fs from "fs";
import db from "./sql.mjs";
export default new class queue {
constructor() {
};
addqueue(e, link) {
//this.#queue.push(e, link);
};
exec(cmd) {
return new Promise((resolve, reject) => {
_exec(cmd, { maxBuffer: 5e3 * 1024 }, (err, stdout, stderr) => {
if(err)
return reject(err);
if(stderr)
console.error(stderr);
resolve({ stdout: stdout });
});
});
};
async genuuid() {
return (await db`
select gen_random_uuid() as uuid
`)[0].uuid.substring(0, 8);
};
async checkrepostlink(link) {
const q_repost = await db`
select id
from "items"
where src = ${link}
`;
return q_repost.length > 0 ? q_repost[0].id : false;
};
async checkrepostsum(checksum) {
const q_repost = await db`
select id
from "items"
where checksum = ${checksum}
`;
return q_repost.length > 0 ? q_repost[0].id : false;
};
async getItemID(filename) {
return (await db`
select *
from "items"
where dest = ${filename}
limit 1
`)[0].id;
};
async genThumbnail(filename, mime, itemid, link) {
if(mime.startsWith('video/') || mime == 'image/gif')
await this.exec(`ffmpegthumbnailer -i./public/b/${filename} -s1024 -o./tmp/${itemid}.png`);
else if(mime.startsWith('image/') && mime != 'image/gif')
await this.exec(`convert "./public/b/${filename}[0]" ./tmp/${itemid}.png`);
else if(mime.startsWith('audio/')) {
if(link.match(/soundcloud/)) {
let cover = (await this.exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --get-thumbnail "${link}"`)).stdout.trim();
if(!cover.match(/default_avatar/)) {
cover = cover.replace(/-(large|original)\./, '-t500x500.');
try {
await this.exec(`wget "${cover}" -O ./tmp/${itemid}.jpg`);
const size = (await fs.promises.stat(`./tmp/${itemid}.jpg`)).size;
if(size >= 0) {
await this.exec(`convert ./tmp/${itemid}.jpg ./tmp/${itemid}.png`);
await this.exec(`convert ./tmp/${itemid}.jpg ./public/ca/${itemid}.webp`);
}
} catch(err) {}
}
else {
await this.exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`);
await this.exec(`convert ./tmp/${itemid}.png ./public/ca/${itemid}.webp`);
}
}
else {
await this.exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`);
await this.exec(`convert ./tmp/${itemid}.png ./public/ca/${itemid}.webp`);
}
}
await this.exec(`convert "./tmp/${itemid}.png" -resize "128x128^" -gravity center -crop 128x128+0+0 +repage ./public/t/${itemid}.webp`);
await fs.promises.unlink(`./tmp/${itemid}.png`).catch(_=>{});
await fs.promises.unlink(`./tmp/${itemid}.jpg`).catch(_=>{});
return true;
};
// tags
async tagSFW(itemid) {
return await db`
insert into "tags_assign" ${
db({
item_id: itemid,
tag_id: 1,
user_id: 1
})
}
`;
};
async tagNSFW(itemid) {
return await db`
insert into "tags_assign" ${
db({
item_id: itemid,
tag_id: 2,
user_id: 1
})
}
`;
};
};

View File

@ -32,7 +32,7 @@ export default {
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` } ${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` } ${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
${ mime ? db`and items.mime ilike ${smime}` : db`` } ${ mime ? db`and items.mime ilike ${smime}` : db`` }
${ !o.session && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` } ${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag group by items.id, tags.tag
`)?.length || 0; `)?.length || 0;
@ -66,7 +66,7 @@ export default {
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` } ${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` } ${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
${ mime ? db`and items.mime ilike ${smime}` : db`` } ${ mime ? db`and items.mime ilike ${smime}` : db`` }
${ !o.session && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` } ${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag, ta.tag_id group by items.id, tags.tag, ta.tag_id
order by items.id desc order by items.id desc
offset ${offset} offset ${offset}
@ -128,7 +128,7 @@ export default {
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` } ${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` } ${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
${ mime ? db`and items.mime ilike ${smime}` : db`` } ${ mime ? db`and items.mime ilike ${smime}` : db`` }
${ !o.session && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` } ${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag, ta.tag_id group by items.id, tags.tag, ta.tag_id
order by items.id desc order by items.id desc
`; `;
@ -224,7 +224,7 @@ export default {
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` } ${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
${ user ? db`and items.username ilike ${'%'+user+'%'}` : db`` } ${ user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
${ mime ? db`and items.mime ilike ${smime}` : db`` } ${ mime ? db`and items.mime ilike ${smime}` : db`` }
${ !o.session && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` } ${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag group by items.id, tags.tag
order by random() order by random()
limit 1 limit 1

View File

@ -16,14 +16,8 @@ const auth = async (req, res, next) => {
export default (router, tpl) => { export default (router, tpl) => {
router.get(/^\/login(\/)?$/, async (req, res) => { router.get(/^\/login(\/)?$/, async (req, res) => {
if(req.cookies.session) { if(req.cookies.session)
return res.reply({ return res.reply({ body: "du bist schon eingeloggt lol" });
body: tpl.render('error', {
message: "you're already logged in lol",
tmp: null
}, req)
});
}
res.reply({ res.reply({
body: tpl.render("login", { theme: req.cookies.theme ?? "f0ck" }) body: tpl.render("login", { theme: req.cookies.theme ?? "f0ck" })
}); });
@ -106,11 +100,7 @@ export default (router, tpl) => {
router.get(/^\/admin(\/)?$/, auth, async (req, res) => { // frontpage router.get(/^\/admin(\/)?$/, auth, async (req, res) => { // frontpage
res.reply({ res.reply({
body: tpl.render("admin", { body: tpl.render("admin", { totals: await lib.countf0cks(), session: req.session }, req)
totals: await lib.countf0cks(),
session: req.session,
tmp: null
}, req)
}); });
}); });
@ -126,8 +116,7 @@ export default (router, tpl) => {
body: tpl.render("admin/sessions", { body: tpl.render("admin/sessions", {
session: req.session, session: req.session,
sessions: rows, sessions: rows,
totals: await lib.countf0cks(), totals: await lib.countf0cks()
tmp: null
}, req) }, req)
}); });
}); });
@ -136,8 +125,7 @@ export default (router, tpl) => {
exec("journalctl -qeu f0ck --no-pager", (err, stdout) => { exec("journalctl -qeu f0ck --no-pager", (err, stdout) => {
res.reply({ res.reply({
body: tpl.render("admin/log", { body: tpl.render("admin/log", {
log: stdout.split("\n").slice(0, -1), log: stdout.split("\n").slice(0, -1)
tmp: null
}, req) }, req)
}); });
}); });
@ -182,7 +170,6 @@ export default (router, tpl) => {
from "items" from "items"
where where
active = 'false' active = 'false'
order by id desc
`; `;
if(_posts.length === 0) { if(_posts.length === 0) {
@ -191,15 +178,11 @@ export default (router, tpl) => {
}); });
} }
const posts = await Promise.all(_posts.map(async p => ({ const posts = await Promise.all(_posts.map(async p => ({ ...p, thumbnail: (await fs.readFile(`./deleted/t/${p.id}.webp`)).toString('base64') })));
...p,
thumbnail: (await fs.readFile(`./deleted/t/${p.id}.webp`)).toString('base64')
})));
res.reply({ res.reply({
body: tpl.render('admin/recover', { body: tpl.render('admin/recover', {
posts, posts
tmp: null
}, req) }, req)
}); });
}); });

View File

@ -14,7 +14,7 @@ export default (router, tpl) => {
const user = decodeURIComponent(req.params.user); const user = decodeURIComponent(req.params.user);
const query = await db` const query = await db`
select "user".user, "user".created_at, user_options.* select "user".user, user_options.*
from user_options from user_options
left join "user" on "user".id = user_options.user_id left join "user" on "user".id = user_options.user_id
where "user".user ilike ${user} where "user".user ilike ${user}
@ -44,24 +44,14 @@ export default (router, tpl) => {
session: !!req.session session: !!req.session
}); });
const count = { if('items' in f0cks)
f0cks: 0,
favs: 0
};
if('items' in f0cks) {
count.f0cks = f0cks.items.length;
f0cks.items = f0cks.items.slice(0, 50); f0cks.items = f0cks.items.slice(0, 50);
} if('items' in favs)
if('items' in favs) {
count.favs = favs.items.length;
favs.items = favs.items.slice(0, 50); favs.items = favs.items.slice(0, 50);
}
const data = { const data = {
user: query[0], user: query[0],
f0cks, f0cks,
count,
favs, favs,
tmp: null tmp: null
}; };
@ -128,5 +118,46 @@ export default (router, tpl) => {
res.redirect(`/${referer}`); res.redirect(`/${referer}`);
}); });
router.get(/^\/ranking$/, async (req, res) => {
try {
const list = await db`
select
"user".user,
coalesce("user_options".avatar, ${await lib.getDefaultAvatar()}) as avatar,
count(distinct(tag_id, item_id)) as count
from "tags_assign"
left join "user" on "user".id = "tags_assign".user_id
left join "user_options" on "user_options".user_id = "user".id
group by "user".user, "user_options".avatar
order by count desc
`;
const stats = await lib.countf0cks();
const hoster = await db`
with t as (
select
split_part(substring(src, position('//' in src)+2), '/', 1) part
from items
)
select t.part, count(t.part) as c
from t
group by t.part
order by c desc
limit 20
`;
res.reply({
body: tpl.render('ranking', {
list,
stats,
hoster,
tmp: null
}, req)
});
} catch(err) {
res.end(JSON.stringify(err.message));
}
});
return router; return router;
}; };

View File

@ -1,97 +0,0 @@
import db from "../../inc/sql.mjs";
import lib from "../lib.mjs";
import config from "../config.mjs";
import fetch from "flumm-fetch";
export default (router, tpl) => {
router.get(/^\/ranking$/, async (req, res) => {
try {
const list = await db`
select
"user".user,
coalesce("user_options".avatar, ${await lib.getDefaultAvatar()}) as avatar,
count(distinct(tag_id, item_id)) as count
from "tags_assign"
left join "user" on "user".id = "tags_assign".user_id
left join "user_options" on "user_options".user_id = "user".id
group by "user".user, "user_options".avatar
order by count desc
`;
const stats = await lib.countf0cks();
const hoster = await db`
with t as (
select
split_part(substring(src, position('//' in src)+2), '/', 1) part
from items
)
select t.part, count(t.part) as c
from t
group by t.part
order by c desc
limit 20
`;
const favotop = await db`
select item_id, count(*) favs
from favorites
group by item_id
having count(*) > 1
order by favs desc
limit 10
`;
res.reply({
body: tpl.render('ranking', {
list,
stats,
hoster,
favotop,
tmp: null
}, req)
});
} catch(err) {
res.end(JSON.stringify(err.message));
}
});
router.get(/^\/top10$/, async (req, res) => {
const d = new Date();
d.setMonth(d.getMonth() - 1);
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const year = d.getFullYear();
const url = `${config.apis.stats.url1}/${year}-${month}/${config.apis.stats.url2}`;
const options = {
method: 'GET',
headers: {
Authorization: config.apis.stats.auth
}
};
const topres = await (await fetch(url, options)).text();
const list = topres.match(/(f0ck.me\/b\/)(?<link>.{8}\.\w*)/g).slice(0, 10).map(e => e.split('/')[2]);
const f0cks = [];
for(const l of list) {
const f = await db`
select id, username
from items
where dest = ${l}
limit 1
`;
f0cks.push(f[0]);
}
res.reply({
body: tpl.render('top10', {
f0cks,
year,
month,
tmp: null
}, req)
});
});
return router;
};

View File

@ -12,19 +12,5 @@ export default (router, tpl) => {
"Location": req.headers.referer ?? "/" "Location": req.headers.referer ?? "/"
}).end(); }).end();
}); });
router.get(/^\/tfull\//, async (req, res) => {
let full = req.session.fullscreen;
if(full == 1)
full = 0;
else
full = 1;
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
"Set-Cookie": `theme=${full}; Path=/`,
"Location": req.headers.referer ?? "/"
}).end();
});
return router; return router;
}; };

View File

@ -1,5 +1,4 @@
import { getLevel } from "../admin.mjs"; import { getLevel } from "../admin.mjs";
import lib from "../lib.mjs";
import fetch from "flumm-fetch"; import fetch from "flumm-fetch";
import vm from "vm"; import vm from "vm";
@ -8,9 +7,7 @@ let context = vm.createContext({
e: null, e: null,
bot: null, bot: null,
admins: null, admins: null,
fetch, fetch: fetch,
lib,
console,
a: null, a: null,
resolve: null resolve: null
@ -22,16 +19,16 @@ export default async bot => {
name: "level", name: "level",
call: /^!level (.*)/i, call: /^!level (.*)/i,
active: true, active: true,
f: async e => { f: e => {
const user = e.message.trim().substring(7); const user = e.message.trim().substring(7);
await e.reply( JSON.stringify( getLevel( e.self.user.get(user) || {} ) ) ); e.reply( JSON.stringify( getLevel( e.self.user.get(user) || {} ) ) );
} }
}, { }, {
name: "self", name: "self",
call: /^!self$/i, call: /^!self$/i,
active: true, active: true,
f: async e => { f: e => {
await e.reply( JSON.stringify( e.user ) ); e.reply( JSON.stringify( e.user ) );
} }
}, { }, {
name: "sandbox_debug", name: "sandbox_debug",
@ -44,7 +41,6 @@ export default async bot => {
context.e = e; context.e = e;
context.bot = bot; context.bot = bot;
context.level = getLevel; context.level = getLevel;
context.hasTag = lib.hasTag;
context.a = null; context.a = null;
await new Promise(resolve => { await new Promise(resolve => {
@ -56,9 +52,9 @@ export default async bot => {
let output = JSON.stringify(context.a); let output = JSON.stringify(context.a);
if(output.length > maxoutput) if(output.length > maxoutput)
return await e.reply(`fuggg, Ausgabe wäre viel zu lang! (${output.length} Zeichen :DDDDDD)`); return e.reply(`fuggg, Ausgabe wäre viel zu lang! (${output.length} Zeichen :DDDDDD)`);
else else
return await e.reply(output); return e.reply(output);
} }
}]; }];
}; };

View File

@ -27,7 +27,7 @@ export default async bot => {
const level = getLevel(e.user).level; const level = getLevel(e.user).level;
if(f0ck.length === 0) { if(f0ck.length === 0) {
await e.reply(`f0ck ${id}: f0ck not found`); e.reply(`f0ck ${id}: f0ck not found`);
continue; continue;
} }
@ -37,12 +37,12 @@ export default async bot => {
f0ck[0].usernetwork !== e.network) && f0ck[0].usernetwork !== e.network) &&
level < 100 level < 100
) { ) {
await e.reply(`f0ck ${id}: insufficient permissions`); e.reply(`f0ck ${id}: insufficient permissions`);
continue; continue;
} }
if(~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) { if(~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
await e.reply(`f0ck ${id}: too late lol`); e.reply(`f0ck ${id}: too late lol`);
continue; continue;
} }
@ -61,7 +61,7 @@ export default async bot => {
deleted.push(id); deleted.push(id);
} }
await e.reply(`deleted ${deleted.length}/${e.args.length} f0cks (${deleted.join(",")})`); e.reply(`deleted ${deleted.length}/${e.args.length} f0cks (${deleted.join(",")})`);
} }
}, { }, {
name: "recover", name: "recover",
@ -86,7 +86,7 @@ export default async bot => {
`; `;
if(f0ck.length === 0) { if(f0ck.length === 0) {
await e.reply(`f0ck ${id}: f0ck not found`); e.reply(`f0ck ${id}: f0ck not found`);
continue; continue;
} }
@ -105,7 +105,7 @@ export default async bot => {
recovered.push(id); recovered.push(id);
} }
await e.reply(`recovered ${recovered.length}/${e.args.length} f0cks (${recovered.join(",")})`); e.reply(`recovered ${recovered.length}/${e.args.length} f0cks (${recovered.join(",")})`);
} }
}] }]
}; };

View File

@ -5,6 +5,29 @@ import cfg from "../config.mjs";
import db from "../sql.mjs"; import db from "../sql.mjs";
import lib from "../lib.mjs"; import lib from "../lib.mjs";
/*const cleanTags = async () => {
const tags = await db`
select *
from "tags"
left join "tags_assign" on "tags_assign".tag_id = "tags".id
where "tags_assign".item_id is null
`;
if(tags.length === 0)
return 0;
let deleteTag = sql("tags");
let dtags = 0;
tags.forEach(tag => {
if(["sfw", "nsfw"].includes(tag.tag.toLowerCase()))
return dtags;
deleteTag = deleteTag.orWhere("id", tag.id);
dtags++;
});
await deleteTag.del();
return dtags;
};*/
export default async bot => { export default async bot => {
return [{ return [{
@ -25,56 +48,60 @@ export default async bot => {
t: lib.formatSize((await Promise.all(dirs.t.map( async file => (await fs.stat(`./public/t/${file}`)).size)) ).reduce((a, b) => b + a)), t: lib.formatSize((await Promise.all(dirs.t.map( async file => (await fs.stat(`./public/t/${file}`)).size)) ).reduce((a, b) => b + a)),
ca: lib.formatSize((await Promise.all(dirs.ca.map(async file => (await fs.stat(`./public/ca/${file}`)).size))).reduce((a, b) => b + a)), ca: lib.formatSize((await Promise.all(dirs.ca.map(async file => (await fs.stat(`./public/ca/${file}`)).size))).reduce((a, b) => b + a)),
}; };
return await e.reply(`${dirs.b.length} f0cks: ${sizes.b}, ${dirs.t.length} thumbnails: ${sizes.t}, ${dirs.ca.length} coverarts: ${sizes.ca}`); return e.reply(`${dirs.b.length} f0cks: ${sizes.b}, ${dirs.t.length} thumbnails: ${sizes.t}, ${dirs.ca.length} coverarts: ${sizes.ca}`);
case "limit": case "limit":
return await e.reply(`up to ${lib.formatSize(cfg.main.maxfilesize)} (${lib.formatSize(cfg.main.maxfilesize * cfg.main.adminmultiplier)} for admins)`); return e.reply(`up to ${lib.formatSize(cfg.main.maxfilesize)} (${lib.formatSize(cfg.main.maxfilesize * cfg.main.adminmultiplier)} for admins)`);
case "thumb": case "thumb":
const rows = await db` const rows = await db`
select id select id
from "items" from "items"
`; `;
const dir = (await fs.readdir("./public/t")).filter(d => d.endsWith(".webp")).map(e => +e.split(".")[0]); const dir = (await fs.readdir("./public/t")).filter(d => d.endsWith(".png")).map(e => +e.split(".")[0]);
const tmp = []; const tmp = [];
for(let row of rows) for(let row of rows)
!dir.includes(row.id) ? tmp.push(row.id) : null; !dir.includes(row.id) ? tmp.push(row.id) : null;
await e.reply(`${tmp.length}, ${rows.length}, ${dir.length}`); e.reply(`${tmp.length}, ${rows.length}, ${dir.length}`);
break; break;
case "cache": case "cache":
cfg.websrv.cache = !cfg.websrv.cache; cfg.websrv.cache = !cfg.websrv.cache;
return await e.reply(`Cache is ${cfg.websrv.cache ? "enabled" : "disabled"}`); return e.reply(`Cache is ${cfg.websrv.cache ? "enabled" : "disabled"}`);
case "uptime": case "uptime":
exec('sudo systemctl status f0ck', async (err, stdout) => { exec('sudo systemctl status f0ck', (err, stdout) => {
if(!err) if(!err)
return await e.reply(stdout.split('\n')[2].trim().replace("Active: active (running)", "i'm active")); return e.reply(stdout.split('\n')[2].trim().replace("Active: active (running)", "i'm active"));
}); });
break; break;
case "restart": case "restart":
await e.reply("hay hay patron, hemen!"); e.reply("hay hay patron, hemen!");
exec("sudo systemctl restart f0ck"); exec("sudo systemctl restart f0ck");
break; break;
/*case "cleanTags":
const tags = await cleanTags();
e.reply(tags + " tags removed");
break;*/
case "clearTmp": case "clearTmp":
await Promise.all((await fs.readdir("./tmp")).filter(d => d !== ".empty").map(async d => fs.unlink(`./tmp/${d}`))); await Promise.all((await fs.readdir("./tmp")).filter(d => d !== ".empty").map(async d => fs.unlink(`./tmp/${d}`)));
await e.reply("cleared lol"); e.reply("cleared lol");
break; break;
case "status": case "status":
const tmpc = await lib.countf0cks(); const tmpc = await lib.countf0cks();
await e.reply(`tagged: ${tmpc.tagged}; untagged: ${tmpc.untagged}; sfw: ${tmpc.sfw}; nsfw: ${tmpc.nsfw}; total: ${tmpc.total}`); e.reply(`tagged: ${tmpc.tagged}; untagged: ${tmpc.untagged}; sfw: ${tmpc.sfw}; nsfw: ${tmpc.nsfw}; total: ${tmpc.total}`);
break; break;
/*case "autotagger": case "autotagger":
const body = { headers: { Authorization: `Basic ${cfg.tagger.btoa}` } }; const body = { headers: { Authorization: `Basic ${cfg.tagger.btoa}` } };
const res = await (await fetch(`${cfg.tagger.endpoint}/usage`, body)).json(); const res = await (await fetch(`${cfg.tagger.endpoint}/usage`, body)).json();
if(res) { if(res) {
const processed = res.result.monthly_processed; const processed = res.result.monthly_processed;
const limit = res.result.monthly_limit; const limit = res.result.monthly_limit;
return await e.reply(`autotagger: usage/limit: ${processed}/${limit}`); return e.reply(`autotagger: usage/limit: ${processed}/${limit}`);
} }
return; return;
break;*/ break;
/*case "renameTag": /*case "renameTag":
const origTag = e.args.slice(1).join(' '); const origTag = e.args.slice(1).join(' ');
if(origTag.length <= 1) if(origTag.length <= 1)
return await e.reply("absichtliche Provokation!"); return e.reply("absichtliche Provokation!");
const origTagID = (await sql('tags').where('tag', origTag))[0].id; const origTagID = (await sql('tags').where('tag', origTag))[0].id;
@ -89,10 +116,10 @@ export default async bot => {
.del() .del()
); );
await e.reply(JSON.stringify({ affected, deleted })); e.reply(JSON.stringify({ affected, deleted }));
break;*/ break;*/
case "help": case "help":
await e.reply("cmds: stats, limit, thumb, cache, uptime, restart, cleanTags, clearTmp, status"); e.reply("cmds: stats, limit, thumb, cache, uptime, restart, cleanTags, clearTmp, status");
break; break;
default: default:
return; return;

View File

@ -22,9 +22,9 @@ export default async bot => {
`; `;
if(rows.length === 0) if(rows.length === 0)
return await e.reply("no f0cks given! lol D:"); return e.reply("no f0cks given! lol D:");
await e.reply([ e.reply([
`${cfg.main.url.full}/${rows[0].id}`, `${cfg.main.url.full}/${rows[0].id}`,
`user: ${rows[0].username} @ ${rows[0].usernetwork} ${rows[0].userchannel}`, `user: ${rows[0].username} @ ${rows[0].usernetwork} ${rows[0].userchannel}`,
`~${lib.formatSize(rows[0].size)}`, `~${lib.formatSize(rows[0].size)}`,

View File

@ -10,7 +10,6 @@ export default async bot => {
active: false, active: false,
f: async e => { f: async e => {
let args = e.args.slice(1); let args = e.args.slice(1);
/*let rows = sql("items").select("id", "username", "mime", "size"); /*let rows = sql("items").select("id", "username", "mime", "size");
for(let i = 0; i < args.length; i++) { for(let i = 0; i < args.length; i++) {
@ -22,21 +21,10 @@ export default async bot => {
rows = await rows.orderByRaw("random()").limit(1);*/ rows = await rows.orderByRaw("random()").limit(1);*/
const rows = await db`
select id, mime, username, size
from "items"
where
${ args.map(a => a.charAt(0) === "!"
? db`username not ilike ${a.slice(1)}`
: db`username ilike ${a}`
).join(' and ')}
order by random()
`;
if(rows.length === 0) if(rows.length === 0)
return await e.reply("nothing found, f0cker"); return e.reply("nothing found, f0cker");
return await e.reply(`f0ckrnd: ${cfg.main.url.full}/${rows[0].id} by: ${rows[0].username} (${rows[0].mime}, ~${lib.formatSize(rows[0].size)})`); return e.reply(`f0ckrnd: ${cfg.main.url.full}/${rows[0].id} by: ${rows[0].username} (${rows[0].mime}, ~${lib.formatSize(rows[0].size)})`);
} }
}]; }];
}; };

View File

@ -2,218 +2,128 @@ import cfg from "../config.mjs";
import db from "../sql.mjs"; import db from "../sql.mjs";
import lib from "../lib.mjs"; import lib from "../lib.mjs";
import { getLevel } from "../admin.mjs"; import { getLevel } from "../admin.mjs";
import queue from "../queue.mjs";
import autotagger from "../autotagger.mjs";
import fetch from "flumm-fetch"; import fetch from "flumm-fetch";
import fs from "fs";
import path from "path";
const regex = { import fs from "fs";
all: /https?:\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gi, import { exec as _exec } from "child_process";
yt: /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\/?\?(?:\S*?&?v\=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/gi,
imgur: /(?:https?:)?\/\/(\w+\.)?imgur\.com\/(\S*)(\.[a-zA-Z]{3})/gm, const exec = cmd => new Promise((resolve, reject) => {
instagram: /(?:https?:\/\/www\.)?instagram\.com\S*?\/(?:p|reel)\/(\w{11})\/?/im _exec(cmd, { maxBuffer: 5e3 * 1024 }, (err, stdout, stderr) => {
}; if(err)
const mediagroupids = new Set(); return reject(err);
if(stderr)
console.error(stderr);
resolve({ stdout: stdout });
});
});
//const regex = /https?:\/\/[\w\S(\.|:|/)]+/gi;
const regex = /https?:\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gi;
export default async bot => { export default async bot => {
return [{ return [{
name: "parser", name: "parser",
call: regex.all, call: regex,
active: true, active: true,
f: e => { f: e => {
const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || []; const links = e.message.match(regex)?.filter(link => !link.includes("f0ck.me")) || [];
let repost;
if(e.media) if(e.media)
links.push(e.media); links.push(e.media);
if(links.length === 0) if(links.length === 0)
return false; return false;
if(e.message.match(/\!i(gnore)?\b/)) if(e.message.match(/(!|-)ignore/))
return false; return false;
if(!e.channel.includes("f0ck") && (!e.message.match(/\!f(0ck)?\b/i) && (typeof e.raw.forward_from == 'undefined'))) if(!e.channel.includes("f0ck") && !e.message.match(/(!|-)f0ck/i))
return false; return false;
if(e.type === 'tg' && // proto: tg
!e.message.match(/\!f(0ck)?\b/i) && // !f / !f0ck
!e.raw.forward_date && // is forwarded?
!mediagroupids.has(e.raw.media_group_id) // prepared mediagroup?
) {
return false;
}
else if(e.raw.media_group_id && e.message.match(/\!f(0ck)?\b/i)) {
mediagroupids.add(e.raw.media_group_id);
}
console.log(`parsing ${links.length} link${links.length > 1 ? "s" : ""}...`); console.log(`parsing ${links.length} link${links.length > 1 ? "s" : ""}...`);
links.forEach(async link => { links.forEach(async link => {
if(regex.imgur.test(link))
await e.reply(`imgur schmimigur`);
if(regex.instagram.test(link))
await e.reply(`insta schminsta`);
// check repost (link) // check repost (link)
repost = await queue.checkrepostlink(link); const q_repost = await db`
if(repost) select id
return await e.reply(`repost motherf0cker (link): ${cfg.main.url.full}/${repost}`); from "items"
where src = ${link}
`;
if(q_repost.length > 0)
return e.reply(`repost motherf0cker (link): ${cfg.main.url.full}/${q_repost[0].id}`);
// generate uuid // generate uuid
const uuid = await queue.genuuid(); const uuid = (await db`
select gen_random_uuid() as uuid
`)[0].uuid.substring(0, 8);
const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize); const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize) / 1024;
let meta;
// read metadata // read metadata
let ext; try {
if(regex.imgur.test(link)) { meta = JSON.parse((await exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
// is imgur
try {
// will die extension von der url
ext = link.split(".").slice(-1).join(".");
} catch(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];
}
} }
else { catch(err) {
// is not instagram //e.reply("[error] f0ck has no bock :(");
try { //console.error(err);
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); return;
ext = meta.ext;
} catch(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];
}
} }
if(!Object.values(cfg.mimes).includes(ext?.toLowerCase())) { if(!Object.values(cfg.mimes).includes(meta.ext.toLowerCase())) {
return console.log('mime schmime ' + ext); const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
if(!Object.keys(cfg.mimes).includes(tmphead))
return;
meta.ext = cfg.mimes[tmphead];
} }
const msg = await e.reply(`[charging the f0cker] downloading: ${uuid}`, { let filename = `${uuid}.${meta.ext}`;
disable_notification: true
}); e.reply(`[charging the f0cker] downloading: ${uuid}`);
// download data // download data
const start = new Date(); const start = new Date();
let source; let source;
if(meta.ext === "mp4") {
try { source = (await exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize}k --merge-output-format mp4 -o ./tmp/${filename}`)).stdout.trim();
if(regex.instagram.test(link)) //change 720 to any other available resolution, higher = better quality but bigger filesize
try { }
// add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited else {
source = (await queue.exec(`yt-dlp -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 exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize}k -o ./tmp/${filename}`)).stdout.trim();
} catch(err) { //change 720 to any other available resolution, higher = better quality but bigger filesize
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "instagram dl error");
return await e.reply("instagram dl error", err);
}
else if(regex.imgur.test(link)) {
console.log("penis123");
try {
await queue.exec(`torsocks wget "${link}" -O "./tmp/${uuid}.${ext}"`);
source = "./tmp/"+uuid+"."+ext;
console.log(source);
} catch(err) {
console.log(err);
}
}
else
{
try {
source = (await queue.exec(`yt-dlp -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, err);
return await e.reply(err);
}
}
} catch(err) {
console.log(err);
} }
// this is how it was before I fucked it up :> if(source.match(/larger than/))
// try { return e.reply("too large lol");
// source = (await queue.exec(`yt-dlp -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, "something went wrong lol");
// return await e.reply("something went wrong lol");
// }
if(!source) {
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "something went wrong lol");
return await e.reply("something went wrong lol");
}
if(source.match(/larger than/)) {
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "too large lol");
return await e.reply("too large lol");
}
const end = ~~((new Date() - start) / 1e3); const end = ~~((new Date() - start) / 1e3);
// filesize check
const size = fs.statSync(source).size;
if(size > maxfilesize) {
await fs.promises.unlink(source).catch(_=>{});
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `too large lol. (${lib.formatSize(size)} / ${lib.formatSize(maxfilesize)})`);
return await e.reply(`too large lol. (${lib.formatSize(size)} / ${lib.formatSize(maxfilesize)})`);
}
// mime check
let mime = (await queue.exec(`file --mime-type -b ${source}`)).stdout.trim();
try {
if(mime == 'video/x-matroska') { // mkv failsafe
await queue.exec(`ffmpeg -i ./tmp/${uuid}.mkv -codec copy ./tmp/${uuid}.mp4`);
await fs.promises.unlink(source).catch(_=>{});
source = source.replace(/\.mkv$/, '.mp4');
mime = 'video/mp4';
}
if(source.match(/\.opus$/)) { // opus failsafe
await queue.exec(`ffmpeg -i ./tmp/${uuid}.opus -codec copy ./tmp/${uuid}.ogg`);
await fs.promises.unlink(source);
source = source.replace(/\.opus$/, '.ogg');
mime = 'audio/ogg';
}
} catch(err) {
await fs.promises.unlink(source).catch(_=>{});
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "something went wrong lol");
return await e.reply("something went wrong lol");
}
if(!Object.keys(cfg.mimes).includes(mime)) {
await fs.promises.unlink(source).catch(_=>{});
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `lol, go f0ck yourself (${mime})`);
return await e.reply(`lol, go f0ck yourself (${mime})`);
}
// generate checksum // generate checksum
const checksum = (await queue.exec(`sha256sum ${source}`)).stdout.trim().split(" ")[0]; const checksum = (await exec(`sha256sum ./tmp/${filename}`)).stdout.trim().split(" ")[0];
const size = fs.statSync(`./tmp/${filename}`).size;
// mime check
const mime = (await exec(`file --mime-type -b ./tmp/${filename}`)).stdout.trim();
if(!Object.keys(cfg.mimes).includes(mime))
return e.reply(`lol, go f0ck yourself (${mime})`);
if(!Object.values(cfg.mimes).includes(meta.ext.toLowerCase())) {
let tmpext = cfg.mimes[meta.ext.toLowerCase()];
fs.renameSync(`./tmp/${filename}`, `./tmp/${uuid}.${tmpext}`);
filename = `${uuid}.${tmpext}`;
}
// check repost (checksum) // check repost (checksum)
repost = await queue.checkrepostsum(checksum); const q_repostc = await db`
if(repost) { select id
await fs.promises.unlink(source).catch(_=>{}); from "items"
if(e.type == 'tg') where checksum = ${checksum}
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `repost motherf0cker (checksum): ${cfg.main.url.full}/${repost}`); `;
return await e.reply(`repost motherf0cker (checksum): ${cfg.main.url.full}/${repost}`); if(q_repostc.length > 0)
} return e.reply(`repost motherf0cker (checksum): ${cfg.main.url.full}/${q_repostc[0].id}`);
const filename = path.basename(source); await fs.promises.copyFile(`./tmp/${filename}`, `./public/b/${filename}`);
await fs.promises.unlink(`./tmp/${filename}`).catch(_=>{});
await fs.promises.copyFile(source, `./public/b/${filename}`);
await fs.promises.unlink(source).catch(_=>{});
await db` await db`
insert into items ${ insert into items ${
@ -232,56 +142,113 @@ export default async bot => {
} }
`; `;
const itemid = await queue.getItemID(filename); const itemid = (await db`
select *
from "items"
where dest = ${filename}
limit 1
`)[0].id;
// generate thumbnail // generate thumbnail
try { try {
await queue.genThumbnail(filename, mime, itemid, link); if(mime.startsWith('video/') || mime == 'image/gif')
await exec(`ffmpegthumbnailer -i./public/b/${filename} -s1024 -o./tmp/${itemid}.png`);
else if(mime.startsWith('image/') && mime != 'image/gif')
await exec(`convert ./public/b/${filename} ./tmp/${itemid}.png`);
else if(mime.startsWith('audio/')) {
if(link.match(/soundcloud/)) {
let cover = (await exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --get-thumbnail "${link}"`)).stdout.trim();
if(!cover.match(/default_avatar/)) {
cover = cover.replace(/-(large|original)\./, '-t500x500.');
try {
await exec(`wget "${cover}" -O ./tmp/${itemid}.jpg`);
const size = (await fs.promises.stat(`./tmp/${itemid}.jpg`)).size;
if(size >= 0) {
await exec(`convert ./tmp/${itemid}.jpg ./tmp/${itemid}.png`);
await exec(`convert ./tmp/${itemid}.jpg ./public/ca/${itemid}.webp`);
}
} catch(err) {}
}
else {
await exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`);
await exec(`convert ./tmp/${itemid}.png ./public/ca/${itemid}.webp`);
}
}
else {
await exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`);
await exec(`convert ./tmp/${itemid}.png ./public/ca/${itemid}.webp`);
}
}
await exec(`convert "./tmp/${itemid}.png" -resize "128x128^" -gravity center -crop 128x128+0+0 +repage ./public/t/${itemid}.webp`);
await fs.promises.unlink(`./tmp/${itemid}.png`).catch(err => {});
await fs.promises.unlink(`./tmp/${itemid}.jpg`).catch(err => {});
} catch(err) { } catch(err) {
await queue.exec(`convert ./mugge.png ./public/t/${itemid}.webp`); await exec(`convert ./mugge.png ./public/t/${itemid}.webp`);
} }
let speed = lib.calcSpeed(size, end); let speed = lib.calcSpeed(size, end);
speed = !Number.isFinite(speed) ? "yes" : `${speed.toFixed(2)} Mbit/s`; speed = !Number.isFinite(speed) ? "yes" : `${speed.toFixed(2)} Mbit/s`;
// autotagger // autotagger
let tags = []; /*let tags = [];
/*if(cfg.apis?.nsfw1 && mime.startsWith('image')) { let score = 0;
const nsfw = await autotagger.isNSFW(filename, size); try {
tags.push(nsfw ? 'nsfw' : 'sfw'); if(mime.startsWith('image')) {
if(nsfw) const res = await lib.detectNSFW(filename);
await queue.tagNSFW(itemid); score = res.score;
else
await queue.tagSFW(itemid); await db`
insert into "tags_assign" ${
db({
item_id: itemid,
tag_id: res.isNSFW ? 2 : 1,
user_id: 1
})
}
`;
tags.push(res.isNSFW ? 'nsfw' : 'sfw');
if(res.hentai >= .7) {
await db`
insert into "tags_assign" ${
db({
item_id: f.id,
tag_id: 4, // hentai
user_id: 1 // autotagger
})
}
`;
tags.push('hentai');
}
}
else if(mime.startsWith('audio')) {
await db`
insert into "tags_assign" ${
db([{
item_id: itemid,
tag_id: 1,
user_id: 1
}, {
item_id: itemid,
tag_id: 3, // audio
user_id: 1
}])
}
`;
tags.push('sfw', 'audio');
}
} catch(err) {
console.error(err);
}*/ }*/
let outputmsgirc = `[f0cked] link: ${cfg.main.url.full}/${itemid} | size: ${lib.formatSize(size)} | speed: ${speed}`; /*e.reply([
let outputmsgtg = `[f0cked] size: ${lib.formatSize(size)} | speed: ${speed}`; `[f0cked] link: ${cfg.main.url.full}/${itemid} | size: ${lib.formatSize(size)} | speed: ${speed}` + (tags.length > 0 ? ` | tags: ${tags.join(', ')} (score: ${score.toFixed(2)})` : '')
]);*/
e.reply([
`[f0cked] link: ${cfg.main.url.full}/${itemid} | size: ${lib.formatSize(size)} | speed: ${speed}`
]);
if(tags?.length > 0) {
const tagstr = tags.join(', ');
outputmsgirc += ` | tags: ${tagstr}`;
outputmsgtg += ` | tags: ${tagstr}`;
}
if(e.type == 'tg') {
await e.deleteMessage(msg.result.chat.id, msg.result.message_id);
await e.reply(outputmsgtg, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(itemid, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${itemid}` },
{ text: (await lib.hasTag(itemid, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${itemid}` },
{ text: 'tags', callback_data: `b_tags:${itemid}` },
{ text: '❌ delete', callback_data: `b_delete:${itemid}` }
], [
{ text: `open f0ck #${itemid}`, url: `${cfg.main.url.full}/${itemid}` }
]]
})
});
}
else {
await e.reply(outputmsgirc);
}
}); });
} }
}]; }];

View File

@ -12,11 +12,11 @@ export default async bot => {
f: async e => { f: async e => {
const id = +e.args[1]; const id = +e.args[1];
if(!id) if(!id)
return await e.reply("lol no"); return e.reply("lol no");
const tags = (await lib.getTags(id)).map(t => t.tag); const tags = (await lib.getTags(id)).map(t => t.tag);
if(tags.length === 0) if(tags.length === 0)
return await e.reply(`item ${cfg.main.url.full}/${id} has no tags!`); return e.reply(`item ${cfg.main.url.full}/${id} has no tags!`);
return await e.reply(`item ${cfg.main.url.full}/${id} is tagged as: ${tags.join(', ')}`); return e.reply(`item ${cfg.main.url.full}/${id} is tagged as: ${tags.join(', ')}`);
} }
}, { }, {
name: "tags add", name: "tags add",
@ -26,7 +26,7 @@ export default async bot => {
f: async e => { f: async e => {
const id = +e.args[1]; const id = +e.args[1];
if(!id) if(!id)
return await e.reply("lol no"); return e.reply("lol no");
const tags = (await lib.getTags(id)).map(t => t.tag); const tags = (await lib.getTags(id)).map(t => t.tag);
@ -35,7 +35,7 @@ export default async bot => {
: e.args.splice(2)).filter(t => !tags.includes(t) && t.length > 0); : e.args.splice(2)).filter(t => !tags.includes(t) && t.length > 0);
if(newtags.length === 0) if(newtags.length === 0)
return await e.reply("no (new) tags provided"); return e.reply("no (new) tags provided");
await Promise.all(newtags.map(async ntag => { await Promise.all(newtags.map(async ntag => {
try { try {
@ -73,8 +73,8 @@ export default async bot => {
const ntags = (await lib.getTags(id)).map(t => t.tag); const ntags = (await lib.getTags(id)).map(t => t.tag);
if(ntags.length === 0) if(ntags.length === 0)
return await e.reply(`item ${cfg.main.url.full}/${id} has no tags!`); return e.reply(`item ${cfg.main.url.full}/${id} has no tags!`);
return await e.reply(`item ${cfg.main.url.full}/${id} is now tagged as: ${ntags.join(', ')}`); return e.reply(`item ${cfg.main.url.full}/${id} is now tagged as: ${ntags.join(', ')}`);
} }
}, { }, {
name: "tags remove", name: "tags remove",
@ -84,7 +84,7 @@ export default async bot => {
f: async e => { f: async e => {
const id = +e.args[1]; const id = +e.args[1];
if(!id) if(!id)
return await e.reply("lol no"); return e.reply("lol no");
const tags = await lib.getTags(id); const tags = await lib.getTags(id);
@ -93,7 +93,7 @@ export default async bot => {
: e.args.splice(2)).filter(t => t.length > 0); : e.args.splice(2)).filter(t => t.length > 0);
if(removetags.length === 0) if(removetags.length === 0)
return await e.reply("no tags provided"); return e.reply("no tags provided");
const res = await Promise.all(removetags.map(async rtag => { const res = await Promise.all(removetags.map(async rtag => {
const tagid = tags.filter(t => t.tag === rtag)[0]?.id ?? null; const tagid = tags.filter(t => t.tag === rtag)[0]?.id ?? null;
@ -122,12 +122,12 @@ export default async bot => {
}; };
})); }));
await e.reply(JSON.stringify(res)); e.reply(JSON.stringify(res));
const ntags = (await lib.getTags(id)).map(t => t.tag); const ntags = (await lib.getTags(id)).map(t => t.tag);
if(ntags.length === 0) if(ntags.length === 0)
return await e.reply(`item ${cfg.main.url.full}/${id} has no tags!`); return e.reply(`item ${cfg.main.url.full}/${id} has no tags!`);
return await e.reply(`item ${cfg.main.url.full}/${id} is now tagged as: ${ntags.join(', ')}`); return e.reply(`item ${cfg.main.url.full}/${id} is now tagged as: ${ntags.join(', ')}`);
} }
}] }]
}; };

View File

@ -1,42 +0,0 @@
import queue from '../queue.mjs';
import db from '../sql.mjs';
export default async bot => {
return [{
name: "thumbnailer",
call: /^\!thumb .*/i,
active: true,
level: 100,
f: async e => {
let processed = [];
for(let id of e.args) {
id = +id;
if(id <= 1)
continue;
const f0ck = await db`
select id, dest, mime, src
from "items"
where
id = ${id} and
active = 'true'
limit 1
`;
if(f0ck.length === 0) {
await e.reply(`f0ck ${id}: f0ck not found`);
continue;
}
// gen thumb
await queue.genThumbnail(f0ck[0].dest, f0ck[0].mime, f0ck[0].id, f0ck[0].src);
processed.push(id);
}
return await e.reply(`thumbnails: ${processed.length}/${e.args.length} (${processed.join(",")})`);
}
}];
};

View File

@ -5,11 +5,6 @@ import cuffeo from "cuffeo";
import { promises as fs } from "fs"; import { promises as fs } from "fs";
import flummpress from "flummpress"; import flummpress from "flummpress";
process.on('unhandledRejection', err => {
console.error(err);
throw err;
});
(async () => { (async () => {
const self = { const self = {
_trigger: new Map(), _trigger: new Map(),
@ -67,7 +62,6 @@ process.on('unhandledRejection', err => {
if(req.url.pathname.match(/^\/(s|b|t|ca)\//)) if(req.url.pathname.match(/^\/(s|b|t|ca)\//))
return; return;
req.theme = req.cookies.theme || 'f0ck'; req.theme = req.cookies.theme || 'f0ck';
req.fullscreen = req.cookies.fullscreen || 0;
if(req.cookies.session) { if(req.cookies.session) {
const user = await db` const user = await db`
@ -102,7 +96,6 @@ process.on('unhandledRejection', err => {
`; `;
req.session.theme = req.cookies.theme; req.session.theme = req.cookies.theme;
req.session.fullscreen = req.cookies.fullscreen;
// update userprofile // update userprofile
await db` await db`
@ -110,14 +103,12 @@ process.on('unhandledRejection', err => {
db({ db({
user_id: +user[0].id, user_id: +user[0].id,
mode: user[0].mode ?? 0, mode: user[0].mode ?? 0,
theme: req.session.theme ?? 'f0ck', theme: req.session.theme ?? 'f0ck'
fullscreen: req.session.fullscreen || 0 }, 'user_id', 'mode', 'theme')
}, 'user_id', 'mode', 'theme', 'fullscreen')
} }
on conflict ("user_id") do update set on conflict ("user_id") do update set
mode = excluded.mode, mode = excluded.mode,
theme = excluded.theme, theme = excluded.theme,
fullscreen = excluded.fullscreen,
user_id = excluded.user_id user_id = excluded.user_id
`; `;
} }

View File

@ -1,9 +1,8 @@
@include(snippets/header) @include(snippets/header)
<div id="main"> <div class="about">
<div class="about">
<div> <div>
<a href="//f0ck.me/48908"><img src="//f0ck.me/s/img/loool.webp" /></a> <a href="//f0ck.me/48908"><img src="//f0ck.me/s/img/loool.webp" /></a>
<p>thanks to our turkish fellow lol@n0xy/#f0ck for this gif &lt;3</p> <p>thanks to our turkish fellow lol@n0xy/#f0ck for this gif &lt;3</p>
</div> </div>
<h5>f0ck Contact</h5> <h5>f0ck Contact</h5>
<p>Whatever it is, we might have a answer, even though it might not be the one you were looking for: <a href="mailto:admin@f0ck.me">admin@f0ck.me</a></p> <p>Whatever it is, we might have a answer, even though it might not be the one you were looking for: <a href="mailto:admin@f0ck.me">admin@f0ck.me</a></p>
@ -15,24 +14,24 @@
<h4>#f0ck on n0xy.net</h4> <h4>#f0ck on n0xy.net</h4>
<p>You can invite f0ck to your channel on the following supported networks by simply typing<br><code>/invite f0ck</code></p> <p>You can invite f0ck to your channel on the following supported networks by simply typing<br><code>/invite f0ck</code></p>
<ul> <ul>
<li><a href="https://n0xy.net">n0xy.net</a></li> <li><a href="https://n0xy.net">n0xy.net</a></li>
<li><a href="https://www.rizon.net/">rizon.net</a></li> <li><a href="https://www.rizon.net/">rizon.net</a></li>
<li><a href="https://libera.chat/">libera.chat</a></li> <li><a href="https://libera.chat/">libera.chat</a></li>
</ul> </ul>
<p>To start f0cking the shit out of something simply add a <code>!f0ck</code> behind the url you want to f0ck, that's it</p> <p>To start f0cking the shit out of something simply add a <code>!f0ck</code> behind the url you want to f0ck, that's it</p>
<p>#f0ck specific: to have f0ck ignore a link add <code>!ignore</code> at the end <br>Example: <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">https://www.youtube.com/watch?v=dQw4w9WgXcQ</a> !ignore</p> <p>#f0ck specific: to have f0ck ignore a link add <code>!ignore</code> at the end <br>Example: <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">https://www.youtube.com/watch?v=dQw4w9WgXcQ</a> !ignore</p>
<p>f0ck supports a variety of websites, in fact all websites supported by <code>yt-dlp</code> are supported by f0ck aswell!</p> <p>f0ck supports a variety of websites, in fact all websites supported by <code>yt-dlp</code> are supported by f0ck aswell!</p>
<h5>f0ck Rules</h5> <h5>f0ck Rules</h5>
<ul> <ul>
<li>You must be 18 years or older to visit or post</li> <li>You must be 18 years or older to visit or post</li>
<li>You shall not post animal cruelty, we like our animals alive and well, living a happy life until they are ready for our Schnitzel!</li> <li>You shall not post animal cruelty, we like our animals alive and well, living a happy life until they are ready for our Schnitzel!</li>
<li>You shall not post under <b>ANY</b> circumstances: Snuff, Beastiality, Rape, Terrorist stuff (Beheadings, First person shootings, warcrimes), Childporn, Childmodeling</li> <li>You shall not post under <b>ANY</b> circumstances: Snuff, Beastiality, Rape, Terrorist stuff (Beheadings, First person shootings, warcrimes), Childporn, Childmodeling</li>
</ul> </ul>
<h5>f0cked up?</h5> <h5>f0cked up?</h5>
<p>To have something removed in case you accidentally f0cked something that actually shouldn't be f0cked you can always contact the admins either via IRC or Email</p> <p>To have something removed in case you accidentally f0cked something that actually shouldn't be f0cked you can always contact the admins either via IRC or Email</p>
<ul> <ul>
<li>irc.n0xy.net #f0ck</li> <li>irc.n0xy.net #f0ck</li>
<li>admin@f0ck.me</li> <li>admin@f0ck.me</li>
</ul> </ul>
<h5>Compatibility</h5> <h5>Compatibility</h5>
<p>f0ck is developed and tested for Firefox and Chromium in their latest versions</p> <p>f0ck is developed and tested for Firefox and Chromium in their latest versions</p>
@ -46,6 +45,5 @@
<h5>f0ck Privacy?</h5> <h5>f0ck Privacy?</h5>
<p>Cookies: Yes, we set 1 cookie for your prefered stylesheet, this is a optional cookie and not required for the site to function, simply cosmetics, you can block this cookie and the site will still work as intended and will default to the default f0ck theme called "f0ck"</p> <p>Cookies: Yes, we set 1 cookie for your prefered stylesheet, this is a optional cookie and not required for the site to function, simply cosmetics, you can block this cookie and the site will still work as intended and will default to the default f0ck theme called "f0ck"</p>
<p>Logs: We do not log users accessing our website.</p> <p>Logs: We do not log users accessing our website.</p>
</div>
</div> </div>
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,22 +1,10 @@
@include(snippets/header) @include(snippets/header_admin)
<div id="main"> <div class="container">
<div class="container"> <h1>Henlo, {{ session.user }}</h1>
<h1>ADMINBEREICH</h1> <p>Hier entsteht eine Internetpräsenz!</p>
<h5>Hallo, {{ session.user }}</h5> <img src="/s/img/favicon.gif" alt="f0ck bash">
<span>Hier entsteht eine Internetpräsenz!</span><br> <p>@if(typeof totals !== "undefined")
<hr>
<p>f0ck stats: @if(typeof totals !== "undefined")
total: {{ totals.total }} | tagged: {{ totals.tagged }} | untagged: {{ totals.untagged }} | sfw: {{ totals.sfw }} | nsfw: {{ totals.nsfw }} total: {{ totals.total }} | tagged: {{ totals.tagged }} | untagged: {{ totals.untagged }} | sfw: {{ totals.sfw }} | nsfw: {{ totals.nsfw }}
@endif</p> @endif</p>
<hr>
<div class="admintools">
<p>Adminwerkzeuge</p>
<ul>
<li><a href="/admin/log">Logs</a></li>
<li><a href="/admin/recover">Recover f0cks</a></li>
<li><a href="/admin/sessions">Sessions</a></li>
</ul>
</div>
</div>
</div> </div>
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,18 +1,16 @@
@include(snippets/header) @include(snippets/header_admin)
<div id="main"> @if(log)
@if(log) <h1>last {{ log.length }} entries:</h1>
<h1>last {{ log.length }} entries:</h1> <div class="logwrap">
<div class="logwrap"> @each(log as line)
@each(log as line) <p>{{ line }}</p>
<p>{{ line }}</p> @endeach
@endeach
</div>
<script>
(() => {
const d = document.querySelector("div.logwrap");
d.scrollTop = d.scrollHeight;
})();
</script>
@endif
</div> </div>
<script>
(() => {
const d = document.querySelector("div.logwrap");
d.scrollTop = d.scrollHeight;
})();
</script>
@endif
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,26 +1,24 @@
@include(snippets/header) @include(snippets/header_admin)
<div id="main"> <table class="table" style="width: 100%">
<table class="table" style="width: 100%"> <thead>
<thead> <tr>
<tr> <td></td>
<td></td> <td>ID</td>
<td>ID</td> <td>f0cker</td>
<td>f0cker</td> <td>mime</td>
<td>mime</td> <td></td>
<td></td> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> @each(posts as post)
@each(posts as post) <tr>
<tr> <td><img src="data:image/webp;base64,{{ post.thumbnail }}" /></td>
<td><img src="data:image/webp;base64,{{ post.thumbnail }}" /></td> <td>{{ post.id }}</td>
<td>{{ post.id }}</td> <td>{{ post.username }}</td>
<td>{{ post.username }}</td> <td>{{ post.mime }}</td>
<td>{{ post.mime }}</td> <td><a href="/admin/recover/?id={{ post.id }}">recover</a></td>
<td><a href="/admin/recover/?id={{ post.id }}">recover</a></td> </tr>
</tr> @endeach
@endeach </tbody>
</tbody> </table>
</table> @include(snippets/footer)
</div>
@include(snippets/footer)

View File

@ -1,32 +1,30 @@
@include(snippets/header) @include(snippets/header_admin)
<div id="main"> <table class="table" style="width: 100%">
<table class="table" style="width: 100%"> <thead>
<thead> <tr>
<tr> <td></td>
<td></td> <td>ID</td>
<td>ID</td> <td>userid</td>
<td>userid</td> <td>user</td>
<td>user</td> <td>browser</td>
<td>browser</td> <td>created_at</td>
<td>created_at</td> <td>last_used</td>
<td>last_used</td> <td>last_action</td>
<td>last_action</td> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> @each(sessions as session)
@each(sessions as session) <tr>
<tr> <td>{{ session.kmsi ? '&#9875;' : '' }}</td>
<td>{{ session.kmsi ? '&#9875;' : '' }}</td> <td>{{ session.id }}</td>
<td>{{ session.id }}</td> <td>{{ session.user_id }}</td>
<td>{{ session.user_id }}</td> <td>{{ session.user }}</td>
<td>{{ session.user }}</td> <td>{{ session.browser }}</td>
<td>{{ session.browser }}</td> <td>{{ new Date(session.created_at * 1e3).toLocaleString("de-DE") }}</td>
<td>{{ new Date(session.created_at * 1e3).toLocaleString("de-DE") }}</td> <td>{{ new Date(session.last_used * 1e3).toLocaleString("de-DE") }}</td>
<td>{{ new Date(session.last_used * 1e3).toLocaleString("de-DE") }}</td> <td>{{ session.last_action }}</td>
<td>{{ session.last_action }}</td> </tr>
</tr> @endeach
@endeach </tbody>
</tbody> </table>
</table> @include(snippets/footer)
</div>
@include(snippets/footer)

165
views/comments.html Normal file
View File

@ -0,0 +1,165 @@
<div class="sidebar">
<div class="header_sidebar">
<span>Comments</span>
</div>
<div class="commentbox">
<textarea name="comments" id="comments1" cols="30" rows="5" placeholder="Write a shitpost?"></textarea>
<button class="commentbutton">Shit</button>
</div>
<div class="commentholder">
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
<div class="comment">
<div class="comment_userimg">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar_comments" />
</div>
<div class="comment_username">
{{ session.user }}
</div>
<div class="comment_content">
<p>Lorem Ipsum Lorem Ipsum <br><img src="https://f0ck.me/b/63144b2b.webp" alt=""></p>
</div>
</div>
</div>
</div>

View File

@ -1,20 +1,18 @@
@include(snippets/header) @include(snippets/header)
<div id="main"> <div class="container">
<div class="container">
<div class="_error_wrapper"> <div class="_error_wrapper">
<div class="err"> <div class="err">
<div class="_error_topbar"> <div class="_error_topbar">
<span>x.x</span> <span>x.x</span>
</div>
<div class="_error_content">
<img src="/s/img/favicon.gif" alt="f0ck?!">
<div class="_error_message">
<span>Error</span>
<code>{{ message }}</code>
</div>
</div>
</div> </div>
<div class="_error_content">
<img src="/s/img/favicon.gif" alt="f0ck?!">
<div class="_error_message">
<span>Error</span>
<code>{{ message }}</code>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,18 +1,14 @@
@include(snippets/header) @include(snippets/header)
<div class="pagewrapper"> <div class="index-container">
<div id="main"> @if(tmp.user)<h2>user: {!! tmp.user.toLowerCase() !!}@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif
<div class="index-container"> @if(tmp.tag)<h2>tag: @if(session)<a href="/search?tag={!! tmp.tag.toLowerCase() !!}" target="_blank">{!! tmp.tag.toLowerCase() !!}</a>@else{!! tmp.tag.toLowerCase() !!}@endif@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif
@if(tmp.user)<h2>user: {!! tmp.user.toLowerCase() !!}@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif <div class="posts">
@if(tmp.tag)<h2>tag: @if(session)<a href="/search?tag={!! tmp.tag.toLowerCase() !!}" target="_blank">{!! tmp.tag.toLowerCase() !!}</a>@else{!! tmp.tag.toLowerCase() !!}@endif@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif @each(items as item)
<div class="posts"> <a href="{{ link.main }}{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.webp')"><p></p></a>
@each(items as item) @endeach
<a href="{{ link.main }}{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.webp')"><p></p></a> </div>
@endeach <div id="footbar">
</div> &#9660;
<div id="footbar">
&#9660;
</div>
</div> </div>
</div> </div>
</div> @include(snippets/footer)
@include(snippets/footer)

View File

@ -1,106 +1,106 @@
@include(snippets/header)
<canvas class="hidden-xs" id="bg"></canvas>
<div class="wrapper"> <div class="wrapper">
<div id="main"> @include(snippets/header)
<div class="container"> <div class="container">
<div class="_204863"> <div class="_204863">
<div class="imageDoor" style="--hover-image: url('/t/{{ item.id }}.webp');"> <div class="imageDoor" style="--hover-image: url('/t/{{ item.id }}.webp');">
<img src="/t/{{ item.id }}.webp" alt="" /> <img src="/t/{{ item.id }}.webp" alt="" />
</div>
<div class="gapLeft">
<span class="populateME"><b>f0ck</b> - {{ item.id }}</span>
</div>
@if(session)
<div class="gapRight">
<svg class="iconset" id="a_favo"><use href="/s/img/iconset.svg#heart_{{ Object.values(item.favorites).filter(u => u.user == session.user)[0] ? 'solid' : 'regular' }}"></use></svg>
<svg class="iconset" id="a_delete"><use href="/s/img/iconset.svg#cross"></use></svg>
</div>
@endif
</div>
<div class="content">
<div class="next-post">
@if(pagination.prev)
<div class="arrow-next">
<a id="next" href="{{ link.main }}{{ pagination.prev }}"></a>
</div> </div>
<div class="gapLeft"> @else
<span class="populateME"><b>f0ck</b> - {{ item.id }}</span> <div class="arrow-next">
</div> <a id="next" href="#" style="color: #ccc !important;"></a>
@if(session)
<div class="gapRight">
<svg class="iconset" id="a_favo"><use href="/s/img/iconset.svg#heart_{{ Object.values(item.favorites).filter(u => u.user == session.user)[0] ? 'solid' : 'regular' }}"></use></svg>
<svg class="iconset" id="a_tfull"><use href="/s/img/iconset.svg#window-{{ fullscreen == 1 ? 'minimize' : 'maximize' }}"></use></svg>
<svg class="iconset" id="a_delete"><use href="/s/img/iconset.svg#cross"></use></svg>
</div> </div>
@endif @endif
</div> </div>
<div class="content"> <div class="media-object">
<div class="next-post"> @if(item.mime.startsWith("video"))
@if(pagination.prev) <div class="embed-responsive embed-responsive-16by9">
<div class="arrow-next"> <video id="my-video" class="embed-responsive-item" width="640" height="360" src="{{ item.dest }}" preload="auto" autoplay controls loop playsinline></video>
<a id="next" href="{{ link.main }}{{ pagination.prev }}"></a>
</div>
@else
<div class="arrow-next">
<a id="next" href="#" style="color: #ccc !important;"></a>
</div>
@endif
</div> </div>
<div class="media-object"> @elseif(item.mime.startsWith("audio"))
@if(item.mime.startsWith("video")) <div class="embed-responsive embed-responsive-16by9" style="background: url('@if(item.coverart)//f0ck.me{{ item.coverart }}@else/s/img/200.gif@endif') no-repeat center / contain black;">
<div class="embed-responsive embed-responsive-16by9"> <audio id="my-video" class="embed-responsive-item" autoplay controls loop src="{{ item.dest }}" data-setup="{}" poster="@if(item.coverart){{ item.coverart }}@else/s/img/200.gif@endif" type="{{ item.mime }}"></audio>
<video id="my-video" class="embed-responsive-item" width="640" height="360" src="{{ item.dest }}" preload="auto" autoplay controls loop playsinline></video>
</div>
@elseif(item.mime.startsWith("audio"))
<div class="embed-responsive embed-responsive-16by9" style="background: url('@if(item.coverart)//f0ck.me{{ item.coverart }}@else/s/img/200.gif@endif') no-repeat center / contain black;">
<audio id="my-video" class="embed-responsive-item" autoplay controls loop src="{{ item.dest }}" data-setup="{}" poster="@if(item.coverart){{ item.coverart }}@else/s/img/200.gif@endif" type="{{ item.mime }}"></audio>
</div>
@elseif(item.mime.startsWith("image"))
<div class="embed-responsive embed-responsive-16by9">
<div class="embed-responsive-image" id="image-scroll">
<a href="{{ item.dest }}" id="elfe" target="_blank"><img id="f0ck-image" class="img-fluid" src="{{ item.dest }}" loading="lazy" decoding="async"/></a>
</div>
</div>
@else
<h1>404 - Not f0cked</h1>
@endif
</div> </div>
<div class="previous-post"> @elseif(item.mime.startsWith("image"))
@if(pagination.next) <div class="embed-responsive embed-responsive-16by9">
<div class="arrow-prev"> <div class="embed-responsive-image" id="image-scroll">
<a id="prev" href="{{ link.main }}{{ pagination.next }}"></a> <a href="{{ item.dest }}" id="elfe" target="_blank"><img id="f0ck-image" class="img-fluid" src="{{ item.dest }}" loading="lazy" decoding="async"/></a>
</div> </div>
@else
<div class="arrow-prev">
<a id="prev" href="#" style="color: #ccc !important;"></a>
</div>
@endif
</div> </div>
@else
<h1>404 - Not f0cked</h1>
@endif
</div> </div>
<div class="metadata"> <div class="previous-post">
<span class="badge badge-dark"> @if(pagination.next)
<a href="/{{ item.id }}" class="id-link">{{ item.id }}</a> <div class="arrow-prev">
@if(session) <a id="prev" href="{{ link.main }}{{ pagination.next }}"></a>
(<a id="a_username" href="/user/{{ user.name.toLowerCase() }}/f0cks@if(tmp.mime)/{{ tmp.mime }}@endif">{{ user.name }}</a>) </div>
@endif @else
</span> <div class="arrow-prev">
<span class="badge badge-dark">{{ user.network }} / {{ user.channel }}</span> <a id="prev" href="#" style="color: #ccc !important;"></a>
<span class="badge badge-dark image-source"> </div>
@if(item.src.long.length)
<a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a>
@else
hidden
@endif
</span>
<span class="badge badge-dark"><a class="dest-link" href="{{ item.dest }}" target="_blank">{{ item.mime }}</a> / {{ item.size }}</span>
<span class="badge badge-dark"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{ item.timestamp.timeago }}</time></span>
<span class="badge badge-dark">{{ phrase }}</span>
<span class="badge badge-dark" id="tags">
@if(typeof item.tags !== "undefined")
@each(item.tags as tag)
<span @if(session)tooltip="{{ tag.user }}"@endif class="badge {{ tag.badge }} mr-2">
<a href="/tag/{{ tag.normalized }}">{!! tag.tag !!}</a>@if(session)&nbsp;<a class="removetag" href="#">&#215;</a>@endif
</span>
@endeach
@endif @endif
@if(session)
<a href="#" id="a_addtag">add tag</a>&nbsp;-&nbsp;<a href="#" id="a_toggle">toggle</a>
<datalist id="testlist"></datalist>
@endif
</span>
<span class="badge" id="favs"@if(!item.favorites.length || !session) hidden@endif>
@if(item.favorites.length && session)
@each(item.favorites as fav)
<a href="/user/{{ fav.user.toLowerCase() }}/favs" tooltip="{{ fav.user }}" flow="up"><img src="@if(fav.avatar)/t/{{ fav.avatar }}.webp@else/s/img/default.png@endif" style="height: 32px; width: 32px" /></a>
@endeach
@endif
</span>
</div> </div>
</div> </div>
<div class="metadata">
<span class="badge badge-dark">
<a href="/{{ item.id }}" class="id-link">{{ item.id }}</a>
@if(session)
(<a id="a_username" href="/user/{{ user.name.toLowerCase() }}/f0cks@if(tmp.mime)/{{ tmp.mime }}@endif">{{ user.name }}</a>)
@endif
</span>
<span class="badge badge-dark">{{ user.network }} / {{ user.channel }}</span>
<span class="badge badge-dark image-source">
@if(item.src.long.length)
<a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a>
@else
hidden
@endif
</span>
<span class="badge badge-dark"><a class="dest-link" href="{{ item.dest }}" target="_blank">{{ item.mime }}</a> / {{ item.size }}</span>
<span class="badge badge-dark"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{ item.timestamp.timeago }}</time></span>
<span class="badge badge-dark">{{ phrase }}</span>
<span class="badge badge-dark" id="tags">
@if(typeof item.tags !== "undefined")
@each(item.tags as tag)
<span @if(session)tooltip="{{ tag.user }}"@endif class="badge {{ tag.badge }} mr-2">
<a href="/tag/{{ tag.tag }}">{!! tag.tag !!}</a>@if(session)&nbsp;<a class="removetag" href="#">&#215;</a>@endif
</span>
@endeach
@endif
@if(session)
<a href="#" id="a_addtag">add tag</a>&nbsp;-&nbsp;<a href="#" id="a_toggle">toggle</a>
<datalist id="testlist"></datalist>
@endif
</span>
<span class="badge" id="favs"@if(!item.favorites.length || !session) hidden@endif>
@if(item.favorites.length && session)
@each(item.favorites as fav)
<a href="/user/{{ fav.user.toLowerCase() }}/favs" tooltip="{{ fav.user }}" flow="up"><img src="@if(fav.avatar)/t/{{ fav.avatar }}.webp@else/s/img/default.png@endif" style="height: 32px; width: 32px" /></a>
@endeach
@endif
</span>
</div>
<div class="random">
<a href="/random"></a>
</div>
</div> </div>
</div> @include(comments)
@include(snippets/footer) @include(snippets/footer)
</div>

View File

@ -1,51 +1,40 @@
@include(snippets/header) @include(snippets/header)
<div id="main"> <div class="topf0ckers">
<div class="topf0ckers"> <h3>Top f0ckers of all time <br>- Ranking -</h3>
<h3>Top f0ckers of all time <br>- Ranking -</h3> </div>
<div class="ranking">
<div class="by-user">
<h3>Biggest taggers</h3>
<table class="table">
<tbody>
@for(let i = 0; i < list.length; i++)
<tr>
<td>{{ i + 1 }}</td>
<td><a href="/{{ list[i].avatar }}"><img class="avatar" src="/t/{{ list[i].avatar }}.webp" /></a></td>
<td><a href="/user/{!! list[i].user !!}">{!! list[i].user !!}</a></td>
<td>{{ list[i].count }}</td>
</tr>
@endfor
</tbody>
</table>
</div> </div>
<div class="ranking"> <div class="by-hoster">
<div class="by-user"> <h3>Top {{ hoster.length }} hoster</h3>
<h3>Biggest taggers</h3> <table class="table">
<table class="table"> @each(hoster as host)
<tbody> <tr><td>{{ host.part.length ? host.part : "Telegram" }}</td><td>{{ host.c }}</td></tr>
@for(let i = 0; i < list.length; i++) @endeach
<tr> </table>
<td>{{ i + 1 }}</td> </div>
<td><a href="/{{ list[i].avatar }}"><img class="avatar" src="/t/{{ list[i].avatar }}.webp" /></a></td> <div class="by-stats">
<td><a href="/user/{!! list[i].user !!}">{!! list[i].user !!}</a></td> <h3>Tag stats</h3>
<td>{{ list[i].count }}</td> <table class="table">
</tr> <tr><td>total</td><td>{{ stats.total }}</td></tr>
@endfor <tr><td>tagged</td><td>{{ stats.tagged }}</td></tr>
</tbody> <tr><td>untagged</td><td>{{ stats.untagged }}</td></tr>
</table> <tr><td>SFW</td><td>{{ stats.sfw }}</td></tr>
</div> <tr><td>NSFW</td><td>{{ stats.nsfw }}</td></tr>
<div class="by-hoster"> </table>
<h3>Top {{ hoster.length }} hoster</h3>
<table class="table">
@each(hoster as host)
<tr><td>{{ host.part.length ? host.part : "Telegram" }}</td><td>{{ host.c }}</td></tr>
@endeach
</table>
</div>
<div class="by-stats">
<h3>Tag stats</h3>
<table class="table">
<tr><td>total</td><td>{{ stats.total }}</td></tr>
<tr><td>tagged</td><td>{{ stats.tagged }}</td></tr>
<tr><td>untagged</td><td>{{ stats.untagged }}</td></tr>
<tr><td>SFW</td><td>{{ stats.sfw }}</td></tr>
<tr><td>NSFW</td><td>{{ stats.nsfw }}</td></tr>
<tr><td>deleted</td><td>{{ stats.deleted }}</td></tr>
<tr><td>missing ids</td><td>{{ stats.untracked }}</td></tr>
</table>
<h3>Top f0cks</h3>
<table class="table">
@each(favotop as favo)
<tr><td><a href="/{{ favo.item_id }}">{{ favo.item_id }}</a></td><td>{{ favo.favs }}</td></tr>
@endeach
</table>
</div>
</div> </div>
</div> </div>
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,47 +1,37 @@
@include(snippets/header) @include(snippets/header)
<div id="main"> <div class="f0ckgle">
<div class="f0ckgle"> <h1 style="text-align: center">f0ckgle</h1>
<div class="search-title"> <form action="/search" class="admin-search">
<span style="color:#4285F4;">f</span> <input type="text" name="tag" value="{!! searchstring || '' !!}" /><button type="submit"><b>f0ck</b></button>
<span style="color:#EA4335;">0</span> </form>
<span style="color:#FBBC05;">c</span> <div class="results">
<span style="color:#4285F4;">k</span> @if(result)
<span style="color:#34A853;">g</span> <h2>{{ count }} f0cks given (page {{ pagination.page }} of {{ pagination.end }}):</h2>
<span style="color:#EA4335;">l</span> <table style="width: 100%" class="table">
<span style="color:#4285F4;">e</span> <thead>
</div> <tr>
<form action="/search" class="admin-search"> <th>Thumbnail</th>
<input type="text" name="tag" value="{!! searchstring || '' !!}" /><button type="submit">🔍</button> <th>ID</th>
</form> <th>Tag</th>
<div class="results"> <th>Mime</th>
@if(result) <th>Username</th>
<h2>{{ count }} f0cks given (page {{ pagination.page }} of {{ pagination.end }}):</h2> <th>Score</th>
<table style="width: 100%" class="table"> </tr>
<thead> </thead>
<tr> <tbody>
<th>Thumbnail</th> @each(result as line)
<th>ID</th> <tr>
<th>Tag</th> <td style="width: 128px;"><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank"><img src="/t/{{ line.id }}.webp" /></a></td>
<th>Mime</th> <td><span class="mview_desc">ID:</span><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank">{{ line.id }}</a></td>
<th>Username</th> <td><span class="mview_desc">Tag:</span><a href="/tag/{!! line.tag !!}">{!! line.tag !!}</a></td>
<th>Score</th> <td><span class="mview_desc">Mime:</span>{{ line.mime }}</td>
</tr> <td><span class="mview_desc">User:</span><a href="/user/{!! line.username !!}/f0cks/{{ line.id }}">{!! line.username !!}</a></td>
</thead> <td><span class="mview_desc">Score:</span>{{ line.score?.toFixed(2) }}</td>
<tbody> </tr>
@each(result as line) @endeach
<tr> </tbody>
<td style="width: 128px;"><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank"><img src="/t/{{ line.id }}.webp" /></a></td> </table>
<td><span class="mview_desc">ID:</span><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank">{{ line.id }}</a></td> @endif
<td><span class="mview_desc">Tag:</span><a href="/tag/{!! line.tag !!}">{!! line.tag !!}</a></td>
<td><span class="mview_desc">Mime:</span>{{ line.mime }}</td>
<td><span class="mview_desc">User:</span><a href="/user/{!! line.username !!}/f0cks/{{ line.id }}">{!! line.username !!}</a></td>
<td><span class="mview_desc">Score:</span>{{ line.score?.toFixed(2) }}</td>
</tr>
@endeach
</tbody>
</table>
@endif
</div>
</div> </div>
</div> </div>
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,54 +1,52 @@
@include(snippets/header) @include(snippets/header)
<div id="main"> <h1>Settings</h1>
<h1>Settings</h1> @if(session.avatar)<a href="/{{ session.avatar }}"><img id="img_avatar" src="/t/{{ session.avatar }}.webp" /></a>@endif
@if(session.avatar)<a href="/{{ session.avatar }}"><img id="img_avatar" src="/t/{{ session.avatar }}.webp" /></a>@endif <h2>Account</h2>
<h2>Account</h2> <table class="table">
<table class="table"> <tbody>
<tbody> <tr>
<tr> <td>UserID</td>
<td>UserID</td> <td>{{ session.id }}</td>
<td>{{ session.id }}</td> </tr>
</tr> <tr>
<tr> <td>level</td>
<td>level</td> <td>{{ session.level }}/100</td>
<td>{{ session.level }}/100</td> </tr>
</tr> <tr>
<tr> <td>username</td>
<td>username</td> <td>{!! session.user !!}</td>
<td>{!! session.user !!}</td> </tr>
</tr> <tr>
<tr> <td>avatar</td>
<td>avatar</td> <td><input type="text" class="input" name="i_avatar" value="{{ session.avatar }}" /><input type="submit" id="s_avatar" value="save" /></td>
<td><input type="text" class="input" name="i_avatar" value="{{ session.avatar }}" /><input type="submit" id="s_avatar" value="save" /></td> </tr>
</tr> </tbody>
</tbody> </table>
</table> <h2>Sessions</h2>
<h2>Sessions</h2> <table class="table">
<table class="table"> <thead>
<thead> <tr>
<tr> <th>&nbsp;</th>
<th>&nbsp;</th> <th>id</th>
<th>id</th> <th>&nbsp;</th>
<th>&nbsp;</th> <th>last action</th>
<th>last action</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> @each(sessions as sess)
@each(sessions as sess) <tr@if(sess.id === session.sess_id) style="background-color: rgb(0, 89, 0)"@endif>
<tr@if(sess.id === session.sess_id) style="background-color: rgb(0, 89, 0)"@endif> <td>{{ sess.kmsi ? '&#9875;' : '' }}</td>
<td>{{ sess.kmsi ? '&#9875;' : '' }}</td> <td tooltip="{{ sess.browser }}" flow="right">
<td tooltip="{{ sess.browser }}" flow="right"> <p>{{ sess.id }}</p>
<p>{{ sess.id }}</p> <p><a href="#" onclick="alert('not yet implemented!');">logout</a></p>
<p><a href="#" onclick="alert('not yet implemented!');">logout</a></p> </td>
</td> <td>
<td> <p>last_used: {{ new Date(sess.last_used * 1e3).toLocaleString("de-DE") }}</p>
<p>last_used: {{ new Date(sess.last_used * 1e3).toLocaleString("de-DE") }}</p> <p>created_at: {{ new Date(sess.created_at * 1e3).toLocaleString("de-DE") }}</p>
<p>created_at: {{ new Date(sess.created_at * 1e3).toLocaleString("de-DE") }}</p> </td>
</td> <td><a href="{{ sess.last_action }}" target="_blank">{{ sess.last_action }}</a></td>
<td><a href="{{ sess.last_action }}" target="_blank">{{ sess.last_action }}</a></td> </tr>
</tr> @endeach
@endeach </tbody>
</tbody> </table>
</table>
</div>
@include(snippets/footer) @include(snippets/footer)

View File

@ -1,4 +1,5 @@
@if(session)<!--<div style="position: fixed; bottom: 0; z-index: 998; background-color: var(--bg); width: 100%; height: 29px; border-top: 1px solid var(--accent)">{{ JSON.stringify(session) }}</div>-->@endif @if(session)<!--<div style="position: fixed; bottom: 0; z-index: 998; background-color: var(--bg); width: 100%; height: 29px; border-top: 1px solid var(--accent)">{{ JSON.stringify(session) }}</div>-->@endif
</div>
<script async src="/s/js/theme.js?v=@mtime(/public/s/js/theme.js)"></script> <script async src="/s/js/theme.js?v=@mtime(/public/s/js/theme.js)"></script>
<script src="/s/js/v0ck.js?v=@mtime(/public/s/js/v0ck.js)"></script> <script src="/s/js/v0ck.js?v=@mtime(/public/s/js/v0ck.js)"></script>
<script src="/s/js/f0ck.js?v=@mtime(/public/s/js/f0ck.js)"></script> <script src="/s/js/f0ck.js?v=@mtime(/public/s/js/f0ck.js)"></script>

View File

@ -1,5 +1,5 @@
<!doctype html> <!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">
<head> <head>
<title>f0ck!</title> <title>f0ck!</title>
<meta name="description" content="Welcome to the internet"/> <meta name="description" content="Welcome to the internet"/>
@ -12,3 +12,4 @@
</head> </head>
<body> <body>
@include(snippets/navbar) @include(snippets/navbar)
<div id="main">

View File

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif">
<head>
<title>@if(typeof data !== "undefined" && data.title){{ data.title }}@elsef0ck!@endif</title>
<link rel="icon" type="image/gif" href="/s/img/favicon.gif" />
<link rel="stylesheet" href="/s/css/f0ck.css">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="f0ck.me is the place where internet purists gather to celebrate content of all kinds">
@if(typeof data !== "undefined" && data.item)
<meta property="og:site_name" content="f0ck.me" />
<meta property="og:description"/>
<meta name="Description"/>
<meta property="og:image" content="{{ item.thumbnail }}" />
@endif
</head>
<body>
@include(snippets/navbar_admin)
<div id="main">

View File

@ -2,54 +2,53 @@
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">F0CK</span></a> <a class="navbar-brand" href="/"><span class="f0ck" width="" height="">F0CK</span></a>
<div class="navigation-links"> <div class="navigation-links">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link user" href="#" content="{{ session.user }}" data-toggle="dropdown"> <a class="nav-link user" href="#" content="{{ session.user }}" data-toggle="dropdown">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar" /><span>{{ session.user }}</span> <img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar" /><span>{{ session.user }}</span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="/user/{{ session.user.toLowerCase() }}">my profile</a></li> <li><a href="/admin">adminpanel</a></li>
<li><a href="/user/{{ session.user.toLowerCase() }}/f0cks">my f0cks</a></li> <li><a href="/user/{{ session.user.toLowerCase() }}/f0cks">my f0cks</a></li>
<li><a href="/user/{{ session.user.toLowerCase() }}/favs">my favs</a></li> <li><a href="/user/{{ session.user.toLowerCase() }}/favs">my favs</a></li>
<li><a href="/search">search</a></li> <li><a href="/settings">settings</a></li>
<li><a href="/admin">Admin</a></li> <li><a href="/search">search</a></li>
<li><a href="/about">About</a></li> <li><a href="/about">About</a></li>
<li><a href="/ranking">ranking</a></li> <li><a href="/ranking">Ranking</a></li>
<li><a href="/settings">settings</a></li> <li><a href="/logout">logout</a></li>
<li><a href="/logout">logout</a></li> </ul>
</ul> </li>
</li> <li class="nav-item dropdown" id="themes">
<li class="nav-item dropdown" id="themes"> <a class="nav-link ddcontent" href="#" content="{{ theme }}" data-toggle="dropdown">Themes</a>
<a class="nav-link ddcontent" href="#" content="{{ theme }}" data-toggle="dropdown">Themes</a> <ul class="dropdown-menu">
<ul class="dropdown-menu"> @each(themes as t)
@each(themes as t) <li><a href="/theme/{{ t }}">{{ t }}</a></li>
<li><a href="/theme/{{ t }}">{{ t }}</a></li> @endeach
@endeach </ul>
</ul> </li>
</li> <li class="nav-item dropdown">
<li class="nav-item dropdown"> <a class="nav-link ddcontent" href="#"@if(tmp?.mime) content="{{ tmp?.mime }}" data-toggle="dropdown"@endif>Filter@if(!tmp?.mime)&nbsp;&#9660;@endif</a>
<a class="nav-link ddcontent" href="#"@if(tmp?.mime) content="{{ tmp?.mime }}" data-toggle="dropdown"@endif>Filter@if(!tmp?.mime)&nbsp;&#9660;@endif</a> <ul class="dropdown-menu">
<ul class="dropdown-menu"> <li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endif">All</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endif">All</a></li> <li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifaudio">Audio</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifaudio">Audio</a></li> <li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifvideo">Video</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifvideo">Video</a></li> <li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifimage">Image</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifimage">Image</a></li> </ul>
</ul> </li>
</li> <li class="nav-item @if(session)dropdown@endif">
<li class="nav-item @if(session)dropdown@endif"> <a class="nav-link ddcontent" href="#"@if(typeof session.mode !== "undefined") content="{{ modes[session.mode] ?? 'sfw' }}" data-toggle="dropdown"@endif>Mode</a>
<a class="nav-link ddcontent" href="#"@if(typeof session.mode !== "undefined") content="{{ modes[session.mode] ?? 'sfw' }}" data-toggle="dropdown"@endif>Mode</a> <ul class="dropdown-menu">
<ul class="dropdown-menu"> @for(let i = 0; i < modes.length; i++)
@for(let i = 0; i < modes.length; i++) <li><a class="dropdown-item" href="/mode/{{ i }}">{{ modes[i] }}</a></li>
<li><a class="dropdown-item" href="/mode/{{ i }}">{{ modes[i] }}</a></li> @endfor
@endfor </ul>
</ul> </li>
</li> <li class="nav-item">
<li class="nav-item"> <a id="random" class="nav-link" href="/random">
<a id="random" class="nav-link" href="/random"> <span class="nav-link-identifier">Random</span>
<span class="nav-link-identifier">Random</span> </a>
</a> </li>
</li> </ul>
</ul>
</div> </div>
<div class="collapse navbar-collapse show" id="navbarSupportedContent"> <div class="collapse navbar-collapse show" id="navbarSupportedContent">
<div class="pagination-container-fluid"> <div class="pagination-container-fluid">

View File

@ -1,133 +0,0 @@
@if(session)
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">F0CK</span></a>
<div class="navigation-links">
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link" href="#" content="{{ session.user }}" data-toggle="dropdown">
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar" /><span>{{ session.user }}</span>
</a>
<ul class="dropdown-menu">
<li><a href="/admin">adminpanel</a></li>
<li><a href="/user/{{ session.user.toLowerCase() }}/f0cks">my f0cks</a></li>
<li><a href="/user/{{ session.user.toLowerCase() }}/favs">my favs</a></li>
<li><a href="/settings">settings</a></li>
<li><a href="/search">search</a></li>
<li><a href="/about">About</a></li>
<li><a href="/ranking">Ranking</a></li>
<li><a href="/logout">logout</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="themes">
<a class="nav-link ddcontent" href="#" content="{{ theme }}" data-toggle="dropdown">Themes</a>
<ul class="dropdown-menu">
@each(themes as t)
<li><a href="/theme/{{ t }}">{{ t }}</a></li>
@endeach
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link ddcontent" href="#"@if(tmp?.mime) content="{{ tmp?.mime }}" data-toggle="dropdown"@endif>Filter@if(!tmp?.mime)&nbsp;&#9660;@endif</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endif">All</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifaudio">Audio</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifvideo">Video</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifimage">Image</a></li>
</ul>
</li>
<li class="nav-item @if(session)dropdown@endif">
<a class="nav-link ddcontent" href="#"@if(typeof session.mode !== "undefined") content="{{ modes[session.mode] ?? 'sfw' }}" data-toggle="dropdown"@endif>Mode</a>
<ul class="dropdown-menu">
@for(let i = 0; i < modes.length; i++)
<li><a class="dropdown-item" href="/mode/{{ i }}">{{ modes[i] }}</a></li>
@endfor
</ul>
</li>
<li class="nav-item">
<a id="random" class="nav-link" href="/random">
<span class="nav-link-identifier">Random</span>
</a>
</li>
</ul>
</div>
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
<div class="pagination-container-fluid">
<div class="pagination-wrapper">
@if(typeof pagination !== "undefined")
<nav class="pagination">
<a href="{{ link.main }}{{ link.path }}{{ pagination.start }}" class="page-item-1 btn start@if(!pagination.prev) disabled@endif">&laquo;</a>
<a href="{{ link.main }}{{ link.path }}{{ pagination.prev }}" class="page-item-2 btn prev@if(!pagination.prev) disabled@endif">&lsaquo;</a>
@each(pagination.cheat as i)
@if(i == pagination.page)
<span class="btn disabled">{{ i }}</span>
@else
<a href="{{ link.main }}{{ link.path }}{{ i }}" class="pagination-int-item btn">{{ i }}</a>
@endif
@endeach
<a href="{{ link.main }}{{ link.path }}{{ pagination.next }}" class="page-item-3 btn next@if(!pagination.next) disabled@endif">&rsaquo;</a>
<a href="{{ link.main }}{{ link.path }}{{ pagination.end }}" class="page-item-4 btn start@if(!pagination.next) disabled@endif">&raquo;</a>
</nav>
@endif
</div>
</div>
</div>
</nav>
@else
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="/"><span class="f0ck" width="" height="">F0CK</span></a>
<div class="navigation-links-guest">
<ul class="navbar-nav-guests">
<li class="nav-item dropdown">
<a class="nav-link" href="/about" data-toggle="dropdown">About</a>
<ul class="dropdown-menu">
<li><a href="/login">login</a></li>
</ul>
</li>
<li class="nav-item dropdown" id="themes">
<a class="nav-link ddcontent" href="#" content="{{ theme }}" data-toggle="dropdown">Themes</a>
<ul class="dropdown-menu">
@each(themes as t)
<li><a href="/theme/{{ t }}">{{ t }}</a></li>
@endeach
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link ddcontent" href="#"@if(tmp?.mime) content="{{ tmp?.mime }}" data-toggle="dropdown"@endif>Filter@if(!tmp?.mime)&nbsp;&#9660;@endif</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endif">All</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifaudio">Audio</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifvideo">Video</a></li>
<li><a class="dropdown-item" href="/@if(tmp?.user)user/{{ tmp?.user }}/@endifimage">Image</a></li>
</ul>
</li>
<li class="nav-item">
<a id="random" class="nav-link" href="/random">
<span class="nav-link-identifier">Random</span>
</a>
</li>
</ul>
</div>
<div class="collapse navbar-collapse show" id="navbarSupportedContent">
<div class="pagination-container-fluid">
<div class="pagination-wrapper">
@if(typeof pagination !== "undefined")
<nav class="pagination">
<a href="{{ link.main }}{{ link.path }}{{ pagination.start }}" class="page-item-1 btn start@if(!pagination.prev) disabled@endif">&laquo;</a>
<a href="{{ link.main }}{{ link.path }}{{ pagination.prev }}" class="page-item-2 btn prev@if(!pagination.prev) disabled@endif">&lsaquo;</a>
@each(pagination.cheat as i)
@if(i == pagination.page)
<span class="btn disabled">{{ i }}</span>
@else
<a href="{{ link.main }}{{ link.path }}{{ i }}" class="pagination-int-item btn">{{ i }}</a>
@endif
@endeach
<a href="{{ link.main }}{{ link.path }}{{ pagination.next }}" class="page-item-3 btn next@if(!pagination.next) disabled@endif">&rsaquo;</a>
<a href="{{ link.main }}{{ link.path }}{{ pagination.end }}" class="page-item-4 btn start@if(!pagination.next) disabled@endif">&raquo;</a>
</nav>
@endif
</div>
</div>
</div>
</nav>
@endif

View File

@ -0,0 +1,33 @@
<nav class="navbar navbar-expand-lg">
<a class="navbar-brand" href="/"><span class="f0ck">F0CK</span></a>
<div class="navigation-links">
<ul class="navbar-nav">
<li class="nav-item dropdown" id="themes">
<a class="nav-link" href="#" content="{{ theme }}" data-toggle="dropdown">Theme</a>
<ul class="dropdown-menu">
@each(themes as t)
<li><a href="/theme/{{ t }}">{{ t }}</a></li>
@endeach
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/sessions">
<span class="nav-link-identifier">sessions</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/log">
<span class="nav-link-identifier">Log</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/recover">
<span class="nav-link-identifier">Recover</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
</ul>
</div>
</nav>

View File

@ -1,14 +0,0 @@
@include(snippets/header)
<h3>Top10 ({{ year }}-{{ month }})</h3>
<table class="table">
@each(f0cks as f0ck)
<tr>
<td><a href="//f0ck.me/{{ f0ck.id }}"><img src="//f0ck.me/t/{{ f0ck.id }}.webp" /></a></td>
<td><a href="//f0ck.me/{{ f0ck.id }}">{{ f0ck.id }}</a></td>
<td>{{ f0ck.username }}</td>
</tr>
@endeach
</table>
@include(snippets/footer)

View File

@ -1,49 +1,28 @@
@include(snippets/header) @include(snippets/header)
<div id="main"> <h1>{{ user.user }}</h1>
<div class="profile_head">
@if(user.avatar) <h2>f0cks:</h2>
<div class="profile_head_avatar"> @if('items' in f0cks)
<img src="/t/{{ user.avatar }}.webp" style="display: grid;width: 55px" /> <div class="posts">
</div> @each(f0cks.items as item)
@endif <a href="{{ f0cks.link.main }}{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.webp')"><p></p></a>
<div class="layersoffear"> @endeach
<div class="profile_head_username">
<span>{{ user.user }}</span>
</div>
<div class="profile_head_user_stats">
ID: {{ user.user_id }} Joined: {{ user.created_at }}
</div>
</div>
</div>
<div class="user_content_wrapper">
<div class="f0cks">
<div class="f0cks-header">
f0ck{{ count.f0cks == 1 ? '' : 's' }}: {{ count.f0cks }} <a href="{{ f0cks.link.main }}">view all</a>
</div>
@if('items' in f0cks)
<div class="posts">
@each(f0cks.items as item)
<a href="{{ f0cks.link.main }}{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.webp')"><p></p></a>
@endeach
</div>
@else
no f0cks given
@endif
</div>
<div class="favs">
<div class="favs-header">
fav{{ count.favs == 1 ? '' : 's' }}: {{ count.favs }} <a href="{{ favs.link.main }}">view all</a>
</div>
@if('items' in favs)
<div class="posts">
@each(favs.items as item)
<a href="{{ favs.link.main }}{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.webp')"><p></p></a>
@endeach
</div>
@else
no favorites
@endif
</div>
</div>
</div> </div>
@include(snippets/footer) <a href="{{ f0cks.link.main }}">show all f0cks</a>
@else
no f0cks given
@endif
<h2>favs:</h2>
@if('items' in favs)
<div class="posts">
@each(favs.items as item)
<a href="{{ favs.link.main }}{{ item.id }}" data-mime="{{ item.mime }}" data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}" style="background-image: url('/t/{{ item.id }}.webp')"><p></p></a>
@endeach
</div>
<a href="{{ favs.link.main }}">show all favs</a>
@else
no favorites
@endif
@include(snippets/footer)