Compare commits

..

2 Commits

Author SHA1 Message Date
6caf2dbcc3 add /vods and /vt/:slug/vods
Some checks failed
ci / test (push) Failing after 9m42s
fp/our CI/CD / build (push) Successful in 1m19s
2025-11-08 12:36:26 -08:00
fe1f318424 add /account settings 2025-11-05 20:49:00 -09:00
102 changed files with 12126 additions and 2 deletions

View File

@ -3,6 +3,7 @@
"redhat.vscode-yaml",
"jetify.devbox",
"redhat.ansible",
"dotjoshjohnson.xml"
"dotjoshjohnson.xml",
"j69.ejs-beautify"
]
}

View File

@ -59,4 +59,7 @@ In other words, pick something for a name and roll with the punches.
### Do what you can, with what you have, where you are at
### Use it up, wear it out, make it do, or do without
### Use it up, wear it out, make it do, or do without
### [Just Culture](https://pilotswhoaskwhy.com/2023/05/15/the-power-of-just-culture-killing-the-blame-game/)

View File

@ -80,6 +80,9 @@ model Vod {
sourceAudioCodec String?
sourceVideoFps Float?
muxAssetId String?
muxPlaybackId String?
// audio analysis
audioIntegratedLufs Float? // Integrated loudness (LUFS-I)
audioLoudnessRange Float? // Loudness Range (LRA)

5
services/pocketbase/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.ftp-deploy-sync-state.json
node_modules
pb_data
.DS_Store
.secrets

View File

@ -0,0 +1 @@
# pocketpages-starter-datastar

View File

@ -0,0 +1,106 @@
# Datastar Starter Kit
This starter kit demonstrates how to build realtime web applications using [Datastar](https://github.com/starfederation/datastar) with PocketPages. It includes a live chat demo and counter example.
## Features
- Realtime chat with emoji avatars
- Live counter with instant updates
- Datastar signal management
- Server-sent events (SSE) integration
- Modern UI with Tailwind CSS
## Installation
```bash
npx tiged benallfree/pocketpages/packages/starters/datastar .
cd datastar
npm i
pocketbase serve --dir=pb_data --dev
```
## Setup
The starter kit is pre-configured with the required Datastar setup:
1. **Plugin Configuration** (`+config.js`): Includes the datastar plugin
2. **Script Injection** (`+layout.ejs`): Includes `<%- datastar.scripts() %>` in the `<head>` section
3. **Realtime Integration**: Configured for realtime updates
## Usage
1. Start the server and visit `http://127.0.0.1:8090`
2. Open multiple browser tabs to see realtime updates
3. Send messages in the chat to see them appear instantly across all tabs
4. Click the counter button to see live updates
## Key Components
- **Chat System**: Real-time messaging with random emoji avatars
- **Counter**: Simple state management with live updates
- **Datastar Integration**: Server-side signal reading and client-side binding
- **Realtime Broadcasting**: Updates sent to all connected clients
## Datastar Features Demonstrated
- `datastar.patchElements()` - DOM updates
- `datastar.patchSignals()` - Signal management
- `datastar.readSignals()` - Form data reading
- `datastar.realtime.*` - Broadcasting to all clients
- Client-side data binding with `data-bind-*` attributes
- Event handling with `data-on-click` attributes
## dev notes
### S3 has some jenk
cache s3 requests at the proxy level
https://github.com/pocketbase/pocketbase/issues/28#issuecomment-1492516269
https://github.com/pocketbase/pocketbase/discussions/4332#discussioncomment-8450890
### search
https://www.meilisearch.com/
### horizontal scaling
https://github.com/fondoger/pocketbase (pg fork)
https://github.com/litesql/pocketbase-ha
### Data migrations
https://github.com/pocketbase/pocketbase/discussions/2836#discussioncomment-6340103
### FTS
https://github.com/pocketbuilds/fts?tab=readme-ov-file#readme
### plugins
https://pocketbuilds.com/
## Deployments
how the pros do it
https://github.com/benallfree/pocketpages/blob/5bc48d4f8df75b2f78ca61fa18c792d814b926e8/packages/starters/deploy-pockethost-manual/deploy-pockethost.ts#L5
## Avoid proxying files via pocketbase
https://github.com/pocketbase/pocketbase/discussions/5995
## realtime updates on db changes
https://github.com/pocketbase/pocketbase/discussions/4427#discussioncomment-8585118
## get a random item
https://github.com/pocketbase/pocketbase/discussions/2725

2897
services/pocketbase/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
{
"name": "futureporn",
"version": "3.1.0",
"private": true,
"description": "Dedication to the preservation of lewdtuber history",
"license": "Unlicense",
"scripts": {
"deploy": "phio deploy"
},
"dependencies": {
"jsonwebtoken": "^9.0.2",
"mime": "^4.1.0",
"nano-spawn": "^2.0.0",
"pg": "^8.16.3",
"pocketpages": ">=0.22.3",
"pocketpages-plugin-auth": "^0.2.2",
"pocketpages-plugin-datastar": ">=0.2.0",
"pocketpages-plugin-micro-dash": "^0.1.0"
},
"files": [
"pb_hooks",
"*.md"
],
"devDependencies": {
"phio": "^0.3.5"
}
}

View File

@ -0,0 +1,107 @@
/// <reference path="../pb_data/types.d.ts" />
/**
* onFileDownloadRequest hook is triggered before each API File download request. Could be used to validate or modify the file response before returning it to the client.
* @see https://pocketbase.io/docs/js-event-hooks/#onfiledownloadrequest
*
* We use this to return a 302 to the CDN asset instead of having the asset proxied via Pocketbase
* @see https://github.com/pocketbase/pocketbase/discussions/5995
*
*/
onFileDownloadRequest((e) => {
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// console.log('onFileDownloadRequest hook has been triggered ~~~');
// e.next()
// e.app
// e.collection
// e.record
// e.fileField
// e.servedPath
// e.servedName
// and all RequestEvent fields...
const securityKey = process.env?.BUNNY_TOKEN_KEY;
const baseUrl = process.env?.BUNNY_ZONE_URL;
console.log(`securityKey=${securityKey}, baseUrl=${baseUrl}`)
if (!securityKey) {
console.error('BUNNY_TOKEN_KEY was missing from env');
return e.next();
}
if (!baseUrl) {
console.error('BUNNY_ZONE_URL was missing from env');
return e.next();
}
/**
* Generates a BunnyCDN-style signed URL using directory tokens.
*
* We sign URLs to make hotlinking difficult
* @see https://support.bunny.net/hc/en-us/articles/360016055099-How-to-sign-URLs-for-BunnyCDN-Token-Authentication
* @see https://github.com/pocketbase/pocketbase/discussions/5983#discussioncomment-11426659 // HMAC in pocketbase
* @see https://github.com/pocketbase/pocketbase/discussions/6772 // base64 encode the hex
*/
function signUrl(securityKey, baseUrl, path, expires) {
if (!path.startsWith('/')) path = '/' + path;
if (baseUrl.endsWith('/')) throw new Error(`baseUrl must not end with a slash. got baseUrl=${baseUrl}`);
const hashableBase = securityKey + path + expires;
// Generate and encode the token
const tokenH = $security.sha256(hashableBase);
const token = Buffer.from(tokenH, "hex")
.toString("base64")
.replace(/\n/g, "") // Remove newlines
.replace(/\+/g, "-") // Replace + with -
.replace(/\//g, "_") // Replace / with _
.replace(/=/g, ""); // Remove =
// Generate the URL
const signedUrl = baseUrl + path + '?token=' + token + '&expires=' + expires;
return signedUrl;
}
console.log(`record: ${JSON.stringify(e.record)}`)
console.log(`collection: ${JSON.stringify(e.collection)}`)
console.log(`app: ${JSON.stringify(e.app)}`)
console.log(`fileField: ${JSON.stringify(e.fileField)}`)
console.log(`servedPath: ${JSON.stringify(e.servedPath)}`)
console.log(`servedName: ${JSON.stringify(e.servedName)}`)
// Our job here is to take the servedPath, and sign it using bunnycdn method
// Then serve a 302 redirect instead of serving the file proxied thru PB
const path = e.servedPath;
const expires = Math.round(Date.now() / 1000) + 3600;
const signedUrl = signUrl(securityKey, baseUrl, path, expires);
console.log(`signedUrl=${signedUrl}`);
// This redirect is a tricky thing. We do this to avoid proxying file requests via our pocketbase origin server.
// The idea is to reduce load.
// HOWEVER, this redirect slows down image loading because it now takes 2 requests per image.
e.redirect(302, signedUrl);
e.next()
})

View File

@ -0,0 +1,37 @@
/// <reference path="../pb_data/types.d.ts" />
// @see https://github.com/pocketbase/pocketbase/discussions/6287
// NOTE: this script must be run from the project root
// ./pocketbase import /path/to/your/data.json YOUR_COLLECTION
$app.rootCmd.addCommand(new Command({
use: "import",
run: (cmd, args) => {
const data = require(args[0])
const collection = $app.findCollectionByNameOrId(args[1]);
// console.log(`data`, data, 'collection', collection);
$app.runInTransaction((txApp) => {
let record;
for (let item of data) {
// check https://pocketbase.io/docs/js-records/
record = new Record(collection)
// bulk set the item field values
//
// note: if you want to set predefined values for "autodate" type fields
// you'll need to manually load them with record.setRaw("created", val)
record.load(item)
// call txApp.saveNoValidate for better performance
// but be aware that the fields validators will be skipped and could result in data integrity issues
// if you haven't checked and verified the imported data beforehand
txApp.save(record)
}
})
},
}))

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
<%=meta('title') || '~~~~~' %>
</title>
<meta name="description" content="<%=meta('description') || 'aaaaa'%>" />
<meta property="og:title" content="<%=meta('title') || 'Futureporn.net'%>" />
<meta property="og:description" content="<%=meta('description') %>" />
<meta property="og:image" content="<%=meta('image') || '/android-chrome-512x512.png'%>" />
<meta property="og:url" content="<%=meta('path') ? `${meta('path')}` : meta('url') || `${request.url}`%>" />
<meta name="RATING" content="RTA-5042-1996-1400-1577-RTA" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
<link rel="alternate" type="application/rss+xml" title="Futureporn VODs (RSS)" href="/vods/feed.xml">
<link rel="alternate" type="application/rss+xml" title="Futureporn VODs (Atom)" href="/vods/rss.xml">
<link rel="alternate" type="application/json" title="Futureporn VODs" href="/vods/feed.json">
<%- slots.head %> <%- datastar.scripts({realtime: true}) %>
</head>
<body class="h-full bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 font-sans antialiased">
<div class="min-h-full flex flex-col">
<main class="flex-1 container mx-auto px-4 py-8 sm:px-6 lg:px-8">
<div class="max-w-4xl mx-auto">
<div class="space-y-8">
<!-- Header Section -->
<nav class="level">
<!-- Left side -->
<div class="level-left">
<div class="level-item">
<p class="subtitle is-5"><a href="/"><strong>Futureporn.net</strong></a></p>
</div>
</div>
<div class="level-right">
<% if (auth) { %>
<div class="level-item">
<a href="/account">Account</a>
</div>
<div class="level-item">
<a href="/auth/logout">Logout</a>
</div>
<% } else { %>
<div class="level-item">
<a href="/auth/login">Login</a>
</div>
<% } %>
</div>
</nav>
<%#
<div class="notification is-info" data-signals='{"showNotif": false}' data-show="$showNotif">
<button class="delete" data-on-click="$showNotif = false"></button>
<span data-text="$message"></span>
</div>
%>
<%- slots.body || slot%>
</div>
</div>
</main>
<footer class="footer mt-5">
<div class="content has-text-centered">
<p>
<strong>Futureporn <%= data.version %></strong> made with love by <a href="https://t.co/I8p0oH0AAB">@CJ_Clippy</a>.
</p>
</div>
</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function ({ meta, redirect, request, auth }) {
meta('title', 'Futureporn.net')
meta('description', 'Dedication to the preservation of Lewdtuber history')
meta('image', 'https://example.com/about-preview.jpg')
const cookies = request.cookies()
// console.log('cookies as follows')
// console.log(JSON.stringify(cookies))
// console.log('auth as follows')
// console.log(auth)
let user;
if (auth) {
console.log('request.auth is present id:', auth.get('id'))
user = $app.findFirstRecordByData('users', 'id', auth.id);
}
return { user, version: require(`../../../package.json`).version }
}
// module.exports = (api, next) => {
// const { auth, redirect } = api
// if (!auth) {
// return redirect('/auth/login', {
// message: 'You must be logged in to access this page',
// })
// }
// next()
// }

View File

@ -0,0 +1,9 @@
module.exports = (api, next) => {
const { auth, redirect } = api
if (!auth) {
return redirect('/auth/login', {
message: 'You must be logged in to access this page',
})
}
next()
}

View File

@ -0,0 +1,37 @@
<h2 class="title is-2">Account Overview</h2>
<p>Welcome back, <strong><%= auth.get('name') %></strong>.</p>
<h3 class="title is-3 mt-5">Patreon Information</h3>
<div class="notification is-info is-light">
Please note: Patron status updates may take up to one minute to synchronize.
</div>
<p><strong>Patreon ID:</strong> <%= auth.get('patreonId') %></p>
<p>
<strong>Role:</strong>
<% if (auth.get('patron')) { %>
Patron
<% } else { %>
Not a Patron
<a class="ml-3" target="_blank" href="https://patreon.com/cj_clippy">Become a Patron</a>
<% } %>
</p>
<% if (auth.get('patron')) { %>
<div class="mt-5">
<h3 class="title is-3">Account Settings</h3>
<label class="checkbox">
<input class="checkbox" type="checkbox" name="publicUsername" data-bind="publicUsername" data-on-input__debounce.300ms.leading="@patch('/api/user/settings')">
Show <%= auth.get('name') %> on the <a href="/patrons">patrons page</a>
</label>
<div id="results" class="mt-5"></div>
<!-- <br />
<input class="button" type="submit" value="Save" /> -->
</div>
<% } %>

View File

@ -0,0 +1,30 @@
<script server>
let error = null
const methods = pb().collection('users').listAuthMethods();
dbg({
methods
})
const {
providers
} = methods.oauth2
</script>
<% if (error) { %>
<mark><%= error %></mark>
<% } %>
<% if (!methods.oauth2.enabled) { %>
<mark>OAuth2 login is disabled. See README.md for details.</mark>
<% } else {%>
<% providers.forEach(provider => { %>
<form method="POST" action="/auth/oauth/login">
<input type="hidden" name="provider" value="<%=provider.name%>">
<button class="button" type="submit">Log in with <%=provider.displayName%></button>
</form>
<% }) %>
<% if (providers.length === 0) { %>
<mark>No OAuth2 providers enabled. See README.md for details.</mark>
<% } %>
<% } %>

View File

@ -0,0 +1,8 @@
<h2 class="title is-2">Login</h2>
<p>Log in to Futureporn using a Patreon account.</p>
<p>Logging in provides access to your patron perks.</p>
<p>To become a patron, visit <a href="https://patreon.com/cj_clippy">CJ_Clippy on Patreon</a>.</p>
<hr />
<%- include('oauth2.ejs') %>

View File

@ -0,0 +1,4 @@
<script server>
signOut()
redirect(`/`)
</script>

View File

@ -0,0 +1,67 @@
<script server>
const {
state,
code
} = params
let error = null
let authData = null
try {
authData = signInWithOAuth2(state, code)
dbg("the shit user id is " + authData.record.id)
if (!authData.record.id) {
throw new Error('invalid authData. authData must contain a record.id');
}
if (!authData.record.created) {
throw new Error('invalid authData. missing record.created')
}
let user = $app.findRecordById('users', authData.record.id)
// if user is new, give them a free trial
const userCreatedAt = new Date(authData.record.created).valueOf();
const anHourAgo = Date.now() - 60 * 60 * 1000; // 1 hour in ms
dbg(`comparing ${userCreatedAt} > ${anHourAgo}`);
if (userCreatedAt > anHourAgo) {
dbg("THE USER IS NEW");
user.set('patron', true);
} else {
dbg("the user is NOT new");
}
console.log(JSON.stringify(authData, null, 2))
dbg("authData:", stringify(authData))
user.set('patreonAccessToken', authData.meta.accessToken);
// user.set('patreonId', authData.meta.id); // we do this via the admin UI
$app.save(user);
// patreon id can be found in _externalAuths system table
// Here we set the patreonId.
// If we're seeing this user for the first time,
// unconditionally grant them patron status.
// The backend later removes the status if they aren't subscribed
// let user = $app.findRecordById('user', )
response.redirect('/')
} catch (e) {
error = e.message
}
</script>
<% if (error) { %>
<mark>
<%=error%>
</mark>
<% } %>

View File

@ -0,0 +1,23 @@
<script server>
let error = null
if (request.method === 'POST') {
let provider
try {
provider = body().provider
const url = requestOAuth2Login(provider)
console.log('url is as follows')
console.log(url)
response.redirect(url)
} catch (error) {
console.log("FAILURE! We failed to get the OAuth2Login from provider", provider)
error = error.message
}
}
</script>
<% if (error) { %>
<p>we got an err</p>
<mark>
<%=error%>
</mark>
<% } %>

View File

@ -0,0 +1,10 @@
Hello humans
futureporn.net
future.porn
R18. For adults only.
Dedication to the preservation of VTuber history
made with love by CJ_Clippy cj@futureporn.net

View File

@ -0,0 +1,23 @@
<% if (!data?.user?.get('patron')) { %>
<div class="notification is-info">
Thank you for your support on <a href="https://patreon.com/cj_clippy">Patreon!</a>
</div>
<% } %>
<ul class="mt-5">
<li class="mb-2">
<a class="button" href="/vt">
<h2 class="title is-2">VTubers</h2>
</a>
</li>
<li class="mb-2">
<a class="button" href="/vods">
<h2 class="title is-2">Vods</h2>
</a>
</li>
<li class="mb-2">
<a class="button" href="/patrons">
<h2 class="title is-2">Patrons</h2>
</a>
</li>
</ul>

View File

@ -0,0 +1,37 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { response } = api;
try {
// Find all users who have both a publicUsername and are patrons
const patronsRaw = $app.findRecordsByFilter(
'users',
'publicUsername != "" && patron = true',
'-created', // sort (optional)
50, // limit
0, // offset
null // params (none used)
);
// Map to plain JSON-safe objects
const patrons = patronsRaw.map((p) => ({
name: p.get('name'),
username: p.get('publicUsername'),
}));
console.log('Patrons:', JSON.stringify(patrons, null, 2));
return { patrons };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
return response.html(404, 'Patrons not found');
} else {
return response.html(500, 'Unknown internal error while fetching patrons');
}
}
};

View File

@ -0,0 +1,19 @@
<p></p>
<div class="menu">
<p class="subtitle">
Thank you to our wonderful patrons who keep this site running 💖
</p>
<% if (Array.isArray(data.patrons) && data.patrons.length > 0) { %>
<ul class="menu-list">
<% for (const patron of data.patrons) { %>
<li>
<%= patron.name %>
</li>
<% } %>
</ul>
<% } else { %>
<p class="has-text-grey-light">Patron names are private by default. Become the first supporter and <a href="/account#settings">opt-in</a> to have your name shown!</p>
<% } %>
</div>

View File

@ -0,0 +1,10 @@
# robots.txt for futureporn.net
# Block all web crawlers from accessing admin and private areas
User-agent: *
Disallow: /_/
Disallow: /auth/
Disallow: /api/
# Allow crawlers to access public content
Allow: /

View File

@ -0,0 +1,30 @@
/**
* @typedef {import('pocketbase').default} PocketBase
* @typedef {import('../pb/pocketbase-types').TypedPocketBase} TypedPocketBase
* @typedef {import('pocketpages').PageDataLoaderFunc} PageDataLoaderFunc
*/
module.exports = function (api) {
const { request, response, params, pb } = api;
try {
const perPage = params.perPage || 25;
const page = params.page || 1;
const client = pb({ request });
const vods = client.collection('vods').getList(page, perPage, {
expand: 'vtubers',
sort: '-streamDate'
})
return { vods };
} catch (e) {
console.error('Error fetching VODs:', e.message);
if (e.message.match(/no rows/)) {
return response.html(404, 'VODs not found');
} else {
return response.html(500, 'Unknown internal error while fetching VODs');
}
}
};

View File

@ -0,0 +1,26 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vod = $app.findFirstRecordByData('vods', 'id', params.id);
$app.expandRecord(vod, ["vtubers"], null);
return { vod };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VOD not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vod')
}
}
};

View File

@ -0,0 +1,79 @@
<div class="players" <% if (data?.user?.get('patron')) { %> data-signals="{'selected':'cdn1'}" <% } else { %> data-signals="{'selected':'cdn2'}" <% } %>>
<!-- CDN2 Player (B2) -->
<div class="b2-player" data-show="$selected == 'cdn2'">
<video controls <% if (!data?.user?.get('patron')) { %> preload="none" <% } %>>
<% if (data.vod?.get('videoSrcB2')) { %>
<source src="<%= data.vod?.get('videoSrcB2') %>" type="video/mp4">
<% } else { %>
Your browser does not support the video tag.
<% } %>
</video>
</div>
<!-- CDN1 Player (Mux, patrons only) -->
<% if (data?.user?.get('patron')) { %>
<div class="mux-player" data-show="$selected == 'cdn1'">
<script src="https://cdn.jsdelivr.net/npm/@mux/mux-player" defer></script>
<mux-player playback-id="<%= data.vod?.get('muxPlaybackId') %>" playback-token="<%= data.vod?.get('muxPlaybackToken') %>"></mux-player>
</div>
<% } %>
</div>
<!-- Player toggle buttons -->
<nav class="level mt-5">
<div class="level-left">
<% if (data?.user?.get('patron')) { %>
<button class="button is-success" data-on-click="$selected = 'cdn1'">CDN1 player</button>
<% } else { %>
<button disabled class="button is-danger">CDN1 player (patrons only)</button>
<% } %>
<button class="button is-success" data-on-click="$selected = 'cdn2'">CDN2 player</button>
</div>
</nav>
<!-- VOD Details -->
<div class="vod-details box mt-5">
<p><b>VOD ID:</b> <%= data.vod?.id ?? 'N/A' %></p>
<p>
<b>VTubers:</b>
<% const vtubers = data.vod?.get('expand')?.vtubers ?? []; %>
<% vtubers.forEach((vtuber, index) => { %>
<% if (vtuber) { %>
<a href="/vt/<%= vtuber.get('slug') ?? '#' %>">
<%= vtuber.get('displayName') ?? 'Unknown VTuber' %>
</a><%= index < vtubers.length - 1 ? ', ' : '' %>
<% } %>
<% }) %>
<% if (vtubers.length === 0) { %>None<% } %>
</p>
<p><b>Stream Date:</b> <%= data.vod?.get('streamDate') ?? 'Unknown date' %></p>
<% if (data.vod?.get('announceUrl')) { %>
<p>
<b>Announce URL:</b>
<a href="<%= data.vod?.get('announceUrl') %>"><%= data.vod?.get('announceUrl') %></a>
</p>
<% } %>
<% if (data.vod?.get('ipfsCid')) { %>
<p><b>IPFS CID:</b> <%= data.vod?.get('ipfsCid') %></p>
<% } %>
<% if (data.vod?.get('notes')) { %>
<p><b>Notes:</b></p>
<pre class="p-2"><%= data.vod?.get('notes') %></pre>
<% } %>
<% if (data.vod?.get('thumbnail')) { %>
<p><b>Thumbnail:</b></p>
<figure class="image">
<img src="/api/files/vods/<%= data.vod?.get('id') %>/<%= data.vod?.get('thumbnail') %>" />
</figure>
<% } %>
</div>
<div class="mb-5"></div>

View File

@ -0,0 +1,26 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate');
$app.expandRecords(vods, ["vtubers"], null);
// vods.expandedAll("vtubers");
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,50 @@
<%#
index.ejs — Atom Feed Generator (Atom 1.0)
Expects: data.vods = [{ id, title, summary, streamDate }]
%>
<%
const siteBase = "https://futureporn.net";
const feedSelf = `${siteBase}/vods/feed.xml`;
const updated = new Date().toISOString();
response.header('Content-Type', 'application/atom+xml')
%>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id><%= siteBase %></id>
<title>Futureporn.net</title>
<updated><%= updated %></updated>
<generator> </generator>
<author>
<name>CJ_Clippy</name>
<email>cj@futureporn.net</email>
<uri><%= siteBase %></uri>
</author>
<link rel="alternate" href="<%= siteBase %>" />
<link rel="self" href="<%= feedSelf %>" />
<subtitle><%= meta('description') %></subtitle>
<logo><%= siteBase %>/images/futureporn-icon.png</logo>
<icon><%= siteBase %>/favicon.ico</icon>
<% for (const vod of data.vods) { %>
<%
const url = `${siteBase}/vods/${vod.id}`;
%>
<entry>
<title type="html">
<![CDATA[ <%= vod.title %> ]]>
</title>
<id><%= url %></id>
<link href="<%= url %>" />
<updated><%= new Date(vod.updated) %></updated>
<% if (vod.notes) { %>
<summary type="html">
<![CDATA[ <%= vod.notes %> ]]>
</summary>
<% } %>
</entry>
<% } %>
</feed>

View File

@ -0,0 +1,3 @@
<h2 class="title is-2">VODs</h2>
<%- include('vod-list.ejs', data) %>

View File

@ -0,0 +1,40 @@
// +middleware.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, request, pb } = api;
const perPage = params.perPage || 25;
const page = params.page || 1;
const vtuber = $app.findFirstRecordByData('vtubers', 'slug', params.slug);
const client = pb({ request });
const vods = client.collection('vods').getList(page, perPage, {
expand: 'vtubers',
sort: '-streamDate',
filter: `vtubers.id ?= "${vtuber.id}"`
})
// await pb.collection("posts").getList(1, 30, {
// filter: "comments_via_post.message ?~ 'hello'"
// expand: "comments_via_post.user",
// })
// console.log(JSON.stringify(vods, null, 2));
// findRecordsByFilter(collectionModelOrIdentifier, filter, sort, limit, offset, ...params): core.Record[]
// $app.expandRecord(vtuber, ['vods_via_vtubers'], null);
// $app.expandRecord(vod, ["vtubers"], null);
// console.log(eerrs)
return { vods, vtuber };
};

View File

@ -0,0 +1 @@
<%- include('vtuber.ejs', data) %>

View File

@ -0,0 +1,3 @@
<h2 class="title is-2"><%= data.vtuber.get('displayName') %> VODs</h2>
<%- include('vod-list.ejs', data) %>

View File

@ -0,0 +1,129 @@
<div id="vtuber" class="">
<div class="flex items-center justify-between">
<section class="section">
<div class="columns">
<div class="column">
<%# VTuber Name %>
<h2 class="title is-2">
<%= data.vtuber?.get?.('displayName') || 'Unknown VTuber' %>
</h2>
<%# VTuber Image %>
<figure class="image is-128x128">
<img src="/api/files/vtubers/<%= data.vtuber?.id %>/<%= data.vtuber?.get('image') %>?thumb=128x128" alt="<%= data.vtuber?.get?.('displayName') || 'VTuber' %>" />
</figure>
</div>
<div class="column">
<h3 class="title is-3">Theme Color</h3>
<% const themeColor = data.vtuber?.get('themeColor') || '#999999' %>
<div class="theme-color" style="
width: 3rem;
height: 3rem;
border-radius: 0.5rem;
border: 1px solid #ccc;
background-color: <%= themeColor %>
" title="<%= themeColor %>"></div>
<p class="has-text-grey is-size-7 mb-5"><%= themeColor %></p>
</div>
</div>
</section>
<% const socials = {
chaturbate: data.vtuber?.get('chaturbate'),
twitter: data.vtuber?.get('twitter'),
patreon: data.vtuber?.get('patreon'),
twitch: data.vtuber?.get('twitch'),
tiktok: data.vtuber?.get('tiktok'),
onlyfans: data.vtuber?.get('onlyfans'),
youtube: data.vtuber?.get('youtube'),
linktree: data.vtuber?.get('linktree'),
carrd: data.vtuber?.get('carrd'),
fansly: data.vtuber?.get('fansly'),
pornhub: data.vtuber?.get('pornhub'),
discord: data.vtuber?.get('discord'),
reddit: data.vtuber?.get('reddit'),
throne: data.vtuber?.get('throne'),
instagram: data.vtuber?.get('instagram'),
facebook: data.vtuber?.get('facebook'),
merch: data.vtuber?.get('merch')
} %>
<% const definedSocials = Object.entries(socials).filter(([_, v]) => v) %>
<% if (definedSocials.length > 0) { %>
<section class="section">
<h3 class="title is-3">Socials</h3>
<div class="tags are-medium">
<% for (const [name, url] of definedSocials) { %>
<a href="<%= url %>" class="tag is-link is-light" target="_blank" rel="noopener">
<%= name.charAt(0).toUpperCase() + name.slice(1) %>
</a>
<% } %>
</div>
</section>
<% } %>
<section class="section">
<h3 class="title is-3">VODs</h3>
<%
const vods = data.vods.items
if (vods.length > 0) {
%>
<div class="columns is-multiline">
<% for (const vod of vods) { %>
<div class="column is-one-quarter-desktop is-half-tablet is-full-mobile">
<a href="/vods/<%= vod.id %>" class="box has-text-centered">
<!-- Thumbnail -->
<% if (vod.thumbnail) { %>
<figure class="image is-16by9 mb-2">
<img src="/api/files/vods/<%= vod.id %>/<%= vod.thumbnail %>?thumb=400x225" alt="VOD thumbnail for <%= vod.id %>" />
</figure>
<% } else { %>
<div class="has-background-grey-lighter py-6 mb-2">
<span class="has-text-grey">No thumbnail</span>
</div>
<% } %>
<!-- Title / ID -->
<p class="is-size-6 has-text-weight-semibold">
<%= vod.title || vod.id %>
</p>
<!-- Date -->
<% if (vod.streamDate) { %>
<p class="is-size-7 has-text-grey">
<%= new Date(vod.streamDate).toLocaleDateString() %>
</p>
<% } %>
</a>
</div>
<% } %>
</div>
<a href="<%= request.url.pathname.replace(/\/$/, '') %>/vods">See all <%= data.vtuber?.get?.('displayName') %> vods</a>
<% } else { %>
<p>No VODs available for this VTuber.</p>
<% } %>
</section>
</div>
</div>

View File

@ -0,0 +1,8 @@
<script server>
const
vtubers = $app.findRecordsByFilter('vtubers')
</script>
<h3>Vtubers</h3>
<%- include('vtuber-list.ejs', { vtubers }) %>

View File

@ -0,0 +1,14 @@
module.exports = {
debug: false,
plugins: [
'pocketpages-plugin-ejs',
'pocketpages-plugin-datastar',
'pocketpages-plugin-realtime',
'pocketpages-plugin-auth',
'pocketpages-plugin-js-sdk',
'pocketpages-plugin-micro-dash',
'../../../src/plugins/patreon'
],
}

View File

@ -0,0 +1,79 @@
<% if (Array.isArray(data.vods.items) && data.vods.items.length > 0) { %>
<div class="table-container">
<table class="table is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th>Stream Date</th>
<th>VTuber</th>
<th>Thumbnail</th>
</tr>
</thead>
<tbody>
<% for (const vod of data.vods.items) { %>
<tr>
<td>
<a href="/vods/<%= vod.id %>" class="is-small is-link">
<%= vod.streamDate ? new Date(vod.streamDate).toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' }) : 'Unknown date' %>
</a>
</td>
<td>
<% const vtubers = vod.expand?.vtubers || []; %>
<% if (vtubers.length) { %>
<% vtubers.forEach(function(v, i){ %>
<a href="/vt/<%= v.slug %>" class="is-small"><%= v.displayName %></a><%= (i === vtubers.length - 2 ? (vtubers.length > 2 ? ', and ' : ' and ') : (i === vtubers.length - 1 ? '' : ', ')) %>
<% }) %>
<% } else { %>
<span>Unknown</span>
<% } %>
</td>
<td style="width: 160px;">
<% if (vod.thumbnail) { %>
<figure class="image is-3by2">
<img src="/api/files/<%= vod.collectionId %>/<%= vod.id %>/<%= vod.thumbnail %>" alt="Thumbnail" style="width: 120px; border-radius: 8px;">
</figure>
<% } else { %>
<span>No thumbnail</span>
<% } %>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<% if (data.vods.totalPages > 1) { %>
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
<% const currentPage = data.vods.page; %>
<% const totalPages = data.vods.totalPages; %>
<!-- Previous Page -->
<% if (currentPage > 1) { %>
<a class="pagination-previous" href="?page=<%= currentPage - 1 %>&perPage=<%= data.vods.perPage %>">Previous</a>
<% } else { %>
<a class="pagination-previous" disabled>Previous</a>
<% } %>
<!-- Next Page -->
<% if (currentPage < totalPages) { %>
<a class="pagination-next" href="?page=<%= currentPage + 1 %>&perPage=<%= data.vods.perPage %>">Next</a>
<% } else { %>
<a class="pagination-next" disabled>Next</a>
<% } %>
<!-- Page Numbers -->
<ul class="pagination-list">
<% for (let i = 1; i <= totalPages; i++) { %>
<li>
<a class="pagination-link <%= i === currentPage ? 'is-current' : '' %>" href="?page=<%= i %>&perPage=<%= data.vods.perPage %>">
<%= i %>
</a>
</li>
<% } %>
</ul>
</nav>
<% } %>
<% } else { %>
<p>No VODs available.</p>
<% } %>

View File

@ -0,0 +1,11 @@
<div id="vtuber-list">
<% for (const vtuber of vtubers) { %>
<div class="">
<span class="">
<a href="/vt/<%= vtuber.get('slug') %>">
<%= vtuber.get('displayName') %>
</a>
</span>
</div>
<% } %>
</div>

View File

@ -0,0 +1,6 @@
<%
response.header("Content-Type",
"application/json");
response.html(410,
"");
%>

View File

@ -0,0 +1,41 @@
<script server>
// const {
// request,
// response
// } = api;
const cookies = request.cookies()
let user;
if (auth) {
console.log('request.auth is present id:', auth.get('id'))
user = $app.findFirstRecordByData('users', 'id', auth.id);
}
if (!user) {
datastar.patchSignals(stringify({
showNotif: true
}))
return response.html(401, "Auth required")
}
console.log('signals as follows')
const signals = datastar.readSignals(request, {})
user.set('publicUsername', signals.publicUsername);
// Determine the publicUsername status
const publicStatus = user.get('publicUsername') ?
'Your username is now <b>public</b>.' :
'Your username is now <b>private</b>.';
// Update the page with a verbose message
datastar.patchElements(`
<div id="results" class="notification is-success mt-5">
Saved! ${publicStatus}
</div>
`);
</script>

View File

@ -0,0 +1,6 @@
<%
response.header("Content-Type",
"application/json");
response.html(410,
"");
%>

View File

@ -0,0 +1 @@
<% response.redirect('/vods/feed.json', 301) %>

View File

@ -0,0 +1 @@
<% response.redirect('/vods/feed.xml', 301) %>

View File

@ -0,0 +1 @@
<% response.redirect('/vods/rss.xml', 301) %>

View File

@ -0,0 +1,26 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate');
$app.expandRecords(vods, ["vtubers"], null);
// vods.expandedAll("vtubers");
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,29 @@
<%#
/**
* index.ejs - Generates a JSON feed
* Expects: data.vods = [{ id, title, url, description, publishedAt, ... }]
*/
%>
<%
const feed = {
version: "https://jsonfeed.org/version/1",
title: `${meta('title')} VODs`,
home_page_url: "https://futureporn.net",
feed_url: "https://futureporn.net/vods/feed.json",
description: meta('description'),
icon: "https://futureporn.net/images/futureporn-icon.png",
author: {
name: "CJ_Clippy",
url: "https://futureporn.net"
},
items: data.vods.map(vod => ({
content_html: "",
url: `https://futureporn.net/vods/${vod.id}`,
title: vod.title,
summary: vod.notes || vod.title,
image: vod.thumbnail,
date_modified: vod.updated
}))
};
%><%- JSON.stringify(feed, null, 2) %>

View File

@ -0,0 +1,28 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate', 99);
$app.expandRecords(vods, ["vtubers"], null);
console.log(JSON.stringify(vods, null, 2))
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,58 @@
<%#
index.ejs — ATOM Feed Generator
Expects: data.vods = [{ id, title, notes, thumbnail, streamDate }]
%>
<%=
response.header("Content-Type", "application/atom+xml");
// Build Atom XML as a string
let atom = `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Futureporn.net</title>
<link href="<%= env('ORIGIN') %>"/>
<updated>${new Date().toISOString()}</updated>
<id><%= env('ORIGIN') %></id>
<author>
<name>CJ_Clippy</name>
<email>cj@futureporn.net</email>
</author>`;
for (const vod of data.vods) {
const url = `${env('ORIGIN')}/vods/${vod.id}`;
const entryTitle = ((vod?.get('expand')?.vtubers?.map(vt => vt.get('displayName')) || [])
.join(', ') || vod.get('streamDate'));
atom += `
<entry>
<title>
<![CDATA[${entryTitle}]]>
</title>
<link href="${url}" />
<id>${url}</id>
<updated>${vod.get('streamDate')}</updated>`;
if (vod.get('notes')) {
atom += `
<summary type="html">
<![CDATA[${vod.get('notes')}]]>
</summary>`;
}
if (vod.get('thumbnail')) {
atom += `
<content type="image">
<![CDATA[<img src="${vod.get('thumbnail')}" />]]>
</content>`;
}
atom += `
</entry>`;
}
atom += `
</feed>`;
// Send the Atom feed
response.html(200, '<p>HELLO</p>');
%>

View File

@ -0,0 +1,28 @@
// +load.js
/** @type {import('pocketpages').PageDataLoaderFunc} */
module.exports = function (api) {
const { params, response } = api;
try {
const vods = $app.findRecordsByFilter('vods', null, '-streamDate', 99);
$app.expandRecords(vods, ["vtubers"], null);
console.log(JSON.stringify(vods, null, 2))
return { vods };
} catch (e) {
console.error('error!', e.message);
if (e.message.match(/no rows/)) {
console.log('we are sending 404')
return response.html(404, 'VODs not found')
} else {
console.log('we are sending error 500')
return response.html(500, 'Unknown internal error while fetching vods')
}
}
};

View File

@ -0,0 +1,56 @@
<%#
index.ejs — RSS Feed Generator (RSS 2.0)
Expects: data.vods = [{ id, title, notes, thumbnail, streamDate }]
%>
<%
response.header("Content-Type", "application/rss+xml")
// Build RSS XML as a string
let rss = `<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Futureporn.net</title>
<link>https://futureporn.net</link>
<description>Dedication to the preservaton of lewdtuber history</description>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<docs>https://validator.w3.org/feed/docs/rss2.html</docs>
<generator> </generator>
<language>en</language>
<image>
<title>Futureporn.net</title>
<url>https://futureporn.net/images/futureporn-icon.png</url>
<link>https://futureporn.net</link>
</image>`;
for (const vod of data.vods) {
const url = `${env('ORIGIN')}/vods/${vod.id}`;
rss += `
<item>
<title><![CDATA[${
((vod?.get('expand')?.vtubers?.map(vt => vt.get('displayName')) || []).join(', ')
|| vod.get('streamDate'))
}]]></title>
<link>${url}</link>
<guid>${url}</guid>
<pubDate>${vod.get('streamDate')}</pubDate>`;
if (vod.notes) {
rss += `
<description><![CDATA[${vod.notes}]]></description>`;
}
if (vod.thumbnail) {
rss += `
<enclosure url="${vod.thumbnail}" length="0" type="image/png" />`;
}
rss += `
</item>`;
}
rss += `
</channel>
</rss>`;
// Send the RSS
response.html(200, rss);
%>

View File

@ -0,0 +1 @@
require('pocketpages')

View File

@ -0,0 +1,79 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "date3986823616",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url3307693472",
"name": "announceUrl",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_144770472",
"indexes": [],
"listRule": null,
"name": "vod",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472");
return app.delete(collection);
})

View File

@ -0,0 +1,57 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_3872109612",
"indexes": [],
"listRule": null,
"name": "vtuber",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612");
return app.delete(collection);
})

View File

@ -0,0 +1,48 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1731158936",
"max": 0,
"min": 0,
"name": "displayName",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(2, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2560465762",
"max": 0,
"min": 0,
"name": "slug",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("text1731158936")
// remove field
collection.fields.removeById("text2560465762")
return app.save(collection)
})

View File

@ -0,0 +1,352 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "pbc_144770472",
"hidden": false,
"id": "relation3825607268",
"maxSelect": 999,
"minSelect": 0,
"name": "vods",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
// add field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "file3309110367",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "image",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(5, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1288386755",
"name": "chaturbate",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(6, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url376077238",
"name": "twitter",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(7, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2042351438",
"name": "patreon",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(8, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1928100768",
"name": "twitch",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(9, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url847873150",
"name": "tiktok",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(10, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1505654846",
"name": "onlyfans",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(11, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url4034435380",
"name": "youtube",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(12, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url3056519709",
"name": "linktree",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(13, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2609473773",
"name": "carrd",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(14, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2799872019",
"name": "fansly",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(15, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1736283674",
"name": "pornhub",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(16, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1322392000",
"name": "discord",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(17, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url3191313207",
"name": "reddit",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(18, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url3425232140",
"name": "throne",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(19, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url2225635011",
"name": "instagram",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(20, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url1802815712",
"name": "facebook",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(21, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url4055117536",
"name": "merch",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(22, new Field({
"convertURLs": false,
"hidden": false,
"id": "editor1843675174",
"maxSize": 0,
"name": "description",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
}))
// add field
collection.fields.addAt(23, new Field({
"hidden": false,
"id": "number2960516043",
"max": null,
"min": null,
"name": "themeColor",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("relation3825607268")
// remove field
collection.fields.removeById("file3309110367")
// remove field
collection.fields.removeById("url1288386755")
// remove field
collection.fields.removeById("url376077238")
// remove field
collection.fields.removeById("url2042351438")
// remove field
collection.fields.removeById("url1928100768")
// remove field
collection.fields.removeById("url847873150")
// remove field
collection.fields.removeById("url1505654846")
// remove field
collection.fields.removeById("url4034435380")
// remove field
collection.fields.removeById("url3056519709")
// remove field
collection.fields.removeById("url2609473773")
// remove field
collection.fields.removeById("url2799872019")
// remove field
collection.fields.removeById("url1736283674")
// remove field
collection.fields.removeById("url1322392000")
// remove field
collection.fields.removeById("url3191313207")
// remove field
collection.fields.removeById("url3425232140")
// remove field
collection.fields.removeById("url2225635011")
// remove field
collection.fields.removeById("url1802815712")
// remove field
collection.fields.removeById("url4055117536")
// remove field
collection.fields.removeById("editor1843675174")
// remove field
collection.fields.removeById("number2960516043")
return app.save(collection)
})

View File

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"name": "vods"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"name": "vod"
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// update collection data
unmarshal({
"name": "vtubers"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// update collection data
unmarshal({
"name": "vtuber"
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,139 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation1668006755",
"maxSelect": 1,
"minSelect": 0,
"name": "uploader",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
// add field
collection.fields.addAt(4, new Field({
"convertURLs": false,
"hidden": false,
"id": "editor18589324",
"maxSize": 0,
"name": "notes",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
}))
// add field
collection.fields.addAt(5, new Field({
"hidden": false,
"id": "json2596236420",
"maxSize": 0,
"name": "segmentKeys",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// add field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "file3785949232",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "sourceVideo",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "file651090729",
"maxSelect": 99,
"maxSize": 0,
"mimeTypes": [],
"name": "segments",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "file3277268710",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "thumbnail",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(9, new Field({
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"ordering",
"pending",
"approved",
"rejected",
"processing",
"processed"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("relation1668006755")
// remove field
collection.fields.removeById("editor18589324")
// remove field
collection.fields.removeById("json2596236420")
// remove field
collection.fields.removeById("file3785949232")
// remove field
collection.fields.removeById("file651090729")
// remove field
collection.fields.removeById("file3277268710")
// remove field
collection.fields.removeById("select2063623452")
return app.save(collection)
})

View File

@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(10, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3872109612",
"hidden": false,
"id": "relation3093501878",
"maxSelect": 999,
"minSelect": 0,
"name": "vtubers",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("relation3093501878")
return app.save(collection)
})

View File

@ -0,0 +1,46 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("number2960516043")
// add field
collection.fields.addAt(23, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2960516043",
"max": 0,
"min": 0,
"name": "themeColor",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(23, new Field({
"hidden": false,
"id": "number2960516043",
"max": null,
"min": null,
"name": "themeColor",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// remove field
collection.fields.removeById("text2960516043")
return app.save(collection)
})

View File

@ -0,0 +1,124 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("file3785949232")
// remove field
collection.fields.removeById("file651090729")
// remove field
collection.fields.removeById("file3277268710")
// add field
collection.fields.addAt(8, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3785949232",
"max": 0,
"min": 0,
"name": "sourceVideo",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(9, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text651090729",
"max": 0,
"min": 0,
"name": "segments",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(10, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3277268710",
"max": 0,
"min": 0,
"name": "thumbnail",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "file3785949232",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "sourceVideo",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "file651090729",
"maxSelect": 99,
"maxSize": 0,
"mimeTypes": [],
"name": "segments",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "file3277268710",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "thumbnail",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
// remove field
collection.fields.removeById("text3785949232")
// remove field
collection.fields.removeById("text651090729")
// remove field
collection.fields.removeById("text3277268710")
return app.save(collection)
})

View File

@ -0,0 +1,67 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(11, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3889096196",
"max": 0,
"min": 0,
"name": "muxAssetId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(12, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1061856600",
"max": 0,
"min": 0,
"name": "muxPlaybackId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(13, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1805914144",
"max": 0,
"min": 0,
"name": "ipfsCid",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text3889096196")
// remove field
collection.fields.removeById("text1061856600")
// remove field
collection.fields.removeById("text1805914144")
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(14, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1646005928",
"max": 0,
"min": 0,
"name": "videoSrcB2",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text1646005928")
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(15, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1642358089",
"max": 0,
"min": 0,
"name": "muxPlaybackToken",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text1642358089")
return app.save(collection)
})

View File

@ -0,0 +1,24 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"enabled": true
}
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"enabled": false
}
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,34 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "select66088533",
"maxSelect": 1,
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"free",
"tier1",
"tier2",
"tier3",
"tier4",
"tier5",
"tier6"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("select66088533")
return app.save(collection)
})

View File

@ -0,0 +1,48 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("select66088533")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool66088533",
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "select66088533",
"maxSelect": 1,
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"free",
"tier1",
"tier2",
"tier3",
"tier4",
"tier5",
"tier6"
]
}))
// remove field
collection.fields.removeById("bool66088533")
return app.save(collection)
})

View File

@ -0,0 +1,38 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("bool66088533")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool3858055773",
"name": "patron",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool66088533",
"name": "supporter",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
// remove field
collection.fields.removeById("bool3858055773")
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(9, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1335658467",
"max": 0,
"min": 0,
"name": "patreonId",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("text1335658467")
return app.save(collection)
})

View File

@ -0,0 +1,58 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_4199263681");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "json3486377887",
"maxSize": 1,
"name": "pending_tasks",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "json907408884",
"maxSize": 1,
"name": "failed_tasks",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}
],
"id": "pbc_4199263681",
"indexes": [],
"listRule": null,
"name": "queue_stats",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT queue as id,\n SUM(CASE WHEN failed IS '' THEN 1 ELSE 0 END) as pending_tasks,\n SUM(CASE WHEN failed IS NOT '' THEN 1 ELSE 0 END) as failed_tasks\n FROM queue_tasks\n GROUP BY queue;",
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,87 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1488669058");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1797306934",
"max": 0,
"min": 0,
"name": "worker_id",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_4107420034",
"hidden": false,
"id": "relation1384045349",
"maxSelect": 1,
"minSelect": 0,
"name": "task",
"presentable": false,
"required": true,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1488669058",
"indexes": [
"CREATE UNIQUE INDEX idx_HSFtKfUArr ON queue_locks (task)",
"CREATE INDEX idx_bn5iw0BzOm ON queue_locks (created)"
],
"listRule": null,
"name": "queue_locks",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,109 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_4107420034");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2147319651",
"max": 0,
"min": 0,
"name": "queue",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "json1384045349",
"maxSize": 2000000,
"name": "task",
"presentable": false,
"required": false,
"system": false,
"type": "json"
},
{
"hidden": false,
"id": "date2659951479",
"max": "",
"min": "",
"name": "failed",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text4016436606",
"max": 0,
"min": 0,
"name": "failed_reason",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_4107420034",
"indexes": [
"CREATE INDEX idx_DjQFazUd5h ON queue_tasks (\n queue,\n failed,\n created\n)",
"CREATE INDEX idx_pUcaFfpCFF ON queue_tasks (\n failed,\n updated\n)"
],
"listRule": null,
"name": "queue_tasks",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,30 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"mappedFields": {
"avatarURL": "",
"id": "patreonId"
}
}
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"oauth2": {
"mappedFields": {
"avatarURL": "avatar",
"id": ""
}
}
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// add field
collection.fields.addAt(10, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3341984971",
"max": 0,
"min": 0,
"name": "patreonAccessToken",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// remove field
collection.fields.removeById("text3341984971")
return app.save(collection)
})

View File

@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// remove field
collection.fields.removeById("relation3825607268")
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// add field
collection.fields.addAt(3, new Field({
"cascadeDelete": false,
"collectionId": "pbc_144770472",
"hidden": false,
"id": "relation3825607268",
"maxSelect": 999,
"minSelect": 0,
"name": "vods",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
})

View File

@ -0,0 +1,48 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// remove field
collection.fields.removeById("text3277268710")
// add field
collection.fields.addAt(15, new Field({
"hidden": false,
"id": "file3277268710",
"maxSelect": 1,
"maxSize": 0,
"mimeTypes": [],
"name": "thumbnail",
"presentable": false,
"protected": false,
"required": false,
"system": false,
"thumbs": [],
"type": "file"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// add field
collection.fields.addAt(10, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3277268710",
"max": 0,
"min": 0,
"name": "thumbnail",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// remove field
collection.fields.removeById("file3277268710")
return app.save(collection)
})

View File

@ -0,0 +1,49 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "_clone_oC8t",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}
],
"id": "pbc_3009055234",
"indexes": [],
"listRule": null,
"name": "vtuber_vods",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT\n vods.id,\n vods.streamDate\nFROM vods\nLEFT JOIN json_each(vods.vtubers) ON json_each.value = 'el_xox'",
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3009055234");
return app.delete(collection);
})

View File

@ -0,0 +1,52 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3009055234")
// update collection data
unmarshal({
"viewQuery": "SELECT\n vods.id,\n vods.streamDate\nFROM vods\nLEFT JOIN json_each(vods.vtubers) ON json_each.value = 'udqmxs649ajf2mk'"
}, collection)
// remove field
collection.fields.removeById("_clone_oC8t")
// add field
collection.fields.addAt(1, new Field({
"hidden": false,
"id": "_clone_1BVl",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3009055234")
// update collection data
unmarshal({
"viewQuery": "SELECT\n vods.id,\n vods.streamDate\nFROM vods\nLEFT JOIN json_each(vods.vtubers) ON json_each.value = 'el_xox'"
}, collection)
// add field
collection.fields.addAt(1, new Field({
"hidden": false,
"id": "_clone_oC8t",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}))
// remove field
collection.fields.removeById("_clone_1BVl")
return app.save(collection)
})

View File

@ -0,0 +1,71 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3009055234")
// update collection data
unmarshal({
"viewQuery": "SELECT\n vods.id,\n vods.streamDate,\n vtubers.displayName\nFROM vods\nLEFT JOIN vtubers"
}, collection)
// remove field
collection.fields.removeById("_clone_1BVl")
// add field
collection.fields.addAt(1, new Field({
"hidden": false,
"id": "_clone_h4Aw",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}))
// add field
collection.fields.addAt(2, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_yNn2",
"max": 0,
"min": 0,
"name": "displayName",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3009055234")
// update collection data
unmarshal({
"viewQuery": "SELECT\n vods.id,\n vods.streamDate\nFROM vods\nLEFT JOIN json_each(vods.vtubers) ON json_each.value = 'udqmxs649ajf2mk'"
}, collection)
// add field
collection.fields.addAt(1, new Field({
"hidden": false,
"id": "_clone_1BVl",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}))
// remove field
collection.fields.removeById("_clone_h4Aw")
// remove field
collection.fields.removeById("_clone_yNn2")
return app.save(collection)
})

View File

@ -0,0 +1,63 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3009055234");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3208210256",
"max": 0,
"min": 0,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"hidden": false,
"id": "_clone_h4Aw",
"max": "",
"min": "",
"name": "streamDate",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "_clone_yNn2",
"max": 0,
"min": 0,
"name": "displayName",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}
],
"id": "pbc_3009055234",
"indexes": [],
"listRule": null,
"name": "vtuber_vods",
"system": false,
"type": "view",
"updateRule": null,
"viewQuery": "SELECT\n vods.id,\n vods.streamDate,\n vtubers.displayName\nFROM vods\nLEFT JOIN vtubers",
"viewRule": null
});
return app.save(collection);
})

View File

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"listRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"listRule": null
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,22 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"listRule": null,
"viewRule": null
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"listRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,22 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"listRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_144770472")
// update collection data
unmarshal({
"listRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View File

@ -0,0 +1,22 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// update collection data
unmarshal({
"listRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3872109612")
// update collection data
unmarshal({
"listRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

View File

@ -0,0 +1 @@
// hello

View File

@ -0,0 +1,957 @@
{
"name": "pocketpages-plugin-patreon",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pocketpages-plugin-patreon",
"version": "0.0.1",
"license": "Unlicense",
"dependencies": {
"pocketpages-plugin-js-sdk": "0.2.0"
},
"devDependencies": {
"tsdown": "^0.15.12",
"typescript": "^5.7.3"
}
},
"node_modules/@babel/generator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz",
"integrity": "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz",
"integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz",
"integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@oxc-project/types": {
"version": "0.95.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.95.0.tgz",
"integrity": "sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@quansync/fs": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.5.tgz",
"integrity": "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"quansync": "^0.2.11"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.45.tgz",
"integrity": "sha512-bfgKYhFiXJALeA/riil908+2vlyWGdwa7Ju5S+JgWZYdR4jtiPOGdM6WLfso1dojCh+4ZWeiTwPeV9IKQEX+4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.45.tgz",
"integrity": "sha512-xjCv4CRVsSnnIxTuyH1RDJl5OEQ1c9JYOwfDAHddjJDxCw46ZX9q80+xq7Eok7KC4bRSZudMJllkvOKv0T9SeA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.45.tgz",
"integrity": "sha512-ddcO9TD3D/CLUa/l8GO8LHzBOaZqWg5ClMy3jICoxwCuoz47h9dtqPsIeTiB6yR501LQTeDsjA4lIFd7u3Ljfw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.45.tgz",
"integrity": "sha512-MBTWdrzW9w+UMYDUvnEuh0pQvLENkl2Sis15fHTfHVW7ClbGuez+RWopZudIDEGkpZXdeI4CkRXk+vdIIebrmg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.45.tgz",
"integrity": "sha512-4YgoCFiki1HR6oSg+GxxfzfnVCesQxLF1LEnw9uXS/MpBmuog0EOO2rYfy69rWP4tFZL9IWp6KEfGZLrZ7aUog==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.45.tgz",
"integrity": "sha512-LE1gjAwQRrbCOorJJ7LFr10s5vqYf5a00V5Ea9wXcT2+56n5YosJkcp8eQ12FxRBv2YX8dsdQJb+ZTtYJwb6XQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.45.tgz",
"integrity": "sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.45.tgz",
"integrity": "sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.45.tgz",
"integrity": "sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.45.tgz",
"integrity": "sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.45.tgz",
"integrity": "sha512-ZJDB7lkuZE9XUnWQSYrBObZxczut+8FZ5pdanm8nNS1DAo8zsrPuvGwn+U3fwU98WaiFsNrA4XHngesCGr8tEQ==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^1.0.7"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.45.tgz",
"integrity": "sha512-zyzAjItHPUmxg6Z8SyRhLdXlJn3/D9KL5b9mObUrBHhWS/GwRH4665xCiFqeuktAhhWutqfc+rOV2LjK4VYQGQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-ia32-msvc": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.45.tgz",
"integrity": "sha512-wODcGzlfxqS6D7BR0srkJk3drPwXYLu7jPHN27ce2c4PUnVVmJnp9mJzUQGT4LpmHmmVdMZ+P6hKvyTGBzc1CA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.45.tgz",
"integrity": "sha512-wiU40G1nQo9rtfvF9jLbl79lUgjfaD/LTyUEw2Wg/gdF5OhjzpKMVugZQngO+RNdwYaNj+Fs+kWBWfp4VXPMHA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.45.tgz",
"integrity": "sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/ansis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz",
"integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
}
},
"node_modules/ast-kit": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.1.3.tgz",
"integrity": "sha512-TH+b3Lv6pUjy/Nu0m6A2JULtdzLpmqF9x1Dhj00ZoEiML8qvVA9j1flkzTKNYgdEhWrjDwtWNpyyCUbfQe514g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.4",
"pathe": "^2.0.3"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/birpc": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz",
"integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"dev": true,
"license": "MIT"
},
"node_modules/diff": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dts-resolver": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-2.1.2.tgz",
"integrity": "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.18.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
},
"peerDependencies": {
"oxc-resolver": ">=11.0.0"
},
"peerDependenciesMeta": {
"oxc-resolver": {
"optional": true
}
}
},
"node_modules/empathic": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/get-tsconfig": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pocketbase-js-sdk-jsvm": {
"version": "0.25.10004",
"resolved": "https://registry.npmjs.org/pocketbase-js-sdk-jsvm/-/pocketbase-js-sdk-jsvm-0.25.10004.tgz",
"integrity": "sha512-/0RkFa6X4LeKMdv4KCXhC5i2cvpG0elvXInRyk8bApjG8jyyPiNDL6o9VBvgxjl6j8/HcQYf3oKYXmZteqAFvQ==",
"license": "MIT"
},
"node_modules/pocketpages-plugin-js-sdk": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/pocketpages-plugin-js-sdk/-/pocketpages-plugin-js-sdk-0.2.0.tgz",
"integrity": "sha512-/uY1nCz7Zt19C/kyoOraH5ekTZa4PLPpHd0qIEoyf1XBPtEpHL4k7jZE0IPEMu1dH/8RfsKb/YzN52GR+9DdzQ==",
"dependencies": {
"pocketbase-js-sdk-jsvm": "^0.25.10004"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rolldown": {
"version": "1.0.0-beta.45",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.45.tgz",
"integrity": "sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.95.0",
"@rolldown/pluginutils": "1.0.0-beta.45"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.0-beta.45",
"@rolldown/binding-darwin-arm64": "1.0.0-beta.45",
"@rolldown/binding-darwin-x64": "1.0.0-beta.45",
"@rolldown/binding-freebsd-x64": "1.0.0-beta.45",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.45",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.45",
"@rolldown/binding-linux-arm64-musl": "1.0.0-beta.45",
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.45",
"@rolldown/binding-linux-x64-musl": "1.0.0-beta.45",
"@rolldown/binding-openharmony-arm64": "1.0.0-beta.45",
"@rolldown/binding-wasm32-wasi": "1.0.0-beta.45",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.45",
"@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.45",
"@rolldown/binding-win32-x64-msvc": "1.0.0-beta.45"
}
},
"node_modules/rolldown-plugin-dts": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.17.3.tgz",
"integrity": "sha512-8mGnNUVNrqEdTnrlcaDxs4sAZg0No6njO+FuhQd4L56nUbJO1tHxOoKDH3mmMJg7f/BhEj/1KjU5W9kZ9zM/kQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.28.5",
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"ast-kit": "^2.1.3",
"birpc": "^2.6.1",
"debug": "^4.4.3",
"dts-resolver": "^2.1.2",
"get-tsconfig": "^4.13.0",
"magic-string": "^0.30.21"
},
"engines": {
"node": ">=20.18.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
},
"peerDependencies": {
"@ts-macro/tsc": "^0.3.6",
"@typescript/native-preview": ">=7.0.0-dev.20250601.1",
"rolldown": "^1.0.0-beta.44",
"typescript": "^5.0.0",
"vue-tsc": "~3.1.0"
},
"peerDependenciesMeta": {
"@ts-macro/tsc": {
"optional": true
},
"@typescript/native-preview": {
"optional": true
},
"typescript": {
"optional": true
},
"vue-tsc": {
"optional": true
}
}
},
"node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tinyexec": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
"dev": true,
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"license": "MIT",
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/tsdown": {
"version": "0.15.12",
"resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.15.12.tgz",
"integrity": "sha512-c8VLlQm8/lFrOAg5VMVeN4NAbejZyVQkzd+ErjuaQgJFI/9MhR9ivr0H/CM7UlOF1+ELlF6YaI7sU/4itgGQ8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansis": "^4.2.0",
"cac": "^6.7.14",
"chokidar": "^4.0.3",
"debug": "^4.4.3",
"diff": "^8.0.2",
"empathic": "^2.0.0",
"hookable": "^5.5.3",
"rolldown": "1.0.0-beta.45",
"rolldown-plugin-dts": "^0.17.2",
"semver": "^7.7.3",
"tinyexec": "^1.0.1",
"tinyglobby": "^0.2.15",
"tree-kill": "^1.2.2",
"unconfig": "^7.3.3"
},
"bin": {
"tsdown": "dist/run.mjs"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
},
"peerDependencies": {
"@arethetypeswrong/core": "^0.18.1",
"publint": "^0.3.0",
"typescript": "^5.0.0",
"unplugin-lightningcss": "^0.4.0",
"unplugin-unused": "^0.5.0",
"unrun": "^0.2.1"
},
"peerDependenciesMeta": {
"@arethetypeswrong/core": {
"optional": true
},
"publint": {
"optional": true
},
"typescript": {
"optional": true
},
"unplugin-lightningcss": {
"optional": true
},
"unplugin-unused": {
"optional": true
},
"unrun": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/unconfig": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.3.3.tgz",
"integrity": "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@quansync/fs": "^0.1.5",
"defu": "^6.1.4",
"jiti": "^2.5.1",
"quansync": "^0.2.11"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "pocketpages-plugin-patreon",
"version": "0.0.1",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsdown",
"dev": "tsdown --watch"
},
"dependencies": {
"pocketpages-plugin-js-sdk": "0.2.0"
},
"devDependencies": {
"tsdown": "^0.15.12",
"typescript": "^5.7.3"
},
"license": "Unlicense"
}

View File

@ -0,0 +1,40 @@
import { type ExtendContextApiContext } from 'pocketpages'
import PocketBase, {
AuthModel,
RecordAuthResponse,
} from 'pocketbase-js-sdk-jsvm'
import { type OAuth2SignInOptions } from 'pocketpages-plugin-auth'
import { PluginFactory } from 'pocketpages'
import { OAuth2ProviderInfo } from 'pocketpages-plugin-auth'
const authPluginFactory: PluginFactory = (config) => {
return {
name: 'patreon',
onExtendContextApi(context: ExtendContextApiContext) {
const { api } = context;
const pb = () => api.pb() as PocketBase
api.savePatreonData = (
authData: AuthModel
) => {
pb.collection('users')
},
api.getPatreonUser = (
authData: AuthModel,
) => {
console.log("AYO we got authData as follo");
console.log(JSON.stringify(authData, null, 2));
// make a request to Patreon
}
},
}
}
export default authPluginFactory;

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"types": [
"pocketbase-jsvm"
],
"strict": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true,
"noImplicitAny": true,
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": [
"src/**/*.ts"
]
}

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: {
index: 'src/index.ts',
},
format: ['cjs'],
clean: true,
outDir: 'dist',
})

View File

@ -0,0 +1,14 @@
[Service]
Type = simple
User = pb
Group = pb
LimitNOFILE = 4096
Restart = always
RestartSec = 5s
StandardOutput = append:/home/pb/pb/std.log
StandardError = append:/home/pb/pb/std.log
WorkingDirectory = /home/pb/pb
ExecStart = /home/pb/pb/pocketbase serve
[Install]
WantedBy = multi-user.target

View File

@ -0,0 +1,8 @@
#!/bin/bash
# This script must run from project root
npx @dotenvx/dotenvx run -f ./.env.development.local -- node ./utils/data_migrations/2025-10-30-v1-export.js ./utils/data_migrations
pocketbase import ./utils/data_migrations/vtubers.json vtubers
pocketbase import ./utils/data_migrations/vods.json vods

View File

@ -0,0 +1,172 @@
/**
* Migration Script: V1 json
* -----------------------------------
* This script migrates VTuber and VOD data from an old Postgres database (V1) into JSON files
* One JSON file per collection, suitable for importing using pocketbase import script
*
* Usage:
* - Ensure environment variables are configured for both databases:
* V1_DB_HOST Hostname of the V1 database (default: "localhost")
* V1_DB_PORT Port of the V1 database (default: "5444")
* V1_DB_USER Username for the V1 database (default: "postgres")
* V1_DB_PASS Password for the V1 database (default: "password")
* V1_DB_NAME Database name for V1 (default: "restoredb")
* DEFAULT_UPLOADER_ID
* An existing user ID in the V2 database that will be set as the uploader for all migrated records.
*
* Usage:
* Run with Node.js:
* $ npx @dotenvx/dotenvx run -f ./.env.local -- node ./2025-10-30-v1-export.js ../pb_data/
*
*/
import pg from 'pg';
import fs from 'fs';
import path from 'path';
const dataDir = process.argv[2]; // e.g., "../../pb_data/"
if (!dataDir) {
console.error("Please provide the output data directory as the first argument.");
process.exit(1);
}
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'
});
async function migrateVtubers() {
console.log('Migrating vtubers...');
const res = await v1.query(`SELECT * FROM vtubers`);
const vtubers = res.rows.map(vt => ({
slug: vt.slug,
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,
}));
console.log('vtubers sample', vtubers.at(0))
const outPath = path.join(dataDir, 'vtubers.json');
fs.writeFileSync(outPath, JSON.stringify(vtubers, null, 2), 'utf-8');
console.log(`Migrated ${res.rows.length} vtubers → ${outPath}`);
}
async function migrateVods() {
console.log('Migrating vods...');
const res = await v1.query(`SELECT * FROM vods`);
const vods = [];
for (const vod of res.rows) {
// Get linked vtubers
const vtuberLinks = await v1.query(
`SELECT vtuber_id FROM vods_vtuber_links WHERE vod_id = $1`,
[vod.id]
);
let vtuberIds = [];
if (vtuberLinks.rows.length > 0) {
const vtuberRes = await v1.query(
`SELECT id FROM vtubers WHERE id = ANY($1)`,
[vtuberLinks.rows.map(r => r.vtuber_id)]
);
vtuberIds = vtuberRes.rows.map(v => v.id).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]
);
// Get Mux asset and playback ID
const muxLink = await v1.query(
`SELECT m.asset_id, m.playback_id
FROM vods_mux_asset_links vmal
JOIN mux_assets m ON vmal.mux_asset_id = m.id
WHERE vmal.vod_id = $1 LIMIT 1`,
[vod.id]
);
const muxAssetId = muxLink.rows[0]?.asset_id || null;
const muxPlaybackId = muxLink.rows[0]?.playback_id || null;
// @chatgpt we need one more
// we need to populate videoSrcB2
// we want the cdn_url from b2_files
// we have to get the link from the vods_video_src_b_2_links table
vods.push({
streamDate: vod.date ?? new Date(),
notes: vod.note,
sourceVideo: (() => {
const u = videoSrcLink.rows[0]?.cdn_url || videoSrcLink.rows[0]?.url || null;
return u ? 'content' + new URL(u).pathname : null;
})(),
thumbnail: (() => {
const u = thumbLink.rows[0]?.cdn_url || thumbLink.rows[0]?.url || null;
return u ? 'content' + new URL(u).pathname : null;
})(),
ipfsCid: vod.video_src_hash,
videoSrcB2: videoSrcLink.rows[0]?.cdn_url || null,
muxAssetId,
muxPlaybackId,
});
}
console.log(vods.at(0))
const outPath = path.join(dataDir, 'vods.json');
fs.writeFileSync(outPath, JSON.stringify(vods, null, 2), 'utf-8');
console.log(`Migrated ${res.rows.length} vods → ${outPath}`);
}
async function main() {
try {
await migrateVtubers();
await migrateVods();
} catch (err) {
console.error(err);
} finally {
await v1.end();
}
}
main();

View File

@ -0,0 +1,64 @@
/**
* Migration Script: Assign missing VOD vtubers to "projektmelody"
* ---------------------------------------------------------------
* This script connects to a PocketBase instance and updates all VOD records
* that do not have a `vtuber` set, assigning them to the vtuber with slug "projektmelody".
*
* Environment variables:
* PB_URL Base URL of your PocketBase instance (e.g. "http://127.0.0.1:8090")
* PB_ADMIN_EMAIL PocketBase admin email
* PB_ADMIN_PASS PocketBase admin password
*
* Usage:
* $ npx @dotenvx/dotenvx run -f .env.local -- node ./2025-11-05-fix-vod-vtuber.js
*/
import PocketBase from 'pocketbase';
const pb = new PocketBase(process.env.PB_URL || 'http://127.0.0.1:8090');
async function main() {
console.log('Authenticating with PocketBase...');
await pb
.collection("_superusers")
.authWithPassword(process.env.PB_USERNAME, process.env.PB_PASSWORD);
console.log('Fetching vtuber "projektmelody"...');
const projekt = await pb.collection('vtubers').getFirstListItem('slug="projektmelody"');
if (!projekt) {
throw new Error('Could not find vtuber with slug "projektelody"');
}
console.log('Fetching VODs...');
let page = 1;
const perPage = 50;
let updatedCount = 0;
while (true) {
const vods = await pb.collection('vods').getList(page, perPage);
if (vods.items.length === 0) break;
for (const vod of vods.items) {
// Only update if vtuber is missing or empty
if (!vod.vtubers || vod.vtubers.length === 0) {
await pb.collection('vods').update(vod.id, {
vtubers: [projekt.id],
});
console.log(`✅ Updated VOD ${vod.id} → projektmelody`);
updatedCount++;
}
}
if (vods.items.length < perPage) break;
page++;
}
console.log(`Done! Updated ${updatedCount} VODs that had no vtuber set.`);
pb.authStore.clear();
}
main().catch((err) => {
console.error('Migration failed:', err);
process.exit(1);
});

View File

@ -0,0 +1,21 @@
import PocketBase from 'pocketbase';
const pb = new PocketBase(process.env.PB_URL || 'http://127.0.0.1:8090');
async function main() {
console.log('Authenticating with PocketBase...');
await pb
.collection("_superusers")
.authWithPassword(process.env.PB_USERNAME, process.env.PB_PASSWORD);
const name = 'mirakink'
const vt = await pb.collection('vtubers').getFirstListItem(`slug="${name}"`);
if (!vt) {
throw new Error(`Could not find vtuber with slug "${name}"`);
}
console.log(`image internals as follows`)
console.log(vt)
}
main()

View File

@ -0,0 +1,118 @@
import PocketBase from 'pocketbase';
import { readFileSync } from 'node:fs';
import { basename, join } from 'node:path';
import spawn from 'nano-spawn';
import { tmpdir } from 'node:os';
import mime from 'mime';
const pb = new PocketBase(process.env.PB_URL || 'http://127.0.0.1:8090');
if (!process.env.PB_USERNAME) throw new Error('PB_USERNAME missing');
if (!process.env.PB_PASSWORD) throw new Error('PB_PASSWORD missing');
if (!process.env.B2_BUCKET_FROM) throw new Error('B2_BUCKET_FROM missing');
if (!process.env.V1_DATA_FILE) throw new Error('V1_DATA_FILE missing');
const vodsCollectionName = 'pbc_144770472';
// pbc_144770472 is the vods collection
// cv6m31vj98gmtsx is a sample vod id
async function main() {
console.log('Authenticating with PocketBase...');
await pb
.collection("_superusers")
.authWithPassword(process.env.PB_USERNAME, process.env.PB_PASSWORD);
// Use vod.streamDate to find the correct entry
// load v1 datafile
const v1VodDataRaw = readFileSync(process.env.V1_DATA_FILE, { encoding: 'utf-8' });
const v1VodData = JSON.parse(v1VodDataRaw);
// console.log('v1VodData sample', v1VodData[0])
// # BUILD A MANIFEST
// For each vod
// Get the expected pocketbase s3 key format, ex: `pbc_144770472/cv6m31vj98gmtsx/projektmelody-fansly-2025-09-17-thumb.png`
// Get the v1 thumbnail s3 key from the v1 export json
let manifest = [];
const vods = await pb.collection('vods').getFullList();
for (const vod of vods) {
// console.log('v2VodData sample', vod)
// console.log(`for this vod`, vod)
const v1Vod = v1VodData.find((vod1) => new Date(vod1.streamDate).getTime() === new Date(vod.streamDate).getTime());
console.log(`v1vod`, v1Vod)
if (!v1Vod) throw new Error(`failed to find matching v1 data vod for vod`);
// skip if there is no thumbnail in the v1 vod
if (!v1Vod.thumbnail) continue;
const inputS3Key = v1Vod.thumbnail.replace('content/', '');
const outputFile = join(tmpdir(), basename(inputS3Key));
const entry = {
vodId: vod.id,
inputS3Key,
outputFile,
};
manifest.push(entry);
}
const invalidVods = manifest.filter((m) => m.inputS3Key.includes('mp4'))
if (invalidVods.length > 0) {
console.error('invalid thumbnails found', invalidVods)
throw new Error('invalid. mp4s found in thumbnails');
}
console.log('manifest', manifest);
// # ACTUAL WORK
// Copy the file from B2_BUCKET_FROM to tmp file
// Update the pocketbase vod.thumbnail entry
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function retry(fn, retries = 6, delayMs = 500) {
let lastError;
for (let attempt = 1; attempt <= retries; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err;
console.warn(`Attempt ${attempt} failed: ${err}. Retrying in ${delayMs}ms...`);
if (attempt < retries) await sleep(delayMs);
}
}
throw lastError;
}
for (const [i, m] of manifest.entries()) {
var from = "b2://" + process.env.B2_BUCKET_FROM + "/" + m.inputS3Key;
var to = m.outputFile;
var vodId = m.vodId;
console.log("processing thumbnail " + i + ". " + from + " -> " + to + " (vodId=" + vodId + ")");
// Retry the download
await retry(function () {
return spawn('b2', ['file', 'download', from, to]);
});
// Retry the PocketBase upload
await retry(async function () {
var fileData = readFileSync(m.outputFile);
var mimetype = mime.getType(m.outputFile) || undefined;
var file = new File([fileData], basename(m.outputFile), { type: mimetype });
var form = new FormData();
form.set("thumbnail", file);
return pb.collection('vods').update(vodId, form);
});
}
console.log("All done.");
}
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
[
{
"slug": "projektmelody",
"displayName": "ProjektMelody",
"chaturbate": "https://chaturbate.com/projektmelody",
"twitter": "https://twitter.com/projektmelody",
"patreon": "https://www.patreon.com/projektmelody",
"twitch": "https://twitch.tv/projektmelody",
"tiktok": "https://www.tiktok.com/@realprojektmelody",
"onlyfans": "https://onlyfans.com/projektbutt",
"youtube": "https://www.youtube.com/projektmelodyofficial",
"linktree": "https://linktr.ee/projektmelody",
"carrd": null,
"fansly": "https://fansly.com/r/scienceteam",
"pornhub": "https://www.pornhub.com/model/projekt-melody",
"discord": null,
"reddit": "https://www.reddit.com/r/projektmelody/",
"throne": "https://throne.com/realprojektmelody",
"instagram": null,
"facebook": null,
"merch": "https://melody.vshojo.com/",
"description": "Also known as, \"Daddy's little grandpa,\" ProjektMelody is the pioneering hentai cam model who embodies a unique blend of sweetness and perversion. She engages with her audience with remarkable patience and politeness, demonstrating her commitment to fostering an inclusive environment.\nOn her livestreams, ProjektMelody's demeanor can range from overtly sexual to adorably reserved and self-censored. Alongside her captivating presence, she actively promotes pro-social behavior, advocates for sexual education, and emphasizes the importance of kindness.",
"themeColor": "#5C23C0"
},
{
"slug": "el_xox",
"displayName": "el_XoX",
"chaturbate": "https://chaturbate.com/el_xox/",
"twitter": "https://twitter.com/el_XoX34",
"patreon": null,
"twitch": "https://www.twitch.tv/el_xox",
"tiktok": null,
"onlyfans": null,
"youtube": null,
"linktree": null,
"carrd": "https://elxox.carrd.co/",
"fansly": null,
"pornhub": null,
"discord": null,
"reddit": null,
"throne": null,
"instagram": null,
"facebook": null,
"merch": "https://elxox34.com/pages/limited-merch",
"description": null,
"themeColor": "#353FFF"
},
{
"slug": "vexruby",
"displayName": "Vexruby",
"chaturbate": "https://chaturbate.com/vexruby",
"twitter": "https://x.com/vexxxruby",
"patreon": "https://www.patreon.com/ViRoClub",
"twitch": null,
"tiktok": null,
"onlyfans": null,
"youtube": null,
"linktree": null,
"carrd": null,
"fansly": null,
"pornhub": null,
"discord": null,
"reddit": null,
"throne": null,
"instagram": null,
"facebook": null,
"merch": null,
"description": null,
"themeColor": "#f882f5"
},
{
"slug": "athena_airis",
"displayName": "AIRIS ATHENA",
"chaturbate": "https://chaturbate.com/athena_airis/",
"twitter": "https://twitter.com/Athena_Airis",
"patreon": null,
"twitch": "https://www.twitch.tv/athena_airis",
"tiktok": "https://tiktok.com/@athena_airis",
"onlyfans": null,
"youtube": "https://www.youtube.com/channel/UClizqUqSuy26sO01qsPh5bw",
"linktree": "https://linktr.ee/athenaairis",
"carrd": null,
"fansly": "https://fansly.com/Athena-Airis",
"pornhub": null,
"discord": "https://discord.gg/eXPTgua",
"reddit": null,
"throne": null,
"instagram": "https://instagram.com/theathenaairis",
"facebook": null,
"merch": null,
"description": null,
"themeColor": "#dde1ec"
}
]

Some files were not shown because too many files have changed in this diff Show More