progress
This commit is contained in:
parent
df05eec5aa
commit
3500bd68ba
|
@ -1,3 +1,5 @@
|
|||
venv
|
||||
|
||||
scrap*
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"extensions": ["ts"],
|
||||
"node-option": [
|
||||
"experimental-specifier-resolution=node",
|
||||
"loader=ts-node/esm"
|
||||
]
|
||||
}
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"node-option": [
|
||||
"experimental-specifier-resolution=node",
|
||||
"loader=ts-node/esm"
|
||||
]
|
||||
}
|
15
README.md
15
README.md
|
@ -195,3 +195,18 @@ I think it needs to go like this.
|
|||
```
|
||||
|
||||
####
|
||||
|
||||
|
||||
|
||||
```
|
||||
debug: handling room error. Bad Request {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:44.535Z"}
|
||||
debug: handling room error. Bad Request {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:44.536Z"}
|
||||
debug: Ably authCallback. Getting a fresh new Ably TokenRequest. {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:44.537Z"}
|
||||
debug: getting pushServiceAuth. {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:44.537Z"}
|
||||
debug: using cookie string csrftoken=gQUnyHUTg7lZBTNn374s96a7DYAVxqZ5xrPi2RzWfrzJT61EfrLHvr6wFRMwhcUz; sbr=sec:sbref421fda-4ec5-4a7f-93ea-9a74a8c85484:1qdk9F:movx7uli5YEZ4UNB_GPURHopOLI {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:44.537Z"}
|
||||
debug: {"token":null,"channels":{"RoomMessageTopic#RoomMessageTopic:G0TWFS5":"room:grouped:G0TWFS5:16","RoomStatusTopic#RoomStatusTopic:G0TWFS5":"room:grouped:G0TWFS5:16","RoomTipAlertTopic#RoomTipAlertTopic:G0TWFS5":"room:grouped:G0TWFS5:16","RoomPasswordProtectedTopic#RoomPasswordProtectedTopic:G0TWFS5":"room:grouped:G0TWFS5:16"},"failures":{"RoomMessageTopic#RoomMessageTopic:G0TWFS5":"Bad Request","RoomStatusTopic#RoomStatusTopic:G0TWFS5":"Bad Request","RoomTipAlertTopic#RoomTipAlertTopic:G0TWFS5":"Bad Request","RoomPasswordProtectedTopic#RoomPasswordProtectedTopic:G0TWFS5":"Bad Request"},"token_request":{},"client_id":"-anonef421fda-4ec5-4a7f-93ea-9a74a8c85484","settings":{"backend":"a","flags":{"pm_enabled":true,"wowza_disabled":true,"userlist_enabled":true,"verify_enabled":false,"fallback_eligible":true,"is_live":true},"rest_host":"realtime.pa.highwebmedia.com","realtime_host":"realtime.pa.highwebmedia.com","fallback_hosts":["a-fallback.pa.highwebmedia.com","b-fallback.pa.highwebmedia.com","c-fallback.pa.highwebmedia.com","d-fallback.pa.highwebmedia.com","e-fallback.pa.highwebmedia.com"]}} {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:46.747Z"}
|
||||
restHost:realtime.pa.highwebmedia.com >> realtimeHost:realtime.pa.highwebmedia.com >> fallbackHosts:a-fallback.pa.highwebmedia.com,b-fallback.pa.highwebmedia.com,c-fallback.pa.highwebmedia.com,d-fallback.pa.highwebmedia.com,e-fallback.pa.highwebmedia.com
|
||||
debug: Got a new TokenRequest {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:46.748Z"}
|
||||
debug: {} {"service":"futureporn/scout","timestamp":"2023-11-20T19:44:46.748Z"}
|
||||
11:44:46.748 Ably: Auth.requestToken(): Expected token request callback to call back with a token string, token request object, or token details object
|
||||
```
|
82
index.ts
82
index.ts
|
@ -8,6 +8,8 @@ import * as faye from './src/faye.js'
|
|||
import * as sound from './src/sound.js'
|
||||
import * as cb from './src/cb.js'
|
||||
import { appEnv, getAppContext } from './src/appContext.js'
|
||||
import { getPushServiceAuth } from './src/headless.js'
|
||||
// import { getSuperRealtimeClient } from './src/realtime.js'
|
||||
import {
|
||||
onCbMessage,
|
||||
onCbTitle,
|
||||
|
@ -73,76 +75,19 @@ async function init () {
|
|||
/**
|
||||
*
|
||||
* listen to room status messages 24/7
|
||||
*
|
||||
* @todo [ ] when room goes online, spawn a woerker
|
||||
* which listens to more of it's events
|
||||
*
|
||||
* @todo [ ] when room goes offline
|
||||
* and remains offline for >5 minutes,
|
||||
* retire the worker
|
||||
*
|
||||
* @todo [ ] 1 TUI row per user
|
||||
* @todo [ ] TUI columns for subscriptins status
|
||||
* when events are received on the ably realtime client, we log the appropriate message to db.
|
||||
*/
|
||||
async function registerRoomStatusListeners(appContext, rooms, cb, ably) {
|
||||
|
||||
// for each room, render a TUI row
|
||||
// for (const room of rooms) {
|
||||
|
||||
// }
|
||||
|
||||
const sampleRoom = rooms.at(0)
|
||||
console.log(sampleRoom)
|
||||
|
||||
// get cookie from cb. We only need one for `chaturbate.com/` (not one per room.)
|
||||
await cb.fetchCookiesIfNeeded(sampleRoom.url)
|
||||
const csrfToken = await cb.getCsrfToken(sampleRoom.url) // doesn't really matter which roomUrl for this token, as long as it is on chaturbate.com domain
|
||||
const cookieString = await cb.cookieJar.getCookieString(sampleRoom.url)
|
||||
|
||||
console.log('here is the cookiestring')
|
||||
console.log(cookieString)
|
||||
|
||||
await Promise.allSettled((rooms.map((r) => r.id)))
|
||||
await Promise.allSettled((rooms.map((r) => r.dossier)))
|
||||
|
||||
|
||||
|
||||
/**
|
||||
get permission form for all the rooms
|
||||
we are interested in.
|
||||
we be greedy with choosing all permissions up-front
|
||||
for channels we may possibly want in the future.
|
||||
Basically we request every permission possible for
|
||||
website visitors. Just because we request perms,
|
||||
doesn't mean we have to subscribe to the relevant channel.
|
||||
we only use what we need, but we don't want the permission
|
||||
form to hold us back and require an extra request.
|
||||
*/
|
||||
const channelsRequests = await Promise.all(
|
||||
rooms.map(async (r) => await r.getChannelsRequest())
|
||||
);
|
||||
|
||||
// const oldPermissionForm = Room.getPermissionsForm(rooms.map((r) => r.id), ['status'], csrfToken)
|
||||
const mergedChannelsRequests = Room.mergeChannelsRequests(channelsRequests)
|
||||
|
||||
console.log('here is the result of merging channels requests')
|
||||
console.log(mergedChannelsRequests)
|
||||
|
||||
const permissionForm = Room.formalizeChannelsRequest(mergedChannelsRequests, csrfToken)
|
||||
|
||||
// console.log('OLD, (known good) FORMDATA AS FOLLOWS')
|
||||
// for (let pair of oldPermissionForm.entries()) {
|
||||
// console.log(pair[0] + ': ' + pair[1]);
|
||||
// }
|
||||
|
||||
console.log(`FORMDATA AS FOLLOWS >>>>>>>> vvvvvvvvv`)
|
||||
for (let pair of permissionForm.entries()) {
|
||||
console.log(pair[0] + ': ' + pair[1]);
|
||||
}
|
||||
|
||||
// get signed ably tokenrequest from cb
|
||||
console.log({ level: 'debug', message: `Ok let's get a signed ably tokenrequest. we are going to use cookieString:${cookieString}` })
|
||||
const psa = await cb.getPushServiceAuth(sampleRoom.url, permissionForm, cookieString)
|
||||
const psa = await getPushServiceAuth(sampleRoom.url, permissionForm, cookieString)
|
||||
|
||||
const ablyWrapper = new AblyWrapper(appContext, {
|
||||
chaturbateAuth: cb,
|
||||
|
@ -238,24 +183,19 @@ async function daemon () {
|
|||
}))
|
||||
|
||||
appContext.faye = faye.fayeFactory(appContext)
|
||||
// registerRoomTimers(appContext, rooms)
|
||||
const chaturbateAuth = new ChaturbateAuth(appContext)
|
||||
const ably = new AblyWrapper(appContext, {
|
||||
chaturbateAuth
|
||||
})
|
||||
// registerRoomListeners(appContext, rooms, cb, ably)
|
||||
|
||||
// @todo [ ] Listen to interesting rooms 24/7
|
||||
// @todo [ ] When interesting room comes online, listen to more of it's events
|
||||
// @todo [ ] When interesting room goes offline, stop listening to more of it's events after 5 minutes
|
||||
// for each room we are scouting, get an Ably realtime client
|
||||
// once we have an Ably realtime client,
|
||||
// we attach our custom callback functions which log events to db
|
||||
for (const room of rooms) {
|
||||
|
||||
}
|
||||
// await registerRoomStatusListeners(appContext, rooms, chaturbateAuth, ably)
|
||||
|
||||
|
||||
// const concurrency = 12
|
||||
// const queue = fastq(worker, 12)
|
||||
|
||||
await registerRoomStatusListeners(appContext, rooms, chaturbateAuth, ably)
|
||||
|
||||
// console.log(`lets kick the webserver`)
|
||||
}
|
||||
|
||||
|
||||
|
|
42
package.json
42
package.json
|
@ -9,21 +9,22 @@
|
|||
"build": "rimraf dist && tsc -p tsconfig.json",
|
||||
"start:daemon": "pm2 start ecosystem.config.cjs; pm2 logs",
|
||||
"start": "node --loader ts-node/esm index.ts daemon",
|
||||
"schema": "pnpm ts-to-zod src/headless.ts src/headlessSchema.ts",
|
||||
"test": "mocha ./test/unit",
|
||||
"dev": "FUTUREPORN_WORKDIR=/home/chris/Downloads pnpm nodemon --exec \"node --loader ts-node/esm \" -e js,ts,mjs,json index.ts daemon"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@neutralinojs/neu": "^9.5.1",
|
||||
"ably": "1.2.13",
|
||||
"better-sqlite3": "^8.3.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"date-fns": "^2.29.3",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"ably": "1.2.37",
|
||||
"better-sqlite3": "^8.7.0",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"date-fns": "^2.30.0",
|
||||
"debounce": "^1.2.1",
|
||||
"dexie-export-import": "^4.0.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"execa": "^7.1.1",
|
||||
"fastify": "^4.20.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"execa": "^7.2.0",
|
||||
"fastify": "^4.24.3",
|
||||
"fastq": "^1.15.0",
|
||||
"faye": "^1.4.0",
|
||||
"fetch-blob": "^3.2.0",
|
||||
|
@ -32,32 +33,35 @@
|
|||
"got-plugin-debounce": "^1.0.3",
|
||||
"http": "0.0.1-security",
|
||||
"ky": "^0.33.3",
|
||||
"node-fetch": "^3.3.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"nunjucks": "^3.2.4",
|
||||
"p-retry": "^5.1.2",
|
||||
"pm2": "^5.3.0",
|
||||
"puppeteer": "^20.9.0",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
"qs": "^6.11.2",
|
||||
"seedrandom": "^3.0.5",
|
||||
"terminal-kit": "^3.0.0",
|
||||
"tough-cookie": "^4.1.2",
|
||||
"terminal-kit": "^3.0.1",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"tough-cookie-file-store": "^2.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tty-table": "^4.2.1",
|
||||
"tty-table": "^4.2.3",
|
||||
"twitter-api-scraper": "^0.0.5",
|
||||
"twitter-openapi-typescript": "^0.0.11",
|
||||
"twitter-v2": "^1.1.0",
|
||||
"typescript": "^5.1.6",
|
||||
"winston": "^3.8.2",
|
||||
"yargs": "^17.7.2"
|
||||
"typescript": "^5.3.2",
|
||||
"winston": "^3.11.0",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.7",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"chai": "^4.3.10",
|
||||
"chai-events": "^0.0.3",
|
||||
"mocha": "^10.2.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"rimraf": "^5.0.1",
|
||||
"sinon": "^15.1.0"
|
||||
"nodemon": "^2.0.22",
|
||||
"rimraf": "^5.0.5",
|
||||
"ts-to-zod": "^3.4.1"
|
||||
}
|
||||
}
|
||||
|
|
2862
pnpm-lock.yaml
2862
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -115,6 +115,15 @@ export default class AblyWrapper {
|
|||
await this.saveRealtimeUrls(restHost, realtimeHost, fallbackHosts)
|
||||
|
||||
this.tokenRequest = pushServiceAuth.token_request;
|
||||
|
||||
this.logger.log({
|
||||
level: 'debug',
|
||||
message: `the following is pushServiceAuth`
|
||||
});
|
||||
this.logger.log({
|
||||
level: 'debug',
|
||||
message: JSON.stringify(pushServiceAuth, null, 2)
|
||||
});
|
||||
this.logger.log({
|
||||
level: 'debug',
|
||||
message: `Got a new TokenRequest`
|
||||
|
@ -123,6 +132,7 @@ export default class AblyWrapper {
|
|||
level: 'debug',
|
||||
message: JSON.stringify(this.tokenRequest)
|
||||
});
|
||||
if (!this?.tokenRequest) throw new Error('tokenRequest was empty')
|
||||
|
||||
callback(null, this.tokenRequest);
|
||||
})
|
||||
|
|
|
@ -160,6 +160,7 @@ export default class ChaturbateAuth {
|
|||
|
||||
return form;
|
||||
}
|
||||
|
||||
static getTokenCookie(cookies) {
|
||||
return cookies.find((c) => c.key == 'csrftoken');
|
||||
}
|
||||
|
|
15
src/cb.js
15
src/cb.js
|
@ -1,20 +1,19 @@
|
|||
import cheerio from 'cheerio'
|
||||
import { execa } from 'execa'
|
||||
import pRetry from 'p-retry'
|
||||
import gotClient from './gotClient';
|
||||
|
||||
export async function getRandomRoom (appContext) {
|
||||
const res = await appContext.gotClient.get('https://chaturbate.com/')
|
||||
const body = await res.text()
|
||||
const $ = cheerio.load(body)
|
||||
let roomsRaw = $('a[data-room]')
|
||||
let rooms = []
|
||||
export function getRandomRoomFromCbHtml (body) {
|
||||
const $ = cheerio.load(body);
|
||||
let roomsRaw = $('a[data-room]');
|
||||
let rooms = [];
|
||||
$(roomsRaw).each((_, e) => {
|
||||
rooms.push($(e).attr('href'))
|
||||
rooms.push($(e).attr('href'));
|
||||
})
|
||||
|
||||
// greets https://stackoverflow.com/a/4435017/1004931
|
||||
var randomIndex = Math.floor(Math.random() * rooms.length);
|
||||
return rooms[randomIndex].replaceAll('/', '')
|
||||
return rooms[randomIndex].replaceAll('/', '');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import puppeteer from 'puppeteer';
|
||||
import { getRandomRoomFromCbHtml } from './cb.js';
|
||||
import { z } from 'zod';
|
||||
import { iPushServiceAuthSchema } from './headlessSchema.js';
|
||||
import qs from 'qs';
|
||||
|
||||
export interface IPushServiceAuth {
|
||||
token: string;
|
||||
channels: Record<string, string>;
|
||||
failures: Record<string, string>;
|
||||
token_request: {
|
||||
keyName: string;
|
||||
clientId: string;
|
||||
ttl: number;
|
||||
nonce: string;
|
||||
capability: string;
|
||||
timestamp: number;
|
||||
mac: string;
|
||||
}
|
||||
client_id: string;
|
||||
settings: {
|
||||
backend: string;
|
||||
flags: {
|
||||
pm_enabled: boolean;
|
||||
wowza_disabled: boolean;
|
||||
userlist_enabled: boolean;
|
||||
verify_enabled: boolean;
|
||||
fallback_eligible: boolean;
|
||||
is_live: boolean;
|
||||
}
|
||||
rest_host: string;
|
||||
realtime_host: string;
|
||||
fallback_hosts: string[];
|
||||
}
|
||||
}
|
||||
|
||||
const roomSchema = z.string().min(3);
|
||||
|
||||
export function getSemverNumber(str: string): string {
|
||||
|
||||
// Regular expression for matching SemVer numbers
|
||||
const semverRegex = /(?:\D|^)(\d+\.\d+\.\d+)(?=\D|$)/;
|
||||
|
||||
// Use the regex to extract the SemVer number
|
||||
const match = str.match(semverRegex);
|
||||
|
||||
// Check if there's a match and extract the SemVer number
|
||||
const semverNumber = match ? match[1] : null;
|
||||
return semverNumber;
|
||||
|
||||
}
|
||||
|
||||
export async function getRandomRoom(): Promise<string> {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new'
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://chaturbate.com')
|
||||
const html = await page.content();
|
||||
await browser.close();
|
||||
const room = await getRandomRoomFromCbHtml(html);
|
||||
roomSchema.parse(room);
|
||||
return room;
|
||||
}
|
||||
|
||||
export async function getAblyAgent(): Promise<string> {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new'
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.goto('https://chaturbate.com')
|
||||
const html = await page.content();
|
||||
const room = await getRandomRoomFromCbHtml(html);
|
||||
roomSchema.parse(room);
|
||||
await page.setRequestInterception(true);
|
||||
|
||||
const url: string = await new Promise((resolve) => {
|
||||
page.on('request', interceptRequest => {
|
||||
const url = interceptRequest.url();
|
||||
if (url.startsWith('https://realtime')) {
|
||||
console.log(`request! ${interceptRequest.url()} ${JSON.stringify(interceptRequest.headers(), null, 2)}`);
|
||||
resolve(interceptRequest.url());
|
||||
}
|
||||
interceptRequest.continue();
|
||||
});
|
||||
|
||||
page.goto(`https://chaturbate.com/${room}`);
|
||||
})
|
||||
|
||||
const query = qs.parse(url);
|
||||
console.log(query);
|
||||
await browser.close();
|
||||
return decodeURIComponent(query?.agent);
|
||||
}
|
||||
|
||||
export async function getAblyAgentVersionSemver(): Promise<string> {
|
||||
const agentString = await getAblyAgent();
|
||||
return getSemverNumber(agentString);
|
||||
}
|
||||
|
||||
|
||||
export async function getPushServiceAuth(roomUrl: string): Promise<IPushServiceAuth> {
|
||||
if (!roomUrl) throw new Error('roomUrl is a required param, but it was undefined.');
|
||||
const roomUrlUrl = new URL(roomUrl);
|
||||
const browser = await puppeteer.launch({
|
||||
headless: 'new'
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
|
||||
await page.setRequestInterception(true);
|
||||
page.on('request', interceptRequest => {
|
||||
// const url = interceptRequest.url();
|
||||
// if (url === 'https://chaturbate.com/push_service/auth/') {
|
||||
// console.log(`request! ${interceptRequest.url()} ${JSON.stringify(interceptRequest.headers(), null, 2)}`);
|
||||
// }
|
||||
interceptRequest.continue();
|
||||
})
|
||||
|
||||
const json = await new Promise(async (resolve) => {
|
||||
page.on('response', async (interceptResponse) => {
|
||||
const url = interceptResponse.url();
|
||||
if (url === 'https://chaturbate.com/push_service/auth/') {
|
||||
// console.log(`response! ${url}`);
|
||||
const json = await interceptResponse.json();
|
||||
resolve(json);
|
||||
}
|
||||
})
|
||||
await page.goto(roomUrlUrl.toString());
|
||||
})
|
||||
|
||||
let psa;
|
||||
try {
|
||||
psa = iPushServiceAuthSchema.parse(json);
|
||||
// console.log(psa);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
console.error(e)
|
||||
}
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
return psa;
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Generated by ts-to-zod
|
||||
import { z } from "zod";
|
||||
|
||||
export const iPushServiceAuthSchema = z.object({
|
||||
token: z.string(),
|
||||
channels: z.record(z.string()),
|
||||
failures: z.record(z.string()),
|
||||
token_request: z.object({
|
||||
keyName: z.string(),
|
||||
clientId: z.string(),
|
||||
ttl: z.number(),
|
||||
nonce: z.string(),
|
||||
capability: z.string(),
|
||||
timestamp: z.number(),
|
||||
mac: z.string(),
|
||||
}),
|
||||
client_id: z.string(),
|
||||
settings: z.object({
|
||||
backend: z.string(),
|
||||
flags: z.object({
|
||||
pm_enabled: z.boolean(),
|
||||
wowza_disabled: z.boolean(),
|
||||
userlist_enabled: z.boolean(),
|
||||
verify_enabled: z.boolean(),
|
||||
fallback_eligible: z.boolean(),
|
||||
is_live: z.boolean(),
|
||||
}),
|
||||
rest_host: z.string(),
|
||||
realtime_host: z.string(),
|
||||
fallback_hosts: z.array(z.string()),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
import * as Ably from 'ably/promises';
|
||||
import { getPushServiceAuth } from './headless';
|
||||
import { Realtime } from 'ably';
|
||||
|
||||
export async function getSuperRealtimeClient(room: string): Promise<Ably.Realtime> {
|
||||
const authCallback: Ably.Types.AuthOptions['authCallback'] = (async (_, callback) => {
|
||||
console.log(`Ably authCallback. Getting a fresh new Ably TokenRequest.`);
|
||||
|
||||
const roomUrl = new URL(`https://chaturbate.comm/${room}`);
|
||||
let pushServiceAuth = await getPushServiceAuth(roomUrl.toString());
|
||||
console.log(pushServiceAuth);
|
||||
|
||||
const tokenRequest = pushServiceAuth.token_request;
|
||||
|
||||
console.log(`Got a new TokenRequest`);
|
||||
console.log(JSON.stringify(tokenRequest, null, 2));
|
||||
if (!tokenRequest) throw new Error('tokenRequest was empty');
|
||||
|
||||
callback(null, tokenRequest);
|
||||
})
|
||||
const options: Ably.Types.ClientOptions = {
|
||||
autoConnect: false,
|
||||
closeOnUnload: true,
|
||||
transportParams: {
|
||||
remainPresentFor: '0'
|
||||
},
|
||||
restHost: 'realtime.pa.highwebmedia.com',
|
||||
realtimeHost: 'realtime.pa.highwebmedia.com',
|
||||
fallbackHosts: [
|
||||
'a-fallback.pa.highwebmedia.com',
|
||||
'b-fallback.pa.highwebmedia.com',
|
||||
'c-fallback.pa.highwebmedia.com',
|
||||
'd-fallback.pa.highwebmedia.com',
|
||||
'e-fallback.pa.highwebmedia.com'
|
||||
],
|
||||
authCallback: authCallback
|
||||
};
|
||||
const realtime = new Ably.Realtime(options);
|
||||
return realtime;
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { expect } from 'chai';
|
||||
import path from 'node:path';
|
||||
import ChaturbateAuth from '../../src/ChaturbateAuth.js';
|
||||
import gotClient from '../../src/gotClient.js'
|
||||
import fs from 'node:fs';
|
||||
import { loggerFactory } from '../../src/logger.js';
|
||||
|
||||
describe('ChaturbateAuth Integration Tests', () => {
|
||||
|
||||
const randomDirectoryName = `test-${Math.random().toString(36).substring(2)}`;
|
||||
const tmpDirectory = path.join('/tmp', randomDirectoryName);
|
||||
|
||||
// Create the random directory in /tmp
|
||||
fs.mkdirSync(tmpDirectory);
|
||||
|
||||
let appContext = {
|
||||
logger: loggerFactory(),
|
||||
dataDir: tmpDirectory,
|
||||
gotClient
|
||||
};
|
||||
|
||||
let chaturbateAuth;
|
||||
|
||||
beforeEach(() => {
|
||||
chaturbateAuth = new ChaturbateAuth(appContext);
|
||||
});
|
||||
|
||||
|
||||
// Add more test cases for other methods as needed
|
||||
|
||||
// You can also add tests for error cases, edge cases, etc.
|
||||
});
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
|
||||
import 'dotenv/config'
|
||||
import chai from "chai";
|
||||
import sinon from 'sinon';
|
||||
import { onCbStart } from '../../src/cbCallbacks.js'
|
||||
import { getAppContext } from '../../src/appContext.js'
|
||||
import * as pubsub from '../../src/pubsub.js'
|
||||
|
||||
|
||||
|
||||
|
||||
describe('onCbStart', () => {
|
||||
let appContext;
|
||||
let roomName;
|
||||
let message;
|
||||
let dbMock;
|
||||
let clearTimeoutMock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock object for appContext
|
||||
appContext = {
|
||||
logger: {
|
||||
log: sinon.stub(),
|
||||
},
|
||||
db: {
|
||||
prepare: sinon.stub(),
|
||||
},
|
||||
roomTimers: {
|
||||
[roomName]: {
|
||||
offline: 'mockTimeout',
|
||||
},
|
||||
},
|
||||
sound: {
|
||||
alert: sinon.stub()
|
||||
},
|
||||
cb: {
|
||||
getPlaylistUrl: sinon.stub()
|
||||
},
|
||||
pubsub: pubsub
|
||||
};
|
||||
|
||||
// Set up test data
|
||||
roomName = 'testRoom';
|
||||
message = {
|
||||
name: 'testName',
|
||||
id: 'testId',
|
||||
encoding: 'testEncoding',
|
||||
data: {
|
||||
tid: 'testTid',
|
||||
ts: 'testTs',
|
||||
status: 'testStatus',
|
||||
message: 'testMessage',
|
||||
hash: 'testHash',
|
||||
method: 'testMethod',
|
||||
pub_ts: 'testPubTs',
|
||||
},
|
||||
};
|
||||
|
||||
// Create mock functions
|
||||
dbMock = {
|
||||
run: sinon.stub(),
|
||||
};
|
||||
appContext.db.prepare.returns(dbMock);
|
||||
|
||||
appContext.cb.getPlaylistUrl.resolves('testPlaylistUrl');
|
||||
console.log('here are the mock')
|
||||
console.log(appContext.cb.getPlaylistUrl)
|
||||
|
||||
// clearTimeoutMock = sinon.stub();
|
||||
|
||||
|
||||
// // Stub global functions
|
||||
// sinon.stub(global, 'clearTimeout').callsFake(clearTimeoutMock);
|
||||
|
||||
// // Stub async function using sinon
|
||||
// sinon.stub(global, 'setTimeout').callsFake((callback) => {
|
||||
// callback();
|
||||
// return 'mockTimeout';
|
||||
// });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('should insert into lifecycles and call start', async () => {
|
||||
|
||||
console.log(appContext)
|
||||
console.log(`>>>>>>>>>>>>>>`)
|
||||
console.log(appContext.pubsub.start)
|
||||
console.log(message)
|
||||
|
||||
// Invoke the function
|
||||
await onCbStart(appContext, roomName, message);
|
||||
|
||||
// Assert the logger was called with the correct arguments
|
||||
sinon.assert.calledWith(
|
||||
appContext.logger.log,
|
||||
{ level: 'debug', message: `🟢🟢🟢 [STREAM START] ${JSON.stringify(message)}` }
|
||||
);
|
||||
|
||||
// Assert the database prepared statement was called with the correct SQL query
|
||||
sinon.assert.calledWith(
|
||||
appContext.db.prepare,
|
||||
'INSERT INTO lifecycles VALUES ($_room, $name, $id, $encoding, $data_tid, $data_ts, $data_status, $data_message, $data_hash, $data_method, $data_pub_ts)'
|
||||
);
|
||||
|
||||
// Assert the database statement was run with the correct parameters
|
||||
sinon.assert.calledWith(
|
||||
dbMock.run,
|
||||
sinon.match({
|
||||
_room: roomName,
|
||||
name: message.name || null,
|
||||
id: message.id || null,
|
||||
encoding: message.encoding || null,
|
||||
data_tid: message.data?.tid || null,
|
||||
data_ts: message.data?.status || null,
|
||||
data_status: message.data?.status || null,
|
||||
data_message: message.data?.message || null,
|
||||
data_hash: message.data?.hash || null,
|
||||
data_method: message.data?.method || null,
|
||||
data_pub_ts: message.data?.pub_ts || null,
|
||||
})
|
||||
);
|
||||
|
||||
// Assert the soundAlert function was called
|
||||
sinon.assert.calledOnce(appContext.sound.alert);
|
||||
|
||||
|
||||
// // Assert the getPlaylistUrl function was called with the correct arguments
|
||||
sinon.assert.calledWith(appContext.cb.getPlaylistUrl, appContext, roomName);
|
||||
|
||||
// Assert the start function was called with the correct arguments
|
||||
sinon.assert.calledWith(
|
||||
appContext.pubsub.start,
|
||||
appContext,
|
||||
roomName,
|
||||
'testPlaylistUrl'
|
||||
);
|
||||
|
||||
// // Assert clearTimeout was called with the correct argument
|
||||
// sinon.assert.calledWith(clearTimeoutMock, 'mockTimeout');
|
||||
|
||||
});
|
||||
});
|
|
@ -1,24 +0,0 @@
|
|||
import got from 'got';
|
||||
import plugin from 'got-plugin-debounce';
|
||||
|
||||
const client = got
|
||||
.extend(plugin) // load plugin
|
||||
.extend({ debounce: 2000 }) // random value from 350ms to 450ms between 2 requests
|
||||
|
||||
const startedAt = Date.now();
|
||||
|
||||
const report = () => new Promise((resolve) => {console.log(`elapsed:${Date.now()-startedAt}`); resolve()})
|
||||
|
||||
await Promise.all([
|
||||
client('https://www.google.com'),
|
||||
report(),
|
||||
client('https://www.google.com'),
|
||||
report(),
|
||||
client('https://www.google.com'),
|
||||
report(),
|
||||
client('https://www.google.com'),
|
||||
report(),
|
||||
]);
|
||||
const elapsed = Date.now() - startedAt;
|
||||
|
||||
console.log(elapsed > 1000); // true
|
|
@ -0,0 +1,89 @@
|
|||
import puppeteer from 'puppeteer';
|
||||
import { expect } from 'chai';
|
||||
import gotClient from '../../src/gotClient.js';
|
||||
import { getRandomRoom, getAblyAgentVersionSemver, getSemverNumber, getPushServiceAuth } from '../../src/headless.js';
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const getLocalAblyVersion = async function () {
|
||||
const content = await readFile(path.join(__dirname, '../../package.json'), { encoding: 'utf-8' });
|
||||
const json = JSON.parse(content, null, 2);
|
||||
if (!json?.dependencies?.ably) throw new Error('failed to get ably version');
|
||||
return json.dependencies.ably;
|
||||
}
|
||||
|
||||
// Once request interception is enabled,
|
||||
// every request will stall unless it's continued, responded or aborted.
|
||||
|
||||
describe('headless browser', function () {
|
||||
// xdescribe('sanity checks', async function (done) {
|
||||
// const browser = await puppeteer.launch({ headless: false });
|
||||
// const page = await browser.newPage();
|
||||
// await page.setRequestInterception(true);
|
||||
// page.on('request', interceptedRequest => {
|
||||
// if (interceptedRequest.isInterceptResolutionHandled()) return;
|
||||
// if (
|
||||
// interceptedRequest.url().endsWith('.png') ||
|
||||
// interceptedRequest.url().endsWith('.jpg') ||
|
||||
// interceptedRequest.url().endsWith('.webp') ||
|
||||
// interceptedRequest.url().endsWith('.avif') ||
|
||||
// interceptedRequest.url().endsWith('.svg')
|
||||
// )
|
||||
// interceptedRequest.abort();
|
||||
// else interceptedRequest.continue();
|
||||
// });
|
||||
// await page.goto('https://google.com');
|
||||
// await browser.close();
|
||||
// done();
|
||||
// })
|
||||
describe('helpers', function () {
|
||||
describe('getLocalAblyVersion', function () {
|
||||
it('should return a version string', async function () {
|
||||
const ver = await getLocalAblyVersion();
|
||||
expect(ver).to.be.a('string');
|
||||
expect(ver).to.match(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/);
|
||||
return ver;
|
||||
})
|
||||
})
|
||||
describe('getSemverNumber', function () {
|
||||
it('should accept an agent string and return a semver number', function () {
|
||||
const input = 'ably-js/1.2.37 browser'
|
||||
const output = '1.2.37'
|
||||
expect(getSemverNumber(input)).to.equal(output)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('chaturbate', function () {
|
||||
this.timeout(1000*30);
|
||||
it('should get a token_request', async function () {
|
||||
const room = await getRandomRoom();
|
||||
const roomUrl = `https://chaturbate.com/${room}`;
|
||||
const psa = await getPushServiceAuth(roomUrl);
|
||||
expect(psa).to.be.a('object');
|
||||
expect(psa).to.have.a.property('token');
|
||||
expect(psa).to.have.a.property('channels');
|
||||
expect(psa).to.have.a.property('failures');
|
||||
expect(psa).to.have.a.property('token_request');
|
||||
expect(psa).to.have.a.property('client_id');
|
||||
expect(psa).to.have.a.property('settings');
|
||||
expect(psa.token).to.be.a('string').that.is.lengthOf.above(2);
|
||||
expect(psa.channels).to.be.a('object');
|
||||
expect(psa.failures).to.be.a('object').that.is.empty;
|
||||
expect(psa.settings).to.have.a.property('rest_host');
|
||||
expect(psa.settings).to.have.a.property('realtime_host');
|
||||
});
|
||||
|
||||
it('cb ably version should match local ably agent version', async function () {
|
||||
const cbAblyVersion = await getAblyAgentVersionSemver();
|
||||
const localAblyVersion = await getLocalAblyVersion();
|
||||
expect(cbAblyVersion).to.equal(localAblyVersion);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
})
|
|
@ -1,83 +0,0 @@
|
|||
import Room from '../../src/Room.ts';
|
||||
import ChaturbateAuth from '../../src/ChaturbateAuth.js';
|
||||
import Realtime from '../../src/Realtime.js';
|
||||
import chai, { expect } from "chai";
|
||||
import chaiEvents from 'chai-events';
|
||||
import gotClient from '../../src/gotClient.js';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
||||
chai.use(chaiEvents);
|
||||
|
||||
|
||||
describe('realtime', function () {
|
||||
|
||||
describe('Can I use my own permission form?', function () {
|
||||
this.timeout(60*1000)
|
||||
it('should subscribe to Ably realtime, error free', async function () {
|
||||
|
||||
|
||||
const fakeAppContext = {
|
||||
logger: {
|
||||
log: (msg) => console.log(JSON.stringify(msg)),
|
||||
},
|
||||
dataDir: path.join(os.homedir(), '.local/share/futureporn-scout'),
|
||||
gotClient
|
||||
}
|
||||
|
||||
expect(fakeAppContext.logger.log).to.be.a('function')
|
||||
|
||||
const cb = new ChaturbateAuth(fakeAppContext)
|
||||
const ably = new Realtime(fakeAppContext, { chaturbate: cb })
|
||||
const rooms = [
|
||||
'projektmelody',
|
||||
'el_xox',
|
||||
'skyeanette',
|
||||
// 'athena_airis'
|
||||
].map((r) => new Room(fakeAppContext, { name: r, cb, ably }))
|
||||
|
||||
const elRoom = rooms[1]
|
||||
expect(elRoom.url).to.equal('https://chaturbate.com/el_xox/')
|
||||
expect(elRoom.id).to.be.a('promise')
|
||||
|
||||
// get cookie from cb. We only need one for `chaturbate.com/` (not one per room.)
|
||||
await cb.fetchCookiesIfNeeded(elRoom.url)
|
||||
const csrfToken = await cb.getCsrfToken(elRoom.url) // doesn't really matter which roomUrl for this token, as long as it is on chaturbate.com domain
|
||||
const cookieString = await cb.cookieJar.getCookieString(elRoom.url)
|
||||
|
||||
console.log('here is the cookiestring')
|
||||
console.log(cookieString)
|
||||
|
||||
await Promise.allSettled((rooms.map((r) => r.id)))
|
||||
|
||||
expect(elRoom.id).to.equal('JQ2YJS5')
|
||||
|
||||
// @todo in index.js, flag any rooms which don't have an ID. This would suggest a passworded room.
|
||||
|
||||
const openRooms = rooms.filter((r) => (!!r.id))
|
||||
|
||||
// get permission form for all the rooms we are interested in
|
||||
const permissionForm = Room.getPermissionsForm(openRooms.map((r) => r.id), ['status', 'silence'], csrfToken)
|
||||
|
||||
for (let pair of permissionForm.entries()) {
|
||||
console.log(pair[0] + ': ' + pair[1]);
|
||||
}
|
||||
|
||||
// get signed ably tokenrequest from cb
|
||||
const token = await cb.getTokenRequest(elRoom.url, permissionForm, cookieString)
|
||||
|
||||
expect(token).to.have.property('keyName')
|
||||
console.log(token)
|
||||
|
||||
// get realtime connection with merged permission form(?)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -0,0 +1,140 @@
|
|||
import Room from '../../src/Room.js';
|
||||
import ChaturbateAuth from '../../src/ChaturbateAuth.js';
|
||||
import * as Ably from 'ably/promises';
|
||||
import chai, { expect } from "chai";
|
||||
import chaiEvents from 'chai-events';
|
||||
import gotClient from '../../src/gotClient.js';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { getPushServiceAuth, getRandomRoom } from '../../src/headless.js';
|
||||
|
||||
chai.use(chaiEvents);
|
||||
|
||||
|
||||
describe('realtime', function () {
|
||||
|
||||
describe(`Ably`, function () {
|
||||
this.timeout(60 * 1000);
|
||||
it(`should subscribe to a room's events`, async function () {
|
||||
const room = await getRandomRoom();
|
||||
const authCallback: Ably.Types.AuthOptions['authCallback'] = (async (_, callback) => {
|
||||
console.log(`Ably authCallback. Getting a fresh new Ably TokenRequest.`);
|
||||
|
||||
// This is where we get a signed Ably TokenRequest from CB!
|
||||
const roomUrl = `https://chaturbate.com/${room}`;
|
||||
let pushServiceAuth = await getPushServiceAuth(roomUrl);
|
||||
console.log(pushServiceAuth);
|
||||
|
||||
const tokenRequest = pushServiceAuth.token_request;
|
||||
|
||||
console.log(`Got a new TokenRequest`);
|
||||
console.log(JSON.stringify(tokenRequest, null, 2));
|
||||
if (!tokenRequest) throw new Error('tokenRequest was empty');
|
||||
|
||||
callback(null, tokenRequest);
|
||||
})
|
||||
const options: Ably.Types.ClientOptions = {
|
||||
autoConnect: false,
|
||||
closeOnUnload: true,
|
||||
transportParams: {
|
||||
remainPresentFor: '0'
|
||||
},
|
||||
restHost: 'realtime.pa.highwebmedia.com',
|
||||
realtimeHost: 'realtime.pa.highwebmedia.com',
|
||||
fallbackHosts: [
|
||||
'a-fallback.pa.highwebmedia.com',
|
||||
'b-fallback.pa.highwebmedia.com',
|
||||
'c-fallback.pa.highwebmedia.com',
|
||||
'd-fallback.pa.highwebmedia.com',
|
||||
'e-fallback.pa.highwebmedia.com'
|
||||
],
|
||||
authCallback: authCallback
|
||||
};
|
||||
const realtime = new Ably.Realtime(options);
|
||||
const goodEnd = new Promise<void>((resolve) => {
|
||||
realtime.connection.once('connected', (idk) => {
|
||||
console.log(` [*] connected! ${JSON.stringify(idk, null, 2)}`);
|
||||
realtime.close();
|
||||
resolve()
|
||||
});
|
||||
console.log('>>> connecting to realtime');
|
||||
realtime.connect();
|
||||
});
|
||||
const badEnd = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject('timeout waiting for realtime connection');
|
||||
}, 30 * 1000);
|
||||
});
|
||||
return Promise.race([goodEnd, badEnd]);
|
||||
})
|
||||
})
|
||||
|
||||
// xdescribe('Can I use my own permission form?', function () {
|
||||
// this.timeout(60*1000)
|
||||
// it('should subscribe to Ably realtime, error free', async function () {
|
||||
|
||||
|
||||
// const fakeAppContext = {
|
||||
// logger: {
|
||||
// log: (msg) => console.log(JSON.stringify(msg)),
|
||||
// },
|
||||
// dataDir: path.join(os.homedir(), '.local/share/futureporn-scout'),
|
||||
// gotClient
|
||||
// }
|
||||
|
||||
// expect(fakeAppContext.logger.log).to.be.a('function')
|
||||
|
||||
// const cb = new ChaturbateAuth(fakeAppContext)
|
||||
// const ably = new Realtime(fakeAppContext, { chaturbate: cb })
|
||||
// const rooms = [
|
||||
// 'projektmelody',
|
||||
// 'el_xox',
|
||||
// 'skyeanette',
|
||||
// // 'athena_airis'
|
||||
// ].map((r) => new Room(fakeAppContext, { name: r, cb, ably }))
|
||||
|
||||
// const elRoom = rooms[1]
|
||||
// expect(elRoom.url).to.equal('https://chaturbate.com/el_xox/')
|
||||
// expect(elRoom.id).to.be.a('promise')
|
||||
|
||||
// // get cookie from cb. We only need one for `chaturbate.com/` (not one per room.)
|
||||
// await cb.fetchCookiesIfNeeded(elRoom.url)
|
||||
// const csrfToken = await cb.getCsrfToken(elRoom.url) // doesn't really matter which roomUrl for this token, as long as it is on chaturbate.com domain
|
||||
// const cookieString = await cb.cookieJar.getCookieString(elRoom.url)
|
||||
|
||||
// console.log('here is the cookiestring')
|
||||
// console.log(cookieString)
|
||||
|
||||
// await Promise.allSettled((rooms.map((r) => r.id)))
|
||||
|
||||
// expect(elRoom.id).to.equal('JQ2YJS5')
|
||||
|
||||
// // @todo in index.js, flag any rooms which don't have an ID. This would suggest a passworded room.
|
||||
|
||||
// const openRooms = rooms.filter((r) => (!!r.id))
|
||||
|
||||
// // get permission form for all the rooms we are interested in
|
||||
// const permissionForm = Room.getPermissionsForm(openRooms.map((r) => r.id), ['status', 'silence'], csrfToken)
|
||||
|
||||
// for (let pair of permissionForm.entries()) {
|
||||
// console.log(pair[0] + ': ' + pair[1]);
|
||||
// }
|
||||
|
||||
// // get signed ably tokenrequest from cb
|
||||
// const token = await cb.getTokenRequest(elRoom.url, permissionForm, cookieString)
|
||||
|
||||
// expect(token).to.have.property('keyName')
|
||||
// console.log(token)
|
||||
|
||||
// // get realtime connection with merged permission form(?)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// })
|
||||
// })
|
||||
|
||||
})
|
|
@ -1,8 +1,6 @@
|
|||
import { expect } from "chai";
|
||||
import Room from '../../src/Room.ts'
|
||||
import { describe } from "pm2";
|
||||
|
||||
describe('Room', function () {
|
||||
describe('')
|
||||
xdescribe('Room', function () {
|
||||
|
||||
})
|
|
@ -1,141 +0,0 @@
|
|||
import chai from 'chai';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
|
||||
|
||||
describe('onCbStart', () => {
|
||||
let appContext;
|
||||
let roomName;
|
||||
let message;
|
||||
let dbMock;
|
||||
let clearTimeoutMock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock object for appContext
|
||||
appContext = {
|
||||
logger: {
|
||||
log: sinon.stub(),
|
||||
},
|
||||
db: {
|
||||
prepare: sinon.stub(),
|
||||
},
|
||||
roomTimers: {
|
||||
[roomName]: {
|
||||
offline: 'mockTimeout',
|
||||
},
|
||||
},
|
||||
sound: {
|
||||
alert: sinon.stub()
|
||||
},
|
||||
cb: {
|
||||
getPlaylistUrl: sinon.stub()
|
||||
},
|
||||
pubsub: {
|
||||
start: sinon.stub()
|
||||
}
|
||||
};
|
||||
|
||||
// Set up test data
|
||||
roomName = 'testRoom';
|
||||
message = {
|
||||
name: 'testName',
|
||||
id: 'testId',
|
||||
encoding: 'testEncoding',
|
||||
data: {
|
||||
tid: 'testTid',
|
||||
ts: 'testTs',
|
||||
status: 'testStatus',
|
||||
message: 'testMessage',
|
||||
hash: 'testHash',
|
||||
method: 'testMethod',
|
||||
pub_ts: 'testPubTs',
|
||||
},
|
||||
};
|
||||
|
||||
// Create mock functions
|
||||
dbMock = {
|
||||
run: sinon.stub(),
|
||||
};
|
||||
appContext.db.prepare.returns(dbMock);
|
||||
|
||||
appContext.cb.getPlaylistUrl.resolves('testPlaylistUrl');
|
||||
console.log('here are the mock')
|
||||
console.log(appContext.cb.getPlaylistUrl)
|
||||
|
||||
// clearTimeoutMock = sinon.stub();
|
||||
|
||||
|
||||
// // Stub global functions
|
||||
// sinon.stub(global, 'clearTimeout').callsFake(clearTimeoutMock);
|
||||
|
||||
// // Stub async function using sinon
|
||||
// sinon.stub(global, 'setTimeout').callsFake((callback) => {
|
||||
// callback();
|
||||
// return 'mockTimeout';
|
||||
// });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('should insert into lifecycles and call start', async () => {
|
||||
|
||||
console.log(appContext)
|
||||
console.log(`>>>>>>>>>>>>>>`)
|
||||
console.log(appContext.pubsub.start)
|
||||
console.log(message)
|
||||
|
||||
// Invoke the function
|
||||
await onCbStart(appContext, roomName, message);
|
||||
|
||||
// Assert the logger was called with the correct arguments
|
||||
sinon.assert.calledWith(
|
||||
appContext.logger.log,
|
||||
{ level: 'debug', message: `🟢🟢🟢 [STREAM START] ${JSON.stringify(message)}` }
|
||||
);
|
||||
|
||||
// Assert the database prepared statement was called with the correct SQL query
|
||||
sinon.assert.calledWith(
|
||||
appContext.db.prepare,
|
||||
'INSERT INTO lifecycles VALUES ($_room, $name, $id, $encoding, $data_tid, $data_ts, $data_status, $data_message, $data_hash, $data_method, $data_pub_ts)'
|
||||
);
|
||||
|
||||
// Assert the database statement was run with the correct parameters
|
||||
sinon.assert.calledWith(
|
||||
dbMock.run,
|
||||
sinon.match({
|
||||
_room: roomName,
|
||||
name: message.name || null,
|
||||
id: message.id || null,
|
||||
encoding: message.encoding || null,
|
||||
data_tid: message.data?.tid || null,
|
||||
data_ts: message.data?.status || null,
|
||||
data_status: message.data?.status || null,
|
||||
data_message: message.data?.message || null,
|
||||
data_hash: message.data?.hash || null,
|
||||
data_method: message.data?.method || null,
|
||||
data_pub_ts: message.data?.pub_ts || null,
|
||||
})
|
||||
);
|
||||
|
||||
// Assert the soundAlert function was called
|
||||
sinon.assert.calledOnce(appContext.sound.alert);
|
||||
|
||||
|
||||
// // Assert the getPlaylistUrl function was called with the correct arguments
|
||||
sinon.assert.calledWith(appContext.cb.getPlaylistUrl, appContext, roomName);
|
||||
|
||||
// Assert the start function was called with the correct arguments
|
||||
sinon.assert.calledWith(
|
||||
appContext.pubsub.start,
|
||||
appContext,
|
||||
roomName,
|
||||
'testPlaylistUrl'
|
||||
);
|
||||
|
||||
// // Assert clearTimeout was called with the correct argument
|
||||
// sinon.assert.calledWith(clearTimeoutMock, 'mockTimeout');
|
||||
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import TuiTable from '../../src/TuiTable.js'
|
||||
|
||||
|
||||
describe('TuiTable', function() {
|
||||
it('should display a table', function() {
|
||||
const tui = new TuiTable()
|
||||
tui.addRow({
|
||||
room: 'foo',
|
||||
pubOrPvt: 'sure',
|
||||
status: 'idk',
|
||||
message: '???',
|
||||
tip: '$',
|
||||
title: '#',
|
||||
silence: 'shhh'
|
||||
})
|
||||
|
||||
|
||||
// Spawn a worker
|
||||
const worker1 = { room: 'Room 1', pubOrPass: 'Pub', status: 'Running', message: 'Working...', tip: 'Tip 1', title: 'Worker 1', silence: 'No' };
|
||||
tui.addRow({
|
||||
room: 'foo',
|
||||
pubOrPvt: 'sure',
|
||||
status: 'idk',
|
||||
message: '???',
|
||||
tip: '$',
|
||||
title: '#',
|
||||
silence: 'shhh'
|
||||
});
|
||||
|
||||
// Simulate worker retirement after some time
|
||||
setTimeout(() => {
|
||||
// tui.removeRow(worker1);
|
||||
tui.addRow({
|
||||
room: 'foo',
|
||||
pubOrPvt: 'sure',
|
||||
status: 'idk',
|
||||
message: '???',
|
||||
tip: '$',
|
||||
title: '#',
|
||||
silence: 'shhh'
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue