update 2026

This commit is contained in:
Henry Jameson 2026-01-13 22:16:12 +02:00
commit 97a0f93836
4 changed files with 180 additions and 1349 deletions

34
biome.json Normal file
View file

@ -0,0 +1,34 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "space"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "single"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

171
index.js
View file

@ -1,98 +1,101 @@
import { ApiClient } from 'twitch';
import { ClientCredentialsAuthProvider } from 'twitch-auth';
import { NgrokAdapter } from 'twitch-webhooks-ngrok';
import { SimpleAdapter, WebHookListener } from 'twitch-webhooks';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
const bearer = process.env.BEARER const botBearer = process.env.BEARER;
const instance = process.env.INSTANCE const userBearer = process.env.USER_BEARER;
const instance = process.env.INSTANCE;
const videoId = process.env.VIDEO_ID;
const clientId = process.env.CLIENT_ID; const globalState = { currentlyLive: false };
const clientSecret = process.env.CLIENT_SECRET;
const userName = process.env.USER;
const hostName = process.env.HOSTNAME;
if (!(clientId && clientSecret && userName)) process.exit(1) const headers = (bearer) => ({
accept: '*/*',
const post = async ({ authorization: `Bearer ${bearer}`,
status, spoiler_text, visibility = 'direct', content_type = 'text/plain' 'cache-control': 'no-cache',
}) => await fetch(`https://${instance}/api/v1/statuses`, { 'content-type': 'application/json',
"headers": { pragma: 'no-cache',
"accept": '*/*',
"accept-language": 'en-US,en;q=0.9',
"authorization": `Bearer ${bearer}`,
"cache-control": 'no-cache',
"content-type": 'application/json',
"pragma": 'no-cache',
"sec-fetch-dest": 'empty',
"sec-fetch-mode": 'cors',
"sec-fetch-site": 'same-origin'
},
"referrerPolicy": "same-origin",
"body": JSON.stringify({
spoiler_text, status, source: 'HJ\'s twitch bot', visibility, content_type
}),
"method": "POST",
"mode": "cors",
"credentials": "include"
}); });
const authProvider = new ClientCredentialsAuthProvider(clientId, clientSecret); const post = ({
const apiClient = new ApiClient({ authProvider }); status,
spoiler_text,
visibility = 'direct',
content_type = 'text/plain',
}) =>
fetch(`https://${instance}/api/v1/statuses`, {
headers: headers(botBearer),
referrerPolicy: 'same-origin',
body: JSON.stringify({
spoiler_text,
status,
source: "HJ's twitch bot",
visibility,
content_type,
}),
method: 'POST',
mode: 'cors',
credentials: 'include',
});
console.info('Getting userid...') const scrobble = ({ title, external_link }) =>
const userId = await apiClient.helix.users.getUserByName(userName); fetch(`https://${instance}/api/v1/pleroma/scrobble`, {
headers: headers(userBearer),
referrerPolicy: 'same-origin',
body: JSON.stringify({
title,
external_link,
}),
method: 'POST',
mode: 'cors',
credentials: 'include',
});
const adapter = hostName // await post({
? new SimpleAdapter({ // spoiler_text: 'Debug',
hostName, // status: '@hj twitch bot initialized',
listenerPort: 8090 // });
})
: new NgrokAdapter()
const listener = new WebHookListener(apiClient, adapter); process.on('SIGINT', () => {
console.info('Starting listener...') process.exit();
await listener.listen(); });
await post ({ const loop = async () => {
spoiler_text: 'Debug', const response = await fetch(
status: '@hj twitch bot initialized' `https://tube.ebin.club/api/v1/videos/${videoId}`,
}) );
const { state } = await response.json();
const isLive = state.id === 1; // published
const stateChange = isLive !== globalState.currentlyLive;
globalState.currentlyLive = isLive;
// we need to track the previous status of the stream because there are other state changes than the live/offline switch if (stateChange) {
let prevStream = await apiClient.helix.streams.getStreamByUserId(userId); if (isLive) {
console.info('Subscribing to events...')
console.log(prevStream)
const subscription = await listener.subscribeToStreamChanges(userId, async stream => {
if (stream) {
console.log(stream)
if (!prevStream) {
console.log(`${stream.userDisplayName} just went live with title: ${stream.title}`);
const status = [
'Hej, hj just went live on twitch :annoying_dog_hole:',
'',
`Streaming ${stream.gameName}: ${stream.title}`,
'https://www.twitch.tv/hjkos',
'',
'Follow this bot and hit the bell to get notifications when hj is live'
].join('\n')
await post({ await post({
spoiler_text: 'Stream announcment', spoiler_text: 'Stream announcment',
status, status: [
visibility: 'public' 'Hej, hj just went live on peertube :annoying_dog_hole:',
}) '',
} `Streaming ${stream.gameName}: ${stream.title}`,
} else { `https://tube.ebin.club/w/${videoId}`,
// no stream, no display name '',
const user = await apiClient.helix.users.getUserById(userId); 'Follow this bot and hit the bell to get notifications when hj is live',
console.log(`${user.displayName} just went offline`); ].join('\n'),
} visibility: 'public',
prevStream = stream ?? null; });
});
console.info('Ready!')
process.on('SIGINT', function() { await scrobble({
subscription.stop() title: 'Streaming on PeerTube',
process.exit() external_link: `https://tube.ebin.club/w/${videoId}`,
}); });
} else {
await scrobble({
title: 'Stream over',
});
}
}
setTimeout(loop, 30 * 1000);
};
loop();
console.info('Ready!');

View file

@ -8,10 +8,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1"
"twitch": "^4.5.5", },
"twitch-auth": "^4.5.5", "devDependencies": {
"twitch-webhooks": "^4.5.5", "@biomejs/biome": "2.3.11"
"twitch-webhooks-ngrok": "^4.5.5"
} }
} }

1315
yarn.lock

File diff suppressed because it is too large Load diff