Compare commits
122 Commits
comments
...
f0ck_local
Author | SHA1 | Date | |
---|---|---|---|
00db7c6113 | |||
050c9e66c8 | |||
d134e4d6be | |||
d4ac155c68 | |||
afb38f9af5 | |||
604e54f238 | |||
84c58479eb | |||
d69f9b8427 | |||
6c6f739110 | |||
f674fe2a40 | |||
f32d712dff | |||
64943719da | |||
4b78b70ede | |||
177e184621 | |||
1f42acb726 | |||
6e561071ee | |||
564f1bf530 | |||
d9e8b63004 | |||
c7329292ca | |||
8e9de0252e | |||
1a4afd22ab | |||
c13ca165c0 | |||
cba5fc0410 | |||
7a42e6ffce | |||
5e39c67147 | |||
226c368945 | |||
8dc448fe13 | |||
b1c9b7de72 | |||
89a93cc50c | |||
a0c5f94921 | |||
95a40bd1c2 | |||
56dcabfa94 | |||
5d79243fe1 | |||
267a1427c3 | |||
4ec8edfb37 | |||
8b9677bb76 | |||
68724890fd | |||
0dff028982 | |||
d9ced4a896 | |||
0e793ecde5 | |||
1b350781f2 | |||
84937c88a1 | |||
1d84180603 | |||
a942fc0b94 | |||
abf4ca92b1 | |||
cc73ee2162 | |||
6590f72b88 | |||
0b2f174ee8 | |||
f9e10abb51 | |||
78a2524ce9 | |||
81e7d50a6b | |||
1380e45794 | |||
9ca17e6fd4 | |||
48596ad17a | |||
85212051d0 | |||
046a7460ca | |||
f2757f08af | |||
e2f15ce862 | |||
aa3a4d0217 | |||
3074b0294a | |||
fb48cccdbb | |||
8dd54811ee | |||
460d4a84aa | |||
a9a5be9fb4 | |||
b2c43e18bb | |||
84104b58bc | |||
a753a3f27d | |||
05c59717fd | |||
51cfc34f75 | |||
a49ea84ac3 | |||
5fcad639dc | |||
b12170382a | |||
afba818021 | |||
5eeaa28612 | |||
2301d15abd | |||
fbf03bfbb4 | |||
63bd1104b7 | |||
e47dfb38fb | |||
cea4a13b4c | |||
8d32dfef24 | |||
f39a136d5d | |||
7dc03936ab | |||
223c8893df | |||
8e02892df0 | |||
85d683ca31 | |||
61e02953c7 | |||
9ccf17ac54 | |||
4e6b337d4f | |||
9a55781a64 | |||
b95ce804ab | |||
e0ac02e742 | |||
d2bf6f7afd | |||
d4218ad1e8 | |||
bab2a03382 | |||
4ca44ee8b9 | |||
9157868699 | |||
dc986edfe4 | |||
ac5c6ea0b5 | |||
8ec51789dd | |||
8b6f68f2e9 | |||
44df4deea3 | |||
b52b1bc19d | |||
7899f3bbd1 | |||
955f844e16 | |||
3f0b502b00 | |||
5da67f2a6a | |||
bbe31594e5 | |||
e16197f8e4 | |||
2186fc85d4 | |||
ca516ded7c | |||
c50048d8e6 | |||
49849a4a12 | |||
5e0ffda6c5 | |||
fdc8b836b3 | |||
d37a55aa1e | |||
cb0a5f7ee8 | |||
0f6164a825 | |||
41e4bb8fae | |||
534fb1d92a | |||
6b6e44eed7 | |||
c806410be6 | |||
9239666291 |
43
.gitea/workflows/build.yaml
Normal file
43
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
@ -1,7 +1,7 @@
|
||||
# how to install:
|
||||
## dependencies
|
||||
```bash
|
||||
sudo pacman -S nodejs npm ffmpeg yt-dlp ffmpegthumbnailer postgresql python python-pip imagemagick git mime-types
|
||||
sudo pacman -S nodejs npm ffmpeg yt-dlp ffmpegthumbnailer postgresql imagemagick git mime-types
|
||||
```
|
||||
## postgres
|
||||
```bash
|
||||
@ -26,7 +26,5 @@ mkdir -p public/ca deleted/{ca,b,t}
|
||||
cp config_example.json config.json
|
||||
- edit config.json
|
||||
- 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
|
||||
```
|
||||
|
@ -22,20 +22,33 @@
|
||||
"thumbnails": "/t",
|
||||
"coverarts": "/ca"
|
||||
},
|
||||
"themes": [ "f0ck", "p1nk", "orange", "atmos", "amoled", "paper", "term", "iced" ],
|
||||
"eps": 294,
|
||||
"themes": [ "f0ck", "p1nk", "orange", "atmos", "amoled", "paper", "term", "iced", "f0ck95", "f0ck95d" ],
|
||||
"eps": 300,
|
||||
"cache": false,
|
||||
"phrases": [
|
||||
"<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": {
|
||||
"host": "localhost",
|
||||
"user": "f0ck",
|
||||
"password": "",
|
||||
"password": "f0ck",
|
||||
"database": "f0ck",
|
||||
"schema": "public",
|
||||
"multipleStatements": true
|
||||
@ -54,8 +67,10 @@
|
||||
"audio/mpeg": "mpg",
|
||||
"audio/mp3": "mp3",
|
||||
"audio/ogg": "ogg",
|
||||
"audio/opus": "opus",
|
||||
"audio/flac": "flac",
|
||||
"audio/x-flac": "flac",
|
||||
"video/x-m4v": "mp4"
|
||||
"video/x-m4v": "mp4",
|
||||
"video/x-matroska": "mkv"
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
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();
|
110
debug/clean.mjs
110
debug/clean.mjs
@ -1,33 +1,95 @@
|
||||
import sql from "../src/inc/sql.mjs";
|
||||
import { promises as fs } from "fs";
|
||||
import cfg from "../src/inc/config.mjs";
|
||||
import db from "../src/inc/sql.mjs";
|
||||
import fs from "node:fs";
|
||||
import readline from 'node:readline/promises';
|
||||
|
||||
const opts = {
|
||||
b: "public/b",
|
||||
t: "public/t",
|
||||
tmp: "tmp"
|
||||
};
|
||||
const count = {
|
||||
b: 0, t: 0, tmp: 0
|
||||
};
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
|
||||
const rows = await sql('items').select('id', 'dest');
|
||||
const ids = rows.map(r => r.id.toString() + ".webp");
|
||||
const dests = rows.map(r => r.dest);
|
||||
// npm run clean -- --dry-run
|
||||
const dry = !!process.argv.filter(a => a == '--dry-run').length;
|
||||
console.log(`dry run? ${dry}`);
|
||||
|
||||
const dirs = {
|
||||
b: "./public/b",
|
||||
t: "./public/t",
|
||||
tmp: "./tmp"
|
||||
};
|
||||
const files = {
|
||||
b: (await fs.readdir(opts.b)).filter(f => f !== '.empty'),
|
||||
t: await fs.readdir(opts.t)
|
||||
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 = {
|
||||
missing: { b: [], t: [] },
|
||||
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`)
|
||||
};
|
||||
|
||||
const unused = {
|
||||
b: files.b.filter(f => !dests.includes(f)),
|
||||
t: files.t.filter(f => !ids.includes(f))
|
||||
// missing
|
||||
for(const row of rows) {
|
||||
if(!fs.existsSync(`${dirs.b}/${row.dest}`))
|
||||
count.missing.b.push(row.id);
|
||||
if(!fs.existsSync(`${dirs.t}/${row.id}.webp`))
|
||||
count.missing.t.push(row.id);
|
||||
}
|
||||
|
||||
count.b = (await Promise.all(unused.b.map(f => fs.rm(`${opts.b}/${f}`)))).length;
|
||||
count.t = (await Promise.all(unused.t.map(f => fs.rm(`${opts.t}/${f}`)))).length;
|
||||
// invalid
|
||||
count.invalid.b = files.b.filter(f => !extensions.includes(f.toLowerCase().split('.')[1]));
|
||||
count.invalid.t = files.t.filter(f => !f.endsWith('.webp'));
|
||||
|
||||
// clear tmp
|
||||
const tmp = (await fs.readdir(opts.tmp)).filter(f => f !== '.empty');
|
||||
count.tmp = (await Promise.all(tmp.map(f => fs.rm(`${opts.tmp}/${f}`)))).length;
|
||||
// spare
|
||||
for(const file of files.b)
|
||||
if(!f0cks.b.includes(file))
|
||||
count.spare.b.push(`${dirs.b}/${file}`);
|
||||
|
||||
console.log(count, unused);
|
||||
for(const file of files.t)
|
||||
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();
|
||||
|
3
f0ck.sql
3
f0ck.sql
@ -140,7 +140,8 @@ CREATE TABLE public.user_options (
|
||||
user_id integer NOT NULL,
|
||||
mode integer 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;
|
||||
|
BIN
nsfw_model.h5
BIN
nsfw_model.h5
Binary file not shown.
32
package-lock.json
generated
32
package-lock.json
generated
@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "f0ckv2",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "f0ckv2",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cuffeo": "^1.1.0",
|
||||
"cuffeo": "^1.2.2",
|
||||
"flumm-fetch": "^1.0.1",
|
||||
"flummpress": "^2.0.5",
|
||||
"postgres": "^3.0.1"
|
||||
"postgres": "^3.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/cuffeo": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.1.0.tgz",
|
||||
"integrity": "sha512-7lmx2dvREqCYwy8oUzk3Q0EkyLZkKQTYTBLEjNqKVbinzdEL45Oimi54lmBDFPlrrvTLzQkFvzKmiJ7Zcegi2w==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz",
|
||||
"integrity": "sha512-Pd2AL/Zp5RCAbaSbT7rLUOyxSEVCFovihaIaje7uYzpQMzIwPbFapQ/mn2d2iz+Tbkjs9LF+FD5JzAvANh9Wzw==",
|
||||
"dependencies": {
|
||||
"flumm-fetch": "^1.0.1"
|
||||
}
|
||||
@ -34,9 +34,9 @@
|
||||
"integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ=="
|
||||
},
|
||||
"node_modules/postgres": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.1.0.tgz",
|
||||
"integrity": "sha512-yQbJtA7z6SsQuFiF01rmHlkG2gU5IEv9D/Pn2fu8Tz6x3/suNWZs4fIPd59rkL+dfVfhDqYQ7DU3YnUqzEDB5Q==",
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz",
|
||||
"integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/porsager"
|
||||
@ -45,9 +45,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cuffeo": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.1.0.tgz",
|
||||
"integrity": "sha512-7lmx2dvREqCYwy8oUzk3Q0EkyLZkKQTYTBLEjNqKVbinzdEL45Oimi54lmBDFPlrrvTLzQkFvzKmiJ7Zcegi2w==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cuffeo/-/cuffeo-1.2.2.tgz",
|
||||
"integrity": "sha512-Pd2AL/Zp5RCAbaSbT7rLUOyxSEVCFovihaIaje7uYzpQMzIwPbFapQ/mn2d2iz+Tbkjs9LF+FD5JzAvANh9Wzw==",
|
||||
"requires": {
|
||||
"flumm-fetch": "^1.0.1"
|
||||
}
|
||||
@ -63,9 +63,9 @@
|
||||
"integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ=="
|
||||
},
|
||||
"postgres": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.1.0.tgz",
|
||||
"integrity": "sha512-yQbJtA7z6SsQuFiF01rmHlkG2gU5IEv9D/Pn2fu8Tz6x3/suNWZs4fIPd59rkL+dfVfhDqYQ7DU3YnUqzEDB5Q=="
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz",
|
||||
"integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
package.json
18
package.json
@ -3,21 +3,21 @@
|
||||
"version": "2.2.1",
|
||||
"description": "f0ck, kennste?",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node --experimental-json-modules src/index.mjs",
|
||||
"trigger": "node --experimental-json-modules debug/trigger.mjs",
|
||||
"autotagger": "node --experimental-json-modules debug/autotagger.mjs",
|
||||
"thumbnailer": "node --experimental-json-modules debug/thumbnailer.mjs",
|
||||
"test": "node --experimental-json-modules debug/test.mjs",
|
||||
"clean": "node --experimental-json-modules debug/clean.mjs",
|
||||
"adduser": "node --experimental-json-modules debug/adduser.mjs"
|
||||
"start": "node --trace-uncaught src/index.mjs",
|
||||
"trigger": "node debug/trigger.mjs",
|
||||
"autotagger": "node debug/autotagger.mjs",
|
||||
"thumbnailer": "node debug/thumbnailer.mjs",
|
||||
"test": "node debug/test.mjs",
|
||||
"clean": "node debug/clean.mjs"
|
||||
},
|
||||
"author": "Flummi",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cuffeo": "^1.1.0",
|
||||
"cuffeo": "^1.2.2",
|
||||
"flumm-fetch": "^1.0.1",
|
||||
"flummpress": "^2.0.5",
|
||||
"postgres": "^3.0.1"
|
||||
"postgres": "^3.3.4"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -72,7 +72,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
.v0ck_player_controls svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: #fff;
|
||||
|
@ -6,5 +6,7 @@
|
||||
<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="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>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.9 KiB |
@ -85,7 +85,7 @@ const flash = ({ type, msg }) => {
|
||||
[...document.querySelectorAll("#tags > .badge")].forEach(tag => tag.parentElement.removeChild(tag));
|
||||
_tags.reverse().forEach(tag => {
|
||||
const a = document.createElement("a");
|
||||
a.href = `/tag/${tag.tag}`;
|
||||
a.href = `/tag/${tag.normalized}`;
|
||||
a.style = "color: inherit !important";
|
||||
a.innerHTML = tag.tag;
|
||||
a.addEventListener("click", editTagEvent); // tmp
|
||||
|
@ -1,3 +1,11 @@
|
||||
|
||||
window.requestAnimFrame = (function(){
|
||||
return window.requestAnimationFrame
|
||||
|| window.webkitRequestAnimationFrame
|
||||
|| window.mozRequestAnimationFrame
|
||||
|| function(callback) { window.setTimeout(callback, 1000 / 60);};
|
||||
})();
|
||||
|
||||
(() => {
|
||||
let video;
|
||||
if(elem = document.querySelector("#my-video")) {
|
||||
@ -8,6 +16,42 @@
|
||||
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;
|
||||
@ -46,19 +90,44 @@
|
||||
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")) {
|
||||
const f0ckimagescroll = document.querySelector("#image-scroll");
|
||||
|
||||
let isImageExpanded = false;
|
||||
console.log("entry point - image unclicked")
|
||||
|
||||
f0ckimage.addEventListener("click", async e => {
|
||||
e.preventDefault();
|
||||
const img = await imgSize(f0ckimage);
|
||||
if(img.width > img.height)
|
||||
return;
|
||||
f0ckimagescroll.hasAttribute("style")
|
||||
? f0ckimagescroll.removeAttribute("style")
|
||||
: f0ckimagescroll.setAttribute("style", "overflow-y: scroll");
|
||||
f0ckimage.hasAttribute("style")
|
||||
? f0ckimage.removeAttribute("style")
|
||||
: 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;");
|
||||
console.log("img clicked");
|
||||
if (isImageExpanded) {
|
||||
isImageExpanded = false;
|
||||
f0ckimagescroll.removeAttribute("style");
|
||||
f0ckimage.removeAttribute("style");
|
||||
console.log("image is not expanded")
|
||||
window.addEventListener('wheel', wheelEventListener);
|
||||
} else {
|
||||
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>
|
||||
@ -68,7 +137,7 @@
|
||||
const scroll_treshold = 1;
|
||||
if([...document.querySelectorAll("div.posts")].length === 1) {
|
||||
document.addEventListener("wheel", e => {
|
||||
if(Math.ceil(window.innerHeight + window.scrollY) >= document.body.offsetHeight && e.deltaY > 0) { // down
|
||||
if(Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down
|
||||
if(elem = document.querySelector(".pagination > .next:not(.disabled)")) {
|
||||
if(tts < scroll_treshold) {
|
||||
document.querySelector("div#footbar").style.boxShadow = "inset 0px 4px 0px var(--footbar-color)";
|
||||
@ -234,4 +303,8 @@
|
||||
});
|
||||
}
|
||||
// </mediakeys>
|
||||
})();
|
||||
|
||||
// <scroller>
|
||||
|
||||
// </scroller>
|
||||
})();
|
@ -8,6 +8,7 @@ const Cookie = {
|
||||
opts['max-age'] = opts.days * 60 * 60 * 24;
|
||||
delete opts.days;
|
||||
}
|
||||
opts.SameSite = 'Strict';
|
||||
opts = Object.entries(opts).reduce((accumulatedStr, [k, v]) => `${accumulatedStr}; ${k}=${v}`, '');
|
||||
document.cookie = name + '=' + encodeURIComponent(value) + opts
|
||||
}
|
||||
@ -44,4 +45,21 @@ const Cookie = {
|
||||
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;
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
@ -18,6 +18,7 @@ const tpl_player = svg => `<div class="v0ck_player_controls">
|
||||
<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>
|
||||
<span style="flex: 30"></span>
|
||||
<button id="togglebg">💡</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>
|
||||
</button>
|
||||
|
32
src/inc/autotagger.mjs
Normal file
32
src/inc/autotagger.mjs
Normal file
@ -0,0 +1,32 @@
|
||||
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;
|
||||
};
|
||||
};
|
318
src/inc/events/callback_query.mjs
Normal file
318
src/inc/events/callback_query.mjs
Normal file
@ -0,0 +1,318 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
};
|
@ -40,7 +40,7 @@ export default async bot => {
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
e.reply(`${t[0]}: An error occured.`);
|
||||
await e.reply(`${t[0]}: An error occured.`);
|
||||
logger.error(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${err.toString ? err : JSON.stringify(err)}`);
|
||||
}
|
||||
});
|
||||
|
@ -84,32 +84,43 @@ export default new class {
|
||||
|
||||
// async funcs
|
||||
async countf0cks() {
|
||||
const tagged = (await db`
|
||||
const tagged = +(await db`
|
||||
select count(*) as total
|
||||
from "items"
|
||||
where id in (select item_id from tags_assign group by item_id)
|
||||
where id in (select item_id from tags_assign group by item_id) and active = true
|
||||
`)[0].total;
|
||||
const untagged = (await db`
|
||||
const untagged = +(await db`
|
||||
select count(*) as total
|
||||
from "items"
|
||||
where id not in (select item_id from tags_assign group by item_id)
|
||||
where id not in (select item_id from tags_assign group by item_id) and active = true
|
||||
`)[0].total;
|
||||
const sfw = (await db`
|
||||
const sfw = +(await db`
|
||||
select count(*) as total
|
||||
from "items"
|
||||
where id in (select item_id from tags_assign where tag_id = 1 group by item_id)
|
||||
where id in (select item_id from tags_assign where tag_id = 1 group by item_id) and active = true
|
||||
`)[0].total;
|
||||
const nsfw = (await db`
|
||||
const nsfw = +(await db`
|
||||
select count(*) as total
|
||||
from "items"
|
||||
where id in (select item_id from tags_assign where tag_id = 2 group by item_id)
|
||||
where id in (select item_id from tags_assign where tag_id = 2 group by item_id) and active = true
|
||||
`)[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 {
|
||||
tagged,
|
||||
untagged,
|
||||
total: +tagged + +untagged,
|
||||
total: tagged + untagged,
|
||||
deleted,
|
||||
untracked: lastf0ck - (tagged + untagged + deleted),
|
||||
sfw,
|
||||
nsfw
|
||||
nsfw,
|
||||
};
|
||||
};
|
||||
async hash(str) {
|
||||
@ -161,6 +172,17 @@ export default new class {
|
||||
}
|
||||
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) {
|
||||
return false;
|
||||
const { stdout, stderr } = await exec(
|
||||
|
122
src/inc/queue.mjs
Normal file
122
src/inc/queue.mjs
Normal file
@ -0,0 +1,122 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
};
|
@ -32,7 +32,7 @@ export default {
|
||||
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
|
||||
${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
|
||||
${ mime ? db`and items.mime ilike ${smime}` : 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`` }
|
||||
${ !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`` }
|
||||
group by items.id, tags.tag
|
||||
`)?.length || 0;
|
||||
|
||||
@ -66,7 +66,7 @@ export default {
|
||||
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
|
||||
${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
|
||||
${ mime ? db`and items.mime ilike ${smime}` : 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`` }
|
||||
${ !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`` }
|
||||
group by items.id, tags.tag, ta.tag_id
|
||||
order by items.id desc
|
||||
offset ${offset}
|
||||
@ -128,7 +128,7 @@ export default {
|
||||
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
|
||||
${ !o.fav && user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
|
||||
${ mime ? db`and items.mime ilike ${smime}` : 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`` }
|
||||
${ !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`` }
|
||||
group by items.id, tags.tag, ta.tag_id
|
||||
order by items.id desc
|
||||
`;
|
||||
@ -224,7 +224,7 @@ export default {
|
||||
${ o.fav ? db`and "user".user ilike ${'%'+user+'%'}` : db`` }
|
||||
${ user ? db`and items.username ilike ${'%'+user+'%'}` : db`` }
|
||||
${ mime ? db`and items.mime ilike ${smime}` : 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`` }
|
||||
${ !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`` }
|
||||
group by items.id, tags.tag
|
||||
order by random()
|
||||
limit 1
|
||||
|
@ -16,8 +16,14 @@ const auth = async (req, res, next) => {
|
||||
export default (router, tpl) => {
|
||||
|
||||
router.get(/^\/login(\/)?$/, async (req, res) => {
|
||||
if(req.cookies.session)
|
||||
return res.reply({ body: "du bist schon eingeloggt lol" });
|
||||
if(req.cookies.session) {
|
||||
return res.reply({
|
||||
body: tpl.render('error', {
|
||||
message: "you're already logged in lol",
|
||||
tmp: null
|
||||
}, req)
|
||||
});
|
||||
}
|
||||
res.reply({
|
||||
body: tpl.render("login", { theme: req.cookies.theme ?? "f0ck" })
|
||||
});
|
||||
@ -100,7 +106,11 @@ export default (router, tpl) => {
|
||||
router.get(/^\/admin(\/)?$/, auth, async (req, res) => { // frontpage
|
||||
|
||||
res.reply({
|
||||
body: tpl.render("admin", { totals: await lib.countf0cks(), session: req.session }, req)
|
||||
body: tpl.render("admin", {
|
||||
totals: await lib.countf0cks(),
|
||||
session: req.session,
|
||||
tmp: null
|
||||
}, req)
|
||||
});
|
||||
});
|
||||
|
||||
@ -116,7 +126,8 @@ export default (router, tpl) => {
|
||||
body: tpl.render("admin/sessions", {
|
||||
session: req.session,
|
||||
sessions: rows,
|
||||
totals: await lib.countf0cks()
|
||||
totals: await lib.countf0cks(),
|
||||
tmp: null
|
||||
}, req)
|
||||
});
|
||||
});
|
||||
@ -125,7 +136,8 @@ export default (router, tpl) => {
|
||||
exec("journalctl -qeu f0ck --no-pager", (err, stdout) => {
|
||||
res.reply({
|
||||
body: tpl.render("admin/log", {
|
||||
log: stdout.split("\n").slice(0, -1)
|
||||
log: stdout.split("\n").slice(0, -1),
|
||||
tmp: null
|
||||
}, req)
|
||||
});
|
||||
});
|
||||
@ -170,6 +182,7 @@ export default (router, tpl) => {
|
||||
from "items"
|
||||
where
|
||||
active = 'false'
|
||||
order by id desc
|
||||
`;
|
||||
|
||||
if(_posts.length === 0) {
|
||||
@ -178,11 +191,15 @@ export default (router, tpl) => {
|
||||
});
|
||||
}
|
||||
|
||||
const posts = await Promise.all(_posts.map(async p => ({ ...p, thumbnail: (await fs.readFile(`./deleted/t/${p.id}.webp`)).toString('base64') })));
|
||||
const posts = await Promise.all(_posts.map(async p => ({
|
||||
...p,
|
||||
thumbnail: (await fs.readFile(`./deleted/t/${p.id}.webp`)).toString('base64')
|
||||
})));
|
||||
|
||||
res.reply({
|
||||
body: tpl.render('admin/recover', {
|
||||
posts
|
||||
posts,
|
||||
tmp: null
|
||||
}, req)
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ export default (router, tpl) => {
|
||||
const user = decodeURIComponent(req.params.user);
|
||||
|
||||
const query = await db`
|
||||
select "user".user, user_options.*
|
||||
select "user".user, "user".created_at, user_options.*
|
||||
from user_options
|
||||
left join "user" on "user".id = user_options.user_id
|
||||
where "user".user ilike ${user}
|
||||
@ -44,14 +44,24 @@ export default (router, tpl) => {
|
||||
session: !!req.session
|
||||
});
|
||||
|
||||
if('items' in f0cks)
|
||||
const count = {
|
||||
f0cks: 0,
|
||||
favs: 0
|
||||
};
|
||||
|
||||
if('items' in f0cks) {
|
||||
count.f0cks = f0cks.items.length;
|
||||
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);
|
||||
}
|
||||
|
||||
const data = {
|
||||
user: query[0],
|
||||
f0cks,
|
||||
count,
|
||||
favs,
|
||||
tmp: null
|
||||
};
|
||||
@ -118,46 +128,5 @@ export default (router, tpl) => {
|
||||
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;
|
||||
};
|
||||
|
97
src/inc/routes/ranking.mjs
Normal file
97
src/inc/routes/ranking.mjs
Normal file
@ -0,0 +1,97 @@
|
||||
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;
|
||||
};
|
@ -12,5 +12,19 @@ export default (router, tpl) => {
|
||||
"Location": req.headers.referer ?? "/"
|
||||
}).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;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getLevel } from "../admin.mjs";
|
||||
import lib from "../lib.mjs";
|
||||
import fetch from "flumm-fetch";
|
||||
import vm from "vm";
|
||||
|
||||
@ -7,7 +8,9 @@ let context = vm.createContext({
|
||||
e: null,
|
||||
bot: null,
|
||||
admins: null,
|
||||
fetch: fetch,
|
||||
fetch,
|
||||
lib,
|
||||
console,
|
||||
|
||||
a: null,
|
||||
resolve: null
|
||||
@ -19,16 +22,16 @@ export default async bot => {
|
||||
name: "level",
|
||||
call: /^!level (.*)/i,
|
||||
active: true,
|
||||
f: e => {
|
||||
f: async e => {
|
||||
const user = e.message.trim().substring(7);
|
||||
e.reply( JSON.stringify( getLevel( e.self.user.get(user) || {} ) ) );
|
||||
await e.reply( JSON.stringify( getLevel( e.self.user.get(user) || {} ) ) );
|
||||
}
|
||||
}, {
|
||||
name: "self",
|
||||
call: /^!self$/i,
|
||||
active: true,
|
||||
f: e => {
|
||||
e.reply( JSON.stringify( e.user ) );
|
||||
f: async e => {
|
||||
await e.reply( JSON.stringify( e.user ) );
|
||||
}
|
||||
}, {
|
||||
name: "sandbox_debug",
|
||||
@ -41,6 +44,7 @@ export default async bot => {
|
||||
context.e = e;
|
||||
context.bot = bot;
|
||||
context.level = getLevel;
|
||||
context.hasTag = lib.hasTag;
|
||||
context.a = null;
|
||||
|
||||
await new Promise(resolve => {
|
||||
@ -52,9 +56,9 @@ export default async bot => {
|
||||
|
||||
let output = JSON.stringify(context.a);
|
||||
if(output.length > maxoutput)
|
||||
return e.reply(`fuggg, Ausgabe wäre viel zu lang! (${output.length} Zeichen :DDDDDD)`);
|
||||
return await e.reply(`fuggg, Ausgabe wäre viel zu lang! (${output.length} Zeichen :DDDDDD)`);
|
||||
else
|
||||
return e.reply(output);
|
||||
return await e.reply(output);
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ export default async bot => {
|
||||
const level = getLevel(e.user).level;
|
||||
|
||||
if(f0ck.length === 0) {
|
||||
e.reply(`f0ck ${id}: f0ck not found`);
|
||||
await e.reply(`f0ck ${id}: f0ck not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -37,12 +37,12 @@ export default async bot => {
|
||||
f0ck[0].usernetwork !== e.network) &&
|
||||
level < 100
|
||||
) {
|
||||
e.reply(`f0ck ${id}: insufficient permissions`);
|
||||
await e.reply(`f0ck ${id}: insufficient permissions`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
|
||||
e.reply(`f0ck ${id}: too late lol`);
|
||||
await e.reply(`f0ck ${id}: too late lol`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ export default async bot => {
|
||||
deleted.push(id);
|
||||
}
|
||||
|
||||
e.reply(`deleted ${deleted.length}/${e.args.length} f0cks (${deleted.join(",")})`);
|
||||
await e.reply(`deleted ${deleted.length}/${e.args.length} f0cks (${deleted.join(",")})`);
|
||||
}
|
||||
}, {
|
||||
name: "recover",
|
||||
@ -86,7 +86,7 @@ export default async bot => {
|
||||
`;
|
||||
|
||||
if(f0ck.length === 0) {
|
||||
e.reply(`f0ck ${id}: f0ck not found`);
|
||||
await e.reply(`f0ck ${id}: f0ck not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ export default async bot => {
|
||||
recovered.push(id);
|
||||
}
|
||||
|
||||
e.reply(`recovered ${recovered.length}/${e.args.length} f0cks (${recovered.join(",")})`);
|
||||
await e.reply(`recovered ${recovered.length}/${e.args.length} f0cks (${recovered.join(",")})`);
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
@ -5,29 +5,6 @@ import cfg from "../config.mjs";
|
||||
import db from "../sql.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 => {
|
||||
|
||||
return [{
|
||||
@ -48,60 +25,56 @@ 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)),
|
||||
ca: lib.formatSize((await Promise.all(dirs.ca.map(async file => (await fs.stat(`./public/ca/${file}`)).size))).reduce((a, b) => b + a)),
|
||||
};
|
||||
return e.reply(`${dirs.b.length} f0cks: ${sizes.b}, ${dirs.t.length} thumbnails: ${sizes.t}, ${dirs.ca.length} coverarts: ${sizes.ca}`);
|
||||
return await e.reply(`${dirs.b.length} f0cks: ${sizes.b}, ${dirs.t.length} thumbnails: ${sizes.t}, ${dirs.ca.length} coverarts: ${sizes.ca}`);
|
||||
case "limit":
|
||||
return e.reply(`up to ${lib.formatSize(cfg.main.maxfilesize)} (${lib.formatSize(cfg.main.maxfilesize * cfg.main.adminmultiplier)} for admins)`);
|
||||
return await e.reply(`up to ${lib.formatSize(cfg.main.maxfilesize)} (${lib.formatSize(cfg.main.maxfilesize * cfg.main.adminmultiplier)} for admins)`);
|
||||
case "thumb":
|
||||
const rows = await db`
|
||||
select id
|
||||
from "items"
|
||||
`;
|
||||
const dir = (await fs.readdir("./public/t")).filter(d => d.endsWith(".png")).map(e => +e.split(".")[0]);
|
||||
const dir = (await fs.readdir("./public/t")).filter(d => d.endsWith(".webp")).map(e => +e.split(".")[0]);
|
||||
const tmp = [];
|
||||
for(let row of rows)
|
||||
!dir.includes(row.id) ? tmp.push(row.id) : null;
|
||||
e.reply(`${tmp.length}, ${rows.length}, ${dir.length}`);
|
||||
await e.reply(`${tmp.length}, ${rows.length}, ${dir.length}`);
|
||||
break;
|
||||
case "cache":
|
||||
cfg.websrv.cache = !cfg.websrv.cache;
|
||||
return e.reply(`Cache is ${cfg.websrv.cache ? "enabled" : "disabled"}`);
|
||||
return await e.reply(`Cache is ${cfg.websrv.cache ? "enabled" : "disabled"}`);
|
||||
case "uptime":
|
||||
exec('sudo systemctl status f0ck', (err, stdout) => {
|
||||
exec('sudo systemctl status f0ck', async (err, stdout) => {
|
||||
if(!err)
|
||||
return e.reply(stdout.split('\n')[2].trim().replace("Active: active (running)", "i'm active"));
|
||||
return await e.reply(stdout.split('\n')[2].trim().replace("Active: active (running)", "i'm active"));
|
||||
});
|
||||
break;
|
||||
case "restart":
|
||||
e.reply("hay hay patron, hemen!");
|
||||
await e.reply("hay hay patron, hemen!");
|
||||
exec("sudo systemctl restart f0ck");
|
||||
break;
|
||||
/*case "cleanTags":
|
||||
const tags = await cleanTags();
|
||||
e.reply(tags + " tags removed");
|
||||
break;*/
|
||||
case "clearTmp":
|
||||
await Promise.all((await fs.readdir("./tmp")).filter(d => d !== ".empty").map(async d => fs.unlink(`./tmp/${d}`)));
|
||||
e.reply("cleared lol");
|
||||
await e.reply("cleared lol");
|
||||
break;
|
||||
case "status":
|
||||
const tmpc = await lib.countf0cks();
|
||||
e.reply(`tagged: ${tmpc.tagged}; untagged: ${tmpc.untagged}; sfw: ${tmpc.sfw}; nsfw: ${tmpc.nsfw}; total: ${tmpc.total}`);
|
||||
await e.reply(`tagged: ${tmpc.tagged}; untagged: ${tmpc.untagged}; sfw: ${tmpc.sfw}; nsfw: ${tmpc.nsfw}; total: ${tmpc.total}`);
|
||||
break;
|
||||
case "autotagger":
|
||||
/*case "autotagger":
|
||||
const body = { headers: { Authorization: `Basic ${cfg.tagger.btoa}` } };
|
||||
const res = await (await fetch(`${cfg.tagger.endpoint}/usage`, body)).json();
|
||||
if(res) {
|
||||
const processed = res.result.monthly_processed;
|
||||
const limit = res.result.monthly_limit;
|
||||
return e.reply(`autotagger: usage/limit: ${processed}/${limit}`);
|
||||
return await e.reply(`autotagger: usage/limit: ${processed}/${limit}`);
|
||||
}
|
||||
return;
|
||||
break;
|
||||
break;*/
|
||||
/*case "renameTag":
|
||||
const origTag = e.args.slice(1).join(' ');
|
||||
|
||||
if(origTag.length <= 1)
|
||||
return e.reply("absichtliche Provokation!");
|
||||
return await e.reply("absichtliche Provokation!");
|
||||
|
||||
const origTagID = (await sql('tags').where('tag', origTag))[0].id;
|
||||
|
||||
@ -116,10 +89,10 @@ export default async bot => {
|
||||
.del()
|
||||
);
|
||||
|
||||
e.reply(JSON.stringify({ affected, deleted }));
|
||||
await e.reply(JSON.stringify({ affected, deleted }));
|
||||
break;*/
|
||||
case "help":
|
||||
e.reply("cmds: stats, limit, thumb, cache, uptime, restart, cleanTags, clearTmp, status");
|
||||
await e.reply("cmds: stats, limit, thumb, cache, uptime, restart, cleanTags, clearTmp, status");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
|
@ -22,9 +22,9 @@ export default async bot => {
|
||||
`;
|
||||
|
||||
if(rows.length === 0)
|
||||
return e.reply("no f0cks given! lol D:");
|
||||
return await e.reply("no f0cks given! lol D:");
|
||||
|
||||
e.reply([
|
||||
await e.reply([
|
||||
`${cfg.main.url.full}/${rows[0].id}`,
|
||||
`user: ${rows[0].username} @ ${rows[0].usernetwork} ${rows[0].userchannel}`,
|
||||
`~${lib.formatSize(rows[0].size)}`,
|
||||
|
@ -10,6 +10,7 @@ export default async bot => {
|
||||
active: false,
|
||||
f: async e => {
|
||||
let args = e.args.slice(1);
|
||||
|
||||
/*let rows = sql("items").select("id", "username", "mime", "size");
|
||||
|
||||
for(let i = 0; i < args.length; i++) {
|
||||
@ -21,10 +22,21 @@ export default async bot => {
|
||||
|
||||
rows = await rows.orderByRaw("random()").limit(1);*/
|
||||
|
||||
if(rows.length === 0)
|
||||
return e.reply("nothing found, f0cker");
|
||||
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()
|
||||
`;
|
||||
|
||||
return e.reply(`f0ckrnd: ${cfg.main.url.full}/${rows[0].id} by: ${rows[0].username} (${rows[0].mime}, ~${lib.formatSize(rows[0].size)})`);
|
||||
if(rows.length === 0)
|
||||
return await 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)})`);
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
@ -2,128 +2,218 @@ import cfg from "../config.mjs";
|
||||
import db from "../sql.mjs";
|
||||
import lib from "../lib.mjs";
|
||||
import { getLevel } from "../admin.mjs";
|
||||
import queue from "../queue.mjs";
|
||||
import autotagger from "../autotagger.mjs";
|
||||
import fetch from "flumm-fetch";
|
||||
|
||||
import fs from "fs";
|
||||
import { exec as _exec } from "child_process";
|
||||
import path from "path";
|
||||
|
||||
const exec = cmd => 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 });
|
||||
});
|
||||
});
|
||||
|
||||
//const regex = /https?:\/\/[\w\S(\.|:|/)]+/gi;
|
||||
const regex = /https?:\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gi;
|
||||
const regex = {
|
||||
all: /https?:\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gi,
|
||||
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,
|
||||
instagram: /(?:https?:\/\/www\.)?instagram\.com\S*?\/(?:p|reel)\/(\w{11})\/?/im
|
||||
};
|
||||
const mediagroupids = new Set();
|
||||
|
||||
export default async bot => {
|
||||
|
||||
return [{
|
||||
name: "parser",
|
||||
call: regex,
|
||||
call: regex.all,
|
||||
active: true,
|
||||
f: e => {
|
||||
const links = e.message.match(regex)?.filter(link => !link.includes("f0ck.me")) || [];
|
||||
|
||||
const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || [];
|
||||
let repost;
|
||||
if(e.media)
|
||||
links.push(e.media);
|
||||
|
||||
|
||||
if(links.length === 0)
|
||||
return false;
|
||||
|
||||
if(e.message.match(/(!|-)ignore/))
|
||||
|
||||
if(e.message.match(/\!i(gnore)?\b/))
|
||||
return false;
|
||||
|
||||
if(!e.channel.includes("f0ck") && !e.message.match(/(!|-)f0ck/i))
|
||||
if(!e.channel.includes("f0ck") && (!e.message.match(/\!f(0ck)?\b/i) && (typeof e.raw.forward_from == 'undefined')))
|
||||
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" : ""}...`);
|
||||
|
||||
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)
|
||||
const q_repost = await db`
|
||||
select id
|
||||
from "items"
|
||||
where src = ${link}
|
||||
`;
|
||||
if(q_repost.length > 0)
|
||||
return e.reply(`repost motherf0cker (link): ${cfg.main.url.full}/${q_repost[0].id}`);
|
||||
repost = await queue.checkrepostlink(link);
|
||||
if(repost)
|
||||
return await e.reply(`repost motherf0cker (link): ${cfg.main.url.full}/${repost}`);
|
||||
|
||||
// generate uuid
|
||||
const uuid = (await db`
|
||||
select gen_random_uuid() as uuid
|
||||
`)[0].uuid.substring(0, 8);
|
||||
const uuid = await queue.genuuid();
|
||||
|
||||
const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize) / 1024;
|
||||
const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize);
|
||||
|
||||
let meta;
|
||||
// read metadata
|
||||
try {
|
||||
meta = JSON.parse((await exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
|
||||
let ext;
|
||||
if(regex.imgur.test(link)) {
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
//e.reply("[error] f0ck has no bock :(");
|
||||
//console.error(err);
|
||||
return;
|
||||
else {
|
||||
// is not instagram
|
||||
try {
|
||||
const meta = JSON.parse((await queue.exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
|
||||
ext = meta.ext;
|
||||
} catch(err) {
|
||||
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(meta.ext.toLowerCase())) {
|
||||
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
|
||||
if(!Object.keys(cfg.mimes).includes(tmphead))
|
||||
return;
|
||||
meta.ext = cfg.mimes[tmphead];
|
||||
if(!Object.values(cfg.mimes).includes(ext?.toLowerCase())) {
|
||||
return console.log('mime schmime ' + ext);
|
||||
}
|
||||
|
||||
let filename = `${uuid}.${meta.ext}`;
|
||||
|
||||
e.reply(`[charging the f0cker] downloading: ${uuid}`);
|
||||
const msg = await e.reply(`[charging the f0cker] downloading: ${uuid}`, {
|
||||
disable_notification: true
|
||||
});
|
||||
|
||||
// download data
|
||||
const start = new Date();
|
||||
let source;
|
||||
if(meta.ext === "mp4") {
|
||||
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();
|
||||
//change 720 to any other available resolution, higher = better quality but bigger filesize
|
||||
}
|
||||
else {
|
||||
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();
|
||||
//change 720 to any other available resolution, higher = better quality but bigger filesize
|
||||
|
||||
try {
|
||||
if(regex.instagram.test(link))
|
||||
try {
|
||||
// add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited
|
||||
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, "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);
|
||||
}
|
||||
|
||||
if(source.match(/larger than/))
|
||||
return e.reply("too large lol");
|
||||
// this is how it was before I fucked it up :>
|
||||
// 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, "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);
|
||||
|
||||
// generate checksum
|
||||
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}`;
|
||||
// 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
|
||||
const checksum = (await queue.exec(`sha256sum ${source}`)).stdout.trim().split(" ")[0];
|
||||
|
||||
// check repost (checksum)
|
||||
const q_repostc = await db`
|
||||
select id
|
||||
from "items"
|
||||
where checksum = ${checksum}
|
||||
`;
|
||||
if(q_repostc.length > 0)
|
||||
return e.reply(`repost motherf0cker (checksum): ${cfg.main.url.full}/${q_repostc[0].id}`);
|
||||
repost = await queue.checkrepostsum(checksum);
|
||||
if(repost) {
|
||||
await fs.promises.unlink(source).catch(_=>{});
|
||||
if(e.type == 'tg')
|
||||
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}`);
|
||||
}
|
||||
|
||||
await fs.promises.copyFile(`./tmp/${filename}`, `./public/b/${filename}`);
|
||||
await fs.promises.unlink(`./tmp/${filename}`).catch(_=>{});
|
||||
const filename = path.basename(source);
|
||||
|
||||
await fs.promises.copyFile(source, `./public/b/${filename}`);
|
||||
await fs.promises.unlink(source).catch(_=>{});
|
||||
|
||||
await db`
|
||||
insert into items ${
|
||||
@ -142,113 +232,56 @@ export default async bot => {
|
||||
}
|
||||
`;
|
||||
|
||||
const itemid = (await db`
|
||||
select *
|
||||
from "items"
|
||||
where dest = ${filename}
|
||||
limit 1
|
||||
`)[0].id;
|
||||
const itemid = await queue.getItemID(filename);
|
||||
|
||||
// generate thumbnail
|
||||
try {
|
||||
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 => {});
|
||||
await queue.genThumbnail(filename, mime, itemid, link);
|
||||
} catch(err) {
|
||||
await exec(`convert ./mugge.png ./public/t/${itemid}.webp`);
|
||||
await queue.exec(`convert ./mugge.png ./public/t/${itemid}.webp`);
|
||||
}
|
||||
|
||||
let speed = lib.calcSpeed(size, end);
|
||||
speed = !Number.isFinite(speed) ? "yes" : `${speed.toFixed(2)} Mbit/s`;
|
||||
|
||||
// autotagger
|
||||
/*let tags = [];
|
||||
let score = 0;
|
||||
try {
|
||||
if(mime.startsWith('image')) {
|
||||
const res = await lib.detectNSFW(filename);
|
||||
score = res.score;
|
||||
|
||||
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 tags = [];
|
||||
/*if(cfg.apis?.nsfw1 && mime.startsWith('image')) {
|
||||
const nsfw = await autotagger.isNSFW(filename, size);
|
||||
tags.push(nsfw ? 'nsfw' : 'sfw');
|
||||
if(nsfw)
|
||||
await queue.tagNSFW(itemid);
|
||||
else
|
||||
await queue.tagSFW(itemid);
|
||||
}*/
|
||||
|
||||
/*e.reply([
|
||||
`[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}`
|
||||
]);
|
||||
let outputmsgirc = `[f0cked] link: ${cfg.main.url.full}/${itemid} | size: ${lib.formatSize(size)} | speed: ${speed}`;
|
||||
let outputmsgtg = `[f0cked] 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
@ -12,11 +12,11 @@ export default async bot => {
|
||||
f: async e => {
|
||||
const id = +e.args[1];
|
||||
if(!id)
|
||||
return e.reply("lol no");
|
||||
return await e.reply("lol no");
|
||||
const tags = (await lib.getTags(id)).map(t => t.tag);
|
||||
if(tags.length === 0)
|
||||
return e.reply(`item ${cfg.main.url.full}/${id} has no tags!`);
|
||||
return e.reply(`item ${cfg.main.url.full}/${id} is tagged as: ${tags.join(', ')}`);
|
||||
return await 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(', ')}`);
|
||||
}
|
||||
}, {
|
||||
name: "tags add",
|
||||
@ -26,7 +26,7 @@ export default async bot => {
|
||||
f: async e => {
|
||||
const id = +e.args[1];
|
||||
if(!id)
|
||||
return e.reply("lol no");
|
||||
return await e.reply("lol no");
|
||||
|
||||
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);
|
||||
|
||||
if(newtags.length === 0)
|
||||
return e.reply("no (new) tags provided");
|
||||
return await e.reply("no (new) tags provided");
|
||||
|
||||
await Promise.all(newtags.map(async ntag => {
|
||||
try {
|
||||
@ -73,8 +73,8 @@ export default async bot => {
|
||||
|
||||
const ntags = (await lib.getTags(id)).map(t => t.tag);
|
||||
if(ntags.length === 0)
|
||||
return e.reply(`item ${cfg.main.url.full}/${id} has no tags!`);
|
||||
return e.reply(`item ${cfg.main.url.full}/${id} is now tagged as: ${ntags.join(', ')}`);
|
||||
return await 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(', ')}`);
|
||||
}
|
||||
}, {
|
||||
name: "tags remove",
|
||||
@ -84,7 +84,7 @@ export default async bot => {
|
||||
f: async e => {
|
||||
const id = +e.args[1];
|
||||
if(!id)
|
||||
return e.reply("lol no");
|
||||
return await e.reply("lol no");
|
||||
|
||||
const tags = await lib.getTags(id);
|
||||
|
||||
@ -93,7 +93,7 @@ export default async bot => {
|
||||
: e.args.splice(2)).filter(t => t.length > 0);
|
||||
|
||||
if(removetags.length === 0)
|
||||
return e.reply("no tags provided");
|
||||
return await e.reply("no tags provided");
|
||||
|
||||
const res = await Promise.all(removetags.map(async rtag => {
|
||||
const tagid = tags.filter(t => t.tag === rtag)[0]?.id ?? null;
|
||||
@ -122,12 +122,12 @@ export default async bot => {
|
||||
};
|
||||
}));
|
||||
|
||||
e.reply(JSON.stringify(res));
|
||||
await e.reply(JSON.stringify(res));
|
||||
|
||||
const ntags = (await lib.getTags(id)).map(t => t.tag);
|
||||
if(ntags.length === 0)
|
||||
return e.reply(`item ${cfg.main.url.full}/${id} has no tags!`);
|
||||
return e.reply(`item ${cfg.main.url.full}/${id} is now tagged as: ${ntags.join(', ')}`);
|
||||
return await 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(', ')}`);
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
42
src/inc/trigger/thumbnails.mjs
Normal file
42
src/inc/trigger/thumbnails.mjs
Normal file
@ -0,0 +1,42 @@
|
||||
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(",")})`);
|
||||
}
|
||||
}];
|
||||
};
|
@ -5,6 +5,11 @@ import cuffeo from "cuffeo";
|
||||
import { promises as fs } from "fs";
|
||||
import flummpress from "flummpress";
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
|
||||
(async () => {
|
||||
const self = {
|
||||
_trigger: new Map(),
|
||||
@ -62,6 +67,7 @@ import flummpress from "flummpress";
|
||||
if(req.url.pathname.match(/^\/(s|b|t|ca)\//))
|
||||
return;
|
||||
req.theme = req.cookies.theme || 'f0ck';
|
||||
req.fullscreen = req.cookies.fullscreen || 0;
|
||||
|
||||
if(req.cookies.session) {
|
||||
const user = await db`
|
||||
@ -96,6 +102,7 @@ import flummpress from "flummpress";
|
||||
`;
|
||||
|
||||
req.session.theme = req.cookies.theme;
|
||||
req.session.fullscreen = req.cookies.fullscreen;
|
||||
|
||||
// update userprofile
|
||||
await db`
|
||||
@ -103,12 +110,14 @@ import flummpress from "flummpress";
|
||||
db({
|
||||
user_id: +user[0].id,
|
||||
mode: user[0].mode ?? 0,
|
||||
theme: req.session.theme ?? 'f0ck'
|
||||
}, 'user_id', 'mode', 'theme')
|
||||
theme: req.session.theme ?? 'f0ck',
|
||||
fullscreen: req.session.fullscreen || 0
|
||||
}, 'user_id', 'mode', 'theme', 'fullscreen')
|
||||
}
|
||||
on conflict ("user_id") do update set
|
||||
mode = excluded.mode,
|
||||
theme = excluded.theme,
|
||||
fullscreen = excluded.fullscreen,
|
||||
user_id = excluded.user_id
|
||||
`;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
@include(snippets/header)
|
||||
<div class="about">
|
||||
<div id="main">
|
||||
<div class="about">
|
||||
<div>
|
||||
<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 <3</p>
|
||||
<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 <3</p>
|
||||
</div>
|
||||
<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>
|
||||
@ -14,24 +15,24 @@
|
||||
<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>
|
||||
<ul>
|
||||
<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://libera.chat/">libera.chat</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://libera.chat/">libera.chat</a></li>
|
||||
</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>#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>
|
||||
<h5>f0ck Rules</h5>
|
||||
<ul>
|
||||
<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 under <b>ANY</b> circumstances: Snuff, Beastiality, Rape, Terrorist stuff (Beheadings, First person shootings, warcrimes), Childporn, Childmodeling</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 under <b>ANY</b> circumstances: Snuff, Beastiality, Rape, Terrorist stuff (Beheadings, First person shootings, warcrimes), Childporn, Childmodeling</li>
|
||||
</ul>
|
||||
<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>
|
||||
<ul>
|
||||
<li>irc.n0xy.net #f0ck</li>
|
||||
<li>admin@f0ck.me</li>
|
||||
<li>irc.n0xy.net #f0ck</li>
|
||||
<li>admin@f0ck.me</li>
|
||||
</ul>
|
||||
<h5>Compatibility</h5>
|
||||
<p>f0ck is developed and tested for Firefox and Chromium in their latest versions</p>
|
||||
@ -45,5 +46,6 @@
|
||||
<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>Logs: We do not log users accessing our website.</p>
|
||||
</div>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
@include(snippets/footer)
|
||||
|
@ -1,10 +1,22 @@
|
||||
@include(snippets/header_admin)
|
||||
<div class="container">
|
||||
<h1>Henlo, {{ session.user }}</h1>
|
||||
<p>Hier entsteht eine Internetpräsenz!</p>
|
||||
<img src="/s/img/favicon.gif" alt="f0ck bash">
|
||||
<p>@if(typeof totals !== "undefined")
|
||||
@include(snippets/header)
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
<h1>ADMINBEREICH</h1>
|
||||
<h5>Hallo, {{ session.user }}</h5>
|
||||
<span>Hier entsteht eine Internetpräsenz!</span><br>
|
||||
<hr>
|
||||
<p>f0ck stats: @if(typeof totals !== "undefined")
|
||||
total: {{ totals.total }} | tagged: {{ totals.tagged }} | untagged: {{ totals.untagged }} | sfw: {{ totals.sfw }} | nsfw: {{ totals.nsfw }}
|
||||
@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>
|
||||
@include(snippets/footer)
|
||||
@include(snippets/footer)
|
||||
|
@ -1,16 +1,18 @@
|
||||
@include(snippets/header_admin)
|
||||
@if(log)
|
||||
<h1>last {{ log.length }} entries:</h1>
|
||||
<div class="logwrap">
|
||||
@each(log as line)
|
||||
<p>{{ line }}</p>
|
||||
@endeach
|
||||
@include(snippets/header)
|
||||
<div id="main">
|
||||
@if(log)
|
||||
<h1>last {{ log.length }} entries:</h1>
|
||||
<div class="logwrap">
|
||||
@each(log as line)
|
||||
<p>{{ line }}</p>
|
||||
@endeach
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
const d = document.querySelector("div.logwrap");
|
||||
d.scrollTop = d.scrollHeight;
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
const d = document.querySelector("div.logwrap");
|
||||
d.scrollTop = d.scrollHeight;
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
@include(snippets/footer)
|
||||
|
@ -1,24 +1,26 @@
|
||||
@include(snippets/header_admin)
|
||||
<table class="table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ID</td>
|
||||
<td>f0cker</td>
|
||||
<td>mime</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(posts as post)
|
||||
<tr>
|
||||
<td><img src="data:image/webp;base64,{{ post.thumbnail }}" /></td>
|
||||
<td>{{ post.id }}</td>
|
||||
<td>{{ post.username }}</td>
|
||||
<td>{{ post.mime }}</td>
|
||||
<td><a href="/admin/recover/?id={{ post.id }}">recover</a></td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
@include(snippets/footer)
|
||||
@include(snippets/header)
|
||||
<div id="main">
|
||||
<table class="table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ID</td>
|
||||
<td>f0cker</td>
|
||||
<td>mime</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(posts as post)
|
||||
<tr>
|
||||
<td><img src="data:image/webp;base64,{{ post.thumbnail }}" /></td>
|
||||
<td>{{ post.id }}</td>
|
||||
<td>{{ post.username }}</td>
|
||||
<td>{{ post.mime }}</td>
|
||||
<td><a href="/admin/recover/?id={{ post.id }}">recover</a></td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
|
@ -1,30 +1,32 @@
|
||||
@include(snippets/header_admin)
|
||||
<table class="table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ID</td>
|
||||
<td>userid</td>
|
||||
<td>user</td>
|
||||
<td>browser</td>
|
||||
<td>created_at</td>
|
||||
<td>last_used</td>
|
||||
<td>last_action</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(sessions as session)
|
||||
<tr>
|
||||
<td>{{ session.kmsi ? '⚓' : '' }}</td>
|
||||
<td>{{ session.id }}</td>
|
||||
<td>{{ session.user_id }}</td>
|
||||
<td>{{ session.user }}</td>
|
||||
<td>{{ session.browser }}</td>
|
||||
<td>{{ new Date(session.created_at * 1e3).toLocaleString("de-DE") }}</td>
|
||||
<td>{{ new Date(session.last_used * 1e3).toLocaleString("de-DE") }}</td>
|
||||
<td>{{ session.last_action }}</td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
@include(snippets/footer)
|
||||
@include(snippets/header)
|
||||
<div id="main">
|
||||
<table class="table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>ID</td>
|
||||
<td>userid</td>
|
||||
<td>user</td>
|
||||
<td>browser</td>
|
||||
<td>created_at</td>
|
||||
<td>last_used</td>
|
||||
<td>last_action</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(sessions as session)
|
||||
<tr>
|
||||
<td>{{ session.kmsi ? '⚓' : '' }}</td>
|
||||
<td>{{ session.id }}</td>
|
||||
<td>{{ session.user_id }}</td>
|
||||
<td>{{ session.user }}</td>
|
||||
<td>{{ session.browser }}</td>
|
||||
<td>{{ new Date(session.created_at * 1e3).toLocaleString("de-DE") }}</td>
|
||||
<td>{{ new Date(session.last_used * 1e3).toLocaleString("de-DE") }}</td>
|
||||
<td>{{ session.last_action }}</td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
|
@ -1,18 +1,20 @@
|
||||
@include(snippets/header)
|
||||
<div class="container">
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
<div class="_error_wrapper">
|
||||
<div class="err">
|
||||
<div class="_error_topbar">
|
||||
<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 class="err">
|
||||
<div class="_error_topbar">
|
||||
<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>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
@include(snippets/footer)
|
@ -1,14 +1,18 @@
|
||||
@include(snippets/header)
|
||||
<div class="index-container">
|
||||
@if(tmp.user)<h2>user: {!! tmp.user.toLowerCase() !!}@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif
|
||||
@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
|
||||
<div class="posts">
|
||||
@each(items as item)
|
||||
<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>
|
||||
@endeach
|
||||
</div>
|
||||
<div id="footbar">
|
||||
▼
|
||||
<div class="pagewrapper">
|
||||
<div id="main">
|
||||
<div class="index-container">
|
||||
@if(tmp.user)<h2>user: {!! tmp.user.toLowerCase() !!}@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif
|
||||
@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
|
||||
<div class="posts">
|
||||
@each(items as item)
|
||||
<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>
|
||||
@endeach
|
||||
</div>
|
||||
<div id="footbar">
|
||||
▼
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
</div>
|
||||
@include(snippets/footer)
|
191
views/item.html
191
views/item.html
@ -1,105 +1,106 @@
|
||||
<div class="wrapper">
|
||||
@include(snippets/header)
|
||||
<div class="container">
|
||||
<div class="_204863">
|
||||
<div class="imageDoor" style="--hover-image: url('/t/{{ item.id }}.webp');">
|
||||
<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>
|
||||
<canvas class="hidden-xs" id="bg"></canvas>
|
||||
<div class="wrapper">
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
<div class="_204863">
|
||||
<div class="imageDoor" style="--hover-image: url('/t/{{ item.id }}.webp');">
|
||||
<img src="/t/{{ item.id }}.webp" alt="" />
|
||||
</div>
|
||||
@else
|
||||
<div class="arrow-next">
|
||||
<a id="next" href="#" style="color: #ccc !important;"></a>
|
||||
<div class="gapLeft">
|
||||
<span class="populateME"><b>f0ck</b> - {{ item.id }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="media-object">
|
||||
@if(item.mime.startsWith("video"))
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<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 class="previous-post">
|
||||
@if(pagination.next)
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="{{ link.main }}{{ pagination.next }}"></a>
|
||||
</div>
|
||||
@else
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
@endif
|
||||
</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>)
|
||||
<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>
|
||||
@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) <a class="removetag" href="#">×</a>@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>
|
||||
@else
|
||||
<div class="arrow-next">
|
||||
<a id="next" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="media-object">
|
||||
@if(item.mime.startsWith("video"))
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<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 class="previous-post">
|
||||
@if(pagination.next)
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="{{ link.main }}{{ pagination.next }}"></a>
|
||||
</div>
|
||||
@else
|
||||
<div class="arrow-prev">
|
||||
<a id="prev" href="#" style="color: #ccc !important;"></a>
|
||||
</div>
|
||||
@endif
|
||||
</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.normalized }}">{!! tag.tag !!}</a>@if(session) <a class="removetag" href="#">×</a>@endif
|
||||
</span>
|
||||
@endeach
|
||||
@endif
|
||||
@if(session)
|
||||
<a href="#" id="a_addtag">add tag</a> - <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
|
||||
@if(session)
|
||||
<a href="#" id="a_addtag">add tag</a> - <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>
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
</div>
|
@ -1,40 +1,51 @@
|
||||
@include(snippets/header)
|
||||
<div class="topf0ckers">
|
||||
<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 id="main">
|
||||
<div class="topf0ckers">
|
||||
<h3>Top f0ckers of all time <br>- Ranking -</h3>
|
||||
</div>
|
||||
<div class="by-hoster">
|
||||
<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>
|
||||
</table>
|
||||
<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 class="by-hoster">
|
||||
<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>
|
||||
@include(snippets/footer)
|
||||
|
@ -1,37 +1,47 @@
|
||||
@include(snippets/header)
|
||||
<div class="f0ckgle">
|
||||
<h1 style="text-align: center">f0ckgle</h1>
|
||||
<form action="/search" class="admin-search">
|
||||
<input type="text" name="tag" value="{!! searchstring || '' !!}" /><button type="submit"><b>f0ck</b></button>
|
||||
</form>
|
||||
<div class="results">
|
||||
@if(result)
|
||||
<h2>{{ count }} f0cks given (page {{ pagination.page }} of {{ pagination.end }}):</h2>
|
||||
<table style="width: 100%" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Thumbnail</th>
|
||||
<th>ID</th>
|
||||
<th>Tag</th>
|
||||
<th>Mime</th>
|
||||
<th>Username</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(result as line)
|
||||
<tr>
|
||||
<td style="width: 128px;"><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank"><img src="/t/{{ line.id }}.webp" /></a></td>
|
||||
<td><span class="mview_desc">ID:</span><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank">{{ line.id }}</a></td>
|
||||
<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 id="main">
|
||||
<div class="f0ckgle">
|
||||
<div class="search-title">
|
||||
<span style="color:#4285F4;">f</span>
|
||||
<span style="color:#EA4335;">0</span>
|
||||
<span style="color:#FBBC05;">c</span>
|
||||
<span style="color:#4285F4;">k</span>
|
||||
<span style="color:#34A853;">g</span>
|
||||
<span style="color:#EA4335;">l</span>
|
||||
<span style="color:#4285F4;">e</span>
|
||||
</div>
|
||||
<form action="/search" class="admin-search">
|
||||
<input type="text" name="tag" value="{!! searchstring || '' !!}" /><button type="submit">🔍</button>
|
||||
</form>
|
||||
<div class="results">
|
||||
@if(result)
|
||||
<h2>{{ count }} f0cks given (page {{ pagination.page }} of {{ pagination.end }}):</h2>
|
||||
<table style="width: 100%" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Thumbnail</th>
|
||||
<th>ID</th>
|
||||
<th>Tag</th>
|
||||
<th>Mime</th>
|
||||
<th>Username</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(result as line)
|
||||
<tr>
|
||||
<td style="width: 128px;"><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank"><img src="/t/{{ line.id }}.webp" /></a></td>
|
||||
<td><span class="mview_desc">ID:</span><a href="/tag/{!! line.tag !!}/{{ line.id }}" target="_blank">{{ line.id }}</a></td>
|
||||
<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>
|
||||
@include(snippets/footer)
|
||||
@include(snippets/footer)
|
||||
|
@ -1,52 +1,54 @@
|
||||
@include(snippets/header)
|
||||
<h1>Settings</h1>
|
||||
@if(session.avatar)<a href="/{{ session.avatar }}"><img id="img_avatar" src="/t/{{ session.avatar }}.webp" /></a>@endif
|
||||
<h2>Account</h2>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UserID</td>
|
||||
<td>{{ session.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>level</td>
|
||||
<td>{{ session.level }}/100</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>username</td>
|
||||
<td>{!! session.user !!}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>avatar</td>
|
||||
<td><input type="text" class="input" name="i_avatar" value="{{ session.avatar }}" /><input type="submit" id="s_avatar" value="save" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Sessions</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>id</th>
|
||||
<th> </th>
|
||||
<th>last action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(sessions as sess)
|
||||
<tr@if(sess.id === session.sess_id) style="background-color: rgb(0, 89, 0)"@endif>
|
||||
<td>{{ sess.kmsi ? '⚓' : '' }}</td>
|
||||
<td tooltip="{{ sess.browser }}" flow="right">
|
||||
<p>{{ sess.id }}</p>
|
||||
<p><a href="#" onclick="alert('not yet implemented!');">logout</a></p>
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
<td><a href="{{ sess.last_action }}" target="_blank">{{ sess.last_action }}</a></td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="main">
|
||||
<h1>Settings</h1>
|
||||
@if(session.avatar)<a href="/{{ session.avatar }}"><img id="img_avatar" src="/t/{{ session.avatar }}.webp" /></a>@endif
|
||||
<h2>Account</h2>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>UserID</td>
|
||||
<td>{{ session.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>level</td>
|
||||
<td>{{ session.level }}/100</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>username</td>
|
||||
<td>{!! session.user !!}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>avatar</td>
|
||||
<td><input type="text" class="input" name="i_avatar" value="{{ session.avatar }}" /><input type="submit" id="s_avatar" value="save" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Sessions</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>id</th>
|
||||
<th> </th>
|
||||
<th>last action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(sessions as sess)
|
||||
<tr@if(sess.id === session.sess_id) style="background-color: rgb(0, 89, 0)"@endif>
|
||||
<td>{{ sess.kmsi ? '⚓' : '' }}</td>
|
||||
<td tooltip="{{ sess.browser }}" flow="right">
|
||||
<p>{{ sess.id }}</p>
|
||||
<p><a href="#" onclick="alert('not yet implemented!');">logout</a></p>
|
||||
</td>
|
||||
<td>
|
||||
<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>
|
||||
</td>
|
||||
<td><a href="{{ sess.last_action }}" target="_blank">{{ sess.last_action }}</a></td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
|
@ -1,5 +1,4 @@
|
||||
@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 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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif">
|
||||
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif" res="@if(typeof fullscreen !== "undefined"){{ fullscreen == 1 ? 'fullscreen' : '' }}@endif">
|
||||
<head>
|
||||
<title>f0ck!</title>
|
||||
<meta name="description" content="Welcome to the internet"/>
|
||||
@ -12,4 +12,3 @@
|
||||
</head>
|
||||
<body>
|
||||
@include(snippets/navbar)
|
||||
<div id="main">
|
@ -1,19 +0,0 @@
|
||||
<!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">
|
@ -2,53 +2,54 @@
|
||||
<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 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>
|
||||
</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) ▼@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>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item 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>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/user/{{ session.user.toLowerCase() }}">my profile</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="/search">search</a></li>
|
||||
<li><a href="/admin">Admin</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/ranking">ranking</a></li>
|
||||
<li><a href="/settings">settings</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) ▼@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">
|
||||
|
133
views/snippets/navbar2.html
Normal file
133
views/snippets/navbar2.html
Normal file
@ -0,0 +1,133 @@
|
||||
@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) ▼@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">«</a>
|
||||
<a href="{{ link.main }}{{ link.path }}{{ pagination.prev }}" class="page-item-2 btn prev@if(!pagination.prev) disabled@endif">‹</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">›</a>
|
||||
<a href="{{ link.main }}{{ link.path }}{{ pagination.end }}" class="page-item-4 btn start@if(!pagination.next) disabled@endif">»</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) ▼@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">«</a>
|
||||
<a href="{{ link.main }}{{ link.path }}{{ pagination.prev }}" class="page-item-2 btn prev@if(!pagination.prev) disabled@endif">‹</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">›</a>
|
||||
<a href="{{ link.main }}{{ link.path }}{{ pagination.end }}" class="page-item-4 btn start@if(!pagination.next) disabled@endif">»</a>
|
||||
</nav>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@endif
|
@ -1,33 +0,0 @@
|
||||
<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>
|
14
views/top10.html
Normal file
14
views/top10.html
Normal file
@ -0,0 +1,14 @@
|
||||
@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)
|
@ -1,28 +1,49 @@
|
||||
@include(snippets/header)
|
||||
<h1>{{ user.user }}</h1>
|
||||
|
||||
<h2>f0cks:</h2>
|
||||
@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 id="main">
|
||||
<div class="profile_head">
|
||||
@if(user.avatar)
|
||||
<div class="profile_head_avatar">
|
||||
<img src="/t/{{ user.avatar }}.webp" style="display: grid;width: 55px" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="layersoffear">
|
||||
<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>
|
||||
<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)
|
||||
@include(snippets/footer)
|
||||
|
Reference in New Issue
Block a user