video support (#7)

real
This commit is contained in:
Lexie
2024-09-14 19:08:20 +02:00
committed by GitHub
parent 8dd4613997
commit 88dc790567
27 changed files with 1098 additions and 5 deletions

13
pkgs/api/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm i -g pnpm && pnpm install
COPY . .
RUN pnpm build
CMD [ "node", "dist/index.js" ]

View File

@@ -0,0 +1,10 @@
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "2598:3000"
environment:
- NODE_ENV=production
- PORT=3000

24
pkgs/api/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc"
},
"keywords": [],
"author": "Lexedia",
"license": "ISC",
"dependencies": {
"fastify": "^4.28.1",
"ffmpeg-static": "^5.2.0",
"fluent-ffmpeg": "^2.1.3"
},
"devDependencies": {
"@types/fluent-ffmpeg": "^2.1.26",
"@types/node": "^22.5.4",
"pino-pretty": "^11.2.2",
"typescript": "^5.6.2"
}
}

741
pkgs/api/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,741 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
fastify:
specifier: ^4.28.1
version: 4.28.1
ffmpeg-static:
specifier: ^5.2.0
version: 5.2.0
fluent-ffmpeg:
specifier: ^2.1.3
version: 2.1.3
devDependencies:
'@types/fluent-ffmpeg':
specifier: ^2.1.26
version: 2.1.26
'@types/node':
specifier: ^22.5.4
version: 22.5.4
pino-pretty:
specifier: ^11.2.2
version: 11.2.2
typescript:
specifier: ^5.6.2
version: 5.6.2
packages:
'@derhuerst/http-basic@8.2.4':
resolution: {integrity: sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==}
engines: {node: '>=6.0.0'}
'@fastify/ajv-compiler@3.6.0':
resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==}
'@fastify/error@3.4.1':
resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==}
'@fastify/fast-json-stringify-compiler@4.3.0':
resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==}
'@fastify/merge-json-schemas@0.1.1':
resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==}
'@types/fluent-ffmpeg@2.1.26':
resolution: {integrity: sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==}
'@types/node@10.17.60':
resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==}
'@types/node@22.5.4':
resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
abstract-logging@2.0.1:
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
ajv-formats@2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
async@0.2.10:
resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==}
atomic-sleep@1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
avvio@8.4.0:
resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
caseless@0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
concat-stream@2.0.0:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0}
cookie@0.6.0:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
fast-content-type-parse@1.1.0:
resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
fast-copy@3.0.2:
resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-json-stringify@5.16.1:
resolution: {integrity: sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==}
fast-querystring@1.1.2:
resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
fast-redact@3.5.0:
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
engines: {node: '>=6'}
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@2.4.0:
resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==}
fast-uri@3.0.1:
resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==}
fastify@4.28.1:
resolution: {integrity: sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==}
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
ffmpeg-static@5.2.0:
resolution: {integrity: sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==}
engines: {node: '>=16'}
find-my-way@8.2.0:
resolution: {integrity: sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==}
engines: {node: '>=14'}
fluent-ffmpeg@2.1.3:
resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==}
engines: {node: '>=18'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
http-response-object@3.0.2:
resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==}
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
json-schema-ref-resolver@1.0.1:
resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==}
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
light-my-request@5.13.0:
resolution: {integrity: sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
on-exit-leak-free@2.1.2:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
parse-cache-control@1.0.1:
resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==}
pino-abstract-transport@1.2.0:
resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
pino-pretty@11.2.2:
resolution: {integrity: sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A==}
hasBin: true
pino-std-serializers@7.0.0:
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
pino@9.4.0:
resolution: {integrity: sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==}
hasBin: true
process-warning@3.0.0:
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
process-warning@4.0.0:
resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
pump@3.0.2:
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
readable-stream@4.5.2:
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
ret@0.4.3:
resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==}
engines: {node: '>=10'}
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-regex2@3.1.0:
resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
semver@7.6.3:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
hasBin: true
set-cookie-parser@2.7.0:
resolution: {integrity: sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==}
sonic-boom@4.1.0:
resolution: {integrity: sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==}
split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
toad-cache@3.7.0:
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
engines: {node: '>=12'}
typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typescript@5.6.2:
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
snapshots:
'@derhuerst/http-basic@8.2.4':
dependencies:
caseless: 0.12.0
concat-stream: 2.0.0
http-response-object: 3.0.2
parse-cache-control: 1.0.1
'@fastify/ajv-compiler@3.6.0':
dependencies:
ajv: 8.17.1
ajv-formats: 2.1.1(ajv@8.17.1)
fast-uri: 2.4.0
'@fastify/error@3.4.1': {}
'@fastify/fast-json-stringify-compiler@4.3.0':
dependencies:
fast-json-stringify: 5.16.1
'@fastify/merge-json-schemas@0.1.1':
dependencies:
fast-deep-equal: 3.1.3
'@types/fluent-ffmpeg@2.1.26':
dependencies:
'@types/node': 22.5.4
'@types/node@10.17.60': {}
'@types/node@22.5.4':
dependencies:
undici-types: 6.19.8
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
abstract-logging@2.0.1: {}
agent-base@6.0.2:
dependencies:
debug: 4.3.7
transitivePeerDependencies:
- supports-color
ajv-formats@2.1.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
ajv-formats@3.0.1(ajv@8.17.1):
optionalDependencies:
ajv: 8.17.1
ajv@8.17.1:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.0.1
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
async@0.2.10: {}
atomic-sleep@1.0.0: {}
avvio@8.4.0:
dependencies:
'@fastify/error': 3.4.1
fastq: 1.17.1
base64-js@1.5.1: {}
buffer-from@1.1.2: {}
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
caseless@0.12.0: {}
colorette@2.0.20: {}
concat-stream@2.0.0:
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 3.6.2
typedarray: 0.0.6
cookie@0.6.0: {}
dateformat@4.6.3: {}
debug@4.3.7:
dependencies:
ms: 2.1.3
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
env-paths@2.2.1: {}
event-target-shim@5.0.1: {}
events@3.3.0: {}
fast-content-type-parse@1.1.0: {}
fast-copy@3.0.2: {}
fast-decode-uri-component@1.0.1: {}
fast-deep-equal@3.1.3: {}
fast-json-stringify@5.16.1:
dependencies:
'@fastify/merge-json-schemas': 0.1.1
ajv: 8.17.1
ajv-formats: 3.0.1(ajv@8.17.1)
fast-deep-equal: 3.1.3
fast-uri: 2.4.0
json-schema-ref-resolver: 1.0.1
rfdc: 1.4.1
fast-querystring@1.1.2:
dependencies:
fast-decode-uri-component: 1.0.1
fast-redact@3.5.0: {}
fast-safe-stringify@2.1.1: {}
fast-uri@2.4.0: {}
fast-uri@3.0.1: {}
fastify@4.28.1:
dependencies:
'@fastify/ajv-compiler': 3.6.0
'@fastify/error': 3.4.1
'@fastify/fast-json-stringify-compiler': 4.3.0
abstract-logging: 2.0.1
avvio: 8.4.0
fast-content-type-parse: 1.1.0
fast-json-stringify: 5.16.1
find-my-way: 8.2.0
light-my-request: 5.13.0
pino: 9.4.0
process-warning: 3.0.0
proxy-addr: 2.0.7
rfdc: 1.4.1
secure-json-parse: 2.7.0
semver: 7.6.3
toad-cache: 3.7.0
fastq@1.17.1:
dependencies:
reusify: 1.0.4
ffmpeg-static@5.2.0:
dependencies:
'@derhuerst/http-basic': 8.2.4
env-paths: 2.2.1
https-proxy-agent: 5.0.1
progress: 2.0.3
transitivePeerDependencies:
- supports-color
find-my-way@8.2.0:
dependencies:
fast-deep-equal: 3.1.3
fast-querystring: 1.1.2
safe-regex2: 3.1.0
fluent-ffmpeg@2.1.3:
dependencies:
async: 0.2.10
which: 1.3.1
forwarded@0.2.0: {}
help-me@5.0.0: {}
http-response-object@3.0.2:
dependencies:
'@types/node': 10.17.60
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.3.7
transitivePeerDependencies:
- supports-color
ieee754@1.2.1: {}
inherits@2.0.4: {}
ipaddr.js@1.9.1: {}
isexe@2.0.0: {}
joycon@3.1.1: {}
json-schema-ref-resolver@1.0.1:
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse@1.0.0: {}
light-my-request@5.13.0:
dependencies:
cookie: 0.6.0
process-warning: 3.0.0
set-cookie-parser: 2.7.0
minimist@1.2.8: {}
ms@2.1.3: {}
on-exit-leak-free@2.1.2: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
parse-cache-control@1.0.1: {}
pino-abstract-transport@1.2.0:
dependencies:
readable-stream: 4.5.2
split2: 4.2.0
pino-pretty@11.2.2:
dependencies:
colorette: 2.0.20
dateformat: 4.6.3
fast-copy: 3.0.2
fast-safe-stringify: 2.1.1
help-me: 5.0.0
joycon: 3.1.1
minimist: 1.2.8
on-exit-leak-free: 2.1.2
pino-abstract-transport: 1.2.0
pump: 3.0.2
readable-stream: 4.5.2
secure-json-parse: 2.7.0
sonic-boom: 4.1.0
strip-json-comments: 3.1.1
pino-std-serializers@7.0.0: {}
pino@9.4.0:
dependencies:
atomic-sleep: 1.0.0
fast-redact: 3.5.0
on-exit-leak-free: 2.1.2
pino-abstract-transport: 1.2.0
pino-std-serializers: 7.0.0
process-warning: 4.0.0
quick-format-unescaped: 4.0.4
real-require: 0.2.0
safe-stable-stringify: 2.5.0
sonic-boom: 4.1.0
thread-stream: 3.1.0
process-warning@3.0.0: {}
process-warning@4.0.0: {}
process@0.11.10: {}
progress@2.0.3: {}
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
pump@3.0.2:
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
quick-format-unescaped@4.0.4: {}
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
readable-stream@4.5.2:
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
real-require@0.2.0: {}
require-from-string@2.0.2: {}
ret@0.4.3: {}
reusify@1.0.4: {}
rfdc@1.4.1: {}
safe-buffer@5.2.1: {}
safe-regex2@3.1.0:
dependencies:
ret: 0.4.3
safe-stable-stringify@2.5.0: {}
secure-json-parse@2.7.0: {}
semver@7.6.3: {}
set-cookie-parser@2.7.0: {}
sonic-boom@4.1.0:
dependencies:
atomic-sleep: 1.0.0
split2@4.2.0: {}
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
strip-json-comments@3.1.1: {}
thread-stream@3.1.0:
dependencies:
real-require: 0.2.0
toad-cache@3.7.0: {}
typedarray@0.0.6: {}
typescript@5.6.2: {}
undici-types@6.19.8: {}
util-deprecate@1.0.2: {}
which@1.3.1:
dependencies:
isexe: 2.0.0
wrappy@1.0.2: {}

69
pkgs/api/src/index.ts Normal file
View File

@@ -0,0 +1,69 @@
import fastify from "fastify";
import { tsToMpeg4 } from "./utils";
const envToLogger = {
development: {
transport: {
target: "pino-pretty",
options: {
translateTime: "HH:MM:ss Z",
ignore: "pid,hostname",
},
},
},
production: true,
test: false,
};
type Env = keyof typeof envToLogger;
declare global {
namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: Env;
readonly PORT: string | undefined;
}
}
}
const { NODE_ENV: env = "development" } = process.env;
const app = fastify({ logger: envToLogger[env] });
app.get("/", async (_, res) => res.redirect("https://bskyx.app"));
app.get<{ Params: { "*": string } }>(
"/generate/*",
{
schema: {
params: {
type: "object",
properties: {
"*": { type: "string" },
},
required: ["*"],
},
},
},
async (req, res) => {
let url = req.params["*"];
if (url.endsWith(".mp4")) {
url = url.slice(0, -4);
}
url = decodeURIComponent(url);
const result = await fetch(url).then((res) => res.arrayBuffer());
const video = await tsToMpeg4(Buffer.from(result));
res.header("Content-Type", "video/mp4");
res.header("Cache-Control", "public, max-age=604800");
res.send(video);
}
);
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
app.listen({ port, host: "0.0.0.0" });

50
pkgs/api/src/utils.ts Normal file
View File

@@ -0,0 +1,50 @@
import ffmpeg from "fluent-ffmpeg";
import ffmpegPath from "ffmpeg-static";
import { PassThrough } from "node:stream";
import fs from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
ffmpeg.setFfmpegPath(ffmpegPath as string);
export function tsToMpeg4(buffer: Buffer | Uint8Array): Promise<Buffer> {
return new Promise((res, rej) => {
const input = new PassThrough();
input.end(buffer);
const tempFilePath = path.join(tmpdir(), `output-${Date.now()}.mp4`);
ffmpeg(input)
.outputOption(
"-c",
"copy",
"-movflags",
"faststart",
"-preset",
"ultrafast"
)
.on("end", async () => {
try {
const ob = await fs.readFile(tempFilePath);
await fs.unlink(tempFilePath);
res(ob);
} catch (e) {
rej(e);
}
})
.on("error", async (err, stdout, stderr) => {
console.error("Error:", err.message);
console.error("ffmpeg stdout:", stdout);
console.error("ffmpeg stderr:", stderr);
try {
await fs.unlink(tempFilePath);
} catch (e) {
rej(e);
}
rej(err);
})
.save(tempFilePath);
});
}

13
pkgs/api/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"module": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"moduleResolution": "NodeNext",
"outDir": "./dist",
},
"include": ["./src/**/*.ts"],
}

15
pkgs/app/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"configurations": [
{
"name": "Wrangler",
"type": "node",
"request": "attach",
"port": 8787,
"cwd": "/",
"resolveSourceMapLocations": null,
"attachExistingChildren": false,
"autoAttachChildProcesses": false,
"sourceMaps": true // works with or without this line
}
]
}

15
pkgs/app/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"scripts": {
"dev": "wrangler dev src/index.ts",
"deploy": "wrangler deploy --minify src/index.ts"
},
"dependencies": {
"@atproto/api": "^0.12.24",
"hono": "^4.5.1"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230628.0",
"typescript": "^5.1.6",
"wrangler": "^3.75.0"
}
}

991
pkgs/app/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,991 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@atproto/api':
specifier: ^0.12.24
version: 0.12.29
hono:
specifier: ^4.5.1
version: 4.5.11
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20230628.0
version: 4.20230710.1
typescript:
specifier: ^5.1.6
version: 5.1.6
wrangler:
specifier: ^3.75.0
version: 3.75.0(@cloudflare/workers-types@4.20230710.1)
packages:
'@atproto/api@0.12.29':
resolution: {integrity: sha512-PyzPLjGWR0qNOMrmj3Nt3N5NuuANSgOk/33Bu3j+rFjjPrHvk9CI6iQPU6zuDaDCoyOTRJRafw8X/aMQw+ilgw==}
'@atproto/common-web@0.3.0':
resolution: {integrity: sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA==}
'@atproto/lexicon@0.4.1':
resolution: {integrity: sha512-bzyr+/VHXLQWbumViX5L7h1NKQObfs8Z+XZJl43OUK8nYFUI4e/sW1IZKRNfw7Wvi5YVNK+J+yP3DWIBZhkCYA==}
'@atproto/syntax@0.3.0':
resolution: {integrity: sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==}
'@atproto/xrpc@0.5.0':
resolution: {integrity: sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog==}
'@cloudflare/kv-asset-handler@0.3.4':
resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==}
engines: {node: '>=16.13'}
'@cloudflare/workerd-darwin-64@1.20240821.1':
resolution: {integrity: sha512-CDBpfZKrSy4YrIdqS84z67r3Tzal2pOhjCsIb63IuCnvVes59/ft1qhczBzk9EffeOE2iTCrA4YBT7Sbn7USew==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20240821.1':
resolution: {integrity: sha512-Q+9RedvNbPcEt/dKni1oN94OxbvuNAeJkgHmrLFTGF8zu21wzOhVkQeRNxcYxrMa9mfStc457NAg13OVCj2kHQ==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-linux-64@1.20240821.1':
resolution: {integrity: sha512-j6z3KsPtawrscoLuP985LbqFrmsJL6q1mvSXOXTqXGODAHIzGBipHARdOjms3UQqovzvqB2lQaQsZtLBwCZxtA==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20240821.1':
resolution: {integrity: sha512-I9bHgZOxJQW0CV5gTdilyxzTG7ILzbTirehQWgfPx9X77E/7eIbR9sboOMgyeC69W4he0SKtpx0sYZuTJu4ERw==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-windows-64@1.20240821.1':
resolution: {integrity: sha512-keC97QPArs6LWbPejQM7/Y8Jy8QqyaZow4/ZdsGo+QjlOLiZRDpAenfZx3CBUoWwEeFwQTl2FLO+8hV1SWFFYw==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
'@cloudflare/workers-shared@0.4.1':
resolution: {integrity: sha512-nYh4r8JwOOjYIdH2zub++CmIKlkYFlpxI1nBHimoiHcytJXD/b7ldJ21TtfzUZMCgI78mxVlymMHA/ReaOxKlA==}
engines: {node: '>=16.7.0'}
'@cloudflare/workers-types@4.20230710.1':
resolution: {integrity: sha512-VqEY/ZqyHKBn6ivdePSWebpqojwbCXVEuwLkMYHs0UoOAqcGylkVcabdZYdQJKeNxXcOUZ9UBId/x9UsPUm2XQ==}
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@esbuild-plugins/node-globals-polyfill@0.2.3':
resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==}
peerDependencies:
esbuild: '*'
'@esbuild-plugins/node-modules-polyfill@0.2.2':
resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==}
peerDependencies:
esbuild: '*'
'@esbuild/android-arm64@0.17.19':
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.17.19':
resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.17.19':
resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.17.19':
resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.17.19':
resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.17.19':
resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.17.19':
resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.17.19':
resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.17.19':
resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.17.19':
resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.17.19':
resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.17.19':
resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.17.19':
resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.17.19':
resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.17.19':
resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.17.19':
resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.17.19':
resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.17.19':
resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.17.19':
resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.17.19':
resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.17.19':
resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.17.19':
resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@fastify/busboy@2.1.1':
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
acorn-walk@8.2.0:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'}
hasBin: true
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
as-table@1.0.55:
resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==}
await-lock@2.2.2:
resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==}
binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
capnp-ts@0.7.0:
resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==}
chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
cookie@0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
data-uri-to-buffer@2.0.2:
resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
esbuild@0.17.19:
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
engines: {node: '>=12'}
hasBin: true
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
estree-walker@0.6.1:
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
exit-hook@2.2.1:
resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
engines: {node: '>=6'}
fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
get-source@2.0.12:
resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hono@4.5.11:
resolution: {integrity: sha512-62FcjLPtjAFwISVBUshryl+vbHOjg8rE4uIK/dxyR8GpLztunZpwFmfEvmJCUI7xoGh/Sr3CGCDPCmYxVw7wUQ==}
engines: {node: '>=16.0.0'}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-core-module@2.15.1:
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
engines: {node: '>= 0.4'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
iso-datestring-validator@2.2.2:
resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==}
magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
miniflare@3.20240821.1:
resolution: {integrity: sha512-81qdiryDG7VXzZuoa0EwhkaIYYrn7+StRIrd/2i7SPqPUNICUBjbhFFKqTnvE1+fqIPPB6l8ShKFaFvmnZOASg==}
engines: {node: '>=16.13'}
hasBin: true
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
multiformats@9.9.0:
resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==}
mustache@4.2.0:
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
hasBin: true
nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
ohash@1.1.3:
resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-to-regexp@6.2.1:
resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
printable-characters@1.0.42:
resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
resolve.exports@2.0.2:
resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==}
engines: {node: '>=10'}
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
rollup-plugin-inject@3.0.2:
resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==}
deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.
rollup-plugin-node-polyfills@0.2.1:
resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==}
rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
selfsigned@2.1.1:
resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==}
engines: {node: '>=10'}
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
deprecated: Please use @jridgewell/sourcemap-codec instead
stacktracey@2.1.8:
resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
stoppable@1.1.0:
resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==}
engines: {node: '>=4', npm: '>=6'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tlds@1.240.0:
resolution: {integrity: sha512-1OYJQenswGZSOdRw7Bql5Qu7uf75b+F3HFBXbqnG/ifHa0fev1XcG+3pJf3pA/KC6RtHQzfKgIf1vkMlMG7mtQ==}
hasBin: true
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
tslib@2.6.0:
resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==}
typescript@5.1.6:
resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
engines: {node: '>=14.17'}
hasBin: true
ufo@1.5.4:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
uint8arrays@3.0.0:
resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==}
undici@5.28.4:
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
engines: {node: '>=14.0'}
unenv-nightly@2.0.0-1724863496.70db6f1:
resolution: {integrity: sha512-r+VIl1gnsI4WQxluruSQhy8alpAf1AsLRLm4sEKp3otCyTIVD6I6wHEYzeQnwsyWgaD4+3BD4A/eqrgOpdTzhw==}
workerd@1.20240821.1:
resolution: {integrity: sha512-y4phjCnEG96u8ZkgkkHB+gSw0i6uMNo23rBmixylWpjxDklB+LWD8dztasvsu7xGaZbLoTxQESdEw956F7VJDA==}
engines: {node: '>=16'}
hasBin: true
wrangler@3.75.0:
resolution: {integrity: sha512-CitNuNj0O1z6qbonUXmpUbxeWpU3nx28Kc4ZT33tMdeooQssb063Ie7+ZCdfS3kPhRHSwGdtOV22xFYytHON8w==}
engines: {node: '>=16.17.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20240821.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xxhash-wasm@1.0.2:
resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==}
youch@3.2.3:
resolution: {integrity: sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw==}
zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
snapshots:
'@atproto/api@0.12.29':
dependencies:
'@atproto/common-web': 0.3.0
'@atproto/lexicon': 0.4.1
'@atproto/syntax': 0.3.0
'@atproto/xrpc': 0.5.0
await-lock: 2.2.2
multiformats: 9.9.0
tlds: 1.240.0
'@atproto/common-web@0.3.0':
dependencies:
graphemer: 1.4.0
multiformats: 9.9.0
uint8arrays: 3.0.0
zod: 3.21.4
'@atproto/lexicon@0.4.1':
dependencies:
'@atproto/common-web': 0.3.0
'@atproto/syntax': 0.3.0
iso-datestring-validator: 2.2.2
multiformats: 9.9.0
zod: 3.23.8
'@atproto/syntax@0.3.0': {}
'@atproto/xrpc@0.5.0':
dependencies:
'@atproto/lexicon': 0.4.1
zod: 3.21.4
'@cloudflare/kv-asset-handler@0.3.4':
dependencies:
mime: 3.0.0
'@cloudflare/workerd-darwin-64@1.20240821.1':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20240821.1':
optional: true
'@cloudflare/workerd-linux-64@1.20240821.1':
optional: true
'@cloudflare/workerd-linux-arm64@1.20240821.1':
optional: true
'@cloudflare/workerd-windows-64@1.20240821.1':
optional: true
'@cloudflare/workers-shared@0.4.1': {}
'@cloudflare/workers-types@4.20230710.1': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)':
dependencies:
esbuild: 0.17.19
'@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)':
dependencies:
esbuild: 0.17.19
escape-string-regexp: 4.0.0
rollup-plugin-node-polyfills: 0.2.1
'@esbuild/android-arm64@0.17.19':
optional: true
'@esbuild/android-arm@0.17.19':
optional: true
'@esbuild/android-x64@0.17.19':
optional: true
'@esbuild/darwin-arm64@0.17.19':
optional: true
'@esbuild/darwin-x64@0.17.19':
optional: true
'@esbuild/freebsd-arm64@0.17.19':
optional: true
'@esbuild/freebsd-x64@0.17.19':
optional: true
'@esbuild/linux-arm64@0.17.19':
optional: true
'@esbuild/linux-arm@0.17.19':
optional: true
'@esbuild/linux-ia32@0.17.19':
optional: true
'@esbuild/linux-loong64@0.17.19':
optional: true
'@esbuild/linux-mips64el@0.17.19':
optional: true
'@esbuild/linux-ppc64@0.17.19':
optional: true
'@esbuild/linux-riscv64@0.17.19':
optional: true
'@esbuild/linux-s390x@0.17.19':
optional: true
'@esbuild/linux-x64@0.17.19':
optional: true
'@esbuild/netbsd-x64@0.17.19':
optional: true
'@esbuild/openbsd-x64@0.17.19':
optional: true
'@esbuild/sunos-x64@0.17.19':
optional: true
'@esbuild/win32-arm64@0.17.19':
optional: true
'@esbuild/win32-ia32@0.17.19':
optional: true
'@esbuild/win32-x64@0.17.19':
optional: true
'@fastify/busboy@2.1.1': {}
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
acorn-walk@8.2.0: {}
acorn@8.10.0: {}
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
as-table@1.0.55:
dependencies:
printable-characters: 1.0.42
await-lock@2.2.2: {}
binary-extensions@2.2.0: {}
blake3-wasm@2.1.5: {}
braces@3.0.2:
dependencies:
fill-range: 7.0.1
capnp-ts@0.7.0:
dependencies:
debug: 4.3.4
tslib: 2.6.0
transitivePeerDependencies:
- supports-color
chokidar@3.5.3:
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
cookie@0.5.0: {}
data-uri-to-buffer@2.0.2: {}
date-fns@3.6.0: {}
debug@4.3.4:
dependencies:
ms: 2.1.2
defu@6.1.4: {}
esbuild@0.17.19:
optionalDependencies:
'@esbuild/android-arm': 0.17.19
'@esbuild/android-arm64': 0.17.19
'@esbuild/android-x64': 0.17.19
'@esbuild/darwin-arm64': 0.17.19
'@esbuild/darwin-x64': 0.17.19
'@esbuild/freebsd-arm64': 0.17.19
'@esbuild/freebsd-x64': 0.17.19
'@esbuild/linux-arm': 0.17.19
'@esbuild/linux-arm64': 0.17.19
'@esbuild/linux-ia32': 0.17.19
'@esbuild/linux-loong64': 0.17.19
'@esbuild/linux-mips64el': 0.17.19
'@esbuild/linux-ppc64': 0.17.19
'@esbuild/linux-riscv64': 0.17.19
'@esbuild/linux-s390x': 0.17.19
'@esbuild/linux-x64': 0.17.19
'@esbuild/netbsd-x64': 0.17.19
'@esbuild/openbsd-x64': 0.17.19
'@esbuild/sunos-x64': 0.17.19
'@esbuild/win32-arm64': 0.17.19
'@esbuild/win32-ia32': 0.17.19
'@esbuild/win32-x64': 0.17.19
escape-string-regexp@4.0.0: {}
estree-walker@0.6.1: {}
exit-hook@2.2.1: {}
fill-range@7.0.1:
dependencies:
to-regex-range: 5.0.1
fsevents@2.3.2:
optional: true
function-bind@1.1.2: {}
get-source@2.0.12:
dependencies:
data-uri-to-buffer: 2.0.2
source-map: 0.6.1
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
glob-to-regexp@0.4.1: {}
graphemer@1.4.0: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
hono@4.5.11: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.2.0
is-core-module@2.15.1:
dependencies:
hasown: 2.0.2
is-extglob@2.1.1: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
iso-datestring-validator@2.2.2: {}
magic-string@0.25.9:
dependencies:
sourcemap-codec: 1.4.8
mime@3.0.0: {}
miniflare@3.20240821.1:
dependencies:
'@cspotcode/source-map-support': 0.8.1
acorn: 8.10.0
acorn-walk: 8.2.0
capnp-ts: 0.7.0
exit-hook: 2.2.1
glob-to-regexp: 0.4.1
stoppable: 1.1.0
undici: 5.28.4
workerd: 1.20240821.1
ws: 8.18.0
youch: 3.2.3
zod: 3.23.8
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
ms@2.1.2: {}
multiformats@9.9.0: {}
mustache@4.2.0: {}
nanoid@3.3.6: {}
node-forge@1.3.1: {}
normalize-path@3.0.0: {}
ohash@1.1.3: {}
path-parse@1.0.7: {}
path-to-regexp@6.2.1: {}
pathe@1.1.2: {}
picomatch@2.3.1: {}
printable-characters@1.0.42: {}
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
resolve.exports@2.0.2: {}
resolve@1.22.8:
dependencies:
is-core-module: 2.15.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
rollup-plugin-inject@3.0.2:
dependencies:
estree-walker: 0.6.1
magic-string: 0.25.9
rollup-pluginutils: 2.8.2
rollup-plugin-node-polyfills@0.2.1:
dependencies:
rollup-plugin-inject: 3.0.2
rollup-pluginutils@2.8.2:
dependencies:
estree-walker: 0.6.1
selfsigned@2.1.1:
dependencies:
node-forge: 1.3.1
source-map@0.6.1: {}
sourcemap-codec@1.4.8: {}
stacktracey@2.1.8:
dependencies:
as-table: 1.0.55
get-source: 2.0.12
stoppable@1.1.0: {}
supports-preserve-symlinks-flag@1.0.0: {}
tlds@1.240.0: {}
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
tslib@2.6.0: {}
typescript@5.1.6: {}
ufo@1.5.4: {}
uint8arrays@3.0.0:
dependencies:
multiformats: 9.9.0
undici@5.28.4:
dependencies:
'@fastify/busboy': 2.1.1
unenv-nightly@2.0.0-1724863496.70db6f1:
dependencies:
defu: 6.1.4
ohash: 1.1.3
pathe: 1.1.2
ufo: 1.5.4
workerd@1.20240821.1:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20240821.1
'@cloudflare/workerd-darwin-arm64': 1.20240821.1
'@cloudflare/workerd-linux-64': 1.20240821.1
'@cloudflare/workerd-linux-arm64': 1.20240821.1
'@cloudflare/workerd-windows-64': 1.20240821.1
wrangler@3.75.0(@cloudflare/workers-types@4.20230710.1):
dependencies:
'@cloudflare/kv-asset-handler': 0.3.4
'@cloudflare/workers-shared': 0.4.1
'@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19)
'@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19)
blake3-wasm: 2.1.5
chokidar: 3.5.3
date-fns: 3.6.0
esbuild: 0.17.19
miniflare: 3.20240821.1
nanoid: 3.3.6
path-to-regexp: 6.2.1
resolve: 1.22.8
resolve.exports: 2.0.2
selfsigned: 2.1.1
source-map: 0.6.1
unenv: unenv-nightly@2.0.0-1724863496.70db6f1
workerd: 1.20240821.1
xxhash-wasm: 1.0.2
optionalDependencies:
'@cloudflare/workers-types': 4.20230710.1
fsevents: 2.3.2
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
ws@8.18.0: {}
xxhash-wasm@1.0.2: {}
youch@3.2.3:
dependencies:
cookie: 0.5.0
mustache: 4.2.0
stacktracey: 2.1.8
zod@3.21.4: {}
zod@3.23.8: {}

View File

@@ -0,0 +1,27 @@
import { html } from "hono/html";
export interface LayoutProps {
url: string;
children: any;
}
export const Layout = ({ url, children }: LayoutProps) => {
const removeLeadingSlash = url.substring(1);
const redirectUrl = removeLeadingSlash.startsWith("https://")
? removeLeadingSlash
: `https://bsky.app/${removeLeadingSlash}`;
return html`
<!DOCTYPE html>
<html>
<head>
<link rel="canonical" href="${url.substring(1)}" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta content="#0085ff" name="theme-color" />
<meta property="og:site_name" content="VixBluesky" />
${children}
<meta http-equiv="refresh" content="0;url=${redirectUrl}" />
</head>
</html>
`;
};

View File

@@ -0,0 +1,128 @@
import { AppBskyEmbedImages, AppBskyFeedDefs } from "@atproto/api";
import { Layout } from "./Layout";
import { OEmbedTypes } from "../routes/getOEmbed";
import { parseEmbedImages } from "../lib/parseEmbedImages";
import { parseEmbedDescription } from "../lib/parseEmbedDescription";
import { StreamInfo } from "../lib/processVideoEmbed";
interface PostProps {
post: AppBskyFeedDefs.PostView;
url: string;
appDomain: string;
videoMetadata?: StreamInfo[] | undefined;
apiUrl: string;
}
const Meta = ({ post }: { post: AppBskyFeedDefs.PostView }) => (
<>
<meta name="twitter:card" content="summary_large_image" />
</>
);
const Video = ({
streamInfo,
apiUrl,
}: {
streamInfo: StreamInfo;
apiUrl: string;
}) => {
const url = `${apiUrl}generate/${encodeURIComponent(streamInfo.uri)}.mp4`;
return (
<>
<meta property="twitter:card" content="player" />
<meta property="twitter:player" content={url} />
<meta property="twitter:player:stream" content={url} />
<meta property="og:type" content="video.other" />
<meta property="og:video" content={url} />
<meta property="og:video:secure_url" content={url} />
<meta property="og:video:type" content="video/mp4" />
<meta
property="og:video:width"
content={streamInfo.resolution.width.toString()}
/>
<meta
property="og:video:height"
content={streamInfo.resolution.height.toString()}
/>
<meta
property="twitter:player:width"
content={streamInfo.resolution.width.toString()}
/>
<meta
property="twitter:player:height"
content={streamInfo.resolution.height.toString()}
/>
</>
);
};
const Images = ({
images,
}: {
images: AppBskyEmbedImages.ViewImage[] | string;
}) => (
<>
{typeof images === "string" ? (
<>
<meta property="og:image" content={images} />
<meta property="twitter:image" content={images} />
</>
) : (
images.map((img, i) => (
<>
<meta property="og:image" content={img.fullsize} />(
{i === 0 && <meta property="twitter:image" content={img.fullsize} />})
</>
))
)}
</>
);
export const Post = ({
post,
url,
appDomain,
videoMetadata,
apiUrl,
}: PostProps) => {
const images = parseEmbedImages(post);
const isAuthor = images === post.author.avatar;
return (
<Layout url={url}>
<meta name="twitter:creator" content={`@${post.author.handle}`} />
<meta property="og:description" content={parseEmbedDescription(post)} />
<meta
property="og:title"
content={`${post.author.displayName} (@${post.author.handle})`}
/>
<meta
property="twitter:title"
content={`${post.author.displayName} (@${post.author.handle})`}
/>
<meta property="og:updated_time" content={post.indexedAt} />
<meta property="article:published_time" content={post.indexedAt} />
{/* <meta property="og:image" content={post.author.avatar} /> */}
{!isAuthor && <Meta post={post} />}
{images.length !== 0 && !videoMetadata && <Images images={images} />}
{videoMetadata && (
<Video apiUrl={apiUrl} streamInfo={videoMetadata.at(-1)!} />
)}
<link
rel="alternate"
type="application/json+oembed"
href={`https:/${appDomain}/oembed?type=${OEmbedTypes.Post}&replies=${
post.replyCount
}&reposts=${post.repostCount}&likes=${
post.likeCount
}&avatar=${encodeURIComponent(post.author.avatar ?? "")}`}
/>
</Layout>
);
};

View File

@@ -0,0 +1,33 @@
import { AppBskyActorDefs } from "@atproto/api";
import { Layout } from "./Layout";
import { OEmbedTypes } from "../routes/getOEmbed";
interface ProfileProps {
profile: AppBskyActorDefs.ProfileViewDetailed;
url: string;
appDomain: string;
}
export const Profile = ({ profile, url, appDomain }: ProfileProps) => (
<Layout url={url}>
<meta name="og:type" content="article" />
<meta name="twitter:creator" content={`@${profile.handle}`} />
<meta property="og:description" content={profile.description ?? ""} />
<meta
property="og:title"
content={`${profile.displayName} (@${profile.handle})`}
/>
<meta property="og:image" content={profile.avatar} />
<meta properpty="article:published_time" content={profile.createdAt} />
<link
type="application/json+oembed"
href={`https://${appDomain}/oembed?type=${OEmbedTypes.Profile}&follows=${
profile.followsCount
}&posts=${profile.postsCount}&avatar=${encodeURIComponent(
profile.avatar ?? ""
)}`}
/>
</Layout>
);

18
pkgs/app/src/globals.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
import { BskyAgent } from "@atproto/api";
import type { KVNamespace } from "@cloudflare/workers-types";
declare global {
interface Env {
Bindings: {
BSKY_SERVICE_URL: string;
BSKY_AUTH_USERNAME: string;
BSKY_AUTH_PASSWORD: string;
VIXBLUESKY_APP_DOMAIN: string;
VIXBLUESKY_API_URL: string;
bskyx: KVNamespace;
};
Variables: {
Agent: BskyAgent;
};
}
}

60
pkgs/app/src/index.ts Normal file
View File

@@ -0,0 +1,60 @@
import { Hono } from "hono";
import { AtpSessionData, BskyAgent } from "@atproto/api";
import { getPost } from "./routes/getPost";
import { getPostData } from "./routes/getPostData";
import { getOEmbed } from "./routes/getOEmbed";
import { getProfileData } from "./routes/getProfileData";
import { getProfile } from "./routes/getProfile";
import { HTTPException } from "hono/http-exception";
const app = new Hono<Env>();
app.use("*", async (c, next) => {
const agent = new BskyAgent({
service: c.env.BSKY_SERVICE_URL,
async persistSession(_, session) {
return c.env.bskyx.put("session", JSON.stringify(session));
},
});
try {
const rawSession = await c.env.bskyx.get("session");
if (rawSession) {
const session = JSON.parse(rawSession) as AtpSessionData;
await agent.resumeSession(session);
} else {
await agent.login({
identifier: c.env.BSKY_AUTH_USERNAME,
password: c.env.BSKY_AUTH_PASSWORD,
});
}
c.set("Agent", agent);
} catch (error) {
const err = new Error("Failed to login to Bluesky!", {
cause: error,
});
throw new HTTPException(500, {
message: `${err.message} \n\n ${err.cause} \n\n ${err.stack}`,
});
}
return next();
});
app.get("/", async (c) => {
return c.redirect("https://github.com/Rapougnac/VixBluesky");
});
app.get("/profile/:user/post/:post", getPost);
app.get("/https://bsky.app/profile/:user/post/:post", getPost);
app.get("/profile/:user/post/:post/json", getPostData);
app.get("/https://bsky.app/profile/:user/post/:post/json", getPostData);
app.get("/profile/:user", getProfile);
app.get("/https://bsky.app/profile/:user", getProfile);
app.get("/profile/:user/json", getProfileData);
app.get("/https://bsky.app/profile/:user/json", getProfileData);
app.get("/oembed", getOEmbed);
export default app;

View File

@@ -0,0 +1,18 @@
import { BskyAgent } from "@atproto/api";
export interface fetchPostOptions {
user: string;
post: string;
}
export async function fetchPost(
agent: BskyAgent,
{ user, post }: fetchPostOptions
) {
const { data: userData } = await agent.getProfile({
actor: user,
});
return agent.getPosts({
uris: [`at://${userData.did}/app.bsky.feed.post/${post}`],
});
}

View File

@@ -0,0 +1,14 @@
import { BskyAgent } from "@atproto/api";
export interface fetchProfileOptions {
user: string;
}
export async function fetchProfile(
agent: BskyAgent,
{ user }: fetchProfileOptions
) {
return agent.getProfile({
actor: user,
});
}

View File

@@ -0,0 +1,39 @@
import {
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AppBskyFeedPost,
} from "@atproto/api";
export function parseEmbedDescription(post: AppBskyFeedDefs.PostView) {
if (AppBskyFeedPost.isRecord(post.record)) {
if (AppBskyEmbedRecord.isView(post.embed)) {
const { success: isView } = AppBskyEmbedRecord.validateView(post.embed);
if (isView && AppBskyEmbedRecord.isViewRecord(post.embed.record)) {
const { success: isViewRecord } = AppBskyEmbedRecord.validateViewRecord(
post.embed.record
);
if (isViewRecord) {
// @ts-expect-error For some reason the original post value is typed as {}
return `${post.record.text}\n\nQuoting @${post.embed.record.author.handle}\n➥ ${post.embed.record.value.text}`;
}
}
}
if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
const { success: isView } = AppBskyEmbedRecordWithMedia.validateView(
post.embed
);
if (isView && AppBskyEmbedRecord.isViewRecord(post.embed.record.record)) {
const { success: isViewRecord } = AppBskyEmbedRecord.validateViewRecord(
post.embed.record.record
);
if (isViewRecord) {
// @ts-expect-error For some reason the original post value is typed as {}
return `${post.record.text}\n\nQuoting @${post.embed.record.record.author.handle}\n➥ ${post.embed.record.record.value.text}`;
}
}
}
return post.record.text;
}
return "";
}

View File

@@ -0,0 +1,56 @@
import {
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
} from "@atproto/api";
export function parseEmbedImages(
post: AppBskyFeedDefs.PostView
): string | AppBskyEmbedImages.ViewImage[] {
let images: AppBskyEmbedImages.ViewImage[] = [];
if (AppBskyEmbedRecord.isView(post.embed)) {
const { success: isView } = AppBskyEmbedRecord.validateView(post.embed);
if (isView && AppBskyEmbedRecord.isViewRecord(post.embed.record)) {
const { success: isViewRecord } = AppBskyEmbedRecord.validateViewRecord(
post.embed.record
);
if (
isViewRecord &&
post.embed.record.embeds &&
AppBskyEmbedImages.isView(post.embed.record.embeds[0])
) {
const { success: isImageView } = AppBskyEmbedImages.validateView(
post.embed.record.embeds[0]
);
if (isImageView) {
images = [...images, ...post.embed.record.embeds[0].images];
}
}
}
}
if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
const { success: isView } = AppBskyEmbedRecordWithMedia.validateView(
post.embed
);
if (isView && AppBskyEmbedImages.isView(post.embed.media)) {
const { success: isImageView } = AppBskyEmbedImages.validateView(
post.embed.media
);
if (isImageView) {
images = [...images, ...post.embed.media.images];
}
}
}
if (AppBskyEmbedImages.isView(post.embed)) {
const { success: isImageView } = AppBskyEmbedImages.validateView(
post.embed
);
if (isImageView) {
images = [...images, ...post.embed.images];
}
}
return images.length === 0 ? post.author.avatar ?? "" : images;
}

View File

@@ -0,0 +1,106 @@
import { AppBskyFeedDefs } from "@atproto/api";
export interface StreamInfo {
bandwidth: number;
resolution: {
width: number;
height: number;
};
codecs: string;
uri: string;
}
export interface M3U8Data {
version: number;
streams: StreamInfo[];
}
export async function processVideoEmbed(post: AppBskyFeedDefs.PostView) {
const videoUrl = post.embed?.playlist as string | undefined;
if (!videoUrl) {
return;
}
const contents = await fetch(videoUrl).then((res) => res.text());
const initalUrl = removeLastPathSegment(videoUrl);
const { streams } = await parseM3U8(initalUrl, contents);
return streams;
}
async function parseM3U8(
initalUrl: string,
contents: string
): Promise<M3U8Data> {
const lines = contents
.split("\n")
.map((l) => l.trim())
.filter((l) => l.length > 0);
let version = 0;
const streams: StreamInfo[] = [];
for (const line of lines) {
if (line.startsWith("#EXT-X-VERSION:")) {
version = Number(line.split(":")[1]);
} else if (line.startsWith("#EXT-X-STREAM-INF:")) {
const attribs = line.split(":")[1].split(",");
const sInfo: StreamInfo = {
bandwidth: 0,
resolution: {
width: 0,
height: 0,
},
codecs: "",
uri: "",
};
for (const attrib of attribs) {
const [key, value] = attrib.split("=");
switch (key) {
case "BANDWIDTH":
sInfo.bandwidth = Number(value);
break;
case "RESOLUTION":
const [width, height] = value.split("x").map(Number);
sInfo.resolution = { width, height };
break;
case "CODECS":
sInfo.codecs = value.replaceAll('"', "");
break;
}
}
streams.push(sInfo);
} else if (line.includes("m3u8")) {
const resolvedUrl = `${initalUrl}/${line}`;
const cont = await fetch(resolvedUrl).then((res) => res.text());
const parsed = await parseM3U8(removeLastPathSegment(resolvedUrl), cont);
streams.at(-1)!.uri = parsed.streams[0].uri;
} else if (line.includes(".ts")) {
streams.push({
bandwidth: 0,
resolution: {
width: 0,
height: 0,
},
codecs: "",
uri: `${initalUrl}/${line}`,
});
} else {
// Discard
continue;
}
}
return { version, streams };
}
function removeLastPathSegment(url: string) {
return url.slice(0, url.lastIndexOf("/"));
}

View File

@@ -0,0 +1,40 @@
import { Handler } from "hono";
export enum OEmbedTypes {
Post = 1,
Profile,
}
export const getOEmbed: Handler<Env, "/oembed"> = async (c) => {
const type = +(c.req.query("type") ?? 0);
const avatar = c.req.query("avatar");
const defaults = {
provider_name: "VixBluesky",
provider_url: "https://bskyx.app/",
thumbnail_width: 1000,
thumbnail_height: 1000,
};
if (avatar !== undefined) {
(defaults as typeof defaults & { thumbnail_url?: string }).thumbnail_url =
decodeURIComponent(avatar);
}
if (type === OEmbedTypes.Post) {
const { replies, reposts, likes } = c.req.query();
return c.json({
author_name: `🗨️ ${replies} ♻️ ${reposts} 💙 ${likes}`,
...defaults,
});
}
if (type === OEmbedTypes.Profile) {
const { follows, posts } = c.req.query();
return c.json({
author_name: `👤 ${follows} followers\n🗨 ${posts} skeets`,
...defaults,
});
}
return c.json(defaults, 400);
};

View File

@@ -0,0 +1,40 @@
import { Handler } from "hono";
import { HTTPException } from "hono/http-exception";
import { fetchPost } from "../lib/fetchPostData";
import { Post } from "../components/Post";
import { processVideoEmbed, StreamInfo } from "../lib/processVideoEmbed";
export const getPost: Handler<
Env,
"/profile/:user/post/:post" | "/https://bsky.app/profile/:user/post/:post"
> = async (c) => {
const { user, post } = c.req.param();
const agent = c.get("Agent");
const { data, success } = await fetchPost(agent, { user, post });
if (!success) {
throw new HTTPException(500, {
message: "Failed to fetch the post!",
});
}
const fetchedPost = data.posts[0];
let videoMetaData: StreamInfo[] | undefined;
if (
typeof fetchedPost.embed?.$type === "string" &&
fetchedPost.embed?.$type.startsWith("app.bsky.embed.video")
) {
videoMetaData = await processVideoEmbed(fetchedPost);
}
return c.html(
<Post
post={fetchedPost}
url={c.req.path}
appDomain={c.env.VIXBLUESKY_APP_DOMAIN}
videoMetadata={videoMetaData}
apiUrl={c.env.VIXBLUESKY_API_URL}
/>
);
};

View File

@@ -0,0 +1,19 @@
import { Handler } from "hono";
import { HTTPException } from "hono/http-exception";
import { fetchPost } from "../lib/fetchPostData";
export const getPostData: Handler<
Env,
| "/profile/:user/post/:post/json"
| "/https://bsky.app/profile/:user/post/:post/json"
> = async (c) => {
const { user, post } = c.req.param();
const agent = c.get("Agent");
const { data, success } = await fetchPost(agent, { user, post });
if (!success) {
throw new HTTPException(500, {
message: "Failed to fetch the post!",
});
}
return c.json(data);
};

View File

@@ -0,0 +1,25 @@
import { Handler } from "hono";
import { HTTPException } from "hono/http-exception";
import { fetchProfile } from "../lib/fetchProfile";
import { Profile } from "../components/Profile";
export const getProfile: Handler<
Env,
"/profile/:user" | "/https://bsky.app/profile/:user"
> = async (c) => {
const { user } = c.req.param();
const agent = c.get("Agent");
const { data, success } = await fetchProfile(agent, { user });
if (!success) {
throw new HTTPException(500, {
message: "Failed to fetch the profile!",
});
}
return c.html(
<Profile
profile={data}
url={c.req.path}
appDomain={c.env.VIXBLUESKY_APP_DOMAIN}
/>
);
};

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { jsx } from "hono/jsx";
import { Handler } from "hono";
import { HTTPException } from "hono/http-exception";
import { fetchProfile } from "../lib/fetchProfile";
export const getProfileData: Handler<
Env,
"/profile/:user/json" | "/https://bsky.app/profile/:user/json"
> = async (c) => {
const { user } = c.req.param();
const agent = c.get("Agent");
const { data, success } = await fetchProfile(agent, { user });
if (!success) {
throw new HTTPException(500, {
message: "Failed to fetch the profile!",
});
}
return c.json(data);
};

13
pkgs/app/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"strict": true,
"lib": ["esnext"],
"types": ["@cloudflare/workers-types"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}

15
pkgs/app/wrangler.toml Normal file
View File

@@ -0,0 +1,15 @@
name = "vixbluesky"
main = "src/index.ts"
compatibility_date = "2023-01-01"
workers_dev = false
route = { pattern = "bskyx.app/*", zone_name = "bskyx.app" }
[vars]
BSKY_SERVICE_URL="https://bsky.social/"
VIXBLUESKY_APP_DOMAIN="bskyx.app"
VIXBLUESKY_API_URL="https://api.bskyx.app/"
[[kv_namespaces]]
binding = "bskyx"
id = "ee913536e1fb47dcb9fa6275725f5a6f"