TDD on scout
ci / build (push) Has been cancelled
Details
ci / build (push) Has been cancelled
Details
This commit is contained in:
parent
8d122573bf
commit
194377dbd9
|
@ -1,6 +1,6 @@
|
|||
git monorepo.
|
||||
|
||||
pnpm required for workspaces.
|
||||
pnpm for workspaces.
|
||||
|
||||
Kubernetes for Development using Tiltfile
|
||||
|
||||
|
@ -13,3 +13,7 @@ dokku for Production, deployed with `git push`.
|
|||
Kubernetes for Production, deployed using FluxCD
|
||||
|
||||
direnv for loading .envrc
|
||||
|
||||
|
||||
Domain Driven Development
|
||||
Test Driven Development
|
||||
|
|
|
@ -44,6 +44,13 @@ spec:
|
|||
key: postgresRealtimeConnectionString
|
||||
- name: STRAPI_URL
|
||||
value: https://strapi.piko.sbtp.xyz
|
||||
- name: SCOUT_NITTER_ACCESS_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: scout
|
||||
key: nitterAccessKey
|
||||
- NAME: SCOUT_NITTER_URL
|
||||
value: https://nitter.sbtp.xyz
|
||||
- name: SCOUT_RECENTS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
|
|
|
@ -10,7 +10,6 @@ import os from 'os'
|
|||
import fs from 'node:fs'
|
||||
import { loggerFactory } from "./src/logger.js"
|
||||
import { verifyStorage } from './src/disk.js'
|
||||
import faye from 'faye'
|
||||
import { record, assertDependencyDirectory, checkFFmpeg } from './src/record.js'
|
||||
import fastq from 'fastq'
|
||||
import pRetry from 'p-retry';
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"extension": ["js"],
|
||||
"spec": "src/**/*.spec.js"
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "common",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"description": "regular expressions, constants, and helper functions which are used app-wide",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"exports": {
|
||||
"./fansly": "./src/fansly.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "@CJ_Clippy",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"chai": "^5.1.1",
|
||||
"mocha": "^10.4.0"
|
||||
}
|
||||
}
|
|
@ -1,621 +0,0 @@
|
|||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
chai:
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
mocha:
|
||||
specifier: ^10.4.0
|
||||
version: 10.4.0
|
||||
|
||||
packages:
|
||||
|
||||
ansi-colors@4.1.1:
|
||||
resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
browser-stdout@1.3.1:
|
||||
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
|
||||
|
||||
camelcase@6.3.0:
|
||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chai@5.1.1:
|
||||
resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
check-error@2.1.1:
|
||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
chokidar@3.5.3:
|
||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
cliui@7.0.4:
|
||||
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
debug@4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@4.0.0:
|
||||
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
deep-eql@5.0.1:
|
||||
resolution: {integrity: sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
diff@5.0.0:
|
||||
resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
escalade@3.1.2:
|
||||
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
escape-string-regexp@4.0.0:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
fill-range@7.1.1:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
flat@5.0.2:
|
||||
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
|
||||
hasBin: true
|
||||
|
||||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-func-name@2.0.2:
|
||||
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
glob@8.1.0:
|
||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
he@1.2.0:
|
||||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
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'}
|
||||
|
||||
is-plain-obj@2.1.0:
|
||||
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-unicode-supported@0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
hasBin: true
|
||||
|
||||
locate-path@6.0.0:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
log-symbols@4.1.0:
|
||||
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
loupe@3.1.1:
|
||||
resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
|
||||
|
||||
minimatch@5.0.1:
|
||||
resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
mocha@10.4.0:
|
||||
resolution: {integrity: sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
p-limit@3.1.0:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-locate@5.0.0:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
pathval@2.0.0:
|
||||
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
|
||||
engines: {node: '>= 14.16'}
|
||||
|
||||
picomatch@2.3.1:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
|
||||
readdirp@3.6.0:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
serialize-javascript@6.0.0:
|
||||
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
supports-color@8.1.1:
|
||||
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
workerpool@6.2.1:
|
||||
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs-parser@20.2.4:
|
||||
resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs-unparser@2.0.0:
|
||||
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yargs@16.2.0:
|
||||
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
snapshots:
|
||||
|
||||
ansi-colors@4.1.1: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
fill-range: 7.1.1
|
||||
|
||||
browser-stdout@1.3.1: {}
|
||||
|
||||
camelcase@6.3.0: {}
|
||||
|
||||
chai@5.1.1:
|
||||
dependencies:
|
||||
assertion-error: 2.0.1
|
||||
check-error: 2.1.1
|
||||
deep-eql: 5.0.1
|
||||
loupe: 3.1.1
|
||||
pathval: 2.0.0
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
check-error@2.1.1: {}
|
||||
|
||||
chokidar@3.5.3:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
braces: 3.0.3
|
||||
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.3
|
||||
|
||||
cliui@7.0.4:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
debug@4.3.4(supports-color@8.1.1):
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
optionalDependencies:
|
||||
supports-color: 8.1.1
|
||||
|
||||
decamelize@4.0.0: {}
|
||||
|
||||
deep-eql@5.0.1: {}
|
||||
|
||||
diff@5.0.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
escalade@3.1.2: {}
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
fill-range@7.1.1:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
find-up@5.0.0:
|
||||
dependencies:
|
||||
locate-path: 6.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
flat@5.0.2: {}
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-func-name@2.0.2: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
glob@8.1.0:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 5.0.1
|
||||
once: 1.4.0
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
he@1.2.0: {}
|
||||
|
||||
inflight@1.0.6:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
dependencies:
|
||||
binary-extensions: 2.3.0
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-plain-obj@2.1.0: {}
|
||||
|
||||
is-unicode-supported@0.1.0: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
locate-path@6.0.0:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
log-symbols@4.1.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
is-unicode-supported: 0.1.0
|
||||
|
||||
loupe@3.1.1:
|
||||
dependencies:
|
||||
get-func-name: 2.0.2
|
||||
|
||||
minimatch@5.0.1:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
mocha@10.4.0:
|
||||
dependencies:
|
||||
ansi-colors: 4.1.1
|
||||
browser-stdout: 1.3.1
|
||||
chokidar: 3.5.3
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
diff: 5.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
find-up: 5.0.0
|
||||
glob: 8.1.0
|
||||
he: 1.2.0
|
||||
js-yaml: 4.1.0
|
||||
log-symbols: 4.1.0
|
||||
minimatch: 5.0.1
|
||||
ms: 2.1.3
|
||||
serialize-javascript: 6.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
supports-color: 8.1.1
|
||||
workerpool: 6.2.1
|
||||
yargs: 16.2.0
|
||||
yargs-parser: 20.2.4
|
||||
yargs-unparser: 2.0.0
|
||||
|
||||
ms@2.1.2: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
p-limit@3.1.0:
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-locate@5.0.0:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
pathval@2.0.0: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
randombytes@2.1.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
readdirp@3.6.0:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
serialize-javascript@6.0.0:
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
supports-color@8.1.1:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
workerpool@6.2.1: {}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yargs-parser@20.2.4: {}
|
||||
|
||||
yargs-unparser@2.0.0:
|
||||
dependencies:
|
||||
camelcase: 6.3.0
|
||||
decamelize: 4.0.0
|
||||
flat: 5.0.2
|
||||
is-plain-obj: 2.1.0
|
||||
|
||||
yargs@16.2.0:
|
||||
dependencies:
|
||||
cliui: 7.0.4
|
||||
escalade: 3.1.2
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
string-width: 4.2.3
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 20.2.4
|
||||
|
||||
yocto-queue@0.1.0: {}
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
const regex = {
|
||||
username: new RegExp(/^https:\/\/fansly\.com\/(?:live\/)?([^\/]+)/)
|
||||
}
|
||||
|
||||
const normalize = (url) => {
|
||||
if (!url) throw new Error('normalized received a null or undefined url.');
|
||||
return fromUsername(fansly.regex.username.exec(url).at(1))
|
||||
}
|
||||
|
||||
const fromUsername = (username) => `https://fansly.com/${username}`
|
||||
|
||||
const url = {
|
||||
normalize,
|
||||
fromUsername
|
||||
}
|
||||
|
||||
const fansly = {
|
||||
regex,
|
||||
url
|
||||
}
|
||||
|
||||
export default fansly
|
|
@ -1,5 +0,0 @@
|
|||
import fansly from './fansly.js'
|
||||
|
||||
export default {
|
||||
fansly
|
||||
}
|
|
@ -7,8 +7,8 @@ import {
|
|||
QueryClient,
|
||||
useQuery,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import Image from 'next/image'
|
||||
import {
|
||||
PaginationState,
|
||||
useReactTable,
|
||||
|
@ -39,28 +39,57 @@ export default function StreamsTable() {
|
|||
const rerender = React.useReducer(() => ({}), {})[1]
|
||||
|
||||
|
||||
// name
|
||||
// image & name
|
||||
// title
|
||||
// platform
|
||||
// date
|
||||
// date & time
|
||||
// archiveStatus
|
||||
const columns = React.useMemo<ColumnDef<IStream>[]>(
|
||||
() => [
|
||||
{
|
||||
header: 'VTuber',
|
||||
accessorFn: d => d.attributes.vtuber.data?.attributes?.displayName,
|
||||
accessorFn: d => ({
|
||||
displayName: d.attributes.vtuber.data?.attributes?.displayName,
|
||||
image: d.attributes.vtuber.data?.attributes.image,
|
||||
imageBlur: d.attributes.vtuber.data?.attributes.imageBlur
|
||||
}),
|
||||
cell: info => {
|
||||
const { displayName, image, imageBlur } = info.getValue<{ displayName: string, image: string, imageBlur: string }>();
|
||||
return (
|
||||
<>
|
||||
<div className="columns is-mobile">
|
||||
<div className="column mr-0 is-flex-grow-0">
|
||||
<figure className="image is-24x24">
|
||||
<Image
|
||||
className="is-rounded"
|
||||
src={image}
|
||||
alt={displayName}
|
||||
placeholder="blur"
|
||||
blurDataURL={imageBlur}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
<div className="column ml-0">
|
||||
<span>{displayName}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
header: 'Date',
|
||||
accessorFn: d => new Date(d.attributes.date2).toISOString().split('T').at(0),
|
||||
cell: info => <Link href={`/archive/${info.row.original.attributes.cuid}`}>{info.getValue() as string}</Link>
|
||||
accessorFn: d => format(new Date(d.attributes.date2), 'yyyy-MM-dd HH:mm'),
|
||||
cell: info => (<Link href={`/archive/${info.row.original.attributes.cuid}`}>{info.getValue() as string}</Link>)
|
||||
},
|
||||
{
|
||||
header: 'Platform',
|
||||
accessorFn: d => [
|
||||
(d.attributes.isChaturbateStream && 'CB'),
|
||||
(d.attributes.isFanslyStream && 'Fansly')
|
||||
].filter(Boolean).join(', ') || '???'
|
||||
].filter(Boolean).join(' ') || '???'
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
|
|
|
@ -321,7 +321,7 @@ export async function fetchStreamData({ pageIndex, pageSize }: { pageIndex: numb
|
|||
const query = qs.stringify({
|
||||
populate: {
|
||||
vtuber: {
|
||||
fields: ['slug', 'displayName', 'publishedAt']
|
||||
fields: ['slug', 'displayName', 'publishedAt', 'image', 'imageBlur']
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
# scout
|
||||
|
||||
Watches an e-mail inbox for going live notifications
|
||||
Vtuber data acquisition.
|
||||
|
||||
## Features
|
||||
|
||||
* [x] Ingests going live notification e-mails
|
||||
* [ ] Sends `startRecording` signals to @futureporn/capture
|
||||
* [ ] Fetches vtuber data from platform
|
||||
* [ ] image
|
||||
* [ ] themeColor
|
||||
* [x] displayName
|
||||
* [ ] Platform Support
|
||||
* [ ] fansly
|
||||
* [ ] chaturbate
|
||||
|
||||
Support for
|
||||
* [ ] Chaturbate
|
||||
* [ ] Fansly
|
||||
|
||||
## Design requirements
|
||||
|
||||
|
@ -18,7 +27,7 @@ Support for
|
|||
* [ ] throws errors when unable to connect
|
||||
* [ ] runs browser headless
|
||||
* [ ] runs in the cloud
|
||||
* [ ] runs in k8s cluster
|
||||
* [x] runs in k8s cluster
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "scout",
|
||||
"type": "module",
|
||||
"version": "3.3.0",
|
||||
"description": "detect when a stream goes live",
|
||||
"description": "vtuber data acquisition",
|
||||
"main": "src/index.email.js",
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
|
@ -13,18 +13,26 @@
|
|||
"author": "@CJ_Clippy",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.583.0",
|
||||
"@aws-sdk/lib-storage": "^3.588.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.588.0",
|
||||
"@book000/twitterts": "^0.62.50",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"common": "workspace:*",
|
||||
"concurrently": "^8.2.2",
|
||||
"date-fns": "^3.6.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"fastq": "^1.17.1",
|
||||
"faye": "^1.4.0",
|
||||
"htmlparser2": "^9.1.0",
|
||||
"imapflow": "^1.0.160",
|
||||
"limiter": "2.0.1",
|
||||
"mailparser": "^3.7.1",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"p-retry": "^5.1.2",
|
||||
"pg-pubsub": "workspace:*",
|
||||
"qs": "^6.12.1",
|
||||
"sharp": "^0.33.4",
|
||||
"slugify": "^1.6.6",
|
||||
"xpath": "^0.0.34"
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
|||
import { download } from './utils.js';
|
||||
import { getTmpFile } from './utils.js';
|
||||
|
||||
const regex = {
|
||||
username: new RegExp(/^https:\/\/fansly\.com\/(?:live\/)?([^\/]+)/)
|
||||
}
|
||||
|
||||
const normalize = (url) => {
|
||||
if (!url) throw new Error('normalized received a null or undefined url.');
|
||||
return fromUsername(fansly.regex.username.exec(url).at(1))
|
||||
}
|
||||
|
||||
const fromUsername = (username) => `https://fansly.com/${username}`
|
||||
|
||||
const image = async function image (limiter, fanslyUserId) {
|
||||
if (!limiter) throw new Error(`first arg passed to fansly.data.image must be a node-rate-limiter instance`);
|
||||
if (!fanslyUserId) throw new Error(`second arg passed to fansly.data.image must be a {string} fanslyUserId`);
|
||||
const url = `https://api.fansly.com/api/v1/account/${fanslyUserId}/avatar`
|
||||
const filePath = getTmpFile('avatar.jpg')
|
||||
return download({ filePath, limiter, url })
|
||||
}
|
||||
|
||||
const url = {
|
||||
normalize,
|
||||
fromUsername
|
||||
}
|
||||
|
||||
const data = {
|
||||
image
|
||||
}
|
||||
|
||||
const fansly = {
|
||||
regex,
|
||||
url,
|
||||
data,
|
||||
}
|
||||
|
||||
export default fansly
|
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -0,0 +1,16 @@
|
|||
// import ColorThief from 'colorthief'
|
||||
import sharp from 'sharp'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export async function getProminentColor(imageFile) {
|
||||
const { dominant } = await sharp(imageFile).stats();
|
||||
const { r, g, b } = dominant;
|
||||
return rgbToHex(r, g, b)
|
||||
}
|
||||
|
||||
export function rgbToHex(r, g, b) {
|
||||
return "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
import { getProminentColor, rgbToHex } from './image.js'
|
||||
import { expect } from 'chai'
|
||||
import { describe } from 'mocha'
|
||||
import path from 'node:path'
|
||||
|
||||
|
||||
describe('image', function () {
|
||||
describe('getProminentColor', function () {
|
||||
it('should get a {String} hex code color', async function () {
|
||||
const color = await getProminentColor(path.join(import.meta.dirname, './fixtures/sample.webp'))
|
||||
expect(color).to.equal('#0878a8')
|
||||
})
|
||||
})
|
||||
describe('rgbToHex', function () {
|
||||
it('should convert color to hex {String} hexidecimal code', function () {
|
||||
const mulberry = [255, 87, 51]
|
||||
const screaminGreen = [77, 255, 106]
|
||||
const amaranth = [227, 64, 81]
|
||||
expect(rgbToHex(...mulberry)).to.equal('#ff5733')
|
||||
expect(rgbToHex(...screaminGreen)).to.equal('#4dff6a')
|
||||
expect(rgbToHex(...amaranth)).to.equal('#e34051')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -18,14 +18,14 @@ async function handleMessage({email, msg}) {
|
|||
const body = await email.loadMessage(msg.uid)
|
||||
|
||||
console.log(' ✏️ checking e-mail')
|
||||
const { isMatch, url, platform, channel, displayName, date, userId } = (await checkEmail(body))
|
||||
const { isMatch, url, platform, channel, displayName, date, userId, avatar } = (await checkEmail(body))
|
||||
|
||||
if (isMatch) {
|
||||
console.log(' ✏️✏️ signalling realtime')
|
||||
await signalRealtime({ url, platform, channel, displayName, date, userId })
|
||||
await signalRealtime({ url, platform, channel, displayName, date, userId, avatar })
|
||||
|
||||
console.log(' ✏️✏️ creating stream entry in db')
|
||||
await createStreamInDb({ source: 'email', platform, channel, date, url, userId })
|
||||
await createStreamInDb({ source: 'email', platform, channel, date, url, userId, avatar })
|
||||
}
|
||||
|
||||
console.log(' ✏️ archiving e-mail')
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import dotenv from 'dotenv'
|
||||
dotenv.config({
|
||||
path: '../../.env'
|
||||
})
|
||||
import { S3Client } from "@aws-sdk/client-s3"
|
||||
import { Upload } from "@aws-sdk/lib-storage"
|
||||
// import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { basename } from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
if (!process.env.S3_BUCKET_NAME) throw new Error('S3_BUCKET was undefined in env');
|
||||
if (!process.env.SCOUT_NITTER_URL) throw new Error('SCOUT_NITTER_URL was undefined in env');
|
||||
|
||||
|
||||
|
||||
export async function uploadFile(filePath) {
|
||||
if (!filePath) throw new Error("first argument, 'filePath' is undefined");
|
||||
const client = new S3Client({
|
||||
endpoint: 'https://s3.us-west-000.backblazeb2.com',
|
||||
region:'us-west-000',
|
||||
credentials:{
|
||||
accessKeyId: process.env.S3_BUCKET_KEY_ID,
|
||||
secretAccessKey: process.env.S3_BUCKET_APPLICATION_KEY
|
||||
}
|
||||
});
|
||||
const target = {
|
||||
Bucket: process.env.S3_BUCKET_NAME,
|
||||
Key: `${createId()}-${basename(filePath)}`,
|
||||
Body: fs.createReadStream(filePath)
|
||||
}
|
||||
|
||||
// greets https://stackoverflow.com/a/70159394/1004931
|
||||
try {
|
||||
const parallelUploads3 = new Upload({
|
||||
client: client,
|
||||
//tags: [...], // optional tags
|
||||
queueSize: 4, // optional concurrency configuration
|
||||
leavePartsOnError: false, // optional manually handle dropped parts
|
||||
params: target,
|
||||
});
|
||||
|
||||
// parallelUploads3.on("httpUploadProgress", (progress) => {
|
||||
// console.log(progress);
|
||||
// });
|
||||
|
||||
const res = await parallelUploads3.done();
|
||||
return res
|
||||
|
||||
} catch (e) {
|
||||
console.error(`while uploading a file to s3, we encountered an error`)
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { describe } from 'mocha'
|
||||
import { expect } from 'chai';
|
||||
import { uploadFile } from './s3.js'
|
||||
import path from 'node:path'
|
||||
|
||||
describe('s3', function () {
|
||||
this.timeout(1000*30)
|
||||
describe('uploadFile', function () {
|
||||
it('should upload a file to specified bucket and return the storage data', async function () {
|
||||
const data = await uploadFile(path.join(import.meta.dirname, './fixtures/sample.webp'))
|
||||
expect(data).to.have.property('VersionId')
|
||||
expect(data).to.have.property('ETag')
|
||||
expect(data).to.have.property('Bucket')
|
||||
expect(data).to.have.property('Key')
|
||||
expect(data).to.have.property('Location')
|
||||
expect(data).to.have.property('$metadata')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -4,7 +4,10 @@ import 'dotenv/config'
|
|||
import { PgPubSub } from 'pg-pubsub'
|
||||
import qs from 'qs'
|
||||
import { subMinutes, addMinutes } from 'date-fns'
|
||||
import slugify from 'slugify'
|
||||
import { fpSlugify, download } from './utils.js'
|
||||
import { getProminentColor } from './image.js'
|
||||
import { RateLimiter } from "limiter"
|
||||
import { getImage } from './vtuber.js'
|
||||
|
||||
// alternative js libraries for postgres notify/wait
|
||||
// * https://github.com/imqueue/pg-pubsub
|
||||
|
@ -15,6 +18,7 @@ if (!process.env.SCOUT_STRAPI_API_KEY) throw new Error('SCOUT_STRAPI_API_KEY is
|
|||
if (!process.env.STRAPI_URL) throw new Error('STRAPI_URL is missing from env');
|
||||
if (!process.env.SCOUT_RECENTS_TOKEN) throw new Error('SCOUT_RECENTS_TOKEN is undefined in env');
|
||||
if (!process.env.POSTGRES_REALTIME_CONNECTION_STRING) throw new Error('POSTGRES_REALTIME_CONNECTION_STRING is undefined in env');
|
||||
if (!process.env.CDN_BUCKET_URL) throw new Error('CDN_BUCKET_URL is undefined in env');
|
||||
|
||||
console.log(`process.env.POSTGRES_REALTIME_CONNECTION_STRING=${process.env.POSTGRES_REALTIME_CONNECTION_STRING}`)
|
||||
|
||||
|
@ -52,9 +56,10 @@ export async function signalRealtime ({ url, platform, channel, displayName, dat
|
|||
*/
|
||||
export async function createStreamInDb ({ source, platform, channel, date, url, userId }) {
|
||||
|
||||
const limiter = new RateLimiter({ tokensPerInterval: 0.3, interval: "second" });
|
||||
let vtuberId, streamId
|
||||
|
||||
console.log('>># Step 1')
|
||||
console.log('>> # Step 1')
|
||||
// # Step 1.
|
||||
// First we find or create the vtuber
|
||||
// The vtuber may already be in the db, so we look for that record. All we need is the Vtuber ID.
|
||||
|
@ -98,6 +103,43 @@ export async function createStreamInDb ({ source, platform, channel, date, url,
|
|||
|
||||
if (!vtuberId) {
|
||||
console.log('>> vtuberId was not found so we create')
|
||||
|
||||
/**
|
||||
* We are creating a vtuber record.
|
||||
* We need a few things.
|
||||
* * image URL
|
||||
* * themeColor
|
||||
*
|
||||
* To get an image, we have to do a few things.
|
||||
* * [x] download image from platform
|
||||
* * [x] get themeColor from image
|
||||
* * [x] upload image to b2
|
||||
* * [x] get B2 cdn link to image
|
||||
*
|
||||
* To get themeColor, we need the image locally where we can then run
|
||||
*/
|
||||
|
||||
// download image from platform
|
||||
// vtuber.getImage expects a vtuber object, which we don't have yet, so we create a dummy one
|
||||
const dummyVtuber = {
|
||||
attributes: {
|
||||
slug: fpSlugify(channel),
|
||||
fansly: fansly.url.fromUsername(channel)
|
||||
}
|
||||
}
|
||||
const platformImageUrl = await getImage(limiter, dummyVtuber)
|
||||
const imageFile = await download({ limiter, url: platformImageUrl })
|
||||
|
||||
// get themeColor from image
|
||||
const themeColor = await getProminentColor(imageFile)
|
||||
|
||||
// upload image to b2
|
||||
const b2FileData = await s3.uploadFile(imageFile)
|
||||
|
||||
// get b2 cdn link to image
|
||||
const imageCdnLink = `https://${process.env.CDN_BUCKET_URL}/${b2FileData.Key}`
|
||||
|
||||
|
||||
const createVtuberRes = await fetch(`${process.env.STRAPI_URL}/api/vtubers`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -110,10 +152,10 @@ export async function createStreamInDb ({ source, platform, channel, date, url,
|
|||
fansly: (platform === 'fansly') ? url : null,
|
||||
fanslyId: (platform === 'fansly') ? userId : null,
|
||||
chaturbate: (platform === 'chaturbate') ? url : null,
|
||||
slug: slugify(channel),
|
||||
slug: fpSlugify(channel),
|
||||
description1: ' ',
|
||||
image: 'https://futureporn-b2.b-cdn.net/200x200.png',
|
||||
themeColor: '#dde1ec'
|
||||
image: imageCdnLink,
|
||||
themeColor: themeColor || '#dde1ec'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -168,22 +210,6 @@ export async function createStreamInDb ({ source, platform, channel, date, url,
|
|||
// For now, the rule is 30 minutes of separation.
|
||||
// Anything <=30m is interpreted as the same stream. Anything >30m is interpreted as a different stream.
|
||||
// If the stream is not in the db, we create the stream record
|
||||
|
||||
|
||||
|
||||
// qs.stringify({
|
||||
// populate: '*',
|
||||
// filters: {
|
||||
// isFanslyStream: {
|
||||
// "$eq": true
|
||||
// },
|
||||
// }
|
||||
// }, {
|
||||
// encode: false
|
||||
// })
|
||||
|
||||
|
||||
|
||||
const dateSinceRange = subMinutes(new Date(date), 30)
|
||||
const dateUntilRange = addMinutes(new Date(date), 30)
|
||||
console.log(`Find a stream within + or - 30 mins of the notif date=${new Date(date).toISOString()}. dateSinceRange=${dateSinceRange.toISOString()}, dateUntilRange=${dateUntilRange.toISOString()}`)
|
||||
|
@ -246,7 +272,6 @@ export async function createStreamInDb ({ source, platform, channel, date, url,
|
|||
if (updateStreamJson?.error) throw new Error(updateStreamJson);
|
||||
console.log(`>> assuming a successful update to the stream record. response as follows.`)
|
||||
console.log(JSON.stringify(updateStreamJson, null, 2))
|
||||
|
||||
}
|
||||
|
||||
if (!streamId) {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
import * as htmlparser2 from "htmlparser2";
|
||||
import { load } from 'cheerio'
|
||||
import { download } from './utils.js';
|
||||
import pRetry, { AbortError } from 'p-retry';
|
||||
|
||||
if (!process.env.SCOUT_NITTER_ACCESS_KEY) throw new Error('SCOUT_NITTER_ACCESS_KEY was undefined in env');
|
||||
if (!process.env.SCOUT_NITTER_URL) throw new Error('SCOUT_NITTER_URL was undefined in env');
|
||||
|
||||
|
||||
|
||||
const regex = {
|
||||
username: new RegExp(/https:\/\/(?:twitter\.com|x\.com)\/([a-zA-Z0-9_]+)/i)
|
||||
}
|
||||
|
||||
const normalize = (url) => {
|
||||
if (!url) throw new Error('normalized received a null or undefined url.');
|
||||
return fromUsername(twitter.regex.username.exec(url).at(1))
|
||||
}
|
||||
|
||||
|
||||
const image = async function image (limiter, twitterUsername) {
|
||||
if (!limiter) throw new Error('first arg to twitter.data.image must be an instance of node-rate-limiter');
|
||||
if (!twitterUsername) throw new Error('second arg to twitter.data.image must be a twitterUsername. It was undefined.');
|
||||
const requestDataFromNitter = async () => {
|
||||
const url = `${process.env.SCOUT_NITTER_URL}/${twitterUsername}/rss?key=${process.env.SCOUT_NITTER_ACCESS_KEY}`
|
||||
// console.log(`fetching from url=${url}`)
|
||||
const response = await fetch(url);
|
||||
// Abort retrying if the resource doesn't exist
|
||||
if (response.status === 404) {
|
||||
throw new AbortError(response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
const body = await pRetry(requestDataFromNitter, { retries: 5 });
|
||||
try {
|
||||
const dom = htmlparser2.parseDocument(body);
|
||||
const $ = load(dom, { _useHtmlParser2: true })
|
||||
const urls = $('url:contains("profile_images")').first()
|
||||
const downloadedImageFile = await download({ limiter, url: urls.text() })
|
||||
return downloadedImageFile
|
||||
} catch (e) {
|
||||
console.error(`while fetching rss from nitter, the following error was encountered.`)
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const fromUsername = (username) => `https://x.com/${username}`
|
||||
|
||||
const url = {
|
||||
normalize,
|
||||
fromUsername
|
||||
}
|
||||
|
||||
const data = {
|
||||
image
|
||||
}
|
||||
|
||||
const twitter = {
|
||||
regex,
|
||||
url,
|
||||
data,
|
||||
}
|
||||
|
||||
|
||||
export default twitter
|
|
@ -0,0 +1,30 @@
|
|||
import { expect } from 'chai'
|
||||
import twitter from './twitter.js'
|
||||
import { describe } from 'mocha'
|
||||
import { tmpFileRegex } from './utils.js'
|
||||
import { RateLimiter } from 'limiter'
|
||||
|
||||
describe('twitter', function () {
|
||||
describe('regex', function () {
|
||||
describe('username', function () {
|
||||
it('should get the username of the channel', function () {
|
||||
expect(twitter.regex.username.exec('https://twitter.com/18Plus').at(1)).to.equal('18Plus')
|
||||
expect(twitter.regex.username.exec('https://twitter.com/projektmelody').at(1)).to.equal('projektmelody')
|
||||
expect(twitter.regex.username.exec('https://twitter.com/GoodKittenVR').at(1)).to.equal('GoodKittenVR')
|
||||
expect(twitter.regex.username.exec('https://x.com/projektmelody').at(1)).to.equal('projektmelody')
|
||||
expect(twitter.regex.username.exec('https://x.com/18Plus').at(1)).to.equal('18Plus')
|
||||
expect(twitter.regex.username.exec('https://x.com/GoodKittenVR').at(1)).to.equal('GoodKittenVR')
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('data', function () {
|
||||
this.timeout(1000*30)
|
||||
const limiter = new RateLimiter({ tokensPerInterval: 10, interval: "second" })
|
||||
describe('image', function () {
|
||||
it("should download the twitter users's avatar and save it to disk", async function () {
|
||||
const imgFile = await twitter.data.image(limiter, 'projektmelody')
|
||||
expect(imgFile).to.match(tmpFileRegex)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1 @@
|
|||
export const ua0 = 'Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0'
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
import slugify from 'slugify'
|
||||
import path, { basename } from 'node:path'
|
||||
import os from 'node:os'
|
||||
import fs from 'node:fs'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { ua0 } from './ua.js'
|
||||
import { Readable } from 'stream'
|
||||
import { finished } from 'stream/promises'
|
||||
import pRetry from 'p-retry'
|
||||
|
||||
export function fpSlugify(str) {
|
||||
return slugify(str, {
|
||||
lower: true,
|
||||
strict: true,
|
||||
locale: 'en',
|
||||
trim: true
|
||||
})
|
||||
}
|
||||
|
||||
export function getTmpFile(str) {
|
||||
return path.join(os.tmpdir(), `${createId()}_${basename(str)}`)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} limiter [https://github.com/jhurliman/node-rate-limiter](node-rate-limiter) instance
|
||||
* @param {String} url
|
||||
* @returns {String} filePath
|
||||
*
|
||||
* greetz https://stackoverflow.com/a/74722818/1004931
|
||||
*/
|
||||
export async function download({ limiter, url, filePath }) {
|
||||
if (!limiter) throw new Error(`first arg passed to download() must be a node-rate-limiter instance.`);
|
||||
if (!url) throw new Error(`second arg passed to download() must be a {string} url`);
|
||||
const fileBaseName = basename(url)
|
||||
filePath = filePath || path.join(os.tmpdir(), `${createId()}_${fileBaseName}`)
|
||||
const stream = fs.createWriteStream(filePath)
|
||||
await limiter.removeTokens(1);
|
||||
|
||||
const requestData = async () => {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'user-agent': ua0
|
||||
}
|
||||
})
|
||||
|
||||
const { body } = response
|
||||
await finished(Readable.fromWeb(body).pipe(stream))
|
||||
|
||||
// Abort retrying if the resource doesn't exist
|
||||
if (response.status === 404) {
|
||||
throw new AbortError(response.statusText);
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await pRetry(requestData, { retries: 3 })
|
||||
} catch (e) {
|
||||
console.error(`utils.download failed to download ${url}`)
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
|
||||
export const tmpFileRegex = /^\/tmp\/.*\.jpg$/
|
|
@ -0,0 +1,27 @@
|
|||
import { fpSlugify, getTmpFile, download } from './utils.js'
|
||||
import { expect } from 'chai'
|
||||
import { describe } from 'mocha'
|
||||
import { RateLimiter } from "limiter"
|
||||
|
||||
|
||||
describe('utils', function () {
|
||||
describe('fpSlugify', function () {
|
||||
it('should remove all capitalization and uppercase and spaces and special characters', function () {
|
||||
expect(fpSlugify('ProjektMelody')).to.equal('projektmelody')
|
||||
expect(fpSlugify('CJ_Clippy')).to.equal('cjclippy')
|
||||
})
|
||||
})
|
||||
describe('getTmpFile', function () {
|
||||
it('should give a /tmp/<random>_<basename> path', function () {
|
||||
expect(getTmpFile('my-cool-image.webp')).to.match(/\/tmp\/.*_my-cool-image\.webp/)
|
||||
expect(getTmpFile('video.mp4')).to.match(/\/tmp\/.*_video\.mp4/)
|
||||
})
|
||||
}),
|
||||
describe('download', function () {
|
||||
const limiter = new RateLimiter({ tokensPerInterval: 100, interval: "second" })
|
||||
it('should get the file', async function () {
|
||||
const file = await download({ limiter, url: 'https://futureporn-b2.b-cdn.net/sample.webp' })
|
||||
expect(file).to.match(/\/tmp\/.*sample\.webp$/)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config({
|
||||
path: '../../.env'
|
||||
})
|
||||
import twitter from './twitter.js'
|
||||
import fansly from './fansly.js'
|
||||
|
||||
|
||||
/**
|
||||
* Acquire a vtuber image from the www
|
||||
*
|
||||
* Sources preference
|
||||
* 1. Twitter
|
||||
* 2. Fansly
|
||||
*
|
||||
* Our task is to download an avatar image of the vtuber.
|
||||
* A slug is good for pulling a record from the database. From there, we can see any social medias such as Twitter or Fansly.
|
||||
* Twitter is preferred.
|
||||
*
|
||||
* We depend on one of these social media URLs. If there is neither Twitter or fansly listed, we throw an error.
|
||||
*
|
||||
* @param {Object} limiter -- instance of node-rate-limiter
|
||||
* @param {Object} vtuber -- vtuber instance from strapi
|
||||
* @returns {String} filePath -- path on disk where the image was saved
|
||||
*/
|
||||
export async function getImage(limiter, vtuber) {
|
||||
if (!limiter) throw new Error('first arg must be node-rate-limiter instace');
|
||||
if (!vtuber) throw new Error('second arg must be vtuber instance');
|
||||
await limiter.removeTokens(1);
|
||||
|
||||
const { twitter: twitterUrl, fanslyId: fanslyId } = vtuber.attributes
|
||||
const twitterUsername = twitterUrl && twitter.regex.username.exec(twitterUrl).at(1)
|
||||
|
||||
let img;
|
||||
if (twitterUrl) {
|
||||
img = await twitter.data.image(limiter, twitterUsername)
|
||||
} else if (fanslyId) {
|
||||
img = await fansly.data.image(limiter, fanslyId)
|
||||
} else {
|
||||
const msg = 'while attempting to get vtuber image, there was neither twitter nor fansly listed. One of these must exist for us to download an image.'
|
||||
console.error(msg)
|
||||
throw new Error(msg)
|
||||
}
|
||||
return img
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
import { expect } from 'chai'
|
||||
import { describe } from 'mocha'
|
||||
import { RateLimiter } from 'limiter'
|
||||
import { getImage } from './vtuber.js'
|
||||
import { tmpFileRegex } from './utils.js'
|
||||
|
||||
const vtuberFixture0 = {
|
||||
id: 0,
|
||||
attributes: {
|
||||
slug: 'projektmelody',
|
||||
twitter: 'https://x.com/projektmelody',
|
||||
fansly: 'https://fansly.com/projektmelody'
|
||||
}
|
||||
}
|
||||
const vtuberFixture1 = {
|
||||
id: 0,
|
||||
attributes: {
|
||||
slug: 'projektmelody',
|
||||
twitter: undefined,
|
||||
fanslyId: '284824898138812416'
|
||||
}
|
||||
}
|
||||
|
||||
describe('vtuber', function () {
|
||||
this.timeout(1000*60)
|
||||
describe('getImage', function () {
|
||||
const limiter = new RateLimiter({ tokensPerInterval: 1, interval: "second" })
|
||||
it('should download an avatar image from twitter', async function () {
|
||||
const file = await getImage(limiter, vtuberFixture0)
|
||||
expect(file).to.match(tmpFileRegex)
|
||||
})
|
||||
it('should download an avatar image from fansly', async function () {
|
||||
const file = await getImage(limiter, vtuberFixture1)
|
||||
expect(file).to.match(tmpFileRegex)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -20,6 +20,14 @@ module.exports = {
|
|||
|
||||
},
|
||||
async afterUpdate(event) {
|
||||
|
||||
|
||||
/**
|
||||
* NOTE
|
||||
*
|
||||
* These hooks do not fire in response to API calls. They only fire in response to UI saves.
|
||||
*/
|
||||
|
||||
console.log(`>>>>>>>>>>>>>> STREAM is afterUpdate !!!!!!!!!!!!`);
|
||||
const { data, where, select, populate } = event.params;
|
||||
console.log(data);
|
||||
|
|
|
@ -39,6 +39,7 @@ kubectl --namespace futureporn create secret generic scout \
|
|||
--from-literal=imapUsername=${SCOUT_IMAP_USERNAME} \
|
||||
--from-literal=imapPassword=${SCOUT_IMAP_PASSWORD} \
|
||||
--from-literal=imapAccessToken=${SCOUT_IMAP_ACCESS_TOKEN} \
|
||||
--from-literal=nitterAccessKey=${SCOUT_NITTER_ACCESS_KEY}
|
||||
|
||||
kubectl --namespace futureporn delete secret link2cid --ignore-not-found
|
||||
kubectl --namespace futureporn create secret generic link2cid \
|
||||
|
@ -85,3 +86,4 @@ kubectl --namespace futureporn create secret generic strapi \
|
|||
kubectl --namespace futureporn delete secret realtime --ignore-not-found
|
||||
kubectl --namespace futureporn create secret generic realtime \
|
||||
--from-literal=postgresRealtimeConnectionString=${POSTGRES_REALTIME_CONNECTION_STRING}
|
||||
|
||||
|
|
|
@ -24,7 +24,23 @@ dotenv(fn='.env')
|
|||
|
||||
# args=['--default_config_file=%s' % os.getenv('TILT_NGROK_DEFAULT_CONFIG_FILE')]
|
||||
|
||||
# load('ext://helm_remote', 'helm_remote')
|
||||
load('ext://helm_remote', 'helm_remote')
|
||||
# helm_remote(
|
||||
# 'redis',
|
||||
# repo_name='redis',
|
||||
# repo_url='https://charts.bitnami.com/bitnami',
|
||||
# namespace='futureporn',
|
||||
# version='19.5.0',
|
||||
# values=['./charts/nitter/redis.values.yaml']
|
||||
# )
|
||||
|
||||
# helm_remote(
|
||||
# 'nitter',
|
||||
# repo_name='truecharts',
|
||||
# repo_url='https://charts.truecharts.org',
|
||||
# namespace='futureporn',
|
||||
# version='7.1.4',
|
||||
# )
|
||||
# helm_remote(
|
||||
# 'frp-operator',
|
||||
# repo_name='frp-operator',
|
||||
|
@ -45,6 +61,11 @@ dotenv(fn='.env')
|
|||
# )
|
||||
|
||||
|
||||
# k8s_yaml(helm(
|
||||
# './charts/nitter',
|
||||
# values=['./charts/nitter/values.yaml'],
|
||||
# ))
|
||||
|
||||
k8s_yaml(helm(
|
||||
'./charts/fp',
|
||||
values=['./charts/fp/values-dev.yaml'],
|
||||
|
@ -187,8 +208,12 @@ k8s_resource(
|
|||
port_forwards=['5432']
|
||||
)
|
||||
|
||||
k8s_resource(
|
||||
workload='pgadmin',
|
||||
port_forwards=['5050']
|
||||
)
|
||||
# k8s_resource(
|
||||
# workload='pgadmin',
|
||||
# port_forwards=['5050']
|
||||
# )
|
||||
|
||||
# k8s_resource(
|
||||
# workload='nitter',
|
||||
# port_forwards=['6060:10606'],
|
||||
# )
|
Loading…
Reference in New Issue