Compare commits

..

2 Commits

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

View File

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

View File

@ -1,7 +1,7 @@
# how to install:
## dependencies
```bash
sudo pacman -S nodejs npm ffmpeg yt-dlp ffmpegthumbnailer postgresql imagemagick git mime-types
sudo pacman -S nodejs npm ffmpeg yt-dlp ffmpegthumbnailer postgresql python python-pip imagemagick git mime-types
```
## postgres
```bash
@ -26,5 +26,7 @@ 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
```

View File

@ -5,7 +5,6 @@
"domain": "f0ck.dev",
"regex": "f0ck\\.dev"
},
"socks": "",
"maxfilesize": 83886080,
"adminmultiplier": 3.5,
"ignored": [
@ -23,33 +22,20 @@
"thumbnails": "/t",
"coverarts": "/ca"
},
"themes": [ "f0ck", "p1nk", "orange", "atmos", "amoled", "paper", "term", "iced", "f0ck95", "f0ck95d" ],
"eps": 300,
"themes": [ "f0ck", "p1nk", "orange", "atmos", "amoled", "paper", "term", "iced" ],
"eps": 294,
"cache": false,
"phrases": [
"<img src=\"/s/img/crap/nyancat.gif\" style=\"margin-top: 5px\" />"
]
},
"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"
]
}],
"clients": [
],
"sql": {
"host": "localhost",
"user": "f0ck",
"password": "f0ck",
"password": "",
"database": "f0ck",
"schema": "public",
"multipleStatements": true
@ -68,10 +54,8 @@
"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-matroska": "mkv"
"video/x-m4v": "mp4"
}
}

32
debug/adduser.mjs Normal file
View File

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

View File

@ -1,95 +1,33 @@
import cfg from "../src/inc/config.mjs";
import db from "../src/inc/sql.mjs";
import fs from "node:fs";
import readline from 'node:readline/promises';
import sql from "../src/inc/sql.mjs";
import { promises as fs } from "fs";
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
// 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 opts = {
b: "public/b",
t: "public/t",
tmp: "tmp"
};
const files = {
b: (await fs.promises.readdir(dirs.b)) .filter(f => f !== '.empty'),
t: (await fs.promises.readdir(dirs.t)) .filter(f => f !== '.empty'),
tmp: (await fs.promises.readdir(dirs.tmp)).filter(f => f !== '.empty')
};
const extensions = [ ...Object.values(cfg.mimes), 'mov' ];
const count = {
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`)
b: 0, t: 0, tmp: 0
};
// 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);
const rows = await sql('items').select('id', 'dest');
const ids = rows.map(r => r.id.toString() + ".webp");
const dests = rows.map(r => r.dest);
const files = {
b: (await fs.readdir(opts.b)).filter(f => f !== '.empty'),
t: await fs.readdir(opts.t)
};
const unused = {
b: files.b.filter(f => !dests.includes(f)),
t: files.t.filter(f => !ids.includes(f))
}
// invalid
count.invalid.b = files.b.filter(f => !extensions.includes(f.toLowerCase().split('.')[1]));
count.invalid.t = files.t.filter(f => !f.endsWith('.webp'));
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;
// spare
for(const file of files.b)
if(!f0cks.b.includes(file))
count.spare.b.push(`${dirs.b}/${file}`);
// 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;
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();
console.log(count, unused);

610
f0ck.sql
View File

@ -1,9 +1,4 @@
--
-- PostgreSQL database dump
--
-- Dumped from database version 16.2
-- Dumped by pg_dump version 16.2
\connect "f0ck";
SET statement_timeout = 0;
SET lock_timeout = 0;
@ -16,33 +11,7 @@ SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: public; Type: SCHEMA; Schema: -; Owner: postgres
--
-- *not* creating schema, since initdb creates it
ALTER SCHEMA public OWNER TO postgres;
--
-- Name: unaccent; Type: EXTENSION; Schema: -; Owner: -
--
CREATE EXTENSION IF NOT EXISTS unaccent WITH SCHEMA public;
--
-- Name: EXTENSION unaccent; Type: COMMENT; Schema: -; Owner:
--
COMMENT ON EXTENSION unaccent IS 'text search dictionary that removes accents';
--
-- Name: delete_unused_tags(); Type: FUNCTION; Schema: public; Owner: f0ck
--
CREATE EXTENSION unaccent;
CREATE FUNCTION public.delete_unused_tags() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -52,17 +21,14 @@ begin
tags.id not in (select tag_id from tags_assign) and
tags.id = OLD.tag_id and
tags.tag != 'sfw' and
tags.tag != 'nsfw';
tags.tag != 'nsfw' and
tags.tag != 'hentai' and
tags.tag != 'audio';
return OLD;
end $$;
ALTER FUNCTION public.delete_unused_tags() OWNER TO f0ck;
--
-- Name: fill_normalized(); Type: FUNCTION; Schema: public; Owner: f0ck
--
CREATE FUNCTION public.fill_normalized() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -71,13 +37,8 @@ begin
return NEW;
end$$;
ALTER FUNCTION public.fill_normalized() OWNER TO f0ck;
--
-- Name: slugify(text); Type: FUNCTION; Schema: public; Owner: f0ck
--
CREATE FUNCTION public.slugify(v text) RETURNS text
LANGUAGE plpgsql
AS $$
@ -86,47 +47,15 @@ BEGIN
END;
$$;
ALTER FUNCTION public.slugify(v text) OWNER TO f0ck;
--
-- Name: unaccent_text(text); Type: FUNCTION; Schema: public; Owner: f0ck
--
CREATE FUNCTION public.unaccent_text(text) RETURNS text
LANGUAGE sql IMMUTABLE COST 1
AS $_$
-- unaccent is STABLE, but the indexes must use IMMUTABLE functions.
-- comment this line out when calling pg_dump.
SELECT unaccent($1);
-- Uncomment this line when calling pg_dump.
--SELECT ''::text;
$_$;
ALTER FUNCTION public.unaccent_text(text) OWNER TO f0ck;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: favorites; Type: TABLE; Schema: public; Owner: f0ck
--
CREATE TABLE public.favorites (
user_id integer NOT NULL,
item_id integer NOT NULL
);
ALTER TABLE public.favorites OWNER TO f0ck;
--
-- Name: items_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ck
--
CREATE SEQUENCE public.items_id_seq
START WITH 1
INCREMENT BY 1
@ -134,12 +63,7 @@ CREATE SEQUENCE public.items_id_seq
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.items_id_seq OWNER TO f0ck;
--
-- Name: items; Type: TABLE; Schema: public; Owner: f0ck
--
ALTER TABLE public.items_id_seq OWNER TO f0ck;
CREATE TABLE public.items (
id integer DEFAULT nextval('public.items_id_seq'::regclass) NOT NULL,
@ -149,115 +73,18 @@ CREATE TABLE public.items (
size integer NOT NULL,
checksum character varying(255) NOT NULL,
username character varying(40) NOT NULL,
userchannel character varying(255) NOT NULL,
userchannel character varying(100) NOT NULL,
usernetwork character varying(40) NOT NULL,
stamp integer NOT NULL,
active boolean NOT NULL,
thumb character varying(100)
);
ALTER TABLE public.items OWNER TO f0ck;
--
-- Name: COLUMN items.src; Type: COMMENT; Schema: public; Owner: f0ck
--
COMMENT ON COLUMN public.items.src IS 'src-Link';
--
-- Name: COLUMN items.dest; Type: COMMENT; Schema: public; Owner: f0ck
--
COMMENT ON COLUMN public.items.dest IS 'filename';
--
-- Name: items_li; Type: VIEW; Schema: public; Owner: f0ck
--
CREATE VIEW public.items_li AS
SELECT
NULL::integer AS id,
NULL::character varying(255) AS src,
NULL::character varying(40) AS dest,
NULL::character varying(100) AS mime,
NULL::integer AS size,
NULL::character varying(255) AS checksum,
NULL::character varying(40) AS username,
NULL::character varying(255) AS userchannel,
NULL::character varying(40) AS usernetwork,
NULL::integer AS stamp;
ALTER VIEW public.items_li OWNER TO f0ck;
--
-- Name: tags_assign; Type: TABLE; Schema: public; Owner: f0ck
--
CREATE TABLE public.tags_assign (
item_id integer NOT NULL,
tag_id integer NOT NULL,
user_id integer DEFAULT 10 NOT NULL
);
ALTER TABLE public.tags_assign OWNER TO f0ck;
--
-- Name: tags_nsfp; Type: TABLE; Schema: public; Owner: f0ck
--
CREATE TABLE public.tags_nsfp (
id integer NOT NULL
);
ALTER TABLE public.tags_nsfp OWNER TO f0ck;
--
-- Name: items_sfw; Type: VIEW; Schema: public; Owner: f0ck
--
CREATE VIEW public.items_sfw AS
SELECT ( SELECT
CASE
WHEN (tags_assign.tag_id > 0) THEN tags_assign.tag_id
ELSE 0
END AS "case"
FROM public.tags_assign
WHERE ((tags_assign.tag_id = ANY (ARRAY[1, 2])) AND (tags_assign.item_id = items.id))) AS sfw,
( SELECT
CASE
WHEN (tags_assign.tag_id > 0) THEN 1
ELSE 0
END AS "case"
FROM public.tags_assign
WHERE ((tags_assign.tag_id IN ( SELECT tags_nsfp.id
FROM public.tags_nsfp)) AND (tags_assign.item_id = items.id))
LIMIT 1) AS nsfp,
id,
src,
dest,
mime,
size,
checksum,
username,
userchannel,
usernetwork,
stamp,
active
FROM public.items;
ALTER VIEW public.items_sfw OWNER TO f0ck;
--
-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ck
--
CREATE SEQUENCE public.tags_id_seq
START WITH 1
INCREMENT BY 1
@ -265,25 +92,30 @@ CREATE SEQUENCE public.tags_id_seq
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.tags_id_seq OWNER TO f0ck;
--
-- Name: tags; Type: TABLE; Schema: public; Owner: f0ck
--
ALTER TABLE public.tags_id_seq OWNER TO f0ck;
CREATE TABLE public.tags (
id integer DEFAULT nextval('public.tags_id_seq'::regclass) NOT NULL,
tag character varying(70) NOT NULL,
normalized character varying(70) NOT NULL
tag character varying(45) NOT NULL,
normalized character varying(45) NOT NULL
);
ALTER TABLE public.tags OWNER TO f0ck;
--
-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ck
--
CREATE TABLE public.tags_alias (
tag_orig_id integer NOT NULL,
tag_alias character varying NOT NULL
);
ALTER TABLE public.tags_alias OWNER TO f0ck;
CREATE TABLE public.tags_assign (
item_id integer NOT NULL,
tag_id integer NOT NULL,
user_id integer DEFAULT 2 NOT NULL
);
ALTER TABLE public.tags_assign OWNER TO f0ck;
CREATE SEQUENCE public.user_id_seq
START WITH 1
@ -292,128 +124,30 @@ CREATE SEQUENCE public.user_id_seq
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.user_id_seq OWNER TO f0ck;
--
-- Name: user; Type: TABLE; Schema: public; Owner: f0ck
--
ALTER TABLE public.user_id_seq OWNER TO f0ck;
CREATE TABLE public."user" (
id integer DEFAULT nextval('public.user_id_seq'::regclass) NOT NULL,
login character varying(255) NOT NULL,
"user" character varying(255) NOT NULL,
password character varying(167) NOT NULL,
admin boolean NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL
level integer NOT NULL
);
ALTER TABLE public."user" OWNER TO f0ck;
--
-- Name: items_tags; Type: VIEW; Schema: public; Owner: f0ck
--
CREATE VIEW public.items_tags AS
SELECT ( SELECT
CASE
WHEN (tags_assign.tag_id > 0) THEN tags_assign.tag_id
ELSE 0
END AS "case"
FROM public.tags_assign
WHERE ((tags_assign.tag_id = ANY (ARRAY[1, 2])) AND (tags_assign.item_id = items.id))) AS sfw,
( SELECT
CASE
WHEN (tags_assign.tag_id > 0) THEN 1
ELSE 0
END AS "case"
FROM public.tags_assign
WHERE ((tags_assign.tag_id IN ( SELECT tags_nsfp.id
FROM public.tags_nsfp)) AND (tags_assign.item_id = items.id))
LIMIT 1) AS nsfp,
( SELECT jsonb_agg(jsonb_build_object('id', tags.id, 'normalized', tags.normalized)) AS jsonb_agg
FROM (public.tags_assign
LEFT JOIN public.tags ON ((tags.id = tags_assign.tag_id)))
WHERE (tags_assign.item_id = items.id)) AS tags,
( SELECT jsonb_agg(jsonb_build_object('id', favorites.user_id, 'user', "user"."user")) AS jsonb_agg
FROM (public.favorites
LEFT JOIN public."user" ON (("user".id = favorites.user_id)))
WHERE (favorites.item_id = items.id)) AS favs,
id,
src,
dest,
mime,
size,
checksum,
username,
userchannel,
usernetwork,
stamp,
active
FROM public.items;
ALTER VIEW public.items_tags OWNER TO f0ck;
--
-- Name: tags_alias; Type: TABLE; Schema: public; Owner: f0ck
--
CREATE TABLE public.tags_alias (
tag_orig_id integer NOT NULL,
tag_alias character varying NOT NULL
);
ALTER TABLE public.tags_alias OWNER TO f0ck;
--
-- Name: user_alias; Type: TABLE; Schema: public; Owner: f0ck
--
CREATE TABLE public.user_alias (
userid integer NOT NULL,
alias character varying(255) NOT NULL
);
ALTER TABLE ONLY public.user_alias REPLICA IDENTITY FULL;
ALTER TABLE public.user_alias OWNER TO f0ck;
--
-- Name: user_options; Type: TABLE; Schema: public; Owner: f0ck
--
CREATE TABLE public.user_options (
user_id integer NOT NULL,
mode integer NOT NULL,
theme character varying(50) NOT NULL,
avatar integer DEFAULT 56660 NOT NULL,
fullscreen smallint DEFAULT '0'::smallint NOT NULL
avatar integer DEFAULT 1 NOT NULL
);
ALTER TABLE public.user_options OWNER TO f0ck;
--
-- Name: user_sessions_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ck
--
CREATE SEQUENCE public.user_sessions_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
CREATE SEQUENCE public.user_sessions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.user_sessions_id_seq OWNER TO f0ck;
--
-- Name: user_sessions; Type: TABLE; Schema: public; Owner: f0ck
--
ALTER TABLE public.user_sessions_id_seq OWNER TO f0ck;
CREATE TABLE public.user_sessions (
id integer DEFAULT nextval('public.user_sessions_id_seq'::regclass) NOT NULL,
@ -426,255 +160,55 @@ CREATE TABLE public.user_sessions (
kmsi smallint DEFAULT '0'::smallint NOT NULL
);
ALTER TABLE public.user_sessions OWNER TO f0ck;
--
-- Name: favorites idx_16521_primary; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
COPY public.items (id, src, dest, mime, size, checksum, username, userchannel, usernetwork, stamp, active, thumb) FROM stdin;
1 b761fa9339.png b761fa9339.png image/png 164 keinPlan Flummi #f0ck n0xy 1471250800 t
\.
ALTER TABLE ONLY public.favorites
ADD CONSTRAINT idx_16521_primary PRIMARY KEY (user_id, item_id);
COPY public.tags (id, tag, normalized) FROM stdin;
1 sfw sfw
2 nsfw nsfw
3 audio audio
4 hentai hentai
\.
COPY public.tags_assign (item_id, tag_id, user_id) FROM stdin;
1 1 1
\.
--
-- Name: items idx_16526_primary; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.items
ADD CONSTRAINT idx_16526_primary PRIMARY KEY (id);
--
-- Name: user idx_16554_primary; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public."user"
ADD CONSTRAINT idx_16554_primary PRIMARY KEY (id);
--
-- Name: user_options idx_16567_user_id; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.user_options
ADD CONSTRAINT idx_16567_user_id UNIQUE (user_id);
--
-- Name: user_sessions idx_16572_primary; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.user_sessions
ADD CONSTRAINT idx_16572_primary PRIMARY KEY (id);
--
-- Name: items items_checksum; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.items
ADD CONSTRAINT items_checksum UNIQUE (checksum);
--
-- Name: tags_alias tags_alias_tag_alias_tag_orig_id; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_alias
ADD CONSTRAINT tags_alias_tag_alias_tag_orig_id UNIQUE (tag_alias, tag_orig_id);
--
-- Name: tags_alias tags_alias_tag_orig_id; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_alias
ADD CONSTRAINT tags_alias_tag_orig_id PRIMARY KEY (tag_orig_id);
--
-- Name: tags_assign tags_assign_item_id_tag_id_primary; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_assign
ADD CONSTRAINT tags_assign_item_id_tag_id_primary PRIMARY KEY (item_id, tag_id);
--
-- Name: tags_assign tags_assign_item_id_tag_id_unique; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_assign
ADD CONSTRAINT tags_assign_item_id_tag_id_unique UNIQUE (item_id, tag_id);
--
-- Name: tags tags_id; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags
ADD CONSTRAINT tags_id PRIMARY KEY (id);
--
-- Name: tags tags_normalized; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags
ADD CONSTRAINT tags_normalized UNIQUE (normalized);
--
-- Name: tags tags_tag; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags
ADD CONSTRAINT tags_tag UNIQUE (tag);
--
-- Name: user_options user_options_user_id; Type: CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.user_options
ADD CONSTRAINT user_options_user_id PRIMARY KEY (user_id);
--
-- Name: items_li _RETURN; Type: RULE; Schema: public; Owner: f0ck
--
CREATE OR REPLACE VIEW public.items_li AS
SELECT items.id,
items.src,
items.dest,
items.mime,
items.size,
items.checksum,
items.username,
items.userchannel,
items.usernetwork,
items.stamp
FROM ((public.items
JOIN public.tags_assign ta1 ON (((ta1.tag_id = 1) AND (ta1.item_id = items.id))))
JOIN public.tags_assign ta2 ON (((NOT (ta2.tag_id IN ( SELECT tags_nsfp.id
FROM public.tags_nsfp))) AND (ta2.item_id = items.id))))
WHERE items.active
GROUP BY items.id;
--
-- Name: tags_assign tags_assign_ad; Type: TRIGGER; Schema: public; Owner: f0ck
--
COPY public."user" (id, login, "user", password, level) FROM stdin;
1 autotagger autotagger f0ck you 0
2 deleted deleted f0ck you 0
\.
SELECT pg_catalog.setval('public.items_id_seq', 2, true);
SELECT pg_catalog.setval('public.tags_id_seq', 5, true);
SELECT pg_catalog.setval('public.user_id_seq', 3, true);
SELECT pg_catalog.setval('public.user_sessions_id_seq', 1, true);
ALTER TABLE ONLY public.favorites ADD CONSTRAINT idx_16521_primary PRIMARY KEY (user_id, item_id);
ALTER TABLE ONLY public.items ADD CONSTRAINT idx_16526_primary PRIMARY KEY (id);
ALTER TABLE ONLY public."user" ADD CONSTRAINT idx_16554_primary PRIMARY KEY (id);
ALTER TABLE ONLY public.user_options ADD CONSTRAINT idx_16567_user_id UNIQUE (user_id);
ALTER TABLE ONLY public.user_sessions ADD CONSTRAINT idx_16572_primary PRIMARY KEY (id);
ALTER TABLE ONLY public.items ADD CONSTRAINT items_checksum UNIQUE (checksum);
ALTER TABLE ONLY public.tags_alias ADD CONSTRAINT tags_alias_tag_alias_tag_orig_id UNIQUE (tag_alias, tag_orig_id);
ALTER TABLE ONLY public.tags_alias ADD CONSTRAINT tags_alias_tag_orig_id PRIMARY KEY (tag_orig_id);
ALTER TABLE ONLY public.tags_assign ADD CONSTRAINT tags_assign_item_id_tag_id_primary PRIMARY KEY (item_id, tag_id);
ALTER TABLE ONLY public.tags_assign ADD CONSTRAINT tags_assign_item_id_tag_id_unique UNIQUE (item_id, tag_id);
ALTER TABLE ONLY public.tags ADD CONSTRAINT tags_id PRIMARY KEY (id);
ALTER TABLE ONLY public.tags ADD CONSTRAINT tags_normalized UNIQUE (normalized);
ALTER TABLE ONLY public.tags ADD CONSTRAINT tags_tag UNIQUE (tag);
ALTER TABLE ONLY public.user_options ADD CONSTRAINT user_options_user_id PRIMARY KEY (user_id);
CREATE TRIGGER tags_assign_ad AFTER DELETE ON public.tags_assign FOR EACH ROW EXECUTE FUNCTION public.delete_unused_tags();
--
-- Name: tags tags_bi; Type: TRIGGER; Schema: public; Owner: f0ck
--
CREATE TRIGGER tags_bi BEFORE INSERT ON public.tags FOR EACH ROW EXECUTE FUNCTION public.fill_normalized();
--
-- Name: tags tags_bu; Type: TRIGGER; Schema: public; Owner: f0ck
--
CREATE TRIGGER tags_bu BEFORE UPDATE ON public.tags FOR EACH ROW EXECUTE FUNCTION public.fill_normalized();
--
-- Name: favorites favorites_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.favorites
ADD CONSTRAINT favorites_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE;
--
-- Name: favorites favorites_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.favorites
ADD CONSTRAINT favorites_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE;
--
-- Name: tags_alias tags_alias_tag_orig_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_alias
ADD CONSTRAINT tags_alias_tag_orig_id_fkey FOREIGN KEY (tag_orig_id) REFERENCES public.tags(id) ON DELETE CASCADE;
--
-- Name: tags_assign tags_assign_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_assign
ADD CONSTRAINT tags_assign_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE;
--
-- Name: tags_assign tags_assign_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_assign
ADD CONSTRAINT tags_assign_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id) ON DELETE CASCADE;
--
-- Name: tags_assign tags_assign_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.tags_assign
ADD CONSTRAINT tags_assign_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE SET DEFAULT;
--
-- Name: user_options user_options_avatar_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.user_options
ADD CONSTRAINT user_options_avatar_fkey FOREIGN KEY (avatar) REFERENCES public.items(id) ON DELETE SET DEFAULT;
--
-- Name: user_options user_options_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.user_options
ADD CONSTRAINT user_options_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE;
--
-- Name: user_sessions user_sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ck
--
ALTER TABLE ONLY public.user_sessions
ADD CONSTRAINT user_sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE;
--
-- Name: alltables; Type: PUBLICATION; Schema: -; Owner: postgres
--
CREATE PUBLICATION alltables FOR ALL TABLES WITH (publish = 'insert, update, delete, truncate');
ALTER PUBLICATION alltables OWNER TO postgres;
--
-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres
--
REVOKE USAGE ON SCHEMA public FROM PUBLIC;
GRANT ALL ON SCHEMA public TO PUBLIC;
--
-- PostgreSQL database dump complete
--
ALTER TABLE ONLY public.favorites ADD CONSTRAINT favorites_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.favorites ADD CONSTRAINT favorites_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.tags_alias ADD CONSTRAINT tags_alias_tag_orig_id_fkey FOREIGN KEY (tag_orig_id) REFERENCES public.tags(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.tags_assign ADD CONSTRAINT tags_assign_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.tags_assign ADD CONSTRAINT tags_assign_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.tags_assign ADD CONSTRAINT tags_assign_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE SET DEFAULT;
ALTER TABLE ONLY public.user_options ADD CONSTRAINT user_options_avatar_fkey FOREIGN KEY (avatar) REFERENCES public.items(id) ON DELETE SET DEFAULT;
ALTER TABLE ONLY public.user_options ADD CONSTRAINT user_options_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.user_sessions ADD CONSTRAINT user_sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE;

BIN
nsfw_model.h5 Normal file

Binary file not shown.

32
package-lock.json generated
View File

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

View File

@ -3,21 +3,21 @@
"version": "2.2.1",
"description": "f0ck, kennste?",
"main": "index.mjs",
"type": "module",
"scripts": {
"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"
"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"
},
"author": "Flummi",
"license": "MIT",
"dependencies": {
"cuffeo": "^1.2.2",
"cuffeo": "^1.1.0",
"flumm-fetch": "^1.0.1",
"flummpress": "^2.0.5",
"postgres": "^3.3.4"
"postgres": "^3.0.1"
}
}

View File

@ -6,7 +6,7 @@
html[theme='f0ck'] {
--accent: #9f0;
--bg: #000000;
--bg: #171717;
--black: #000;
--white: #fff;
--gray: #262626;
@ -24,7 +24,7 @@ html[theme='f0ck'] {
--dropdown-bg: #232323;
--dropdown-item-hover: #0d0d0d;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #2b2b2b;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -42,10 +42,6 @@ html[theme='f0ck'] {
--loading-indicator-color: #9f0;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #9f0;
--bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%);
/* appearance */
background: black;
}
html[theme="f0ck"] .admin-search button {
@ -75,7 +71,7 @@ html[theme='p1nk'] {
--nav-bg: #2b2b2b;
--nav-brand-border: inset 1px #242424;
--nav-brand-bg: #171717;
--navigation-links-bg: #201f1f;
--navigation-links-bg: #2b2b2b;
--navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0);
--navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5);
--navigation-links-box-shadow: rgba(255, 255, 255, .05);
@ -85,8 +81,8 @@ html[theme='p1nk'] {
--dropdown-bg: #232323;
--dropdown-item-hover: #0d0d0d;
--nav-brand-font: 'VCR';
--font: monospace;
--pagination-background: #201f1f;
--font: 'monospace';
--pagination-background: #2b2b2b;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
;
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -104,10 +100,6 @@ html[theme='p1nk'] {
--loading-indicator-color: #ff00d0;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #ff00d0;
--bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%);
/* appearance */
background: black;
}
html[theme="p1nk"] .pagination>a,
@ -143,7 +135,7 @@ html[theme='orange'] {
--dropdown-bg: #232323;
--dropdown-item-hover: #0d0d0d;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #2b2b2b;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -161,10 +153,6 @@ html[theme='orange'] {
--loading-indicator-color: #ff6f00;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #ff6f00;
--bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%);
/* appearance */
background: black;
}
html[theme="orange"] .pagination>a,
@ -202,7 +190,7 @@ html[theme='amoled'] {
--dropdown-bg: #232323;
--dropdown-item-hover: #0d0d0d;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #000;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0px rgba(255, 255, 255, 0), inset 0 0px rgba(248, 248, 248, 0), 0 0px 0px rgba(255, 255, 255, 0);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgb(92, 92, 92), inset 0 1px rgb(92, 92, 92), inset 0 -1px rgb(92, 92, 92), 0 1px 1px rgba(92, 92, 92, 0);
@ -220,9 +208,6 @@ html[theme='amoled'] {
--loading-indicator-color: #fff;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #fff;
--bg-gradient: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 10%, rgb(0, 0, 0) 100%);
background: black;
}
html[theme="amoled"] .metadata {
@ -260,10 +245,6 @@ html[theme="amoled"] table.table a {
text-decoration: underline;
}
html[theme="amoled"] .badge.badge-dark {
box-shadow: 1px 1px 1px var(--gray);
}
html[theme="paper"] {
--accent: #000;
--bg: #fff;
@ -284,7 +265,7 @@ html[theme="paper"] {
--dropdown-bg: #fff;
--dropdown-item-hover: #3939354a;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #fff;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -302,7 +283,6 @@ html[theme="paper"] {
--loading-indicator-color: #000;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #fff;
}
html[theme="paper"] .err {
@ -422,7 +402,7 @@ html[theme="paper"] .v0ck_player_button svg:hover {
html[theme="paper"] .badge-dark,
#themeselector {
box-shadow: 1px 1px 0px 0px var(--black);
border: 1px solid var(--white);
}
html[theme="paper"] span#favs {
@ -451,18 +431,6 @@ html[theme="paper"] a {
color: var(--black);
}
html[theme="paper"] .login-form {
background: white;
}
html[theme="paper"] .login-form input {
color: black;
}
html[theme="paper"] .login-form button[type="submit"] {
color: black;
}
html[theme="atmos"] {
--accent: #1fb2b0;
--bg: #161618;
@ -483,7 +451,7 @@ html[theme="atmos"] {
--dropdown-bg: #232323;
--dropdown-item-hover: #0d0d0d;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: rgb(32, 32, 32);
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -500,10 +468,6 @@ html[theme="atmos"] {
--loading-indicator-color: #1fb2b0;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #1fb2b0;
--bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%);
/* appearance */
background: black;
}
html[theme="atmos"] .pagination>a,
@ -540,7 +504,7 @@ html[theme="term"] {
--dropdown-bg: #040404;
--dropdown-item-hover: #282828;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #040404;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -559,10 +523,6 @@ html[theme="term"] {
--loading-indicator-color: #00DF00;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #00DF00;
--bg-gradient: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 10%, rgb(0, 0, 0) 100%);
/* appearance */
background: black;
}
html[theme="term"] .pagination>a,
@ -615,7 +575,7 @@ html[theme="iced"] {
--dropdown-bg: #111d37;
--dropdown-item-hover: #0a3f53;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #111d37;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -634,10 +594,6 @@ html[theme="iced"] {
--loading-indicator-color: #0084ff;
--img-border-width: 0;
--img-border-color: #363636;
--maximize_button: #0084ff;
--bg-gradient: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 10%, #062631 100%);
/* appearance */
background: black;
}
html[theme="iced"] ._204863 {
@ -691,7 +647,7 @@ html[theme='f0ck95'] {
--dropdown-bg: silver;
--dropdown-item-hover: #8a8989;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: silver;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -709,10 +665,6 @@ html[theme='f0ck95'] {
--loading-indicator-color: #000;
--img-border-width: 0;
--img-border-color: #808080;
--maximize_button: #000;
--bg-gradient: linear-gradient(0deg, rgba(0, 128, 128, 0.514), rgba(0, 0, 0, 0) 10%, rgba(0, 128, 128, 0.363) 100%);
/* appearance */
background: teal;
}
html[theme="f0ck95"] html,
@ -963,7 +915,7 @@ html[theme='f0ck95d'] {
--dropdown-bg: #333131;
--dropdown-item-hover: #444242;
--nav-brand-font: 'VCR';
--font: monospace;
--font: 'monospace';
--pagination-background: #0b0a0a;
--pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1);
--pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1);
@ -981,10 +933,6 @@ html[theme='f0ck95d'] {
--loading-indicator-color: #fff;
--img-border-width: 0;
--img-border-color: #808080;
--maximize_button: #fff;
--bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%);
/* appearance */
background: black;
}
html[theme="f0ck95d"] .err {
@ -1153,21 +1101,6 @@ html[theme="f0ck95d"] .admin-search button {
color: black;
}
/* fullscreen */
html[res="fullscreen"] .embed-responsive-16by9::before {
padding-top: 32.99%;
}
html[res="fullscreen"] .container {
max-width: 100% !important;
padding: 0 !important;
}
html[res="fullscreen"] span#favs {
padding: 0 !important;
}
::-webkit-scrollbar {
width: 2px;
}
@ -1197,7 +1130,6 @@ html[res="fullscreen"] span#favs {
html,
body {
background-color: var(--bg);
background: var(--bg-gradient);
color: var(--white);
margin: 0;
font-family: var(--font);
@ -1207,7 +1139,6 @@ body {
overscroll-behavior-y: contain;
overflow: unset;
font-size: 14px;
height: /* 100%; */auto;
}
.wrapper {
@ -1255,7 +1186,7 @@ h5 {
div.posts {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
justify-items: center;
grid-gap: 5px;
margin: 0;
@ -1263,12 +1194,11 @@ div.posts {
margin-bottom: 35px;
}
/* nervt auf handy xd */
/*@media (max-width: 376px) {
@media (max-width: 376px) {
div.posts {
grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
}
}*/
}
div.posts>a {
display: inline-block;
@ -1763,8 +1693,8 @@ span.placeholder {
}
.imageDoor {
width: 25px;
height: 29px;
width: 27px;
height: 27px;
display: inline-flex;
padding: 2px;
}
@ -1792,8 +1722,8 @@ span.placeholder {
.index-container {
width: 100%;
padding: 5px;
/* background-color: var(--navigation-links-bg);
*/}
background-color: var(--navigation-links-bg);
}
@media (min-width: 361px) {
.embed-responsive-image {
@ -2348,8 +2278,8 @@ div#footbar {
img.avatar {
height: auto;
position: relative;
width: 25px;
margin: 1px;
width: 28px;
margin: 0;
left: -7px;
top: 0;
border-top-left-radius: 3px;
@ -2370,7 +2300,7 @@ div.logwrap>p {
table.table {
border-collapse: collapse;
margin: 25px 0;
min-width: 100%;
min-width: max-content;
}
table.table thead tr {
@ -2410,7 +2340,6 @@ table.table tbody tr:nth-of-type(odd) {
background: var(--nav-bg);
border: 1px solid black;
border-radius: 3px;
min-width: 272px;
}
.by-user {
@ -2839,180 +2768,185 @@ ul.navbar-nav-guests li.nav-item {
}
}
/* fadeIn effect */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
/* mobile random button */
/* .random {
display: none;
}
img#f0ck-image, div.imageDoor, div.posts a, video {
animation: 1s ease-out 0s 1 fadeInFX;
@media (max-width: 768px) {
.metadata {
margin-bottom: 0;
}
/* f0ckgle */
.f0ckgle {
display: grid;
align-content: center;
height: auto;
.random {
width: 100%;
height: 100px;
transition: .5s ease-out;
display: block;
}
.search-title {
text-align: center;
font-size: xxx-large;
}
.admin-search input {
padding: 15px;
border: 1px solid var(--accent);
min-width: 30%;
}
/* profile */
.profile_head {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
background: var(--nav-bg);
padding: 5px;
}
.profile_head_avatar {
margin: 0;
}
.profile_head_username {
font-weight: bold;
padding-left: 5px;
}
.profile_head_user_stats {
padding-left: 5px;
}
.layersoffear {
display: grid;
}
.user_content_wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 5px;
padding: 5px;
}
.f0cks h5, .favs h5 {
background: var(--dropdown-bg);
}
.f0cks-header, .favs-header {
display: grid;
grid-template-columns: 1fr auto;
background: var(--img-border-color);
padding: 0px 5px 0px 5px;
}
div.f0cks div.posts {
padding: 5px;
background: var(--dropdown-bg);
}
div.favs div.posts {
padding: 5px;
background: var(--dropdown-bg);
}
#bg {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
.random a {
width: 100%;
height: 100%;
-webkit-filter: blur(100px);
filter: blur(100px);
transform: translate3d(0, 0, 0);
z-index: 0;
transition: 2s ease;
opacity: 0.2;
}
button#togglebg {
display: inline;
cursor: pointer;
background: transparent;
border: 0;
.random a::after {
content: "";
}
@keyframes fadeInFX {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
.random:active {
background: #00000036;
}
} */
@keyframes fadeOutFX {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
/* changes for comment mockup */
body {
overflow: hidden;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 0.2;
}
.container {
padding-top: 0;
max-width: 100%;
overflow: scroll;
padding-bottom: 0;
}
@keyframes fadeOut {
0% {
opacity: 0.2;
}
100% {
opacity: 0;
}
}
.fader-in {
animation: fadeIn .8s steps(100) forwards;
}
.fader-out {
animation: fadeOut .8s steps(100) forwards
}
.settings {
#main {
display: grid;
justify-content: center;
grid-template-columns: 3fr 1fr;
height: calc(100% - 40px);
}
input[name="i_avatar"] {
text-align: center;
width: 50px;
padding: 10px;
#comments1 {
width: 100%;
}
input#s_avatar {
.comment {
background: black;
padding: 5px;
border: 1px solid var(--black);
border-radius: 3px;
background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient));
box-shadow: var(--nav-link-box-shadow);
cursor: pointer;
margin: 10px;
display: grid;
grid-template-rows: 0fr 1fr;
grid-template-columns: 0fr 1fr;
}
.sidebar {
display: grid;
grid-template-columns: auto;
grid-template-rows: 0fr 0fr auto 0fr;
border-top: 1px solid #363636;
border-right: 1px solid #363636;
background-color: var(--metadata-bg);
}
#s_avatar:hover {
background: #ffffff0f;
.commentbox {
display: grid;
grid-template-rows: 1fr 0fr;
padding: 5px;
background: #373737;
grid-gap: 5px;
}
#comments1 {
background: #131313;
border: 1px solid black;
color: white;
}
button.commentbutton {
width: 5rem;
border: 1px solid black;
background: #131313;
color: white;
}
.commentholder {
overflow: scroll;
min-height: 100%;
width: 100%;
height: 0;
}
img.avatar_comments {
height: auto;
position: relative;
width: 35px;
margin: 0;
left: 0;
top: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.comment_content {
grid-column: 2;
}
.comment_content p {
margin: 0;
padding: 5px;
}
.comment_username {
align-items: baseline;
display: grid;
position: relative;
height: 100%;
padding-left: 5px;
position: relative;
top: -5px;
}
.comment {
background: #171717;
padding: 5px;
margin: 10px;
display: grid;
grid-template-rows: 0fr 0fr;
grid-template-columns: 0fr 1fr;
border: 1px solid black;
}
.comment_content img {
max-width: 150px;
}
.header_sidebar {
background: -webkit-linear-gradient(left, #4f4a4f, #131313);
color: var(--accent);
}
.header_sidebar span {
position: relative;
left: 4px;
font-weight: bold;
}
/* comments responsive */
@media (max-width: 799px) {
#main {
display: grid;
grid-template-columns: 1fr;
height: calc(100% - 40px);
}
.commentholder {
overflow: unset;
}
.container {
overflow: unset;
}
body {
overflow: scroll;
}
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
}

View File

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

View File

@ -6,7 +6,5 @@
<symbol id="heart_regular" viewBox="0 0 512 512"><path d="M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"/></symbol>
<symbol id="heart_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.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -85,7 +85,7 @@ const flash = ({ type, msg }) => {
[...document.querySelectorAll("#tags > .badge")].forEach(tag => tag.parentElement.removeChild(tag));
_tags.reverse().forEach(tag => {
const a = document.createElement("a");
a.href = `/tag/${tag.normalized}`;
a.href = `/tag/${tag.tag}`;
a.style = "color: inherit !important";
a.innerHTML = tag.tag;
a.addEventListener("click", editTagEvent); // tmp
@ -333,7 +333,6 @@ const flash = ({ type, msg }) => {
document.querySelector("a#a_toggle").addEventListener("click", toggleEvent);
[...document.querySelectorAll("#tags > .badge > a:first-child")].map(t => t.addEventListener("click", editTagEvent));
[...document.querySelectorAll("#tags > .badge > a:last-child")].map(t => t.addEventListener("click", deleteEvent));
if(document.querySelector("svg#a_delete"))
document.querySelector("svg#a_delete").addEventListener("click", deleteButtonEvent);
document.querySelector("svg#a_favo").addEventListener("click", toggleFavEvent);

View File

@ -1,11 +1,3 @@
window.requestAnimFrame = (function(){
return window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| function(callback) { window.setTimeout(callback, 1000 / 60);};
})();
(() => {
let video;
if(elem = document.querySelector("#my-video")) {
@ -16,42 +8,6 @@ window.requestAnimFrame = (function(){
document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden');
}
});
document.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;
@ -90,44 +46,19 @@ window.requestAnimFrame = (function(){
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);
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;");
}
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;");
});
}
// </image-responsive>
@ -137,7 +68,7 @@ window.requestAnimFrame = (function(){
const scroll_treshold = 1;
if([...document.querySelectorAll("div.posts")].length === 1) {
document.addEventListener("wheel", e => {
if(Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down
if(Math.ceil(window.innerHeight + window.scrollY) >= document.body.offsetHeight && e.deltaY > 0) { // down
if(elem = document.querySelector(".pagination > .next:not(.disabled)")) {
if(tts < scroll_treshold) {
document.querySelector("div#footbar").style.boxShadow = "inset 0px 4px 0px var(--footbar-color)";
@ -303,8 +234,4 @@ window.requestAnimFrame = (function(){
});
}
// </mediakeys>
// <scroller>
// </scroller>
})();

View File

@ -8,7 +8,6 @@ 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
}
@ -45,21 +44,4 @@ 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;
});
}
})();

View File

@ -18,7 +18,6 @@ const tpl_player = svg => `<div class="v0ck_player_controls">
<input type="range" name="volume" min="0" max="1" step="0.01" value="1" />
<button class="v0ck_player_button v0ck_playtime">00:00 / 00:00</button>
<span style="flex: 30"></span>
<button id="togglebg">&#128161;</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>

View File

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

View File

@ -1,4 +1,4 @@
import _config from "../../config.json" with { type: "json" };
import _config from "../../config.json" assert { type: "json" };
let config = JSON.parse(JSON.stringify(_config));

View File

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

View File

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

View File

@ -84,43 +84,32 @@ 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) and active = true
where id in (select item_id from tags_assign group by item_id)
`)[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) and active = true
where id not in (select item_id from tags_assign group by item_id)
`)[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) and active = true
where id in (select item_id from tags_assign where tag_id = 1 group by item_id)
`)[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) and active = true
where id in (select item_id from tags_assign where tag_id = 2 group by item_id)
`)[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,
deleted,
untracked: lastf0ck - (tagged + untagged + deleted),
total: +tagged + +untagged,
sfw,
nsfw,
nsfw
};
};
async hash(str) {
@ -134,6 +123,15 @@ export default new class {
const derivedKey = await scrypt(str, salt, 64);
return crypto.timingSafeEqual(keyBuffer, derivedKey);
};
async auth(req, res, next) {
if(!req.session) {
return res.reply({
code: 401,
body: "401 - Unauthorized"
});
}
return next();
};
async getTags(itemid) {
const tags = await db`
select "tags".id, "tags".tag, "tags".normalized, "user".user
@ -163,17 +161,6 @@ 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(
@ -208,27 +195,6 @@ export default new class {
TABLE_NAME='user_options' and
COLUMN_NAME = 'avatar'
`)[0].avatar;
};
// meddlware
async auth(req, res, next) {
if(!req.session || !req.session.admin) {
return res.reply({
code: 401,
body: "401 - Unauthorized"
});
}
return next();
};
async loggedin(req, res, next) {
if(!req.session) {
return res.reply({
code: 401,
body: "401 - Unauthorized"
});
}
return next();
};
};

View File

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

View File

@ -7,13 +7,12 @@ import url from "url";
const globalfilter = cfg.nsfp.map(n => `tag_id = ${n}`).join(' or ');
export default {
getf0cks: async (o = { user, tag, mime, page, mode, fav, session, limit }) => {
getf0cks: async (o = { user, tag, mime, page, mode, fav, session }) => {
const user = o.user ? decodeURI(o.user) : null;
const tag = lib.parseTag(o.tag ?? null);
const mime = o.mime ?? null;
const page = +(o.page ?? 1);
const smime = cfg.allowedMimes.includes(mime) ? mime + "/%" : mime === "" ? "%" : "%";
const eps = o.limit ?? cfg.websrv.eps;
const tmp = { user, tag, mime, smime, page, mode: o.mode };
const modequery = mime == "audio" ? lib.getMode(0) : lib.getMode(o.mode ?? 0);
@ -33,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 && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag
`)?.length || 0;
@ -67,11 +66,11 @@ 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 && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag, ta.tag_id
order by items.id desc
offset ${offset}
limit ${eps}
limit ${cfg.websrv.eps}
`;
const cheat = [];
@ -129,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 && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag, ta.tag_id
order by items.id desc
`;
@ -225,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 && globalfilter ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
${ !o.session ? db`and items.id not in (select item_id from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db`` }
group by items.id, tags.tag
order by random()
limit 1

View File

@ -3,16 +3,21 @@ import lib from "../lib.mjs";
import { exec } from "child_process";
import { promises as fs } from "fs";
export default (router, tpl) => {
router.get(/^\/login(\/)?$/, async (req, res) => {
if(req.cookies.session) {
const auth = async (req, res, next) => {
if(!req.session) {
return res.reply({
body: tpl.render('error', {
message: "you're already logged in lol",
tmp: null
}, req)
code: 401,
body: "401 - Unauthorized"
});
}
return next();
};
export default (router, tpl) => {
router.get(/^\/login(\/)?$/, async (req, res) => {
if(req.cookies.session)
return res.reply({ body: "du bist schon eingeloggt lol" });
res.reply({
body: tpl.render("login", { theme: req.cookies.theme ?? "f0ck" })
});
@ -61,7 +66,7 @@ export default (router, tpl) => {
}).end();
});
router.get(/^\/logout$/, lib.loggedin, async (req, res) => {
router.get(/^\/logout$/, auth, async (req, res) => {
const usersession = await db`
select *
from "user_sessions"
@ -92,18 +97,14 @@ export default (router, tpl) => {
});
});
router.get(/^\/admin(\/)?$/, lib.auth, async (req, res) => { // frontpage
router.get(/^\/admin(\/)?$/, auth, async (req, res) => { // frontpage
res.reply({
body: tpl.render("admin", {
totals: await lib.countf0cks(),
session: req.session,
tmp: null
}, req)
body: tpl.render("admin", { totals: await lib.countf0cks(), session: req.session }, req)
});
});
router.get(/^\/admin\/sessions(\/)?$/, lib.auth, async (req, res) => {
router.get(/^\/admin\/sessions(\/)?$/, auth, async (req, res) => {
const rows = await db`
select "user_sessions".*, "user".user
from "user_sessions"
@ -115,24 +116,22 @@ export default (router, tpl) => {
body: tpl.render("admin/sessions", {
session: req.session,
sessions: rows,
totals: await lib.countf0cks(),
tmp: null
totals: await lib.countf0cks()
}, req)
});
});
router.get(/^\/admin\/log(\/)?$/, lib.auth, async (req, res) => {
router.get(/^\/admin\/log(\/)?$/, auth, async (req, res) => {
exec("journalctl -qeu f0ck --no-pager", (err, stdout) => {
res.reply({
body: tpl.render("admin/log", {
log: stdout.split("\n").slice(0, -1),
tmp: null
log: stdout.split("\n").slice(0, -1)
}, req)
});
});
});
router.get(/^\/admin\/recover\/?/, lib.auth, async (req, res) => {
router.get(/^\/admin\/recover\/?/, auth, async (req, res) => {
if(req.url.qs?.id) {
const id = +req.url.qs.id;
const f0ck = await db`
@ -171,7 +170,6 @@ export default (router, tpl) => {
from "items"
where
active = 'false'
order by id desc
`;
if(_posts.length === 0) {
@ -180,15 +178,11 @@ 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,
tmp: null
posts
}, req)
});
});

View File

@ -139,7 +139,7 @@ export default router => {
// tags lol
group.put(/\/admin\/tags\/(?<tagname>.*)/, lib.loggedin, async (req, res) => {
group.put(/\/admin\/tags\/(?<tagname>.*)/, lib.auth, async (req, res) => {
if(!req.params.tagname || !req.post.newtag) {
return res.json({
success: false,
@ -187,7 +187,7 @@ export default router => {
return res.json(q, tagname === newtag ? 200 : 201); // created (modified)
});
group.get(/\/admin\/tags\/suggest$/, lib.loggedin, async (req, res) => {
group.get(/\/admin\/tags\/suggest$/, lib.auth, async (req, res) => {
const reply = {
success: false,
suggestions: {}
@ -267,7 +267,7 @@ export default router => {
});
});
group.post(/\/admin\/togglefav$/, lib.loggedin, async (req, res) => {
group.post(/\/admin\/togglefav$/, lib.auth, async (req, res) => {
const postid = +req.post.postid;
let favs = await db`

View File

@ -3,7 +3,7 @@ import lib from '../../lib.mjs';
export default router => {
router.group(/^\/api\/v2\/settings/, group => {
group.put(/\/setAvatar/, lib.loggedin, async (req, res) => {
group.put(/\/setAvatar/, lib.auth, async (req, res) => {
if(!req.post.avatar) {
return res.json({
msg: 'no avatar provided',

View File

@ -3,7 +3,7 @@ import lib from '../../lib.mjs';
export default router => {
router.group(/^\/api\/v2\/admin\/(?<postid>\d+)\/tags/, group => {
group.get(/$/, lib.loggedin, async (req, res) => {
group.get(/$/, lib.auth, async (req, res) => {
// get tags
if(!req.params.postid) {
return res.json({
@ -18,7 +18,7 @@ export default router => {
});
});
group.post(/$/, lib.loggedin, async (req, res) => {
group.post(/$/, lib.auth, async (req, res) => {
// assign and/or create tag
if(!req.params.postid || !req.post.tagname) {
return res.json({
@ -80,7 +80,7 @@ export default router => {
});
});
group.put(/\/toggle$/, lib.loggedin, async (req, res) => {
group.put(/\/toggle$/, lib.auth, async (req, res) => {
// xD
if(!req.params.postid) {
return res.json({

View File

@ -14,7 +14,7 @@ export default (router, tpl) => {
const user = decodeURIComponent(req.params.user);
const query = await db`
select "user".user, "user".admin, "user".created_at, user_options.*
select "user".user, user_options.*
from user_options
left join "user" on "user".id = user_options.user_id
where "user".user ilike ${user}
@ -31,48 +31,27 @@ export default (router, tpl) => {
});
}
let f0cks, favs;
const count = {
f0cks: 0,
favs: 0
};
try {
f0cks = await f0cklib.getf0cks({
const f0cks = await f0cklib.getf0cks({
user: user,
mode: req.session.mode,
fav: false,
session: !!req.session,
limit: 99999999
session: !!req.session
});
if('items' in f0cks) {
count.f0cks = f0cks.items.length;
f0cks.items = f0cks.items.slice(0, 50);
}
} catch(err) {
f0cks = false;
count.f0cks = 0;
}
try {
favs = await f0cklib.getf0cks({
const favs = await f0cklib.getf0cks({
user: user,
mode: req.session.mode,
fav: true,
session: !!req.session,
limit: 99999999
session: !!req.session
});
if('items' in favs) {
count.favs = favs.items.length;
if('items' in f0cks)
f0cks.items = f0cks.items.slice(0, 50);
if('items' in favs)
favs.items = favs.items.slice(0, 50);
}
} catch(err) {
favs = false;
count.favs = 0;
}
const data = {
user: query[0],
f0cks,
count,
favs,
tmp: null
};
@ -111,7 +90,7 @@ export default (router, tpl) => {
});
});
router.get(/^\/mode\/(\d)/, lib.loggedin, async (req, res) => {
router.get(/^\/mode\/(\d)/, auth, async (req, res) => {
const mode = +req.url.split[1];
let referertmp = req.headers.referer;
let referer = "";
@ -139,5 +118,46 @@ 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;
};

View File

@ -1,26 +0,0 @@
import db from "../../inc/sql.mjs";
import cfg from "../../inc/config.mjs";
import f0cklib from "../routeinc/f0cklib.mjs";
export default (router, tpl) => {
router.get(/^\/picdump$/, async (req, res) => {
const dump = await db`
SELECT *
FROM items
WHERE (
to_timestamp(stamp) >= date_trunc('week', CURRENT_TIMESTAMP - interval '1 week') AND
to_timestamp(stamp) < date_trunc('week', CURRENT_TIMESTAMP)
) AND
mime LIKE 'image/%'
ORDER BY stamp DESC
`;
res.reply({
body: tpl.render('picdump', {
dump,
tmp: null
}, req)
});
});
return router;
};

View File

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

View File

@ -5,7 +5,7 @@ import search from "../routeinc/search.mjs";
const _eps = 20;
export default (router, tpl) => {
router.get(/^\/search(\/)?$/, lib.loggedin, async (req, res) => {
router.get(/^\/search(\/)?$/, lib.auth, async (req, res) => {
let ret;
let tag = req.url.qs.tag ?? [];
let page = req.url.qs.page ?? 1;

View File

@ -12,19 +12,5 @@ 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;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,227 +2,128 @@ 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 path from "path";
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*)(\..{3,4})/i,
instagram: /(?:https?:\/\/www\.)?instagram\.com\S*?\/(?:p|reel)\/(\w{11})\/?/im
};
const mediagroupids = new Set();
import fs from "fs";
import { exec as _exec } from "child_process";
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;
export default async bot => {
return [{
name: "parser",
call: regex.all,
call: regex,
active: true,
f: e => {
const links = e.message.match(regex.all)?.filter(link => !link.includes(cfg.main.url.domain)) || [];
let repost;
const links = e.message.match(regex)?.filter(link => !link.includes("f0ck.me")) || [];
if(e.media)
links.push(e.media);
if(links.length === 0)
return false;
if(e.message.match(/\!i(gnore)?\b/))
if(e.message.match(/(!|-)ignore/))
return false;
if(!e.channel.includes("f0ck") && (!e.message.match(/\!f(0ck)?\b/i) && (typeof e.raw.forward_from == 'undefined')))
if(!e.channel.includes("f0ck") && !e.message.match(/(!|-)f0ck/i))
return false;
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))
// return await e.reply(`fuck imgur... seriously`);
if(regex.instagram.test(link))
await e.reply(`insta schminsta`);
// check repost (link)
repost = await queue.checkrepostlink(link);
if(repost)
return await e.reply(`repost motherf0cker (link): ${cfg.main.url.full}/${repost}`);
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}`);
// generate uuid
const uuid = await queue.genuuid();
const uuid = (await db`
select gen_random_uuid() as uuid
`)[0].uuid.substring(0, 8);
const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize);
const maxfilesize = (getLevel(e.user).level > 50 ? cfg.main.maxfilesize * cfg.main.adminmultiplier : cfg.main.maxfilesize) / 1024;
let meta;
// read metadata
let ext;
if(link.match(regex.instagram)) {
// is instagram
try {
// @flummi -> is there a variable for the actual work directory so it doesn't have to be hardcoded?
const meta = JSON.parse((await queue.exec(`yt-dlp ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext;
} catch(err) {
meta = JSON.parse((await exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
}
catch(err) {
//e.reply("[error] f0ck has no bock :(");
//console.error(err);
return;
}
if(!Object.values(cfg.mimes).includes(meta.ext.toLowerCase())) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
// this can be undefined for unsupported mime types, but will be caught in the general mime check below
ext = cfg.mimes[tmphead];
}
}
else if(link.match(regex.imgur)) {
// imghure
ext = link.split('.').pop();
}
else {
// is not instagram
try {
const meta = JSON.parse((await queue.exec(`yt-dlp ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --skip-download --dump-json "${link}"`)).stdout);
ext = meta.ext;
} catch(err) {
const tmphead = (await fetch(link, { method: "HEAD" })).headers["content-type"];
// this can be undefined for unsupported mime types, but will be caught in the general mime check below
ext = cfg.mimes[tmphead];
}
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}`;
const msg = await e.reply(`[charging the f0cker] downloading: ${uuid}`, {
disable_notification: true
});
e.reply(`[charging the f0cker] downloading: ${uuid}`);
// <download data>
// download data
const start = new Date();
let source;
if(link.match(regex.instagram)) {
try {
// add --cookies <path-to-cookies-file> on local instance if you want to avoid getting rate limited
source = (await queue.exec(`yt-dlp ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
} catch(err) {
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, "instagram dl error");
return await e.reply("instagram dl error", err);
}
}
else if(link.match(regex.imgur)) {
// imghure via torsocks
try {
await queue.exec(`torsocks wget ${link} -O ./tmp/${uuid}.${ext}`);
source = `./tmp/${uuid}.${ext}`;
} catch(err) {
console.error('err:', err);
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, err);
return await e.reply('something went wrong lol');
}
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 {
try {
source = (await queue.exec(`yt-dlp ${cfg.main.socks} -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' "${link}" --max-filesize ${maxfilesize / 1024}k --postprocessor-args "ffmpeg:-bitexact" -o "./tmp/${uuid}.%(ext)s" --print after_move:filepath --merge-output-format "mp4"`)).stdout.trim();
} catch(err) {
console.error('err:', err);
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, err);
return await e.reply('something went wrong lol');
}
}
// </download data>
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");
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
}
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");
}
if(source.match(/larger than/))
return e.reply("too large lol");
const end = ~~((new Date() - start) / 1e3);
// filesize check
const size = fs.statSync(source).size;
if(size > maxfilesize) {
await fs.promises.unlink(source).catch(_=>{});
if(e.type == 'tg')
return await e.editMessageText(msg.result.chat.id, msg.result.message_id, `too large lol. (${lib.formatSize(size)} / ${lib.formatSize(maxfilesize)})`);
return await e.reply(`too large lol. (${lib.formatSize(size)} / ${lib.formatSize(maxfilesize)})`);
}
// generate checksum
const checksum = (await exec(`sha256sum ./tmp/${filename}`)).stdout.trim().split(" ")[0];
const size = fs.statSync(`./tmp/${filename}`).size;
// 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");
}
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.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})`);
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}`;
}
// generate checksum
const checksum = (await queue.exec(`sha256sum ${source}`)).stdout.trim().split(" ")[0];
// check repost (checksum)
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}`);
}
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}`);
const filename = path.basename(source);
await fs.promises.copyFile(source, `./public/b/${filename}`);
await fs.promises.unlink(source).catch(_=>{});
// user alias
let username = e.user.nick || e.user.username;
const alias = (await db`
select "user"."user"
from "user_alias"
join "user" on "user".id = user_alias.userid
where lower(user_alias.alias) ilike ${username}
limit 1
`)?.[0]?.user;
if(alias) {
username = alias;
}
await fs.promises.copyFile(`./tmp/${filename}`, `./public/b/${filename}`);
await fs.promises.unlink(`./tmp/${filename}`).catch(_=>{});
await db`
insert into items ${
@ -232,7 +133,7 @@ export default async bot => {
mime: mime,
size: size,
checksum: checksum,
username: username,
username: e.user.nick || e.user.username,
userchannel: e.channel,
usernetwork: e.network,
stamp: ~~(new Date() / 1000),
@ -241,56 +142,113 @@ export default async bot => {
}
`;
const itemid = await queue.getItemID(filename);
const itemid = (await db`
select *
from "items"
where dest = ${filename}
limit 1
`)[0].id;
// generate thumbnail
try {
await queue.genThumbnail(filename, mime, itemid, link);
if(mime.startsWith('video/') || mime == 'image/gif')
await exec(`ffmpegthumbnailer -i./public/b/${filename} -s1024 -o./tmp/${itemid}.png`);
else if(mime.startsWith('image/') && mime != 'image/gif')
await exec(`convert ./public/b/${filename} ./tmp/${itemid}.png`);
else if(mime.startsWith('audio/')) {
if(link.match(/soundcloud/)) {
let cover = (await exec(`yt-dlp -f 'bv*[height<=720]+ba/b[height<=720] / wv*+ba/w' --get-thumbnail "${link}"`)).stdout.trim();
if(!cover.match(/default_avatar/)) {
cover = cover.replace(/-(large|original)\./, '-t500x500.');
try {
await exec(`wget "${cover}" -O ./tmp/${itemid}.jpg`);
const size = (await fs.promises.stat(`./tmp/${itemid}.jpg`)).size;
if(size >= 0) {
await exec(`convert ./tmp/${itemid}.jpg ./tmp/${itemid}.png`);
await exec(`convert ./tmp/${itemid}.jpg ./public/ca/${itemid}.webp`);
}
} catch(err) {}
}
else {
await exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`);
await exec(`convert ./tmp/${itemid}.png ./public/ca/${itemid}.webp`);
}
}
else {
await exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`);
await exec(`convert ./tmp/${itemid}.png ./public/ca/${itemid}.webp`);
}
}
await exec(`convert "./tmp/${itemid}.png" -resize "128x128^" -gravity center -crop 128x128+0+0 +repage ./public/t/${itemid}.webp`);
await fs.promises.unlink(`./tmp/${itemid}.png`).catch(err => {});
await fs.promises.unlink(`./tmp/${itemid}.jpg`).catch(err => {});
} catch(err) {
await queue.exec(`convert ./mugge.png ./public/t/${itemid}.webp`);
await exec(`convert ./mugge.png ./public/t/${itemid}.webp`);
}
let speed = lib.calcSpeed(size, end);
speed = !Number.isFinite(speed) ? "yes" : `${speed.toFixed(2)} Mbit/s`;
// autotagger
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);
/*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 outputmsgirc = `[f0cked] link: ${cfg.main.url.full}/${itemid} | size: ${lib.formatSize(size)} | speed: ${speed}`;
let outputmsgtg = `[f0cked] size: ${lib.formatSize(size)} | speed: ${speed}`;
/*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}`
]);
if(tags?.length > 0) {
const tagstr = tags.join(', ');
outputmsgirc += ` | tags: ${tagstr}`;
outputmsgtg += ` | tags: ${tagstr}`;
}
if(e.type == 'tg') {
await e.deleteMessage(msg.result.chat.id, msg.result.message_id);
await e.reply(outputmsgtg, {
reply_markup: JSON.stringify({
inline_keyboard: [[
{ text: (await lib.hasTag(itemid, 1) ? '✓ ' : '') + 'sfw', callback_data: `b_sfw:${itemid}` },
{ text: (await lib.hasTag(itemid, 2) ? '✓ ' : '') + 'nsfw', callback_data: `b_nsfw:${itemid}` },
{ text: 'tags', callback_data: `b_tags:${itemid}` },
{ text: '❌ delete', callback_data: `b_delete:${itemid}` }
], [
{ text: `open f0ck #${itemid}`, url: `${cfg.main.url.full}/${itemid}` }
]]
})
});
}
else {
await e.reply(outputmsgirc);
}
});
}
}];

View File

@ -12,11 +12,11 @@ export default async bot => {
f: async e => {
const id = +e.args[1];
if(!id)
return await e.reply("lol no");
return e.reply("lol no");
const tags = (await lib.getTags(id)).map(t => t.tag);
if(tags.length === 0)
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(', ')}`);
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(', ')}`);
}
}, {
name: "tags add",
@ -26,7 +26,7 @@ export default async bot => {
f: async e => {
const id = +e.args[1];
if(!id)
return await e.reply("lol no");
return 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 await e.reply("no (new) tags provided");
return 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 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(', ')}`);
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(', ')}`);
}
}, {
name: "tags remove",
@ -84,7 +84,7 @@ export default async bot => {
f: async e => {
const id = +e.args[1];
if(!id)
return await e.reply("lol no");
return 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 await e.reply("no tags provided");
return 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 => {
};
}));
await e.reply(JSON.stringify(res));
e.reply(JSON.stringify(res));
const ntags = (await lib.getTags(id)).map(t => t.tag);
if(ntags.length === 0)
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(', ')}`);
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(', ')}`);
}
}]
};

View File

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

View File

@ -5,11 +5,6 @@ import cuffeo from "cuffeo";
import { promises as fs } from "fs";
import flummpress from "flummpress";
process.on('unhandledRejection', err => {
console.error(err);
throw err;
});
(async () => {
const self = {
_trigger: new Map(),
@ -67,11 +62,10 @@ process.on('unhandledRejection', err => {
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`
select "user".id, "user".login, "user".user, "user".admin, "user_sessions".id as sess_id, "user_options".*
select "user".id, "user".login, "user".user, "user".level, "user_sessions".id as sess_id, "user_options".*
from "user_sessions"
left join "user" on "user".id = "user_sessions".user_id
left join "user_options" on "user_options".user_id = "user_sessions".user_id
@ -102,7 +96,6 @@ process.on('unhandledRejection', err => {
`;
req.session.theme = req.cookies.theme;
req.session.fullscreen = req.cookies.fullscreen;
// update userprofile
await db`
@ -110,14 +103,12 @@ process.on('unhandledRejection', err => {
db({
user_id: +user[0].id,
mode: user[0].mode ?? 0,
theme: req.session.theme ?? 'f0ck',
fullscreen: req.session.fullscreen || 0
}, 'user_id', 'mode', 'theme', 'fullscreen')
theme: req.session.theme ?? 'f0ck'
}, 'user_id', 'mode', 'theme')
}
on conflict ("user_id") do update set
mode = excluded.mode,
theme = excluded.theme,
fullscreen = excluded.fullscreen,
user_id = excluded.user_id
`;
}

View File

@ -1,5 +1,4 @@
@include(snippets/header)
<div id="main">
<div class="about">
<div>
<a href="//f0ck.me/48908"><img src="//f0ck.me/s/img/loool.webp" /></a>
@ -47,5 +46,4 @@
<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)

View File

@ -1,22 +1,10 @@
@include(snippets/header)
<div id="main">
@include(snippets/header_admin)
<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")
<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")
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)

View File

@ -1,5 +1,4 @@
@include(snippets/header)
<div id="main">
@include(snippets/header_admin)
@if(log)
<h1>last {{ log.length }} entries:</h1>
<div class="logwrap">
@ -14,5 +13,4 @@
})();
</script>
@endif
</div>
@include(snippets/footer)

View File

@ -1,5 +1,4 @@
@include(snippets/header)
<div id="main">
@include(snippets/header_admin)
<table class="table" style="width: 100%">
<thead>
<tr>
@ -22,5 +21,4 @@
@endeach
</tbody>
</table>
</div>
@include(snippets/footer)

View File

@ -1,5 +1,4 @@
@include(snippets/header)
<div id="main">
@include(snippets/header_admin)
<table class="table" style="width: 100%">
<thead>
<tr>
@ -28,5 +27,4 @@
@endeach
</tbody>
</table>
</div>
@include(snippets/footer)

165
views/comments.html Normal file
View File

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

View File

@ -1,5 +1,4 @@
@include(snippets/header)
<div id="main">
<div class="container">
<div class="_error_wrapper">
<div class="err">
@ -16,5 +15,4 @@
</div>
</div>
</div>
</div>
@include(snippets/footer)

View File

@ -1,8 +1,6 @@
@include(snippets/header)
<div class="pagewrapper">
<div id="main">
<div class="index-container">
@if(tmp.user)<h2>user: <a href="/user/{{ tmp.user.toLowerCase() }}">{!! tmp.user.toLowerCase() !!}</a>@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif
@if(tmp.user)<h2>user: {!! tmp.user.toLowerCase() !!}@if(tmp.mime) ({{ tmp.mime }}s)@else (all)@endif</h2>@endif
@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)
@ -13,6 +11,4 @@
&#9660;
</div>
</div>
</div>
</div>
@include(snippets/footer)

View File

@ -1,7 +1,5 @@
@include(snippets/header)
<canvas class="hidden-xs" id="bg"></canvas>
<div class="wrapper">
<div id="main">
@include(snippets/header)
<div class="container">
<div class="_204863">
<div class="imageDoor" style="--hover-image: url('/t/{{ item.id }}.webp');">
@ -13,8 +11,7 @@
@if(session)
<div class="gapRight">
<svg class="iconset" id="a_favo"><use href="/s/img/iconset.svg#heart_{{ Object.values(item.favorites).filter(u => u.user == session.user)[0] ? 'solid' : 'regular' }}"></use></svg>
<svg class="iconset" id="a_tfull"><use href="/s/img/iconset.svg#window-{{ fullscreen == 1 ? 'minimize' : 'maximize' }}"></use></svg>
@if(session.admin)<svg class="iconset" id="a_delete"><use href="/s/img/iconset.svg#cross"></use></svg>@endif
<svg class="iconset" id="a_delete"><use href="/s/img/iconset.svg#cross"></use></svg>
</div>
@endif
</div>
@ -83,7 +80,7 @@
@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.admin)&nbsp;<a class="removetag" href="#">&#215;</a>@endif
<a href="/tag/{{ tag.tag }}">{!! tag.tag !!}</a>@if(session)&nbsp;<a class="removetag" href="#">&#215;</a>@endif
</span>
@endeach
@endif
@ -100,7 +97,10 @@
@endif
</span>
</div>
<div class="random">
<a href="/random"></a>
</div>
</div>
</div>
@include(comments)
@include(snippets/footer)
</div>

View File

@ -1,22 +0,0 @@
@include(snippets/header)
<div id="main">
<h2>Picdump (last week)</h2>
<div class="picdump">
@each(dump as line)
<a href="/{{ line.id }}"><img src="/t/{{ line.id }}.webp"></a>
@endeach
</div>
</div>
<style>
.picdump {
width: 100%;
padding: 10px;
}
.picdump > a {
margin-top: 12px;
}
.picdump > a > img {
width: 100%;
}
</style>
@include(snippets/footer)

View File

@ -1,5 +1,4 @@
@include(snippets/header)
<div id="main">
<div class="topf0ckers">
<h3>Top f0ckers of all time <br>- Ranking -</h3>
</div>
@ -11,8 +10,8 @@
@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>@if(list[i].admin)&#11088;&nbsp;@endif<a href="/user/{!! list[i].user !!}">{!! list[i].user !!}</a></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
@ -35,17 +34,7 @@
<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)

View File

@ -1,17 +1,8 @@
@include(snippets/header)
<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>
<h1 style="text-align: center">f0ckgle</h1>
<form action="/search" class="admin-search">
<input type="text" name="tag" value="{!! searchstring || '' !!}" /><button type="submit">🔍</button>
<input type="text" name="tag" value="{!! searchstring || '' !!}" /><button type="submit"><b>f0ck</b></button>
</form>
<div class="results">
@if(result)
@ -43,5 +34,4 @@
@endif
</div>
</div>
</div>
@include(snippets/footer)

View File

@ -1,6 +1,6 @@
@include(snippets/header)
<div class="settings">
<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>
@ -9,23 +9,16 @@
<td>{{ session.id }}</td>
</tr>
<tr>
<td>admin</td>
<td>{{ !!session.admin }}</td>
<td>level</td>
<td>{{ session.level }}/100</td>
</tr>
<tr>
<td>username</td>
<td>{!! session.user !!}</td>
</tr>
<tr>
<td>@if(session.avatar)<a href="/{{ session.avatar }}"><img id="img_avatar" src="/t/{{ session.avatar }}.webp"></a>@endif</td>
<td><input type="text" class="input" name="i_avatar" value="{{ session.avatar }}"></td>
</tr>
<tr>
<td>mail</td>
<td><input type="text" class="input" name="i_mail" placeholder="hashed" disabled></td>
</tr>
<tr>
<td colspan="2"><input type="submit" id="s_avatar" value="save"></td>
<td>avatar</td>
<td><input type="text" class="input" name="i_avatar" value="{{ session.avatar }}" /><input type="submit" id="s_avatar" value="save" /></td>
</tr>
</tbody>
</table>
@ -56,5 +49,4 @@
@endeach
</tbody>
</table>
</div>
@include(snippets/footer)

View File

@ -1,4 +1,5 @@
@if(session)<!--<div style="position: fixed; bottom: 0; z-index: 998; background-color: var(--bg); width: 100%; height: 29px; border-top: 1px solid var(--accent)">{{ JSON.stringify(session) }}</div>-->@endif
</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>

View File

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif" res="@if(typeof fullscreen !== "undefined"){{ fullscreen == 1 ? 'fullscreen' : '' }}@endif">
<html lang="en" theme="@if(typeof theme !== "undefined"){{ theme }}@endif">
<head>
<title>f0ck!</title>
<meta name="description" content="Welcome to the internet"/>
@ -12,3 +12,4 @@
</head>
<body>
@include(snippets/navbar)
<div id="main">

View File

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

View File

@ -5,17 +5,16 @@
<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>@if(session.admin)&#11088;&nbsp;@endif{{ session.user }}</span>
<img src="@if(session.avatar)/t/{{ session.avatar }}.webp@else/s/img/ava/default.png@endif" class="avatar" /><span>{{ session.user }}</span>
</a>
<ul class="dropdown-menu">
<li><a href="/user/{{ session.user.toLowerCase() }}">my profile</a></li>
<li><a href="/admin">adminpanel</a></li>
<li><a href="/user/{{ session.user.toLowerCase() }}/f0cks">my f0cks</a></li>
<li><a href="/user/{{ session.user.toLowerCase() }}/favs">my favs</a></li>
<li><a href="/search">search</a></li>
@if(session.admin)<li><a href="/admin">Admin</a></li>@endif
<li><a href="/about">About</a></li>
<li><a href="/ranking">ranking</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>

View File

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

View File

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

View File

@ -1,49 +1,28 @@
@include(snippets/header)
<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>@if(user.admin)&#11088;&nbsp;@endif{{ 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(count.f0cks)
<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>
<a href="{{ f0cks.link.main }}">show all f0cks</a>
@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(count.favs)
<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
</div>
</div>
</div>
@include(snippets/footer)