1
0

🎨 Format code

This commit is contained in:
2021-06-20 00:51:50 +02:00
parent 6735ee5519
commit 71f7546165
6 changed files with 295 additions and 253 deletions

View File

@@ -7,10 +7,10 @@ import { start } from "./scheduledActions";
let chatClient: ChatClient; let chatClient: ChatClient;
export { export {
chatClient, chatClient,
connect, connect,
handleClientAction, handleClientAction,
say say
}; };
// TODO: clean/refactor code // TODO: clean/refactor code
@@ -18,76 +18,88 @@ export {
const LOG_PREFIX = "[ChatClient] "; const LOG_PREFIX = "[ChatClient] ";
async function connect(channels: Array<any>): Promise<void> { async function connect(channels: Array<any>): Promise<void> {
const authProvider = await getAuthProvider(); const authProvider = await getAuthProvider();
if ( if (
chatClient && chatClient &&
( (
chatClient.isConnecting || chatClient.isConnecting ||
chatClient.isConnected chatClient.isConnected
) )
) { ) {
return; return;
} }
chatClient = new ChatClient(authProvider, { channels: channels }); chatClient = new ChatClient(authProvider, { channels: channels });
chatClient.onConnect(onConnect); chatClient.onConnect(onConnect);
chatClient.onDisconnect((e: any) => { chatClient.onDisconnect((e: any) => {
console.log(`${LOG_PREFIX}Disconnected ${e.message}`); console.log(`${LOG_PREFIX}Disconnected ${e.message}`);
}); });
chatClient.onNoPermission((channel, message) => { chatClient.onNoPermission((channel, message) => {
console.log(`${LOG_PREFIX}No permission on ${channel}: ${message}`); console.log(`${LOG_PREFIX}No permission on ${channel}: ${message}`);
}); });
await chatClient.connect(); await chatClient.connect();
} }
async function onConnect(): Promise<void> { async function onConnect(): Promise<void> {
console.log(`${LOG_PREFIX}Connected`); console.log(`${LOG_PREFIX}Connected`);
start(); start();
} }
async function handleClientAction(action: any): Promise<void> { async function handleClientAction(action: any): Promise<void> {
if (action.channel && !isNaN(action.channel)) { if (
action.channel = await getUsernameFromId(parseInt(action.channel)); action.channel &&
} !isNaN(action.channel)
if (action.username && !isNaN(action.username)) { ) {
action.username = await getUsernameFromId(parseInt(action.username)); action.channel = await getUsernameFromId(parseInt(action.channel));
} }
// TODO: create a interface for action messages if (
if (!action.channel) { action.username &&
action.channel = "alexbcberio"; !isNaN(action.username)
} ) {
action.username = await getUsernameFromId(parseInt(action.username));
}
// TODO: create a interface for action messages
if (!action.channel) {
action.channel = "alexbcberio";
}
switch (action.action) { switch (action.action) {
case "say": case "say":
say(action.channel, action.message); say(action.channel, action.message);
break; break;
case "timeout": case "timeout":
await timeout(action.channel, action.username, action.time, action.reason); await timeout(
break; action.channel,
case "broadcast": action.username,
broadcast(action.message); action.time,
break; action.reason
case "addVip": );
await addVip(action.channel, action.username); break;
break; case "broadcast":
case "removeVip": broadcast(action.message);
await removeVip(action.channel, action.username); break;
break; case "addVip":
default: await addVip(action.channel, action.username);
console.log(`${[LOG_PREFIX]}Couldn't handle action:`, action); break;
} case "removeVip":
await removeVip(action.channel, action.username);
break;
default:
console.log(`${[LOG_PREFIX]}Couldn't handle action:`, action);
}
} }
// send a chat message // send a chat message
async function say(channel: string, message: string): Promise<void> { async function say(channel: string, message: string): Promise<void> {
await chatClient.say(channel, message); await chatClient.say(channel, message);
} }
// timeouts a user in a channel // timeouts a user in a channel
@@ -97,37 +109,45 @@ async function timeout(
time?: number, time?: number,
reason?: string reason?: string
): Promise<void> { ): Promise<void> {
if (!time) { if (!time) {
time = 60; time = 60;
} }
if (!reason) { if (!reason) {
reason = ""; reason = "";
} }
try { try {
await chatClient.timeout(channel, username, time, reason); await chatClient.timeout(channel, username, time, reason);
} catch (e) { } catch (e) {
// user cannot be timed out // user cannot be timed out
} }
} }
// adds a user to vips // adds a user to vips
async function addVip(channel: string, username: string, message?: string): Promise<void> { async function addVip(
if (!message) { channel: string,
message = `Otorgado VIP a @${username}.`; username: string,
} message?: string
): Promise<void> {
if (!message) {
message = `Otorgado VIP a @${username}.`;
}
await chatClient.addVip(channel, username); await chatClient.addVip(channel, username);
say(channel, message); say(channel, message);
} }
// removes a user from vips // removes a user from vips
async function removeVip(channel: string, username: string, message?: string): Promise<void> { async function removeVip(
if (!message) { channel: string,
message = `VIP de @${username} eliminado.`; username: string,
} message?: string
): Promise<void> {
if (!message) {
message = `VIP de @${username} eliminado.`;
}
await chatClient.removeVip(channel, username); await chatClient.removeVip(channel, username);
say(channel, message); say(channel, message);
} }

View File

@@ -7,59 +7,59 @@ import { UserIdResolvable } from "twitch";
import { broadcast } from "./webServer"; import { broadcast } from "./webServer";
export { export {
registerUserListener registerUserListener
} };
// TODO: clean/refactor code // TODO: clean/refactor code
const LOG_PREFIX = "[PubSub] "; const LOG_PREFIX = "[PubSub] ";
async function registerUserListener(user: UserIdResolvable) { async function registerUserListener(user: UserIdResolvable) {
const apiClient = await getApiClient(); const apiClient = await getApiClient();
const pubSubClient = new PubSubClient(); const pubSubClient = new PubSubClient();
const userId = await pubSubClient.registerUserListener(apiClient, user); const userId = await pubSubClient.registerUserListener(apiClient, user);
/*const listener = */ await pubSubClient.onRedemption(userId, onRedemption); /*const listener = */ await pubSubClient.onRedemption(userId, onRedemption);
console.log(`${LOG_PREFIX}Connected & registered`); console.log(`${LOG_PREFIX}Connected & registered`);
} }
async function onRedemption(message: PubSubRedemptionMessage) { async function onRedemption(message: PubSubRedemptionMessage) {
console.log( console.log(
`${LOG_PREFIX}Reward: "${message.rewardName}" (${message.rewardId}) redeemed by ${message.userDisplayName}` `${LOG_PREFIX}Reward: "${message.rewardName}" (${message.rewardId}) redeemed by ${message.userDisplayName}`
); );
// @ts-ignore // @ts-ignore
const reward = message._data.data.redemption.reward; const reward = message._data.data.redemption.reward;
const msg = { const msg = {
id: message.id, id: message.id,
channelId: message.channelId, channelId: message.channelId,
rewardId: message.rewardId, rewardId: message.rewardId,
rewardName: message.rewardName, rewardName: message.rewardName,
rewardImage: message.rewardImage rewardImage: message.rewardImage
? message.rewardImage.url_4x ? message.rewardImage.url_4x
: "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png", : "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png",
message: message.message, message: message.message,
userDisplayName: message.userDisplayName, userDisplayName: message.userDisplayName,
// non directly available values from PubSubRedemptionMessage // non directly available values from PubSubRedemptionMessage
backgroundColor: reward.background_color backgroundColor: reward.background_color
}; };
switch (msg.rewardId) { switch (msg.rewardId) {
// robar vip // robar vip
case "ac750bd6-fb4c-4259-b06d-56953601243b": case "ac750bd6-fb4c-4259-b06d-56953601243b":
if (await stealVip(msg)) { if (await stealVip(msg)) {
msg.message = `@${msg.userDisplayName} ha robado el VIP a @${msg.message}.`; msg.message = `@${msg.userDisplayName} ha robado el VIP a @${msg.message}.`;
broadcast(JSON.stringify(msg)); broadcast(JSON.stringify(msg));
} }
break; break;
default: default:
console.log(LOG_PREFIX, msg); console.log(LOG_PREFIX, msg);
broadcast(JSON.stringify(msg)); broadcast(JSON.stringify(msg));
break; break;
} }
} }
// TODO: extract methods // TODO: extract methods
@@ -70,61 +70,69 @@ async function stealVip(msg: {
userDisplayName: string; userDisplayName: string;
message: string; message: string;
}): Promise<boolean> { }): Promise<boolean> {
const channel = await getUsernameFromId(parseInt(msg.channelId)); const channel = await getUsernameFromId(parseInt(msg.channelId));
if (!channel) { if (!channel) {
console.log(`${LOG_PREFIX}No channel found`); console.log(`${LOG_PREFIX}No channel found`);
return false; return false;
} }
const addVipUser = msg.userDisplayName; const addVipUser = msg.userDisplayName;
const removeVipUser = msg.message; const removeVipUser = msg.message;
if (await hasVip(channel, removeVipUser)) { if (await hasVip(channel, removeVipUser)) {
await removeVip(channel, removeVipUser); await removeVip(channel, removeVipUser);
await addVip(channel, addVipUser); await addVip(channel, addVipUser);
const scheduledRemoveVipIndex = scheduledActions.findIndex( const scheduledRemoveVipIndex = scheduledActions.findIndex(
s => s.action === "removeVip" && s.username === removeVipUser s => s.action === "removeVip" && s.username === removeVipUser
); );
if (scheduledRemoveVipIndex > -1) { if (scheduledRemoveVipIndex > -1) {
scheduledActions[scheduledRemoveVipIndex].username = addVipUser; scheduledActions[scheduledRemoveVipIndex].username = addVipUser;
saveScheduledActions(); saveScheduledActions();
} }
return true; return true;
} }
return false; return false;
} }
// adds a user to vips // adds a user to vips
async function addVip(channel: string, username: string, message?: string): Promise<void> { async function addVip(
if (!message) { channel: string,
message = `Otorgado VIP a @${username}.`; username: string,
} message?: string
): Promise<void> {
if (!message) {
message = `Otorgado VIP a @${username}.`;
}
await chatClient.addVip(channel, username); await chatClient.addVip(channel, username);
say(channel, message); say(channel, message);
} }
async function hasVip(channel: string, username: string): Promise<boolean> { async function hasVip(channel: string, username: string): Promise<boolean> {
if (!username) { if (!username) {
return false; return false;
} }
const vips = await chatClient.getVips(channel); const vips = await chatClient.getVips(channel);
return vips.includes(username); return vips.includes(username);
} }
// removes a user from vips // removes a user from vips
async function removeVip(channel: string, username: string, message?: string): Promise<void> { async function removeVip(
if (!message) { channel: string,
message = `Se ha acabado el chollo, VIP de @${username} eliminado.`; username: string,
} message?: string
): Promise<void> {
if (!message) {
message = `Se ha acabado el chollo, VIP de @${username} eliminado.`;
}
await chatClient.removeVip(channel, username); await chatClient.removeVip(channel, username);
say(channel, message); say(channel, message);
} }

View File

@@ -3,10 +3,10 @@ import { handleClientAction } from "./chatClient";
import { resolve } from "path"; import { resolve } from "path";
export { export {
start, start,
scheduledActions, scheduledActions,
checkScheduledActions, checkScheduledActions,
saveScheduledActions saveScheduledActions
}; };
const LOG_PREFIX = "[Scheduled] "; const LOG_PREFIX = "[Scheduled] ";
@@ -44,7 +44,7 @@ async function start(): Promise<void> {
async function checkScheduledActions(): Promise<void> { async function checkScheduledActions(): Promise<void> {
if (checkingScheduled) { if (checkingScheduled) {
return; return;
}; }
checkingScheduled = true; checkingScheduled = true;

View File

@@ -1,5 +1,5 @@
import { TokenData } from "../../interfaces/TokenData"; import { TokenData } from "../../interfaces/TokenData";
import {promises as fs} from "fs"; import { promises as fs } from "fs";
import { resolve } from "path"; import { resolve } from "path";
const TOKENS_FILE = "tokens.json"; const TOKENS_FILE = "tokens.json";
@@ -8,44 +8,45 @@ const LOG_PREFIX = "[TokenData] ";
export { export {
getTokenData, getTokenData,
saveTokenData saveTokenData
} };
function getTokenDataFilePath(): string { function getTokenDataFilePath(): string {
return resolve(process.cwd(), TOKENS_FILE); return resolve(process.cwd(), TOKENS_FILE);
} }
async function getTokenData(): Promise<TokenData> { async function getTokenData(): Promise<TokenData> {
const tokenDataFilePath = getTokenDataFilePath(); const tokenDataFilePath = getTokenDataFilePath();
let buffer: Buffer; let buffer: Buffer;
try { try {
buffer = await fs.readFile(tokenDataFilePath); buffer = await fs.readFile(tokenDataFilePath);
} catch (e) { } catch (e) {
console.error(`${LOG_PREFIX}${TOKENS_FILE} not found on ${tokenDataFilePath}.`); console.error(
process.exit(1); `${LOG_PREFIX}${TOKENS_FILE} not found on ${tokenDataFilePath}.`
} );
process.exit(1);
}
const tokenData = await JSON.parse(buffer.toString()); const tokenData = await JSON.parse(buffer.toString());
checkTokenData(tokenData); checkTokenData(tokenData);
return tokenData; return tokenData;
} }
async function saveTokenData(tokenData: TokenData): Promise<void> { async function saveTokenData(tokenData: TokenData): Promise<void> {
const tokenDataFilePath = getTokenDataFilePath(); const tokenDataFilePath = getTokenDataFilePath();
const jsonTokenData = JSON.stringify(tokenData); const jsonTokenData = JSON.stringify(tokenData);
await fs.writeFile(tokenDataFilePath, jsonTokenData); await fs.writeFile(tokenDataFilePath, jsonTokenData);
console.log(`${LOG_PREFIX}Token data saved`); console.log(`${LOG_PREFIX}Token data saved`);
} }
function checkTokenData(tokenData: TokenData): void { function checkTokenData(tokenData: TokenData): void {
if ( if (!tokenData.access_token || !tokenData.refresh_token) {
!tokenData.access_token || console.error(
!tokenData.refresh_token `${LOG_PREFIX}Missing refresh_token or access_token in ${TOKENS_FILE}.`
) { );
console.error(`${LOG_PREFIX}Missing refresh_token or access_token in ${TOKENS_FILE}.`); process.exit(1);
process.exit(1); }
}
} }

View File

@@ -1,4 +1,8 @@
import { AccessToken, RefreshableAuthProvider, StaticAuthProvider } from "twitch-auth"; import {
AccessToken,
RefreshableAuthProvider,
StaticAuthProvider
} from "twitch-auth";
import { getTokenData, saveTokenData } from "./tokenData"; import { getTokenData, saveTokenData } from "./tokenData";
import { ApiClient } from "twitch"; import { ApiClient } from "twitch";
@@ -13,92 +17,91 @@ export {
getAuthProvider, getAuthProvider,
getApiClient, getApiClient,
getUsernameFromId getUsernameFromId
} };
function getClientCredentials(): ClientCredentials { function getClientCredentials(): ClientCredentials {
if ( if (
!process.env.TWITCH_CLIENT_ID || !process.env.TWITCH_CLIENT_ID ||
!process.env.TWITCH_CLIENT_SECRET !process.env.TWITCH_CLIENT_SECRET
) { ) {
console.error( console.error(
`${LOG_PREFIX}Missing environment parameters TWITCH_CLIENT_ID or TWITCH_CLIENT_SECRET` `${LOG_PREFIX}Missing environment parameters TWITCH_CLIENT_ID or TWITCH_CLIENT_SECRET`
); );
process.exit(1); process.exit(1);
} }
return { return {
clientId: process.env.TWITCH_CLIENT_ID, clientId: process.env.TWITCH_CLIENT_ID,
clientSecret: process.env.TWITCH_CLIENT_SECRET clientSecret: process.env.TWITCH_CLIENT_SECRET
}; };
} }
async function createStaticAuthProvider(): Promise<StaticAuthProvider> { async function createStaticAuthProvider(): Promise<StaticAuthProvider> {
let tokenData = await getTokenData(); let tokenData = await getTokenData();
const credentials = getClientCredentials(); const credentials = getClientCredentials();
return new StaticAuthProvider( return new StaticAuthProvider(credentials.clientId, tokenData.access_token);
credentials.clientId,
tokenData.access_token
);
} }
async function getAuthProvider(): Promise<RefreshableAuthProvider> { async function getAuthProvider(): Promise<RefreshableAuthProvider> {
if (refreshAuthProvider) { if (refreshAuthProvider) {
return refreshAuthProvider; return refreshAuthProvider;
} }
let tokenData = await getTokenData(); let tokenData = await getTokenData();
const staticAuthProvider = await createStaticAuthProvider(); const staticAuthProvider = await createStaticAuthProvider();
const credentials = getClientCredentials(); const credentials = getClientCredentials();
const expiry = tokenData.expiryTimestamp === null const expiry =
? null tokenData.expiryTimestamp === null
: new Date(tokenData.expiryTimestamp); ? null
: new Date(tokenData.expiryTimestamp);
refreshAuthProvider = new RefreshableAuthProvider( refreshAuthProvider = new RefreshableAuthProvider(staticAuthProvider, {
staticAuthProvider, clientSecret: credentials.clientSecret,
{ refreshToken: tokenData.refresh_token,
clientSecret: credentials.clientSecret, expiry,
refreshToken: tokenData.refresh_token, onRefresh: onRefresh
expiry, });
onRefresh: onRefresh
}
);
return refreshAuthProvider; return refreshAuthProvider;
} }
async function onRefresh(refreshData: AccessToken): Promise<void> { async function onRefresh(refreshData: AccessToken): Promise<void> {
const { accessToken, refreshToken, expiryDate } = refreshData; const {
console.log(`${LOG_PREFIX}Tokens refreshed`); accessToken,
refreshToken,
expiryDate
} = refreshData;
console.log(`${LOG_PREFIX}Tokens refreshed`);
const expiryTimestamp = expiryDate === null const expiryTimestamp = expiryDate === null
? 0 ? 0
: expiryDate.getTime() : expiryDate.getTime();
const newTokenData: TokenData = { const newTokenData: TokenData = {
access_token: accessToken, access_token: accessToken,
refresh_token: refreshToken, refresh_token: refreshToken,
expiryTimestamp expiryTimestamp
}; };
await saveTokenData(newTokenData); await saveTokenData(newTokenData);
} }
async function getApiClient(): Promise<ApiClient> { async function getApiClient(): Promise<ApiClient> {
const authProvider = await getAuthProvider(); const authProvider = await getAuthProvider();
return await new ApiClient({ authProvider }); return await new ApiClient({ authProvider });
} }
async function getUsernameFromId(userId: number): Promise<string | null> { async function getUsernameFromId(userId: number): Promise<string | null> {
const apiClient = await getApiClient(); const apiClient = await getApiClient();
const user = await apiClient.helix.users.getUserById(userId); const user = await apiClient.helix.users.getUserById(userId);
if (!user) { if (!user) {
return null; return null;
} }
return user.displayName; return user.displayName;
} }

View File

@@ -22,40 +22,44 @@ const wsServer = new WebSocket.Server({
let server: Server; let server: Server;
export { export {
listen, listen,
broadcast broadcast
} };
wsServer.on("connection", onConnection); wsServer.on("connection", onConnection);
app.use(express.static(join(process.cwd(), "client"))); app.use(express.static(join(process.cwd(), "client")));
function listen() { function listen() {
if (server) { if (server) {
console.log(`${LOG_PREFIX_HTTP}Server is already running`); console.log(`${LOG_PREFIX_HTTP}Server is already running`);
return; return;
} }
server = app.listen(!isDevelopment ? 8080 : 8081, "0.0.0.0"); server = app.listen(!isDevelopment ? 8080 : 8081, "0.0.0.0");
server.on("listening", onListening); server.on("listening", onListening);
server.on("upgrade", onUpgrade); server.on("upgrade", onUpgrade);
} }
function onListening() { function onListening() {
console.log( console.log(
`${LOG_PREFIX_HTTP}Listening on port ${(server.address() as AddressInfo).port}` `${LOG_PREFIX_HTTP}Listening on port ${
); (server.address() as AddressInfo).port
}`
);
} }
function onUpgrade(req: IncomingMessage, socket: Socket, head: Buffer) { function onUpgrade(req: IncomingMessage, socket: Socket, head: Buffer) {
wsServer.handleUpgrade(req, socket, head, socket => { wsServer.handleUpgrade(req, socket, head, socket => {
wsServer.emit("connection", socket, req); wsServer.emit("connection", socket, req);
}); });
} }
function onConnection(socket: WebSocket, req: IncomingMessage) { function onConnection(socket: WebSocket, req: IncomingMessage) {
console.log(`${LOG_PREFIX_WS}${req.socket.remoteAddress} New connection established`); console.log(
`${LOG_PREFIX_WS}${req.socket.remoteAddress} New connection established`
);
sockets.push(socket); sockets.push(socket);
socket.send( socket.send(
JSON.stringify({ JSON.stringify({
@@ -69,11 +73,11 @@ function onConnection(socket: WebSocket, req: IncomingMessage) {
// broadcast a message to all clients // broadcast a message to all clients
function broadcast(msg: string, socket?: any) { function broadcast(msg: string, socket?: any) {
const filteredSockets = socket const filteredSockets = socket
? sockets.filter(s => s !== socket) ? sockets.filter(s => s !== socket)
: sockets; : sockets;
filteredSockets.forEach(s => s.send(msg)); filteredSockets.forEach(s => s.send(msg));
} }
async function onMessage(msg: string) { async function onMessage(msg: string) {
@@ -81,7 +85,10 @@ async function onMessage(msg: string) {
const socket = this as WebSocket; const socket = this as WebSocket;
const data = JSON.parse(msg); const data = JSON.parse(msg);
if (!data.actions || data.actions.length === 0) { if (
!data.actions ||
data.actions.length === 0
) {
broadcast(msg, socket); broadcast(msg, socket);
return; return;
} }
@@ -96,14 +103,17 @@ async function onMessage(msg: string) {
} }
} }
console.log(`${LOG_PREFIX_WS}Received message with ${data.actions.length} actions:`, data); console.log(
`${LOG_PREFIX_WS}Received message with ${data.actions.length} actions:`,
data
);
} }
function onClose() { function onClose() {
// @ts-ignore // @ts-ignore
const socket: WebSocket = this as WebSocket; const socket: WebSocket = this as WebSocket;
const socketIdx = sockets.indexOf(socket); const socketIdx = sockets.indexOf(socket);
sockets.splice(socketIdx, 1); sockets.splice(socketIdx, 1);
console.log(`${LOG_PREFIX_WS}Connection closed`); console.log(`${LOG_PREFIX_WS}Connection closed`);
} }