1
0

🚨 Fix most of the linting error and warnings

This commit is contained in:
2022-01-06 18:15:56 +01:00
parent 0a18826978
commit 931cc57b1b
18 changed files with 829 additions and 695 deletions

View File

@@ -1,11 +1,17 @@
import { hasVip, say } from "..";
import { chatClient } from "../.."; import { chatClient } from "../..";
import { say } from "..";
async function addVip( async function addVip(
channel: string, channel: string,
username: string, username: string,
message?: string message?: string
): Promise<boolean> { ): Promise<boolean> {
username = username.toLowerCase();
if (!(await hasVip(channel, username))) {
return false;
}
try { try {
await chatClient.addVip(channel, username); await chatClient.addVip(channel, username);
} catch (e) { } catch (e) {

View File

@@ -2,18 +2,49 @@ import { chatClient } from "../..";
type CacheType = Record<string, Array<string>>; type CacheType = Record<string, Array<string>>;
const cache: CacheType = {}; const cache: CacheType = {};
const cacheKeepTime = 2.5e3;
async function hasVip(channel: string, username: string): Promise<boolean> { interface ChannelFetching {
if (!username) { channel: string;
return false; promise: Promise<unknown>;
} }
if (!cache[channel]) { const channelsFetching: Array<ChannelFetching> = [];
cache[channel] = await chatClient.getVips(channel);
async function fetchVips(channel: string): Promise<void> {
const alreadyChecking = channelsFetching.find((c) => c.channel === channel);
if (alreadyChecking) {
await alreadyChecking.promise;
} else {
const promise = new Promise<void>((res) => {
chatClient.getVips(channel).then((vips) => {
cache[channel] = vips.map((u) => u.toLowerCase());
res();
});
});
channelsFetching.push({ channel, promise });
// eslint-disable-next-line no-magic-numbers
const addedIdx = channelsFetching.length - 1;
await promise;
channelsFetching.splice(addedIdx);
setTimeout(() => { setTimeout(() => {
delete cache[channel]; delete cache[channel];
}, 2500); }, cacheKeepTime);
}
}
async function hasVip(channel: string, username: string): Promise<boolean> {
username = username.toLowerCase();
if (!cache[channel]) {
await fetchVips(channel);
} }
const vips = cache[channel]; const vips = cache[channel];

View File

@@ -1,7 +1,18 @@
import { say, sayError, sayInfo, saySuccess, sayWarn } from "./say";
import { addVip } from "./addVip"; import { addVip } from "./addVip";
import { hasVip } from "./hasVip"; import { hasVip } from "./hasVip";
import { removeVip } from "./removeVip"; import { removeVip } from "./removeVip";
import { say } from "./say";
import { timeout } from "./timeout"; import { timeout } from "./timeout";
export { say, timeout, addVip, removeVip, hasVip }; export {
addVip,
hasVip,
removeVip,
say,
sayError,
sayInfo,
saySuccess,
sayWarn,
timeout,
};

View File

@@ -1,11 +1,18 @@
import { hasVip, say } from "..";
import { chatClient } from "../.."; import { chatClient } from "../..";
import { say } from "..";
async function removeVip( async function removeVip(
channel: string, channel: string,
username: string, username: string,
message?: string message?: string
): Promise<boolean> { ): Promise<boolean> {
username = username.toLowerCase();
if (await hasVip(channel, username)) {
return false;
}
try { try {
await chatClient.removeVip(channel, username); await chatClient.removeVip(channel, username);
} catch (e) { } catch (e) {

View File

@@ -1,14 +1,15 @@
import { chatClient } from "../.."; import { chatClient } from "../..";
const maxMessageLength = 500;
async function say(channel: string, message: string): Promise<void> { async function say(channel: string, message: string): Promise<void> {
const maxMessageLength = 500;
// message = `MrDestructoid ${message}`; // message = `MrDestructoid ${message}`;
if (message.length > 500) { if (message.length > maxMessageLength) {
const startIndex = 0;
const suffix = "..."; const suffix = "...";
message = `${message.substring( message = `${message.substring(
0, startIndex,
maxMessageLength - suffix.length maxMessageLength - suffix.length
)}${suffix}`; )}${suffix}`;
} }
@@ -16,4 +17,20 @@ async function say(channel: string, message: string): Promise<void> {
await chatClient.say(channel, message); await chatClient.say(channel, message);
} }
export { say }; async function sayError(channel: string, message: string): Promise<void> {
await say(channel, `[ERROR] ${message}`);
}
async function sayWarn(channel: string, message: string): Promise<void> {
await say(channel, `[WARN] ${message}`);
}
async function sayInfo(channel: string, message: string) {
await say(channel, `[INFO] ${message}`);
}
async function saySuccess(channel: string, message: string): Promise<void> {
await say(channel, `[SUCCESS] ${message}`);
}
export { say, sayError, sayWarn, sayInfo, saySuccess };

View File

@@ -1,6 +1,7 @@
import { chatClient } from "../.."; import { chatClient } from "../..";
// timeouts a user in a channel const defaultTime = 60;
async function timeout( async function timeout(
channel: string, channel: string,
username: string, username: string,
@@ -8,7 +9,7 @@ async function timeout(
reason?: string reason?: string
): Promise<void> { ): Promise<void> {
if (!time) { if (!time) {
time = 60; time = defaultTime;
} }
if (!reason) { if (!reason) {

View File

@@ -1,36 +1,36 @@
import { LOG_PREFIX, say } from ".."; import { sayError, saySuccess } from "../clientActions";
import { LOG_PREFIX } from "..";
import { TwitchPrivateMessage } from "@twurple/chat/lib/commands/TwitchPrivateMessage"; import { TwitchPrivateMessage } from "@twurple/chat/lib/commands/TwitchPrivateMessage";
import { createReward as createChannelPointsReward } from "../../helpers/twitch"; import { createReward as createChannelPointsReward } from "../../helpers/twitch";
async function createReward( async function createReward(
channel: string, channel: string,
user: string, _user: string,
message: string, message: string,
msg: TwitchPrivateMessage msg: TwitchPrivateMessage
): Promise<void> { ): Promise<void> {
const args = message.split(" "); const args = message.split(" ");
const title = args.shift(); const title = args.shift();
const cost = Math.max(1, parseInt(args.shift() ?? "0"));
if (!title || !cost) { if (!title) {
await say( await sayError(channel, "Debes indicar el título de la recompensa");
channel,
"No se ha especificado el nombre de la recompensa o costo"
);
return; return;
} }
const minRewardPrice = 1;
const cost = Math.max(minRewardPrice, parseInt(args.shift() ?? "0"));
try { try {
await createChannelPointsReward(msg.channelId as string, { await createChannelPointsReward(msg.channelId as string, {
title, title,
cost cost,
}); });
say( saySuccess(
channel, channel,
`Creada recompensa de canal "${title}" con un costo de ${cost}` `Creada recompensa de canal "${title}" con un costo de ${cost}`
); );
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {

View File

@@ -12,77 +12,14 @@ import { start } from "../helpers/miniDb";
let chatClient: ChatClient; let chatClient: ChatClient;
export { chatClient, connect, handleClientAction, say, LOG_PREFIX };
const LOG_PREFIX = "[ChatClient] "; const LOG_PREFIX = "[ChatClient] ";
async function connect(channels: Array<any>): Promise<void> { function onConnect(): Promise<void> {
const authProvider = await getAuthProvider();
if (chatClient && (chatClient.isConnecting || chatClient.isConnected)) {
return;
}
chatClient = new ChatClient({
authProvider,
channels
});
chatClient.onConnect(onConnect);
chatClient.onDisconnect((e: any) => {
console.log(`${LOG_PREFIX}Disconnected ${e.message}`);
});
chatClient.onNoPermission((channel, message) => {
console.log(`${LOG_PREFIX}No permission on ${channel}: ${message}`);
});
chatClient.onMessage(onMessage);
await chatClient.connect();
}
async function onConnect(): Promise<void> {
console.log(`${LOG_PREFIX}Connected`); console.log(`${LOG_PREFIX}Connected`);
start(); start();
}
async function handleClientAction(action: Action): Promise<void> { return Promise.resolve();
const [channel, username] = await Promise.all([
getUsernameFromId(parseInt(action.channelId)),
getUsernameFromId(parseInt(action.userId))
]);
if (!channel || !username) {
console.log(`${[LOG_PREFIX]}ChannelId or userId could not be solved`);
return;
}
switch (action.type) {
case ActionType.Say:
say(channel, action.data.message);
break;
case ActionType.Timeout:
try {
await timeout(channel, username, action.data.time, action.data.reason);
} catch (e) {
// user cannot be timed out
}
break;
case ActionType.Broadcast:
broadcast(action.data.message);
break;
case ActionType.AddVip:
await addVip(channel, username, `Otorgado VIP a @${username}`);
break;
case ActionType.RemoveVip:
await removeVip(channel, username, `Eliminado VIP de @${username}`);
break;
default:
console.log(`${[LOG_PREFIX]}Couldn't handle action:`, action);
}
} }
const commandPrefix = "!"; const commandPrefix = "!";
@@ -116,3 +53,74 @@ async function onMessage(
} }
} }
} }
async function connect(channels: Array<string>): Promise<void> {
const authProvider = await getAuthProvider();
if (chatClient && (chatClient.isConnecting || chatClient.isConnected)) {
return;
}
chatClient = new ChatClient({
authProvider,
channels,
webSocket: true,
});
chatClient.onConnect(onConnect);
chatClient.onDisconnect((manually, reason) => {
if (manually) {
console.log(`${LOG_PREFIX}Disconnected manually`);
return;
}
console.log(`${LOG_PREFIX}Disconnected ${reason}`);
});
chatClient.onNoPermission((channel, message) => {
console.log(`${LOG_PREFIX}No permission on ${channel}: ${message}`);
});
chatClient.onMessage(onMessage);
await chatClient.connect();
}
async function handleClientAction(action: Action): Promise<void> {
const [channel, username] = await Promise.all([
getUsernameFromId(parseInt(action.channelId)),
getUsernameFromId(parseInt(action.userId)),
]);
if (!channel || !username) {
console.log(`${[LOG_PREFIX]}ChannelId or userId could not be solved`);
return;
}
switch (action.type) {
case ActionType.Say:
say(channel, action.data.message);
break;
case ActionType.Timeout:
try {
await timeout(channel, username, action.data.time, action.data.reason);
} catch (e) {
// user cannot be timed out
}
break;
case ActionType.Broadcast:
broadcast(action.data.message);
break;
case ActionType.AddVip:
await addVip(channel, username, `Otorgado VIP a @${username}`);
break;
case ActionType.RemoveVip:
await removeVip(channel, username, `Eliminado VIP de @${username}`);
break;
default:
console.log(`${[LOG_PREFIX]}Couldn't handle action:`, action);
}
}
export { chatClient, connect, handleClientAction, say, LOG_PREFIX };

View File

@@ -1,3 +1,4 @@
import { Action } from "../../interfaces/actions/Action";
import { promises as fs } from "fs"; import { promises as fs } from "fs";
import { handleClientAction } from "../chatClient"; import { handleClientAction } from "../chatClient";
import { resolve } from "path"; import { resolve } from "path";
@@ -12,14 +13,70 @@ const FILES_BASE = resolve(process.cwd(), "./storage");
const SCHEDULED_FILE = resolve(FILES_BASE, "./scheduled.json"); const SCHEDULED_FILE = resolve(FILES_BASE, "./scheduled.json");
const VIP_USERS_FILE = resolve(FILES_BASE, "./vips.json"); const VIP_USERS_FILE = resolve(FILES_BASE, "./vips.json");
const scheduledActions: Array<any> = []; const defaultScheduledTimestamp = 0;
let scheduledActions: Array<Action> = [];
const vipUsers: Record<string, Array<string>> = {}; const vipUsers: Record<string, Array<string>> = {};
let checkingScheduled = false; let checkingScheduled = false;
let scheduledActionsInterval: NodeJS.Timeout; let scheduledActionsInterval: NodeJS.Timeout;
let saveScheduledActionsTimeout: NodeJS.Timeout | null; let saveScheduledActionsTimeout: NodeJS.Timeout | null;
// *Check this, not working function save(): void {
if (saveScheduledActionsTimeout) {
clearTimeout(saveScheduledActionsTimeout);
saveScheduledActionsTimeout = null;
console.log(`${LOG_PREFIX}Removed save timeout.`);
}
saveScheduledActionsTimeout = setTimeout(async () => {
await Promise.all([
fs.writeFile(SCHEDULED_FILE, JSON.stringify(scheduledActions)),
fs.writeFile(VIP_USERS_FILE, JSON.stringify(vipUsers)),
]);
console.log(`${LOG_PREFIX}Saved actions.`);
saveScheduledActionsTimeout = null;
}, SAVE_TIMEOUT);
}
async function checkScheduledActions(): Promise<void> {
if (checkingScheduled) {
return;
}
checkingScheduled = true;
let hasToSave = false;
for (
let i = 0;
i < scheduledActions.length &&
(scheduledActions[i].scheduledAt ?? defaultScheduledTimestamp) <=
Date.now();
i++
) {
hasToSave = true;
const deleteCount = 1;
const deletedActions = scheduledActions.splice(i, deleteCount);
const action = deletedActions.shift();
if (action) {
await handleClientAction(action);
}
console.log(`${LOG_PREFIX}Executed: ${JSON.stringify(action)}`);
}
if (hasToSave) {
save();
}
checkingScheduled = false;
}
async function start(): Promise<void> { async function start(): Promise<void> {
if (scheduledActionsInterval) { if (scheduledActionsInterval) {
return; return;
@@ -35,10 +92,21 @@ async function start(): Promise<void> {
} }
} }
scheduledActions.push.apply(scheduledActions, savedActions); scheduledActions = [...scheduledActions, ...savedActions];
scheduledActions.sort((a, b) => a.scheduledAt - b.scheduledAt); scheduledActions.sort((a, b) => {
if (typeof a.scheduledAt === "undefined") {
a.scheduledAt = defaultScheduledTimestamp;
}
if (typeof b.scheduledAt === "undefined") {
b.scheduledAt = defaultScheduledTimestamp;
}
return a.scheduledAt - b.scheduledAt;
});
setTimeout(checkScheduledActions, FIRST_CHECK_TIMEOUT); setTimeout(checkScheduledActions, FIRST_CHECK_TIMEOUT);
// eslint-disable-next-line require-atomic-updates
scheduledActionsInterval = setInterval(checkScheduledActions, CHECK_INTERVAL); scheduledActionsInterval = setInterval(checkScheduledActions, CHECK_INTERVAL);
try { try {
@@ -57,35 +125,6 @@ async function start(): Promise<void> {
} }
} }
async function checkScheduledActions(): Promise<void> {
if (checkingScheduled) {
return;
}
checkingScheduled = true;
let hasToSave = false;
for (
let i = 0;
i < scheduledActions.length &&
scheduledActions[i].scheduledAt <= Date.now();
i++
) {
hasToSave = true;
const action = scheduledActions.splice(i, 1)[0];
await handleClientAction(action);
console.log(`${LOG_PREFIX}Executed: ${JSON.stringify(action)}`);
}
if (hasToSave) {
save();
}
checkingScheduled = false;
}
async function createSaveDirectory() { async function createSaveDirectory() {
try { try {
await fs.stat(FILES_BASE); await fs.stat(FILES_BASE);
@@ -94,24 +133,6 @@ async function createSaveDirectory() {
} }
} }
function save(): void {
if (saveScheduledActionsTimeout) {
clearTimeout(saveScheduledActionsTimeout);
saveScheduledActionsTimeout = null;
console.log(`${LOG_PREFIX}Removed save timeout.`);
}
saveScheduledActionsTimeout = setTimeout(async () => {
await Promise.all([
fs.writeFile(SCHEDULED_FILE, JSON.stringify(scheduledActions)),
fs.writeFile(VIP_USERS_FILE, JSON.stringify(vipUsers))
]);
console.log(`${LOG_PREFIX}Saved actions.`);
saveScheduledActionsTimeout = null;
}, SAVE_TIMEOUT);
}
createSaveDirectory(); createSaveDirectory();
export { start, scheduledActions, save, vipUsers }; export { start, scheduledActions, save, vipUsers };

View File

@@ -5,12 +5,22 @@ import { resolve } from "path";
const TOKENS_FILE = "tokens.json"; const TOKENS_FILE = "tokens.json";
const LOG_PREFIX = "[TokenData] "; const LOG_PREFIX = "[TokenData] ";
export { getTokenData, saveTokenData };
function getTokenDataFilePath(): string { function getTokenDataFilePath(): string {
return resolve(process.cwd(), TOKENS_FILE); return resolve(process.cwd(), TOKENS_FILE);
} }
function checkTokenData(tokenData: AccessToken): void {
if (!tokenData.accessToken || !tokenData.refreshToken) {
console.error(
`${LOG_PREFIX}Missing refreshToken or accessToken in ${TOKENS_FILE}.`
);
const exitCode = 1;
process.exit(exitCode);
}
}
async function getTokenData(): Promise<AccessToken> { async function getTokenData(): Promise<AccessToken> {
const tokenDataFilePath = getTokenDataFilePath(); const tokenDataFilePath = getTokenDataFilePath();
let buffer: Buffer; let buffer: Buffer;
@@ -21,7 +31,10 @@ async function getTokenData(): Promise<AccessToken> {
console.error( console.error(
`${LOG_PREFIX}${TOKENS_FILE} not found on ${tokenDataFilePath}.` `${LOG_PREFIX}${TOKENS_FILE} not found on ${tokenDataFilePath}.`
); );
process.exit(1);
const exitCode = 1;
process.exit(exitCode);
} }
const tokenData = await JSON.parse(buffer.toString()); const tokenData = await JSON.parse(buffer.toString());
@@ -39,11 +52,4 @@ async function saveTokenData(tokenData: AccessToken): Promise<void> {
console.log(`${LOG_PREFIX}Token data saved`); console.log(`${LOG_PREFIX}Token data saved`);
} }
function checkTokenData(tokenData: AccessToken): void { export { getTokenData, saveTokenData };
if (!tokenData.accessToken || !tokenData.refreshToken) {
console.error(
`${LOG_PREFIX}Missing refresh_token or access_token in ${TOKENS_FILE}.`
);
process.exit(1);
}
}

View File

@@ -2,7 +2,7 @@ import { AccessToken, RefreshingAuthProvider } from "@twurple/auth";
import { import {
ApiClient, ApiClient,
HelixCreateCustomRewardData, HelixCreateCustomRewardData,
UserIdResolvable UserIdResolvable,
} from "@twurple/api"; } from "@twurple/api";
import { getTokenData, saveTokenData } from "./tokenData"; import { getTokenData, saveTokenData } from "./tokenData";
@@ -17,46 +17,49 @@ function getClientCredentials(): ClientCredentials {
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);
const exitCode = 1;
process.exit(exitCode);
} }
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 getAuthProvider(): Promise<RefreshingAuthProvider> {
if (refreshAuthProvider) {
return refreshAuthProvider;
}
let tokenData = await getTokenData();
const credentials = getClientCredentials();
refreshAuthProvider = new RefreshingAuthProvider(
{
clientId: credentials.clientId,
clientSecret: credentials.clientSecret,
onRefresh
},
tokenData
);
return refreshAuthProvider;
}
async function onRefresh(refreshData: AccessToken): Promise<void> { async function onRefresh(refreshData: AccessToken): Promise<void> {
console.log(`${LOG_PREFIX}Tokens refreshed`); console.log(`${LOG_PREFIX}Tokens refreshed`);
await saveTokenData(refreshData); await saveTokenData(refreshData);
} }
async function getAuthProvider(): Promise<RefreshingAuthProvider> {
if (refreshAuthProvider) {
return refreshAuthProvider;
}
const tokenData = await getTokenData();
const credentials = getClientCredentials();
// eslint-disable-next-line require-atomic-updates
refreshAuthProvider = new RefreshingAuthProvider(
{
clientId: credentials.clientId,
clientSecret: credentials.clientSecret,
onRefresh,
},
tokenData
);
return refreshAuthProvider;
}
async function getApiClient(): Promise<ApiClient> { async function getApiClient(): Promise<ApiClient> {
const authProvider = await getAuthProvider(); const authProvider = await getAuthProvider();
return await new ApiClient({ authProvider }); return new ApiClient({ authProvider });
} }
async function getUsernameFromId(userId: number): Promise<string | null> { async function getUsernameFromId(userId: number): Promise<string | null> {
@@ -123,5 +126,5 @@ export {
getUsernameFromId, getUsernameFromId,
completeRewards, completeRewards,
cancelRewards, cancelRewards,
createReward createReward,
}; };

View File

@@ -1,9 +1,8 @@
import { AddressInfo, Socket } from "net";
import { IncomingMessage, Server } from "http"; import { IncomingMessage, Server } from "http";
import { save, scheduledActions } from "./miniDb"; import { save, scheduledActions } from "./miniDb";
import { Action } from "../../interfaces/actions/Action"; import { Action } from "../../interfaces/actions/Action";
import { AddressInfo } from "net";
import { Socket } from "net";
import WebSocket from "ws"; import WebSocket from "ws";
import express from "express"; import express from "express";
import { handleClientAction } from "../chatClient"; import { handleClientAction } from "../chatClient";
@@ -17,72 +16,24 @@ const app = express();
const sockets: Array<WebSocket> = []; const sockets: Array<WebSocket> = [];
const wsServer = new WebSocket.Server({ const wsServer = new WebSocket.Server({
noServer: true noServer: true,
}); });
let server: Server; let server: Server;
export { listen, broadcast }; function broadcast(msg: string, socket?: WebSocket) {
const filteredSockets = socket
? sockets.filter((s) => s !== socket)
: sockets;
wsServer.on("connection", onConnection); filteredSockets.forEach((s) => s.send(msg));
app.use(express.static(join(process.cwd(), "client")));
function listen() {
if (server) {
console.log(`${LOG_PREFIX_HTTP}Server is already running`);
return;
}
server = app.listen(!isDevelopment ? 8080 : 8081, "0.0.0.0");
server.on("listening", onListening);
server.on("upgrade", onUpgrade);
} }
function onListening() { async function onMessage(this: WebSocket, msg: string) {
console.log(
`${LOG_PREFIX_HTTP}Listening on port ${
(server.address() as AddressInfo).port
}`
);
}
function onUpgrade(req: IncomingMessage, socket: Socket, head: Buffer) {
wsServer.handleUpgrade(req, socket, head, socket => {
wsServer.emit("connection", socket, req);
});
}
function onConnection(socket: WebSocket, req: IncomingMessage) {
console.log(
`${LOG_PREFIX_WS}${req.socket.remoteAddress} New connection established`
);
sockets.push(socket);
socket.send(
JSON.stringify({
env: isDevelopment ? "dev" : "prod"
})
);
socket.on("message", onMessage);
socket.on("close", onClose);
}
// broadcast a message to all clients
function broadcast(msg: string, socket?: any) {
const filteredSockets = socket ? sockets.filter(s => s !== socket) : sockets;
filteredSockets.forEach(s => s.send(msg));
}
async function onMessage(msg: string) {
// @ts-ignore
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) {
broadcast(msg, socket); broadcast(msg, this);
return; return;
} }
@@ -93,6 +44,7 @@ async function onMessage(msg: string) {
await handleClientAction(action); await handleClientAction(action);
} else { } else {
scheduledActions.push(action); scheduledActions.push(action);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
scheduledActions.sort((a: any, b: any) => a.scheduledAt - b.scheduledAt); scheduledActions.sort((a: any, b: any) => a.scheduledAt - b.scheduledAt);
save(); save();
} }
@@ -104,11 +56,63 @@ async function onMessage(msg: string) {
); );
} }
function onClose() { function onClose(this: WebSocket) {
// @ts-ignore const socketIdx = sockets.indexOf(this);
const socket: WebSocket = this as WebSocket; const deleteCount = 1;
const socketIdx = sockets.indexOf(socket); sockets.splice(socketIdx, deleteCount);
sockets.splice(socketIdx, 1);
console.log(`${LOG_PREFIX_WS}Connection closed`); console.log(`${LOG_PREFIX_WS}Connection closed`);
} }
function onListening() {
console.log(
`${LOG_PREFIX_HTTP}Listening on port ${
(server.address() as AddressInfo).port
}`
);
}
function onUpgrade(req: IncomingMessage, socket: Socket, head: Buffer) {
wsServer.handleUpgrade(req, socket, head, (socket) => {
wsServer.emit("connection", socket, req);
});
}
function onConnection(socket: WebSocket, req: IncomingMessage) {
console.log(
`${LOG_PREFIX_WS}${req.socket.remoteAddress} New connection established`
);
sockets.push(socket);
socket.send(
JSON.stringify({
env: isDevelopment ? "dev" : "prod",
})
);
socket.on("message", onMessage);
socket.on("close", onClose);
}
wsServer.on("connection", onConnection);
app.use(express.static(join(process.cwd(), "client")));
function listen() {
if (server) {
console.log(`${LOG_PREFIX_HTTP}Server is already running`);
return;
}
let port = 8080;
if (isDevelopment) {
port++;
}
server = app.listen(port, "0.0.0.0");
server.on("listening", onListening);
server.on("upgrade", onUpgrade);
}
export { listen, broadcast };

View File

@@ -13,7 +13,7 @@ async function highlightMessage(
} }
const urlRegex = const urlRegex =
/(https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&/=]*)/; /(https?:\/\/)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/;
if (urlRegex.test(msg.message)) { if (urlRegex.test(msg.message)) {
console.log(`${LOG_PREFIX}Message contains a url`); console.log(`${LOG_PREFIX}Message contains a url`);
@@ -27,8 +27,9 @@ async function highlightMessage(
try { try {
const reason = "No se permite enviar enlaces en mensajes destacados"; const reason = "No se permite enviar enlaces en mensajes destacados";
const timeoutSeconds = 10;
await timeout(channel, msg.userDisplayName, 10, reason); await timeout(channel, msg.userDisplayName, timeoutSeconds, reason);
} catch (e) { } catch (e) {
// user probably cannot be timed out // user probably cannot be timed out
} }

View File

@@ -27,14 +27,18 @@ async function russianRoulette(
gunsSafeShots[channelId] = maxSafeShots; gunsSafeShots[channelId] = maxSafeShots;
} }
const noShots = 0;
const win = const win =
gunsSafeShots[channelId] > 0 && gunsSafeShots[channelId] > noShots &&
// eslint-disable-next-line no-magic-numbers
randomInt(gunsSafeShots[channelId]-- + 1) !== 0; randomInt(gunsSafeShots[channelId]-- + 1) !== 0;
if (gunsSafeShots[channelId] < 0 || !win) { if (gunsSafeShots[channelId] < noShots || !win) {
gunsSafeShots[channelId] = maxSafeShots; gunsSafeShots[channelId] = maxSafeShots;
} }
// eslint-disable-next-line require-atomic-updates
msg.message = win ? "" : "got shot"; msg.message = win ? "" : "got shot";
const promises: Array<Promise<unknown>> = []; const promises: Array<Promise<unknown>> = [];

View File

@@ -28,8 +28,9 @@ async function stealVip(
const removeVipUser = msg.message.toLowerCase(); const removeVipUser = msg.message.toLowerCase();
const channelVips = vipUsers[channelId]; const channelVips = vipUsers[channelId];
if (!channelVips.find(u => u.toLowerCase() === removeVipUser)) { if (!channelVips.find((u) => u.toLowerCase() === removeVipUser)) {
const message = const message =
// eslint-disable-next-line no-magic-numbers
channelVips.length === 0 channelVips.length === 0
? "No hay nadie a quien puedas robar el VIP" ? "No hay nadie a quien puedas robar el VIP"
: `Solo puedes robar el VIP de: "${channelVips.sort().join('", "')}"`; : `Solo puedes robar el VIP de: "${channelVips.sort().join('", "')}"`;
@@ -60,14 +61,16 @@ async function stealVip(
} }
const removeIdx = channelVips.findIndex( const removeIdx = channelVips.findIndex(
u => u.toLowerCase() === removeVipUser (u) => u.toLowerCase() === removeVipUser
); );
channelVips.splice(removeIdx); channelVips.splice(removeIdx);
channelVips.push(addVipUser); channelVips.push(addVipUser);
save(); save();
// eslint-disable-next-line require-atomic-updates
msg.message = `@${addVipUser} ha "tomado prestado" el VIP de @${removeVipUser}`; msg.message = `@${addVipUser} ha "tomado prestado" el VIP de @${removeVipUser}`;
await say(channel, msg.message); await say(channel, msg.message);
return msg; return msg;

View File

@@ -27,6 +27,7 @@ async function timeoutFriend(
try { try {
await timeout(channel, msg.message, time, reason); await timeout(channel, msg.message, time, reason);
// eslint-disable-next-line require-atomic-updates
msg.message = `@${userDisplayName} ha expulsado a @${message} por ${time} segundos`; msg.message = `@${userDisplayName} ha expulsado a @${message} por ${time} segundos`;
} catch (e) { } catch (e) {
// user can not be timed out // user can not be timed out

View File

@@ -2,7 +2,7 @@ import { PubSubClient, PubSubRedemptionMessage } from "@twurple/pubsub";
import { import {
cancelRewards, cancelRewards,
completeRewards, completeRewards,
getAuthProvider getAuthProvider,
} from "../helpers/twitch"; } from "../helpers/twitch";
import { RedemptionIds } from "../../enums/Redemptions"; import { RedemptionIds } from "../../enums/Redemptions";
@@ -20,17 +20,6 @@ import { timeoutFriend } from "./actions/timeoutFriend";
const LOG_PREFIX = "[PubSub] "; const LOG_PREFIX = "[PubSub] ";
async function registerUserListener(user: UserIdResolvable) {
const pubSubClient = new PubSubClient();
const userId = await pubSubClient.registerUserListener(
await getAuthProvider(),
user
);
/*const listener = */ await pubSubClient.onRedemption(userId, onRedemption);
console.log(`${LOG_PREFIX}Connected & registered`);
}
async function onRedemption(message: PubSubRedemptionMessage) { async function onRedemption(message: PubSubRedemptionMessage) {
console.log( console.log(
`${LOG_PREFIX}Reward: "${message.rewardTitle}" (${message.rewardId}) redeemed by ${message.userDisplayName}` `${LOG_PREFIX}Reward: "${message.rewardTitle}" (${message.rewardId}) redeemed by ${message.userDisplayName}`
@@ -49,29 +38,32 @@ async function onRedemption(message: PubSubRedemptionMessage) {
message: message.message, message: message.message,
userId: message.userId, userId: message.userId,
userDisplayName: message.userDisplayName, userDisplayName: message.userDisplayName,
backgroundColor: raw.data.redemption.reward.background_color backgroundColor: raw.data.redemption.reward.background_color,
}; };
let handledMessage: RedemptionMessage | undefined; let handledMessage: RedemptionMessage | undefined;
try {
// TODO: extract to function
// Returns the executor function, then call it inside a try/catch
switch (msg.rewardId) { switch (msg.rewardId) {
case RedemptionIds.RussianRoulette: case RedemptionIds.GetVip:
handledMessage = await russianRoulette(msg); handledMessage = await getVip(msg);
break; break;
case RedemptionIds.TimeoutFriend: case RedemptionIds.Hidrate:
handledMessage = await timeoutFriend(msg); handledMessage = await hidrate(msg);
break; break;
case RedemptionIds.HighlightMessage: case RedemptionIds.HighlightMessage:
handledMessage = await highlightMessage(msg); handledMessage = await highlightMessage(msg);
break; break;
case RedemptionIds.GetVip: case RedemptionIds.RussianRoulette:
handledMessage = await getVip(msg); handledMessage = await russianRoulette(msg);
break; break;
case RedemptionIds.StealVip: case RedemptionIds.StealVip:
handledMessage = await stealVip(msg); handledMessage = await stealVip(msg);
break; break;
case RedemptionIds.Hidrate: case RedemptionIds.TimeoutFriend:
handledMessage = await hidrate(msg); handledMessage = await timeoutFriend(msg);
break; break;
default: default:
console.log(`${LOG_PREFIX}Unhandled redemption ${msg.rewardId}`); console.log(`${LOG_PREFIX}Unhandled redemption ${msg.rewardId}`);
@@ -79,6 +71,11 @@ async function onRedemption(message: PubSubRedemptionMessage) {
handledMessage = msg; handledMessage = msg;
break; break;
} }
} catch (e) {
if (e instanceof Error) {
console.error(`${LOG_PREFIX}${e.message}`);
}
}
if (handledMessage) { if (handledMessage) {
const rewardEnumValues = Object.values(RedemptionIds); const rewardEnumValues = Object.values(RedemptionIds);
@@ -123,4 +120,16 @@ async function onRedemption(message: PubSubRedemptionMessage) {
} }
} }
async function registerUserListener(user: UserIdResolvable) {
const pubSubClient = new PubSubClient();
const userId = await pubSubClient.registerUserListener(
await getAuthProvider(),
user
);
await pubSubClient.onRedemption(userId, onRedemption);
console.log(`${LOG_PREFIX}Connected & registered`);
}
export { registerUserListener, LOG_PREFIX }; export { registerUserListener, LOG_PREFIX };

View File

@@ -5,5 +5,6 @@ export interface Action {
channelId: string; channelId: string;
userId: string; userId: string;
scheduledAt?: number; scheduledAt?: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any; data: any;
} }