update 2026
This commit is contained in:
parent
11b8cd1b0a
commit
97a0f93836
4 changed files with 180 additions and 1349 deletions
34
biome.json
Normal file
34
biome.json
Normal 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
171
index.js
|
|
@ -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!');
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue