switch to bulma
This commit is contained in:
parent
db3977940f
commit
7f871b6b0a
@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[slug]` on the table `Vtuber` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Vtuber_slug_key" ON "Vtuber"("slug");
|
@ -96,7 +96,7 @@ model Vod {
|
||||
model Vtuber {
|
||||
id String @id @default(cuid(2))
|
||||
image String?
|
||||
slug String?
|
||||
slug String? @unique
|
||||
displayName String?
|
||||
chaturbate String?
|
||||
twitter String?
|
||||
|
117
services/our/scripts/2025-08-12-migrate-from-v1.ts
Normal file
117
services/our/scripts/2025-08-12-migrate-from-v1.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { PrismaClient } from '../generated/prisma';
|
||||
import pg from 'pg';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const v1 = new pg.Pool({
|
||||
host: process.env.V1_DB_HOST || 'localhost',
|
||||
port: +(process.env.V1_DB_PORT || '5444'),
|
||||
user: process.env.V1_DB_USER || 'postgres',
|
||||
password: process.env.V1_DB_PASS || 'password',
|
||||
database: process.env.V1_DB_NAME || 'restoredb'
|
||||
});
|
||||
|
||||
// Set this to an existing user ID in v2
|
||||
const DEFAULT_UPLOADER_ID = process.env.DEFAULT_UPLOADER_ID || 'REPLACE_WITH_V2_USER_ID';
|
||||
|
||||
async function migrateVtubers() {
|
||||
console.log('Migrating vtubers...');
|
||||
const res = await v1.query(`SELECT * FROM vtubers`);
|
||||
for (const vt of res.rows) {
|
||||
await prisma.vtuber.create({
|
||||
data: {
|
||||
slug: vt.slug,
|
||||
image: vt.image,
|
||||
displayName: vt.display_name,
|
||||
chaturbate: vt.chaturbate,
|
||||
twitter: vt.twitter,
|
||||
patreon: vt.patreon,
|
||||
twitch: vt.twitch,
|
||||
tiktok: vt.tiktok,
|
||||
onlyfans: vt.onlyfans,
|
||||
youtube: vt.youtube,
|
||||
linktree: vt.linktree,
|
||||
carrd: vt.carrd,
|
||||
fansly: vt.fansly,
|
||||
pornhub: vt.pornhub,
|
||||
discord: vt.discord,
|
||||
reddit: vt.reddit,
|
||||
throne: vt.throne,
|
||||
instagram: vt.instagram,
|
||||
facebook: vt.facebook,
|
||||
merch: vt.merch,
|
||||
description: `${vt.description_1 ?? ''}\n${vt.description_2 ?? ''}`.trim() || null,
|
||||
themeColor: vt.theme_color,
|
||||
uploaderId: DEFAULT_UPLOADER_ID
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(`Migrated ${res.rows.length} vtubers`);
|
||||
}
|
||||
|
||||
async function migrateVods() {
|
||||
console.log('Migrating vods...');
|
||||
const vods = await v1.query(`SELECT * FROM vods`);
|
||||
|
||||
for (const vod of vods.rows) {
|
||||
// Get linked vtubers
|
||||
const vtuberLinks = await v1.query(
|
||||
`SELECT vtuber_id FROM vods_vtuber_links WHERE vod_id = $1`,
|
||||
[vod.id]
|
||||
);
|
||||
|
||||
let vtuberSlugs: string[] = [];
|
||||
if (vtuberLinks.rows.length > 0) {
|
||||
const vtuberRes = await v1.query(
|
||||
`SELECT slug FROM vtubers WHERE id = ANY($1)`,
|
||||
[vtuberLinks.rows.map(r => r.vtuber_id)]
|
||||
);
|
||||
vtuberSlugs = vtuberRes.rows.map(v => v.slug).filter(Boolean);
|
||||
}
|
||||
|
||||
// Get thumbnail
|
||||
const thumbLink = await v1.query(
|
||||
`SELECT b2.cdn_url, b2.url FROM vods_thumbnail_links vtl
|
||||
JOIN b2_files b2 ON vtl.b_2_file_id = b2.id
|
||||
WHERE vtl.vod_id = $1 LIMIT 1`,
|
||||
[vod.id]
|
||||
);
|
||||
|
||||
// Get source video
|
||||
const videoSrcLink = await v1.query(
|
||||
`SELECT b2.cdn_url, b2.url FROM vods_video_src_b_2_links vsl
|
||||
JOIN b2_files b2 ON vsl.b_2_file_id = b2.id
|
||||
WHERE vsl.vod_id = $1 LIMIT 1`,
|
||||
[vod.id]
|
||||
);
|
||||
|
||||
await prisma.vod.create({
|
||||
data: {
|
||||
uploaderId: DEFAULT_UPLOADER_ID,
|
||||
streamDate: vod.date ?? new Date(),
|
||||
notes: vod.note,
|
||||
sourceVideo: videoSrcLink.rows[0]?.cdn_url || videoSrcLink.rows[0]?.url || null,
|
||||
thumbnail: thumbLink.rows[0]?.cdn_url || thumbLink.rows[0]?.url || null,
|
||||
vtubers: {
|
||||
connect: vtuberSlugs.map(slug => ({ slug }))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Migrated ${vods.rows.length} vods`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await migrateVtubers();
|
||||
await migrateVods();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
await v1.end();
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
269
services/our/scripts/2025-08-12-migrate-from-v1.ts.prompt
Normal file
269
services/our/scripts/2025-08-12-migrate-from-v1.ts.prompt
Normal file
@ -0,0 +1,269 @@
|
||||
// 2025-08-12-migrate-from-futureporn.ts
|
||||
//
|
||||
// we are migrating from v1 futureporn.net (a strapi site) to v2 future.porn (a fastify site with prisma)
|
||||
//
|
||||
//
|
||||
// the idea is to read a local backup of the strapi site from a .sql
|
||||
// and use the data contained within to populate the database on the v2 site using prisma.
|
||||
//
|
||||
|
||||
// here's the schema from the v1 site
|
||||
|
||||
```sql
|
||||
CREATE TABLE "public"."b2_files" (
|
||||
"id" SERIAL,
|
||||
"url" VARCHAR(255) NULL,
|
||||
"key" VARCHAR(255) NULL,
|
||||
"upload_id" VARCHAR(255) NULL,
|
||||
"created_at" TIMESTAMP NULL,
|
||||
"updated_at" TIMESTAMP NULL,
|
||||
"created_by_id" INTEGER NULL,
|
||||
"updated_by_id" INTEGER NULL,
|
||||
"cdn_url" VARCHAR(255) NULL,
|
||||
CONSTRAINT "b2_files_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."mux_assets" (
|
||||
"id" SERIAL,
|
||||
"playback_id" VARCHAR(255) NULL,
|
||||
"asset_id" VARCHAR(255) NULL,
|
||||
"created_at" TIMESTAMP NULL,
|
||||
"updated_at" TIMESTAMP NULL,
|
||||
"created_by_id" INTEGER NULL,
|
||||
"updated_by_id" INTEGER NULL,
|
||||
"deletion_queued_at" TIMESTAMP NULL,
|
||||
CONSTRAINT "mux_assets_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."vods" (
|
||||
"id" SERIAL,
|
||||
"video_src_hash" VARCHAR(255) NULL,
|
||||
"video_720_hash" VARCHAR(255) NULL,
|
||||
"video_480_hash" VARCHAR(255) NULL,
|
||||
"video_360_hash" VARCHAR(255) NULL,
|
||||
"video_240_hash" VARCHAR(255) NULL,
|
||||
"thin_hash" VARCHAR(255) NULL,
|
||||
"thicc_hash" VARCHAR(255) NULL,
|
||||
"announce_title" VARCHAR(255) NULL,
|
||||
"announce_url" VARCHAR(255) NULL,
|
||||
"note" TEXT NULL,
|
||||
"date" TIMESTAMP NULL,
|
||||
"spoilers" TEXT NULL,
|
||||
"created_at" TIMESTAMP NULL,
|
||||
"updated_at" TIMESTAMP NULL,
|
||||
"published_at" TIMESTAMP NULL,
|
||||
"created_by_id" INTEGER NULL,
|
||||
"updated_by_id" INTEGER NULL,
|
||||
"title" VARCHAR(255) NULL,
|
||||
"chat_log" TEXT NULL,
|
||||
"date_2" VARCHAR(255) NULL,
|
||||
"cuid" VARCHAR(255) NULL,
|
||||
"archive_status" VARCHAR(255) NULL,
|
||||
CONSTRAINT "vods_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."vods_mux_asset_links" (
|
||||
"id" SERIAL,
|
||||
"vod_id" INTEGER NULL,
|
||||
"mux_asset_id" INTEGER NULL,
|
||||
CONSTRAINT "vods_mux_asset_links_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "vods_mux_asset_links_unique" UNIQUE ("vod_id", "mux_asset_id")
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."vods_thumbnail_links" (
|
||||
"id" SERIAL,
|
||||
"vod_id" INTEGER NULL,
|
||||
"b_2_file_id" INTEGER NULL,
|
||||
CONSTRAINT "vods_thumbnail_links_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "vods_thumbnail_links_unique" UNIQUE ("vod_id", "b_2_file_id")
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."vods_video_src_b_2_links" (
|
||||
"id" SERIAL,
|
||||
"vod_id" INTEGER NULL,
|
||||
"b_2_file_id" INTEGER NULL,
|
||||
CONSTRAINT "vods_video_src_b_2_links_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "vods_video_src_b_2_links_unique" UNIQUE ("vod_id", "b_2_file_id")
|
||||
);
|
||||
CREATE TABLE "public"."vods_vtuber_links" (
|
||||
"id" SERIAL,
|
||||
"vod_id" INTEGER NULL,
|
||||
"vtuber_id" INTEGER NULL,
|
||||
"vod_order" DOUBLE PRECISION NULL,
|
||||
CONSTRAINT "vods_vtuber_links_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "vods_vtuber_links_unique" UNIQUE ("vod_id", "vtuber_id")
|
||||
);
|
||||
|
||||
CREATE TABLE "public"."vtubers" (
|
||||
"id" SERIAL,
|
||||
"chaturbate" VARCHAR(255) NULL,
|
||||
"twitter" VARCHAR(255) NULL,
|
||||
"patreon" VARCHAR(255) NULL,
|
||||
"twitch" VARCHAR(255) NULL,
|
||||
"tiktok" VARCHAR(255) NULL,
|
||||
"onlyfans" VARCHAR(255) NULL,
|
||||
"youtube" VARCHAR(255) NULL,
|
||||
"linktree" VARCHAR(255) NULL,
|
||||
"carrd" VARCHAR(255) NULL,
|
||||
"fansly" VARCHAR(255) NULL,
|
||||
"pornhub" VARCHAR(255) NULL,
|
||||
"discord" VARCHAR(255) NULL,
|
||||
"reddit" VARCHAR(255) NULL,
|
||||
"throne" VARCHAR(255) NULL,
|
||||
"instagram" VARCHAR(255) NULL,
|
||||
"facebook" VARCHAR(255) NULL,
|
||||
"merch" VARCHAR(255) NULL,
|
||||
"slug" VARCHAR(255) NULL,
|
||||
"image" VARCHAR(255) NULL,
|
||||
"display_name" VARCHAR(255) NULL,
|
||||
"description_1" TEXT NULL,
|
||||
"description_2" TEXT NULL,
|
||||
"created_at" TIMESTAMP NULL,
|
||||
"updated_at" TIMESTAMP NULL,
|
||||
"published_at" TIMESTAMP NULL,
|
||||
"created_by_id" INTEGER NULL,
|
||||
"updated_by_id" INTEGER NULL,
|
||||
"theme_color" VARCHAR(255) NULL,
|
||||
"image_blur" VARCHAR(255) NULL,
|
||||
CONSTRAINT "vtubers_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
|
||||
// here's the schema from the v2 site
|
||||
|
||||
```prisma
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../generated/prisma"
|
||||
binaryTargets = ["native", "debian-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
patreonId String @unique
|
||||
patreonFullName String?
|
||||
imageUrl String?
|
||||
roles Role[]
|
||||
vods Vod[]
|
||||
Vtuber Vtuber[]
|
||||
}
|
||||
|
||||
enum RoleName {
|
||||
user
|
||||
supporterTier1
|
||||
supporterTier2
|
||||
supporterTier3
|
||||
supporterTier4
|
||||
supporterTier5
|
||||
supporterTier6
|
||||
moderator
|
||||
admin
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @default(cuid(2))
|
||||
name String @unique
|
||||
users User[]
|
||||
}
|
||||
|
||||
model RateLimiterFlexible {
|
||||
key String @id
|
||||
points Int
|
||||
expire DateTime?
|
||||
}
|
||||
|
||||
model Stream {
|
||||
id String @id @default(cuid(2))
|
||||
date DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
announcementUrl String?
|
||||
|
||||
vods Vod[]
|
||||
|
||||
@@map("stream_entity")
|
||||
}
|
||||
|
||||
enum VodStatus {
|
||||
ordering
|
||||
pending
|
||||
approved
|
||||
rejected
|
||||
processing
|
||||
processed
|
||||
}
|
||||
|
||||
model Vod {
|
||||
id String @id @default(cuid(2))
|
||||
streamId String?
|
||||
stream Stream? @relation(fields: [streamId], references: [id])
|
||||
uploaderId String // previously in Upload
|
||||
uploader User @relation(fields: [uploaderId], references: [id])
|
||||
|
||||
streamDate DateTime
|
||||
notes String?
|
||||
segmentKeys Json?
|
||||
sourceVideo String?
|
||||
hlsPlaylist String?
|
||||
thumbnail String?
|
||||
asrVttKey String?
|
||||
slvttSheetKeys Json?
|
||||
slvttVTTKey String?
|
||||
magnetLink String?
|
||||
|
||||
status VodStatus @default(pending)
|
||||
sha256sum String?
|
||||
cidv1 String?
|
||||
funscript String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
vtubers Vtuber[]
|
||||
}
|
||||
|
||||
model Vtuber {
|
||||
id String @id @default(cuid(2))
|
||||
image String?
|
||||
slug String?
|
||||
displayName String?
|
||||
chaturbate String?
|
||||
twitter String?
|
||||
patreon String?
|
||||
twitch String?
|
||||
tiktok String?
|
||||
onlyfans String?
|
||||
youtube String?
|
||||
linktree String?
|
||||
carrd String?
|
||||
fansly String?
|
||||
pornhub String?
|
||||
discord String?
|
||||
reddit String?
|
||||
throne String?
|
||||
instagram String?
|
||||
facebook String?
|
||||
merch String?
|
||||
description String?
|
||||
themeColor String?
|
||||
|
||||
fanslyId String?
|
||||
chaturbateId String?
|
||||
twitterId String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
vods Vod[]
|
||||
uploaderId String
|
||||
uploader User @relation(fields: [uploaderId], references: [id])
|
||||
}
|
||||
|
||||
```
|
22
services/our/scripts/README.md
Normal file
22
services/our/scripts/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
# scripts
|
||||
|
||||
This directory is for **DATA MIGRATIONS ONLY**.
|
||||
|
||||
Data migrations are **not** the same as schema migrations.
|
||||
|
||||
- **Schema migrations** (handled by Prisma Migrate) change the database structure — adding/removing columns, altering data types, creating new tables, etc.
|
||||
- **Data migrations** modify the *contents* of the database to align with business logic changes, clean up old data, or backfill new fields without altering the schema.
|
||||
|
||||
Examples of data migrations:
|
||||
- Populating a new column with default or computed values.
|
||||
- Normalizing inconsistent text formats (e.g., fixing casing, trimming whitespace).
|
||||
- Converting old enum/string values to new ones.
|
||||
- Moving data between tables after a structural change has already been applied.
|
||||
|
||||
Guidelines:
|
||||
1. **Idempotent when possible** — running the script twice should not break data integrity.
|
||||
2. **Version-controlled** — keep a clear history of changes.
|
||||
3. **Document assumptions** — include comments explaining why the migration is needed and what it affects.
|
||||
4. **Run after schema changes** — if both schema and data changes are required, update the schema first.
|
||||
|
||||
> ⚠️ Always back up your database before running data migrations in production.
|
@ -181,11 +181,11 @@ export function buildApp() {
|
||||
})
|
||||
|
||||
// @todo we are going to need to use redis or similar https://github.com/fastify/fastify-caching
|
||||
app.register(fastifyCaching, {
|
||||
expiresIn: 300,
|
||||
privacy: 'private',
|
||||
serverExpiresIn: 300,
|
||||
})
|
||||
// app.register(fastifyCaching, {
|
||||
// expiresIn: 300,
|
||||
// privacy: 'private',
|
||||
// serverExpiresIn: 300,
|
||||
// })
|
||||
|
||||
app.register(graphileWorker)
|
||||
app.register(prismaPlugin)
|
||||
|
@ -29,9 +29,12 @@ export default async function vodsRoutes(
|
||||
fastify: FastifyInstance,
|
||||
): Promise<void> {
|
||||
fastify.get('/vods', async function (request, reply) {
|
||||
const { format } = request.query as { format: 'rss' | 'html' };
|
||||
|
||||
const userId = request.session.get('userId');
|
||||
|
||||
|
||||
|
||||
let user = null
|
||||
if (userId !== undefined) {
|
||||
user = await prisma.user.findUnique({
|
||||
@ -65,6 +68,34 @@ export default async function vodsRoutes(
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
// RSS branch
|
||||
if (format === 'rss') {
|
||||
const items = vods.map(vod => {
|
||||
const vtuberNames = vod.vtubers.map(v => v.displayName || v.slug).join(', ');
|
||||
return {
|
||||
title: `${vtuberNames} stream on ${vod.streamDate.toDateString()}`,
|
||||
link: `${env.ORIGIN}/vods/${vod.id}`,
|
||||
guid: `${env.ORIGIN}/vods/${vod.id}`,
|
||||
pubDate: vod.streamDate.toUTCString(),
|
||||
description: vod.notes
|
||||
? `${vod.notes}\n\nFeaturing: ${vtuberNames}`
|
||||
: `Featuring: ${vtuberNames}`,
|
||||
};
|
||||
});
|
||||
|
||||
return reply
|
||||
.type('application/rss+xml')
|
||||
.view('/feed.hbs', {
|
||||
title: 'future.porn - VODs',
|
||||
description: 'All VODs and their featured VTubers',
|
||||
link: `${env.ORIGIN}/vods`,
|
||||
items,
|
||||
}, { layout: 'layouts/xml.hbs' });
|
||||
}
|
||||
// rss branch
|
||||
|
||||
return reply.viewAsync('vods.hbs', {
|
||||
user,
|
||||
vods,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"
|
||||
import { constants } from "../config/constants";
|
||||
|
||||
import { PrismaClient, Vtuber } from '../../generated/prisma'
|
||||
@ -12,12 +12,14 @@ const prisma = new PrismaClient().$extends(withAccelerate())
|
||||
const hexColorRegex = /^#([0-9a-fA-F]{6})$/;
|
||||
|
||||
|
||||
|
||||
export default async function vtubersRoutes(
|
||||
fastify: FastifyInstance,
|
||||
): Promise<void> {
|
||||
|
||||
|
||||
fastify.get('/vtubers', async function (request, reply) {
|
||||
|
||||
const vtuberIndexHandler = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
const userId = request.session.get('userId')
|
||||
console.log(`userId=${userId}`)
|
||||
|
||||
@ -39,10 +41,18 @@ export default async function vtubersRoutes(
|
||||
vtubers,
|
||||
site: constants.site
|
||||
}, { layout: 'layouts/main.hbs' });
|
||||
});
|
||||
};
|
||||
|
||||
['/vtubers', '/vt'].forEach(path => {
|
||||
fastify.route({
|
||||
method: ['GET'], // you could define multiple methods
|
||||
url: path,
|
||||
handler: vtuberIndexHandler
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
fastify.get('/vtubers/new', async function (request, reply) {
|
||||
fastify.get('/vt/new', async function (request, reply) {
|
||||
const userId = request.session.get('userId');
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
@ -64,7 +74,7 @@ export default async function vtubersRoutes(
|
||||
}, { layout: 'layouts/main.hbs' })
|
||||
})
|
||||
|
||||
fastify.post('/vtubers/create', async function (request, reply) {
|
||||
fastify.post('/vt/create', async function (request, reply) {
|
||||
const {
|
||||
displayName,
|
||||
themeColor,
|
||||
@ -123,7 +133,7 @@ export default async function vtubersRoutes(
|
||||
|
||||
if (!uppyResult) {
|
||||
request.flash('error', '❌ missing uppyResult')
|
||||
reply.redirect('/vtubers/new')
|
||||
reply.redirect('/vt/new')
|
||||
|
||||
// return reply.status(400).view('vtubers/new.hbs', {
|
||||
// message: '❌ Missing uppyResult',
|
||||
@ -135,7 +145,7 @@ export default async function vtubersRoutes(
|
||||
|
||||
if (!themeColor) {
|
||||
request.flash('error', '❌ Missing themeColor')
|
||||
reply.redirect('/vtubers/new')
|
||||
reply.redirect('/vt/new')
|
||||
// return reply.status(400).view('vtubers/new.hbs', {
|
||||
// message: '❌ Missing themeColor',
|
||||
// vtubers,
|
||||
@ -240,14 +250,15 @@ export default async function vtubersRoutes(
|
||||
|
||||
|
||||
// successful upload
|
||||
request.flash('info', `✅ Successfully created vtuber <a href="/vtubers/${vtuber.id}">${vtuber.id}</a>`)
|
||||
return reply.redirect('/vtubers/new')
|
||||
request.flash('info', `✅ Successfully created vtuber <a href="/vt/${vtuber.id}">${vtuber.id}</a>`)
|
||||
return reply.redirect('/vt/new')
|
||||
|
||||
|
||||
})
|
||||
|
||||
fastify.get('/vtubers/:idOrSlug', async function (request, reply) {
|
||||
fastify.get('/vt/:idOrSlug', async function (request, reply) {
|
||||
const { idOrSlug } = request.params as { idOrSlug: string };
|
||||
|
||||
const userId = request.session.get('userId');
|
||||
|
||||
|
||||
@ -257,7 +268,7 @@ export default async function vtubersRoutes(
|
||||
});
|
||||
|
||||
if (!idOrSlug) {
|
||||
return reply.status(400).send({ error: 'Invalid VTuber identifier' });
|
||||
return reply.redirect('/vt')
|
||||
}
|
||||
|
||||
// Determine if it's a CUID (starts with "c" and length of 24)
|
||||
@ -273,7 +284,11 @@ export default async function vtubersRoutes(
|
||||
],
|
||||
},
|
||||
include: {
|
||||
vods: true,
|
||||
vods: {
|
||||
orderBy: {
|
||||
streamDate: 'desc'
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -289,14 +304,21 @@ export default async function vtubersRoutes(
|
||||
});
|
||||
|
||||
|
||||
fastify.get('/vtubers/:idOrSlug/rss', async function (request, reply) {
|
||||
fastify.get('/vt/:idOrSlug/vods', async function (request, reply) {
|
||||
const { idOrSlug } = request.params as { idOrSlug: string };
|
||||
const { format } = request.query as { format: 'rss' | 'html' };
|
||||
|
||||
|
||||
|
||||
if (!idOrSlug) {
|
||||
return reply.status(400).send({ error: 'Invalid VTuber identifier' });
|
||||
return reply.status(400).send({ error: 'Invalid VTuber identifier ~' });
|
||||
}
|
||||
|
||||
if (format !== 'rss') {
|
||||
return reply.status(404).send({ error: 'the only available format for this endpoint is rss' });
|
||||
}
|
||||
|
||||
|
||||
// Determine if it's a CUID (starts with "c" and length of 24)
|
||||
const isCuid = /^c[a-z0-9]{23}$/i.test(idOrSlug);
|
||||
|
||||
@ -337,7 +359,7 @@ export default async function vtubersRoutes(
|
||||
.view('/feed.hbs', {
|
||||
title,
|
||||
description: vtuber.description || title,
|
||||
link: `${env.ORIGIN}/vtubers/${vtuber.slug || vtuber.id}`,
|
||||
link: `${env.ORIGIN}/vt/${vtuber.slug || vtuber.id}`,
|
||||
items,
|
||||
}, { layout: 'layouts/xml.hbs' });
|
||||
});
|
||||
|
@ -1,22 +1,21 @@
|
||||
{{#> main}}
|
||||
<!-- Header -->
|
||||
<header class="container">
|
||||
<hgroup>
|
||||
<h1>{{ site.title }}</h1>
|
||||
<p>{{ site.description }}</p>
|
||||
</hgroup>
|
||||
|
||||
<header>
|
||||
{{> navbar}}
|
||||
</header>
|
||||
<!-- ./ Header -->
|
||||
|
||||
<!-- Main -->
|
||||
<main class="container pico">
|
||||
<main class="container">
|
||||
|
||||
<!-- Latest Vods -->
|
||||
<section id="tables">
|
||||
<h2>Latest VODs</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<section class="hero">
|
||||
<div class="hero-body">
|
||||
<h1 class="title is-1">{{ site.title }}</h1>
|
||||
<p class="subtitle">{{ site.description }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2 class="title is-2">Latest VODs</h2>
|
||||
<table class="table striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
@ -40,16 +39,12 @@
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ./ Latest Vods -->
|
||||
|
||||
|
||||
<!-- Latest VTubers -->
|
||||
<section id="tables">
|
||||
<h2>Latest VTubers</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<section class="section">
|
||||
<h2 class="title is-2">Latest VTubers</h2>
|
||||
<table class="table striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
@ -61,7 +56,7 @@
|
||||
<tbody>
|
||||
{{#each vtubers}}
|
||||
<tr>
|
||||
<td><a href="/vtubers/{{this.id}}">{{this.id}}</a></td>
|
||||
<td><a href="/vt/{{this.id}}">{{this.id}}</a></td>
|
||||
<td>
|
||||
{{this.displayName}}
|
||||
</td>
|
||||
@ -70,17 +65,14 @@
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ./ Latest Streams -->
|
||||
|
||||
|
||||
|
||||
<!-- Latest Streams -->
|
||||
<section id="tables">
|
||||
<h2>Latest Streams</h2>
|
||||
{{!--
|
||||
<section>
|
||||
<h2 class="title is-2">Latest Streams</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<table class="title striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">🚧 ID</th>
|
||||
@ -121,13 +113,11 @@
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- ./ Latest Streams -->
|
||||
|
||||
<!-- Latest Tags -->
|
||||
<section id="tables">
|
||||
<h2>Latest Tags</h2>
|
||||
<section>
|
||||
<h2 class="title is-2">Latest Tags</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<table class="title striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">🚧 ID</th>
|
||||
@ -166,8 +156,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ./ Latest Tags -->
|
||||
</section> --}}
|
||||
|
||||
{{>footer}}
|
||||
</main>
|
||||
|
@ -11,8 +11,9 @@
|
||||
<meta name="twitter:creator" content="@cj_clippy" />
|
||||
<meta name="twitter:title" content="Futureporn.net" />
|
||||
<meta name="twitter:description" content="{{site.description}}" />
|
||||
<link rel="stylesheet" href="/css/pico.conditional.pink.min.css">
|
||||
<style>
|
||||
{{!-- <link rel="stylesheet" href="/css/pico.conditional.pink.min.css"> --}}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
||||
{{!-- <style>
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -33,14 +34,14 @@
|
||||
img.avatar {
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
</style> --}}
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
{{{body}}}
|
||||
|
||||
<script src="/js/htmx.min.js"></script>
|
||||
|
||||
<script>
|
||||
@ -56,6 +57,31 @@
|
||||
</script>
|
||||
<script src="/js/alpine/cdn.min.js"></script>
|
||||
|
||||
{{!-- JS for Bulma's navbar --}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Get all "navbar-burger" elements
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
|
||||
// Get the target from the "data-target" attribute
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
|
@ -1,45 +1,55 @@
|
||||
<div class="pico">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/">
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<div class="logo">
|
||||
<img class="logo" src="/favicon.ico">
|
||||
<img class="logo" src="/favicon.ico" alt="Logo">
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li><a href="/vods">VODs</a></li>
|
||||
<li><a href="/streams"><s>🚧 Streams</s></a></li>
|
||||
<li><a href="/vtubers">VTubers</a></li>
|
||||
<li><a href="/perks">Perks</a></li>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navMenu">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/vods">VODs</a>
|
||||
{{!-- <a class="navbar-item" href="/streams"><s>🚧 Streams</s></a> @todo --}}
|
||||
<a class="navbar-item" href="/vt">VTubers</a>
|
||||
<a class="navbar-item" href="/perks">Perks</a>
|
||||
|
||||
{{#if (hasRole "supporterTier1" "moderator" "admin" user)}}
|
||||
<li><a href="/uploads">Uploads</a></li>
|
||||
<a class="navbar-item" href="/uploads">Uploads</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if (hasRole "supporterTier1" "supporterTier2" "supporterTier3" "supporterTier4" "supporterTier5" "supporterTier6" "moderator" "admin" user)}}
|
||||
<li>
|
||||
<a href="/upload">
|
||||
<a class="navbar-item" href="/upload">
|
||||
<span class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M6 20q-.825 0-1.412-.587T4 18v-2q0-.425.288-.712T5 15t.713.288T6 16v2h12v-2q0-.425.288-.712T19 15t.713.288T20 16v2q0 .825-.587 1.413T18 20zm5-12.15L9.125 9.725q-.3.3-.712.288T7.7 9.7q-.275-.3-.288-.7t.288-.7l3.6-3.6q.15-.15.325-.212T12 4.425t.375.063t.325.212l3.6 3.6q.3.3.288.7t-.288.7q-.3.3-.712.313t-.713-.288L13 7.85V15q0 .425-.288.713T12 16t-.712-.288T11 15z" />
|
||||
</svg>
|
||||
</span>
|
||||
Upload
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/vtubers/new">
|
||||
|
||||
<a class="navbar-item" href="/vt/new">
|
||||
<span class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z" />
|
||||
</svg>
|
||||
</span>
|
||||
Add VTuber
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if user.id}}
|
||||
<li><a href="/profile">Profile</a></li>
|
||||
{{else}}
|
||||
<li><a href="/auth/patreon">Log in via Patreon</a></li>
|
||||
{{/if}}
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
{{#if user.id}}
|
||||
<a class="navbar-item" href="/profile">Profile</a>
|
||||
{{else}}
|
||||
<a class="navbar-item" href="/auth/patreon">Log in via Patreon</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
@ -1,18 +1,19 @@
|
||||
{{#> main}}
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar}}
|
||||
</header>
|
||||
|
||||
<main class="container pico">
|
||||
<main class="container">
|
||||
<section id="perks">
|
||||
<h2>Perks</h2>
|
||||
<h2 class="title is-1">Perks</h2>
|
||||
|
||||
<p>future.porn is free to use, but to keep the site running we need your help! In return, we offer extra perks
|
||||
<p class="subtitle">We need your help to keep the site running! In return, we offer
|
||||
extra perks
|
||||
to supporters.</p>
|
||||
|
||||
|
||||
<table>
|
||||
<table class="table striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
@ -24,78 +25,90 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>View</td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RSS</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Torrent Downloads</td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CDN Downloads</td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>❌</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ad-Free</td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>❌</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload</td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>❌</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><abbr title="Sex toy playback syncronization">Funscripts</abbr></td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>❌</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Closed Captions</td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>❌</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
{{!--
|
||||
@todo add these things
|
||||
<tr>
|
||||
<td><abbr title="Closed Captions">CC</abbr> Search</td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✔️</td>
|
||||
<td>✅</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSV Export</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SQL Export</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>vibeui PyTorch Model</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>✔️</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
--}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<article>
|
||||
<article class="notification">
|
||||
<p>Become a patron at <a target="_blank" href="https://patreon.com/CJ_Clippy">patreon.com/CJ_Clippy</a></p>
|
||||
</article>
|
||||
|
||||
|
@ -1,21 +1,23 @@
|
||||
{{#> main}}
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar}}
|
||||
</header>
|
||||
|
||||
<main class="container pico">
|
||||
<main class="container">
|
||||
|
||||
|
||||
<section id="tables">
|
||||
<h2>Profile</h2>
|
||||
<p><strong>ID:</strong> <small>{{user.id}}</small></p>
|
||||
<section class="section">
|
||||
<h2 class="title is-1">Profile</h2>
|
||||
<div class="box">
|
||||
<p><strong>Identicon:</strong> {{{identicon user.id 48}}}</p>
|
||||
<p><strong>ID:</strong> <small>{{user.id}}</small></p>
|
||||
<p><strong>Roles:</strong> {{#each user.roles}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}}
|
||||
</p>
|
||||
<p><strong>Perks:</strong> @todo
|
||||
{{!-- @todo {{#each user.perks}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}} --}}
|
||||
</p>
|
||||
</div>
|
||||
{{!-- <p><strong>Perks:</strong> @todo
|
||||
@todo {{#each user.perks}}{{this.name}}{{#unless @last}}, {{/unless}}{{/each}}
|
||||
</p> --}}
|
||||
</section>
|
||||
|
||||
<a href="/logout">Logout</a>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<!-- Main -->
|
||||
<main class="container">
|
||||
|
||||
<section id="tables">
|
||||
<section>
|
||||
<h1>
|
||||
{{#each vod.vtubers}}
|
||||
{{this.displayName}}{{#unless @last}}, {{/unless}}
|
||||
|
@ -4,25 +4,25 @@
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar }}
|
||||
</header>
|
||||
|
||||
|
||||
<main class="container pico">
|
||||
<main class="main">
|
||||
|
||||
{{#each info}}
|
||||
<article id="article">
|
||||
<section id="article">
|
||||
<p>
|
||||
{{{this}}}
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
{{/each}}
|
||||
|
||||
<section id="tables">
|
||||
<h2>Uploads</h2>
|
||||
<section class="section">
|
||||
<h2 class="title is-1">Uploads</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<table class="table striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Upload ID</th>
|
||||
|
@ -4,52 +4,71 @@
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar }}
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<h1>Upload Lewdtuber VODs here.</h1>
|
||||
<h1 class="title is-1">Upload</h1>
|
||||
<p class="subtitle">Upload Lewdtuber VODs here.</p>
|
||||
|
||||
<article>
|
||||
<article class="notification">
|
||||
<p><strong>Instructions:</strong> Enter the metadata first. Vtuber name and original stream date is required. Then
|
||||
upload the vod
|
||||
segment(s). Wait for the vod segment(s) to fully upload before pressing the submit button.</p>
|
||||
</article>
|
||||
|
||||
<form id="details" method="POST" action="/upload">
|
||||
<div class="pico">
|
||||
<!-- VTuber select input -->
|
||||
<label for="vtuberIds">VTuber(s)</label>
|
||||
<div class="field">
|
||||
<label class="label" for="vtuberIds">VTuber(s)</label>
|
||||
<div class="control">
|
||||
<div class="select is-multiple is-fullwidth">
|
||||
<select id="vtuberIds" name="vtuberIds" multiple required>
|
||||
{{#each vtubers}}
|
||||
<option value="{{this.id}}">{{this.displayName}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Original stream date -->
|
||||
<label for="streamDate">Original Stream Datetime</label>
|
||||
<input type="datetime-local" id="streamDate" name="streamDate" required />
|
||||
<div class="field">
|
||||
<label class="label" for="streamDate">Original Stream Datetime</label>
|
||||
<div class="control">
|
||||
<input class="input" type="datetime-local" id="streamDate" name="streamDate" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes textarea -->
|
||||
<label for="notes">Notes (optional)</label>
|
||||
<textarea id="notes" name="notes" rows="4" placeholder="Any notes about the upload…"></textarea>
|
||||
<div class="field">
|
||||
<label class="label" for="notes">Notes (optional)</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" id="notes" name="notes" rows="4"
|
||||
placeholder="Any notes about the upload…"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="uploader"></div>
|
||||
<h3></h3>
|
||||
<!-- File uploader -->
|
||||
<div class="field mb-5" id="uploader"></div>
|
||||
|
||||
<!-- Message display -->
|
||||
{{#if message}}
|
||||
<p class="help is-info" id="messages" hx-swap-oob="true">{{message}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if message}}<p id="messages" hx-swap-oob="true">{{message}}</p>{{/if}}
|
||||
<div class="pico">
|
||||
<button>Submit</button>
|
||||
<!-- Submit button -->
|
||||
<div class="field mb-5">
|
||||
<div class="control">
|
||||
<button class="button is-primary">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
{{> footer }}
|
||||
</main>
|
||||
|
||||
@ -60,6 +79,7 @@
|
||||
import ImageEditor from 'https://esm.sh/@uppy/image-editor@3.3.3'
|
||||
import AwsS3 from 'https://esm.sh/@uppy/aws-s3@4.2.3'
|
||||
import Form from 'https://esm.sh/@uppy/form@4.1.1'
|
||||
//import Link from 'https://esm.sh/@uppy/url@4.3.2' // requires companion instance 💢
|
||||
|
||||
|
||||
window.vtuberOptions = {{ safeJson vtubers }}
|
||||
@ -93,6 +113,7 @@
|
||||
// 'Authorization': `Bearer @todo`
|
||||
// }
|
||||
//})
|
||||
// .use(Link) requires companion instance 💢
|
||||
.use(AwsS3, {
|
||||
id: 's3Plugin',
|
||||
endpoint: '/',
|
||||
|
@ -15,7 +15,7 @@
|
||||
</style>
|
||||
|
||||
|
||||
<main class="container pico">
|
||||
<main class="container">
|
||||
|
||||
<section>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{#> main}}
|
||||
<!-- Header -->
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/video.js@8.23.3/dist/video-js.min.css">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/videojs-vtt-thumbnails@0.0.13/dist/videojs-vtt-thumbnails.min.css">
|
||||
@ -35,14 +35,12 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar}}
|
||||
|
||||
</header>
|
||||
<!-- ./ Header -->
|
||||
|
||||
|
||||
<main class="container" x-data="{}">
|
||||
<div class="section" x-data="{}">
|
||||
<section>
|
||||
{{#if vod.hlsPlaylist}}
|
||||
<div class="video-container">
|
||||
@ -63,15 +61,15 @@
|
||||
{{else}}
|
||||
<video id="player" class="hidden"></video>
|
||||
|
||||
<div class="pico">
|
||||
<article>
|
||||
<div class="section">
|
||||
<div class="notification">
|
||||
|
||||
{{icon "processing" 24}} HTTP Live Streaming is processing.
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
<section id="tables" class="pico">
|
||||
<div class="container">
|
||||
|
||||
{{!--
|
||||
<h2>Details</h2>
|
||||
@ -98,32 +96,38 @@
|
||||
type="application/x-mpegURL">
|
||||
</video> --}}
|
||||
|
||||
<h1>
|
||||
<nav class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
{{#each vod.vtubers}}
|
||||
<a href="/vtubers/{{this.slug}}">{{this.displayName}}</a>{{#unless @last}}, {{/unless}}
|
||||
<a href="/vt/{{this.slug}}">{{this.displayName}}</a>{{#unless @last}}, {{/unless}}
|
||||
{{/each}}
|
||||
- {{formatDate vod.streamDate}}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="level-item">{{formatDate vod.streamDate}}</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="overflow-auto">
|
||||
|
||||
{{#if vod.notes}}
|
||||
<h2>Notes</h2>
|
||||
<article>
|
||||
<p class="breaklines">{{vod.notes}}</p>
|
||||
<footer>
|
||||
— {{{identicon vod.uploader.id 24}}} ❦
|
||||
<h2 id="notes" class="title is-4 mb-5">Notes</h2>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="content breaklines">{{vod.notes}}</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<div class="card-footer-item">— {{{identicon vod.uploader.id 24}}} ❦</div>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
|
||||
|
||||
<h2>Downloads</h2>
|
||||
<h2 class="title is-4">Downloads</h2>
|
||||
|
||||
|
||||
<h3>VOD</h3>
|
||||
<article>
|
||||
<h3 class="title is-5">VOD</h3>
|
||||
<div class="box">
|
||||
{{#if vod.sourceVideo}}
|
||||
|
||||
{{#if (hasRole "supporterTier1" user)}}
|
||||
@ -133,7 +137,7 @@
|
||||
target="_blank">{{icon "download" 24}} Download</a>
|
||||
</p>
|
||||
{{else}}
|
||||
<p>
|
||||
<p class="mb-3">
|
||||
<a href="/perks">{{icon "patreon" 24}}</a>
|
||||
<del>
|
||||
CDN Download
|
||||
@ -145,22 +149,22 @@
|
||||
{{#if vod.cidv1}}
|
||||
<p><b>IPFS cidv1</b> {{vod.cidv1}}</p>
|
||||
{{else}}
|
||||
<article>
|
||||
<article class="notification">
|
||||
IPFS CID is processing.
|
||||
</article>
|
||||
{{/if}}
|
||||
{{#if vod.magnetLink}}
|
||||
<p><a href="{{vod.magnetLink}}">{{icon "magnet" 24}} Magnet Link</a></p>
|
||||
{{else}}
|
||||
<article>
|
||||
<article class="notification">
|
||||
Magnet Link is processing.
|
||||
</article>
|
||||
{{/if}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
<h4>Raw Segments</h4>
|
||||
<article>
|
||||
<h4 class="title is-5">Raw Segments</h4>
|
||||
<div class="mb-5">
|
||||
{{#if vod.segmentKeys}}
|
||||
{{#if (hasRole "supporterTier1" user)}}
|
||||
<ul>
|
||||
@ -181,28 +185,28 @@
|
||||
{{/if}}
|
||||
{{else}}
|
||||
|
||||
<article>
|
||||
<div class="notification">
|
||||
This VOD has no file segments.
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{{/if}} {{!-- end of raw segments --}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{{else}}
|
||||
<article>
|
||||
<div class="notification">
|
||||
Video Source is processing.
|
||||
</article>
|
||||
</div>
|
||||
{{/if}} {{!-- end of vod.sourceVideo --}}
|
||||
|
||||
|
||||
|
||||
|
||||
<h4>HLS Playlist</h4>
|
||||
<article>
|
||||
<h4 class="title is-5">HLS Playlist</h4>
|
||||
<div class="mb-5">
|
||||
{{#if vod.hlsPlaylist}}
|
||||
{{#if (hasRole "supporterTier1" user)}}
|
||||
<a href="{{signedHlsUrl vod.hlsPlaylist}}">{{signedHlsUrl vod.hlsPlaylist}}</a>
|
||||
@ -215,26 +219,28 @@
|
||||
</p>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<article>
|
||||
<div class="notification">
|
||||
HLS Playlist is processing.
|
||||
</article>
|
||||
</div>
|
||||
{{/if}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<h4>Thumbnail Image</h4>
|
||||
<div class="mb-5">
|
||||
<h4 class="title is-5">Thumbnail Image</h4>
|
||||
{{#if vod.thumbnail}}
|
||||
<img src="{{getCdnUrl vod.thumbnail}}" alt="{{vtuber.displayName}} thumbnail">
|
||||
<div class="mx-5"></div>
|
||||
{{else}}
|
||||
<article>
|
||||
<div class="notification">
|
||||
Thumbnail is processing.
|
||||
</article>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h4>Funscripts (sex toy sync)</h4>
|
||||
<article>
|
||||
<div class="mb-5">
|
||||
<h4 class="title is-5">Funscripts (sex toy sync)</h4>
|
||||
{{#if vod.funscript}}
|
||||
|
||||
{{#if (hasRole "supporterTier1" user)}}
|
||||
@ -259,13 +265,14 @@
|
||||
{{/if}}
|
||||
<div class="mx-5"></div>
|
||||
{{else}}
|
||||
<article>
|
||||
<div class="notification">
|
||||
Funscript file is processing.
|
||||
</article>
|
||||
</div>
|
||||
{{/if}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<h4>Closed Captions / Subtitles</h4>
|
||||
<div class="mb-5">
|
||||
<h4 class="title is-5">Closed Captions / Subtitles</h4>
|
||||
<article>
|
||||
|
||||
{{#if vod.asrVttKey}}
|
||||
@ -282,12 +289,13 @@
|
||||
</p>
|
||||
{{/if}}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
{{else}}
|
||||
<article>
|
||||
<div class="notification">
|
||||
Closed captions are processing.
|
||||
</article>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -295,23 +303,30 @@
|
||||
|
||||
|
||||
{{#if (isModerator user)}}
|
||||
<article>
|
||||
<h2>Moderator section</h2>
|
||||
<article class="mb-5">
|
||||
<h2 class="title is-3">Moderator section</h2>
|
||||
<div class="box">
|
||||
|
||||
<h3>Storyboard Images</h3>
|
||||
<div class="mb-5">
|
||||
<h3 class="title is-4">Storyboard Images</h3>
|
||||
{{#if vod.slvttVTTKey}}
|
||||
<a id="slvtt" data-url="{{getCdnUrl vod.slvttVTTKey}}" data-file-name="{{basename vod.slvttVTTKey}}"
|
||||
x-on:click.prevent="download($el.dataset.url, $el.dataset.fileName)" href="{{getCdnUrl vod.slvttVTTKey}}"
|
||||
alt="slvttVTTKey">{{icon "download" 24}} slvttVTTKey</a>
|
||||
{{else}}
|
||||
<article>
|
||||
<article class="notification">
|
||||
Storyboard Images are processing
|
||||
</article>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<h3>Controls</h3>
|
||||
<button hx-post="/vods/{{vod.id}}/process" hx-target="body">{{icon "processing" 24}} Re-Schedule Vod
|
||||
<h3 class="title is-4">Controls</h3>
|
||||
<button class="button" hx-post="/vods/{{vod.id}}/process" hx-target="body"><span
|
||||
class="icon mr-2">{{icon "processing" 24}}</span>
|
||||
Re-Schedule
|
||||
Vod
|
||||
Processing</button>
|
||||
</div>
|
||||
</article>
|
||||
{{/if}}
|
||||
|
||||
@ -322,11 +337,11 @@
|
||||
{{!-- <h2>Comments</h2>
|
||||
{{>commentForm}} --}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
{{>footer}}
|
||||
</main>
|
||||
</div>
|
||||
<script src=" https://cdn.jsdelivr.net/npm/video.js@8.23.3/dist/video.min.js "></script>
|
||||
<script>
|
||||
|
||||
|
@ -1,14 +1,18 @@
|
||||
{{#> main}}
|
||||
<header class="container">
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" href="/vods?format=rss" />
|
||||
<header>
|
||||
{{> navbar}}
|
||||
</header>
|
||||
|
||||
<main class="container pico">
|
||||
|
||||
<section id="tables">
|
||||
<h2>VODs</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<section>
|
||||
<h2 class="title is-1">VODs
|
||||
<a href="/vods?format=rss" alt="RSS feed for all VODs">
|
||||
{{icon "rss" 32}}</a>
|
||||
</h2>
|
||||
<table class="table striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Upload Date</th>
|
||||
@ -38,7 +42,6 @@
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
{{#> main}}
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar}}
|
||||
</header>
|
||||
|
||||
<main class="container pico">
|
||||
<main class="container">
|
||||
|
||||
{{#each info}}
|
||||
<article id="article">
|
||||
@ -15,10 +15,10 @@
|
||||
{{/each}}
|
||||
|
||||
|
||||
<section id="tables">
|
||||
<h2>VTubers</h2>
|
||||
<section>
|
||||
<h2 class="title is-1">VTubers</h2>
|
||||
<div class="overflow-auto">
|
||||
<table class="striped">
|
||||
<table class="table striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@ -30,7 +30,7 @@
|
||||
<tbody>
|
||||
{{#each vtubers}}
|
||||
<tr>
|
||||
<td><a href="/vtubers/{{this.slug}}">{{this.displayName}}</a></td>
|
||||
<td><a href="/vt/{{this.slug}}">{{this.displayName}}</a></td>
|
||||
<td>{{#if this.image}}<img src="{{getCdnUrl this.image}}" alt="{{this.displayName}}" class="avatar">{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
|
@ -8,7 +8,7 @@
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<h1>Add a new VTuber</h1>
|
||||
<h1 class="title is-1">Add a new VTuber</h1>
|
||||
|
||||
{{#if message}}<p id="messages" hx-swap-oob="true">{{message}}</p>{{/if}}
|
||||
{{#each info}}
|
||||
@ -27,74 +27,172 @@
|
||||
{{/each}}
|
||||
|
||||
|
||||
<h3></h3>
|
||||
<form id="details" method="POST" action="/vtubers/create">
|
||||
<div class="pico">
|
||||
<label for="displayName">VTuber Name <span style="color: red;">*</span></label>
|
||||
<input type="text" id="displayName" name="displayName" required>
|
||||
<form id="details" method="POST" action="/vt/create">
|
||||
|
||||
<label for="themeColor">Theme Color <span style="color: red;">*</span></label>
|
||||
<input type="color" id="themeColor" name="themeColor" required>
|
||||
|
||||
<label for="chaturbate">Chaturbate URL</label>
|
||||
<input type="url" id="chaturbate" name="chaturbate">
|
||||
|
||||
<label for="twitter">Twitter URL</label>
|
||||
<input type="url" id="twitter" name="twitter">
|
||||
|
||||
<label for="patreon">Patreon URL</label>
|
||||
<input type="url" id="patreon" name="patreon">
|
||||
|
||||
<label for="twitch">Twitch URL</label>
|
||||
<input type="url" id="twitch" name="twitch">
|
||||
|
||||
<label for="tiktok">TikTok URL</label>
|
||||
<input type="url" id="tiktok" name="tiktok">
|
||||
|
||||
<label for="onlyfans">OnlyFans URL</label>
|
||||
<input type="url" id="onlyfans" name="onlyfans">
|
||||
|
||||
<label for="youtube">YouTube URL</label>
|
||||
<input type="url" id="youtube" name="youtube">
|
||||
|
||||
<label for="linktree">Linktree URL</label>
|
||||
<input type="url" id="linktree" name="linktree">
|
||||
|
||||
<label for="carrd">Carrd URL</label>
|
||||
<input type="url" id="carrd" name="carrd">
|
||||
|
||||
<label for="fansly">Fansly URL</label>
|
||||
<input type="url" id="fansly" name="fansly">
|
||||
|
||||
<label for="pornhub">Pornhub URL</label>
|
||||
<input type="url" id="pornhub" name="pornhub">
|
||||
|
||||
<label for="discord">Discord URL</label>
|
||||
<input type="url" id="discord" name="discord">
|
||||
|
||||
<label for="reddit">Reddit URL</label>
|
||||
<input type="url" id="reddit" name="reddit">
|
||||
|
||||
<label for="throne">Throne URL</label>
|
||||
<input type="url" id="throne" name="throne">
|
||||
|
||||
<label for="instagram">Instagram URL</label>
|
||||
<input type="url" id="instagram" name="instagram">
|
||||
|
||||
<label for="facebook">Facebook URL</label>
|
||||
<input type="url" id="facebook" name="facebook">
|
||||
|
||||
<label for="merch">Merch URL</label>
|
||||
<input type="url" id="merch" name="merch">
|
||||
{{!-- VTuber Name --}}
|
||||
<div class="field">
|
||||
<label class="label" for="displayName">VTuber Name <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="displayName" name="displayName" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="uploader"></div>
|
||||
{{!-- Theme Color --}}
|
||||
<div class="field">
|
||||
<label class="label" for="themeColor">Theme Color <span class="has-text-danger">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="color" id="themeColor" name="themeColor" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3></h3>
|
||||
<div class="pico">
|
||||
<button type="submit">Submit</button>
|
||||
{{!-- Social / Platform URLs --}}
|
||||
{{!-- Chaturbate --}}
|
||||
<div class="field">
|
||||
<label class="label" for="chaturbate">Chaturbate URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="chaturbate" name="chaturbate">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Twitter --}}
|
||||
<div class="field">
|
||||
<label class="label" for="twitter">Twitter URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="twitter" name="twitter">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Patreon --}}
|
||||
<div class="field">
|
||||
<label class="label" for="patreon">Patreon URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="patreon" name="patreon">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Twitch --}}
|
||||
<div class="field">
|
||||
<label class="label" for="twitch">Twitch URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="twitch" name="twitch">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- TikTok --}}
|
||||
<div class="field">
|
||||
<label class="label" for="tiktok">TikTok URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="tiktok" name="tiktok">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- OnlyFans --}}
|
||||
<div class="field">
|
||||
<label class="label" for="onlyfans">OnlyFans URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="onlyfans" name="onlyfans">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- YouTube --}}
|
||||
<div class="field">
|
||||
<label class="label" for="youtube">YouTube URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="youtube" name="youtube">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Linktree --}}
|
||||
<div class="field">
|
||||
<label class="label" for="linktree">Linktree URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="linktree" name="linktree">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Carrd --}}
|
||||
<div class="field">
|
||||
<label class="label" for="carrd">Carrd URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="carrd" name="carrd">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Fansly --}}
|
||||
<div class="field">
|
||||
<label class="label" for="fansly">Fansly URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="fansly" name="fansly">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Pornhub --}}
|
||||
<div class="field">
|
||||
<label class="label" for="pornhub">Pornhub URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="pornhub" name="pornhub">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Discord --}}
|
||||
<div class="field">
|
||||
<label class="label" for="discord">Discord URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="discord" name="discord">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Reddit --}}
|
||||
<div class="field">
|
||||
<label class="label" for="reddit">Reddit URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="reddit" name="reddit">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Throne --}}
|
||||
<div class="field">
|
||||
<label class="label" for="throne">Throne URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="throne" name="throne">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Instagram --}}
|
||||
<div class="field">
|
||||
<label class="label" for="instagram">Instagram URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="instagram" name="instagram">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Facebook --}}
|
||||
<div class="field">
|
||||
<label class="label" for="facebook">Facebook URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="facebook" name="facebook">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Merch --}}
|
||||
<div class="field">
|
||||
<label class="label" for="merch">Merch URL</label>
|
||||
<div class="control">
|
||||
<input class="input" type="url" id="merch" name="merch">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- File uploader --}}
|
||||
<div class="field" id="uploader"></div>
|
||||
|
||||
{{!-- Submit button --}}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-primary" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{#if message}}<p id="messages" hx-swap-oob="true">{{message}}</p>{{/if}}
|
||||
|
||||
{{>footer}}
|
||||
|
@ -1,16 +1,16 @@
|
||||
{{#> main}}
|
||||
<link rel="alternate" type="application/rss+xml" href="/vtubers/{{vtuber.slug}}/rss" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/vt/{{vtuber.slug}}/vods?format=rss" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/video.js@8.22.0/dist/video-js.min.css">
|
||||
|
||||
<header class="container">
|
||||
<header>
|
||||
{{> navbar}}
|
||||
</header>
|
||||
|
||||
|
||||
<main class="container">
|
||||
|
||||
<section id="tables" class="pico">
|
||||
<section class="">
|
||||
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
type="application/x-mpegURL">
|
||||
</video> --}}
|
||||
|
||||
<h1>
|
||||
<h1 class="title is-1">
|
||||
{{vtuber.displayName}}
|
||||
</h1>
|
||||
|
||||
@ -59,10 +59,11 @@
|
||||
|
||||
|
||||
<section>
|
||||
<h2>Socials</h2>
|
||||
<div class="grid">
|
||||
<div class="mb-5">
|
||||
<h2 class="title is-2">Socials</h2>
|
||||
<div class="columns is-mobile is-multiline">
|
||||
{{#if vtuber.chaturbate}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.chaturbate}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210.63 189.77" width="20" class="icon_icon__aycE9">
|
||||
<path fill="currentColor"
|
||||
@ -71,90 +72,90 @@
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.twitter}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.twitter}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="m17.687 3.063l-4.996 5.711l-4.32-5.711H2.112l7.477 9.776l-7.086 8.099h3.034l5.469-6.25l4.78 6.25h6.102l-7.794-10.304l6.625-7.571zm-1.064 16.06L5.654 4.782h1.803l10.846 14.34z" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.patreon}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.patreon}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M22.957 7.21c-.004-3.064-2.391-5.576-5.191-6.482c-3.478-1.125-8.064-.962-11.384.604C2.357 3.231 1.093 7.391 1.046 11.54c-.039 3.411.302 12.396 5.369 12.46c3.765.047 4.326-4.804 6.068-7.141c1.24-1.662 2.836-2.132 4.801-2.618c3.376-.836 5.678-3.501 5.673-7.031" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.twitch}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.twitch}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M11.64 5.93h1.43v4.28h-1.43m3.93-4.28H17v4.28h-1.43M7 2L3.43 5.57v12.86h4.28V22l3.58-3.57h2.85L20.57 12V2m-1.43 9.29l-2.85 2.85h-2.86l-2.5 2.5v-2.5H7.71V3.43h11.43Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.tiktok}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.tiktok}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M16.6 5.82s.51.5 0 0A4.28 4.28 0 0 1 15.54 3h-3.09v12.4a2.59 2.59 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6c0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64c0 3.33 2.76 5.7 5.69 5.7c3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.onlyfans}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.onlyfans}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M24 4.003h-4.015c-3.45 0-5.3.197-6.748 1.957a7.996 7.996 0 1 0 2.103 9.211c3.182-.231 5.39-2.134 6.085-5.173c0 0-2.399.585-4.43 0c4.018-.777 6.333-3.037 7.005-5.995M5.61 11.999A2.391 2.391 0 0 1 9.28 9.97a2.966 2.966 0 0 1 2.998-2.528h.008c-.92 1.778-1.407 3.352-1.998 5.263A2.392 2.392 0 0 1 5.61 12Zm2.386-7.996a7.996 7.996 0 1 0 7.996 7.996a7.996 7.996 0 0 0-7.996-7.996m0 10.394A2.399 2.399 0 1 1 10.395 12a2.396 2.396 0 0 1-2.399 2.398Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.youtube}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.youtube}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="m10 15l5.19-3L10 9zm11.56-7.83c.13.47.22 1.1.28 1.9c.07.8.1 1.49.1 2.09L22 12c0 2.19-.16 3.8-.44 4.83c-.25.9-.83 1.48-1.73 1.73c-.47.13-1.33.22-2.65.28c-1.3.07-2.49.1-3.59.1L12 19c-4.19 0-6.8-.16-7.83-.44c-.9-.25-1.48-.83-1.73-1.73c-.13-.47-.22-1.1-.28-1.9c-.07-.8-.1-1.49-.1-2.09L2 12c0-2.19.16-3.8.44-4.83c.25-.9.83-1.48 1.73-1.73c.47-.13 1.33-.22 2.65-.28c1.3-.07 2.49-.1 3.59-.1L12 5c4.19 0 6.8.16 7.83.44c.9.25 1.48.83 1.73 1.73" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.linktree}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.linktree}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="m13.736 5.853l4.005-4.117l2.325 2.38l-4.2 4.005h5.908v3.305h-5.937l4.229 4.108l-2.325 2.334l-5.74-5.769l-5.741 5.769l-2.325-2.325l4.229-4.108H2.226V8.121h5.909l-4.2-4.004l2.324-2.381l4.005 4.117V0h3.472zm-3.472 10.306h3.472V24h-3.472z" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.carrd}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.carrd}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M21.254 18.42L9.746 23.948a.5.5 0 0 1-.23.053a.55.55 0 0 1-.284-.08a.53.53 0 0 1-.247-.45v-5.474l-6.217-2.602a.53.53 0 0 1-.327-.49V.531c0-.181.093-.354.248-.45A.54.54 0 0 1 3.202.05l11.964 5.743l5.632-2.703a.53.53 0 0 1 .513.03a.53.53 0 0 1 .248.452v14.37a.54.54 0 0 1-.305.479M3.503 1.378V14.55l5.482 2.297V14.2l-3.447-1.39a.537.537 0 0 1-.296-.69a.533.533 0 0 1 .69-.296l3.053 1.23V10.88L5.538 9.492a.537.537 0 0 1-.296-.69a.534.534 0 0 1 .69-.297l3.053 1.23v-.632c0-.204.115-.39.3-.478l.788-.38l-4.562-2.076a.534.534 0 0 1-.265-.703a.536.536 0 0 1 .704-.266l5.367 2.447L13.93 6.39zm16.99 3.04L10.047 9.435v13.193l10.446-5.022zm-8.45 6.867l5.985-2.894a.53.53 0 0 1 .708.248a.527.527 0 0 1-.247.708l-5.987 2.894a.55.55 0 0 1-.23.053a.53.53 0 0 1-.23-1.01m0 3.318l5.985-2.893a.53.53 0 0 1 .708.248a.527.527 0 0 1-.247.707l-5.987 2.894a.55.55 0 0 1-.23.053a.53.53 0 0 1-.23-1.009m0 3.314l5.985-2.893a.53.53 0 0 1 .708.247a.527.527 0 0 1-.247.708L12.5 18.872a.55.55 0 0 1-.23.053a.53.53 0 0 1-.23-1.009" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.fansly}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.fansly}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" baseProfile="tiny" viewBox="0 0 394.7 324.7"
|
||||
width="20" height="20">
|
||||
@ -166,10 +167,10 @@
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.pornhub}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.pornhub}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" overflow="visible" viewBox="0 0 79.6 84.5"
|
||||
width="20" height="20" class="icon_icon__aycE9">
|
||||
@ -187,20 +188,20 @@
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.discord}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.discord}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.1.1 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.1 16.1 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02M8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12m6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.reddit}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.reddit}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
@ -209,10 +210,10 @@
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m5.8 11.33c.02.14.03.29.03.44c0 2.24-2.61 4.06-5.83 4.06s-5.83-1.82-5.83-4.06c0-.15.01-.3.03-.44c-.51-.23-.86-.74-.86-1.33a1.455 1.455 0 0 1 2.47-1.05c1.01-.73 2.41-1.19 3.96-1.24l.74-3.49c.01-.07.05-.13.11-.16c.06-.04.13-.05.2-.04l2.42.52a1.04 1.04 0 1 1 .93 1.5c-.56 0-1.01-.44-1.04-.99l-2.17-.46l-.66 3.12c1.53.05 2.9.52 3.9 1.24a1.455 1.455 0 1 1 1.6 2.38" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.throne}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.throne}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 67 58" fill="none" width="20" height="20">
|
||||
<path fill="currentColor"
|
||||
@ -229,100 +230,76 @@
|
||||
</defs>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.instagram}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.instagram}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M7.8 2h8.4C19.4 2 22 4.6 22 7.8v8.4a5.8 5.8 0 0 1-5.8 5.8H7.8C4.6 22 2 19.4 2 16.2V7.8A5.8 5.8 0 0 1 7.8 2m-.2 2A3.6 3.6 0 0 0 4 7.6v8.8C4 18.39 5.61 20 7.6 20h8.8a3.6 3.6 0 0 0 3.6-3.6V7.6C20 5.61 18.39 4 16.4 4zm9.65 1.5a1.25 1.25 0 0 1 1.25 1.25A1.25 1.25 0 0 1 17.25 8A1.25 1.25 0 0 1 16 6.75a1.25 1.25 0 0 1 1.25-1.25M12 7a5 5 0 0 1 5 5a5 5 0 0 1-5 5a5 5 0 0 1-5-5a5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3a3 3 0 0 0 3 3a3 3 0 0 0 3-3a3 3 0 0 0-3-3" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.facebook}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.facebook}}" target="_blank">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M22 12c0-5.52-4.48-10-10-10S2 6.48 2 12c0 4.84 3.44 8.87 8 9.8V15H8v-3h2V9.5C10 7.57 11.57 6 13.5 6H16v3h-2c-.55 0-1 .45-1 1v2h3v3h-3v6.95c5.05-.5 9-4.76 9-9.95" />
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if vtuber.merch}}
|
||||
<p>
|
||||
<div class="column">
|
||||
<a href="{{vtuber.merch}}" target="_blank">
|
||||
<svg focusable="false" data-prefix="fas" data-icon="bag-shopping" class="svg-inline--fa fas fa-bag-shopping"
|
||||
role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="24" height="24">
|
||||
<svg focusable="false" data-prefix="fas" data-icon="bag-shopping"
|
||||
class="svg-inline--fa fas fa-bag-shopping" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512" width="24" height="24">
|
||||
<path fill="currentColor"
|
||||
d="M160 112c0-35.3 28.7-64 64-64s64 28.7 64 64v48H160V112zm-48 48H48c-26.5 0-48 21.5-48 48V416c0 53 43 96 96 96H352c53 0 96-43 96-96V208c0-26.5-21.5-48-48-48H336V112C336 50.1 285.9 0 224 0S112 50.1 112 112v48zm24 48a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm152 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>VODs</h2>
|
||||
<h2 class="title is-2">VODs <a href="/vt/{{vtuber.slug}}/vods?format=rss"
|
||||
alt="RSS feed for {{vtuber.displayName}}">
|
||||
{{icon "rss" 32}}</a>
|
||||
</h2>
|
||||
<div class="grid">
|
||||
{{#each vtuber.vods}}
|
||||
<p>
|
||||
<div>
|
||||
<a href="/vods/{{this.id}}">
|
||||
{{#if this.thumbnail}}
|
||||
<img src="{{getCdnUrl this.thumbnail}}">
|
||||
{{/if}}
|
||||
<span>{{formatDate this.streamDate}}</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{!--
|
||||
@todo implement
|
||||
<h2><s>🚧Streams</s></h2>
|
||||
<p>🚧</p>
|
||||
<p>🚧</div>
|
||||
|
||||
<h2><s>🚧Toys</s></h2>
|
||||
<p>🚧</p>
|
||||
|
||||
<h2>Feeds</h2>
|
||||
<p>
|
||||
<a href="/vtubers/{{vtuber.slug}}/rss" alt="RSS feed for {{vtuber.displayName}}">RSS {{icon "rss" 32}}</a>
|
||||
</p>
|
||||
|
||||
|
||||
<div class="overflow-auto">
|
||||
|
||||
|
||||
{{!-- <h2>Thumbnail Image</h2>
|
||||
{{#if vod.thumbnail}}
|
||||
<img src="{{getCdnUrl vod.thumbnail}}" alt="{{this.vtuber.displayName}} thumbnail">
|
||||
<div class="mx-5"></div>
|
||||
{{else}}
|
||||
<article>
|
||||
Thumbnail is still processing.
|
||||
</article>
|
||||
{{/if}} --}}
|
||||
|
||||
|
||||
{{!-- {{#if (isModerator user)}}
|
||||
<h2>Moderator Controls</h2>
|
||||
<button hx-post="/vods/{{vod.id}}/process" hx-target="body"><svg xmlns="http://www.w3.org/2000/svg" width="24"
|
||||
height="24" viewBox="0 0 2048 2048">
|
||||
<path fill="currentColor"
|
||||
d="M1930 630q0 22-2 43t-8 43l123 51l-49 118l-124-51q-46 74-120 120l51 125l-118 49l-52-124q-21 5-42 7t-43 3q-22 0-43-2t-43-8l-23 56l-111-67l16-39q-74-46-120-120l-125 51l-49-118l124-51q-5-21-7-42t-3-44q0-22 2-43t8-42l-124-52l49-118l125 52q23-37 53-67t67-54l-51-124l118-49l51 123q21-5 42-7t44-3q22 0 43 2t42 8l52-123l118 49l-51 124q74 46 120 120l124-51l49 118l-123 52q5 21 7 42t3 43m-384 256q53 0 99-20t82-55t55-81t20-100q0-53-20-99t-55-82t-81-55t-100-20q-53 0-99 20t-82 55t-55 81t-20 100q0 53 20 99t55 82t81 55t100 20m-577 220l139-58l44 106v15l-133 55q7 27 11 54t4 56q0 28-4 55t-11 55l133 55v15l-44 106l-139-58q-29 48-68 87t-87 69l58 139l-119 49l-57-139q-27 7-54 11t-56 4q-28 0-55-4t-55-11l-58 139l-118-49l58-140q-97-58-155-155l-140 58l-48-118l138-58q-7-27-11-54t-4-56q0-28 4-55t11-55l-138-57l48-119l140 58q58-97 155-155l-58-139l118-49l58 138q27-7 54-11t56-4q28 0 55 4t55 11l57-138l119 49l-58 139q97 58 155 155m-383 548q66 0 124-25t101-68t69-102t26-125t-25-124t-69-101t-102-69t-124-26t-124 25t-102 69t-69 102t-25 124t25 124t68 102t102 69t125 25m694 394v-896l747 448zm128-670v444l370-222z" />
|
||||
</svg> Re-Schedule VTuber Processing</button>
|
||||
{{/if}} --}}
|
||||
<p>🚧</div> --}}
|
||||
|
||||
|
||||
|
||||
{{!-- this is feature creep-- we will add this once we get the basic site running --}}
|
||||
|
||||
{{!-- <h2>Comments</h2>
|
||||
{{>commentForm}} --}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user