first commit
This commit is contained in:
10
src/config.js
Normal file
10
src/config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
clientId: process.env.CLIENT_ID,
|
||||
token: process.env.APP_ACCESS_TOKEN,
|
||||
broadcasterId: process.env.BROADCASTER_ID,
|
||||
|
||||
rewards: {
|
||||
HYDRATE: process.env.REWARD_HYDRATE_ID,
|
||||
SOUND: process.env.REWARD_SOUND_ID,
|
||||
},
|
||||
};
|
||||
3
src/index.js
Normal file
3
src/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { connect } = require("./twitch/websocket");
|
||||
|
||||
connect();
|
||||
3
src/rewards/hydrate.js
Normal file
3
src/rewards/hydrate.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (event) => {
|
||||
console.log("💧 Hydrate triggered by", event.user_name);
|
||||
};
|
||||
5
src/rewards/sound.js
Normal file
5
src/rewards/sound.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = (event) => {
|
||||
console.log("🔊 Sound triggered by", event.user_name);
|
||||
|
||||
// later: play sound, trigger OBS, etc.
|
||||
};
|
||||
36
src/twitch/handler.js
Normal file
36
src/twitch/handler.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const config = require("../config");
|
||||
|
||||
const hydrate = require("../rewards/hydrate");
|
||||
const sound = require("../rewards/sound");
|
||||
|
||||
const seen = new Set();
|
||||
|
||||
function handleEvent(payload) {
|
||||
const { subscription, event } = payload;
|
||||
|
||||
if (subscription.type !== "channel.channel_points_custom_reward_redemption.add") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deduplication
|
||||
if (seen.has(event.id)) return;
|
||||
seen.add(event.id);
|
||||
setTimeout(() => seen.delete(event.id), 60_000);
|
||||
|
||||
console.log(`${event.user_name} redeemed ${event.reward.title}`);
|
||||
|
||||
switch (event.reward.id) {
|
||||
case config.rewards.HYDRATE:
|
||||
hydrate(event);
|
||||
break;
|
||||
|
||||
case config.rewards.SOUND:
|
||||
sound(event);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("No handler for reward:", event.reward.id);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { handleEvent };
|
||||
36
src/twitch/subscriptions.js
Normal file
36
src/twitch/subscriptions.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const axios = require("axios");
|
||||
const config = require("../config");
|
||||
|
||||
async function createSubscription(sessionId) {
|
||||
try {
|
||||
const res = await axios.post(
|
||||
"https://api.twitch.tv/helix/eventsub/subscriptions",
|
||||
{
|
||||
type: "channel.channel_points_custom_reward_redemption.add",
|
||||
version: "1",
|
||||
condition: {
|
||||
broadcaster_user_id: config.broadcasterId,
|
||||
},
|
||||
transport: {
|
||||
method: "websocket",
|
||||
session_id: sessionId,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Client-ID": config.clientId,
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log("Subscription created:", res.data);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Subscription error:",
|
||||
err.response?.data || err.message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { createSubscription };
|
||||
56
src/twitch/websocket.js
Normal file
56
src/twitch/websocket.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const WebSocket = require("ws");
|
||||
const { createSubscription } = require("./subscriptions");
|
||||
const { handleEvent } = require("./handler");
|
||||
|
||||
let reconnectUrl = "wss://eventsub.wss.twitch.tv/ws";
|
||||
|
||||
function connect(url = reconnectUrl) {
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
ws.on("open", () => {
|
||||
console.log("Connected to Twitch EventSub");
|
||||
});
|
||||
|
||||
ws.on("message", async (data) => {
|
||||
const message = JSON.parse(data);
|
||||
const { metadata, payload } = message;
|
||||
|
||||
switch (metadata.message_type) {
|
||||
case "session_welcome":
|
||||
console.log("Session started:", payload.session.id);
|
||||
|
||||
reconnectUrl = payload.session.reconnect_url;
|
||||
await createSubscription(payload.session.id);
|
||||
break;
|
||||
|
||||
case "notification":
|
||||
handleEvent(payload);
|
||||
break;
|
||||
|
||||
case "session_keepalive":
|
||||
break;
|
||||
|
||||
case "session_reconnect":
|
||||
console.log("Reconnect requested");
|
||||
|
||||
ws.close();
|
||||
reconnectUrl = payload.session.reconnect_url;
|
||||
connect(reconnectUrl);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unhandled message:", message);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Disconnected. Reconnecting in 3s...");
|
||||
setTimeout(() => connect(), 3000);
|
||||
});
|
||||
|
||||
ws.on("error", (err) => {
|
||||
console.error("WebSocket error:", err.message);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { connect };
|
||||
Reference in New Issue
Block a user