🚨 Fix most of the linting error and warnings
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import { hasVip, say } from "..";
|
||||
|
||||
import { chatClient } from "../..";
|
||||
import { say } from "..";
|
||||
|
||||
async function addVip(
|
||||
channel: string,
|
||||
username: string,
|
||||
message?: string
|
||||
): Promise<boolean> {
|
||||
username = username.toLowerCase();
|
||||
if (!(await hasVip(channel, username))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await chatClient.addVip(channel, username);
|
||||
} catch (e) {
|
||||
|
@@ -2,18 +2,49 @@ import { chatClient } from "../..";
|
||||
|
||||
type CacheType = Record<string, Array<string>>;
|
||||
const cache: CacheType = {};
|
||||
const cacheKeepTime = 2.5e3;
|
||||
|
||||
async function hasVip(channel: string, username: string): Promise<boolean> {
|
||||
if (!username) {
|
||||
return false;
|
||||
interface ChannelFetching {
|
||||
channel: string;
|
||||
promise: Promise<unknown>;
|
||||
}
|
||||
|
||||
if (!cache[channel]) {
|
||||
cache[channel] = await chatClient.getVips(channel);
|
||||
const channelsFetching: Array<ChannelFetching> = [];
|
||||
|
||||
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(() => {
|
||||
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];
|
||||
|
@@ -1,7 +1,18 @@
|
||||
import { say, sayError, sayInfo, saySuccess, sayWarn } from "./say";
|
||||
|
||||
import { addVip } from "./addVip";
|
||||
import { hasVip } from "./hasVip";
|
||||
import { removeVip } from "./removeVip";
|
||||
import { say } from "./say";
|
||||
import { timeout } from "./timeout";
|
||||
|
||||
export { say, timeout, addVip, removeVip, hasVip };
|
||||
export {
|
||||
addVip,
|
||||
hasVip,
|
||||
removeVip,
|
||||
say,
|
||||
sayError,
|
||||
sayInfo,
|
||||
saySuccess,
|
||||
sayWarn,
|
||||
timeout,
|
||||
};
|
||||
|
@@ -1,11 +1,18 @@
|
||||
import { hasVip, say } from "..";
|
||||
|
||||
import { chatClient } from "../..";
|
||||
import { say } from "..";
|
||||
|
||||
async function removeVip(
|
||||
channel: string,
|
||||
username: string,
|
||||
message?: string
|
||||
): Promise<boolean> {
|
||||
username = username.toLowerCase();
|
||||
|
||||
if (await hasVip(channel, username)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await chatClient.removeVip(channel, username);
|
||||
} catch (e) {
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { chatClient } from "../..";
|
||||
|
||||
const maxMessageLength = 500;
|
||||
|
||||
async function say(channel: string, message: string): Promise<void> {
|
||||
const maxMessageLength = 500;
|
||||
// message = `MrDestructoid ${message}`;
|
||||
|
||||
if (message.length > 500) {
|
||||
if (message.length > maxMessageLength) {
|
||||
const startIndex = 0;
|
||||
const suffix = "...";
|
||||
|
||||
message = `${message.substring(
|
||||
0,
|
||||
startIndex,
|
||||
maxMessageLength - suffix.length
|
||||
)}${suffix}`;
|
||||
}
|
||||
@@ -16,4 +17,20 @@ async function say(channel: string, message: string): Promise<void> {
|
||||
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 };
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { chatClient } from "../..";
|
||||
|
||||
// timeouts a user in a channel
|
||||
const defaultTime = 60;
|
||||
|
||||
async function timeout(
|
||||
channel: string,
|
||||
username: string,
|
||||
@@ -8,7 +9,7 @@ async function timeout(
|
||||
reason?: string
|
||||
): Promise<void> {
|
||||
if (!time) {
|
||||
time = 60;
|
||||
time = defaultTime;
|
||||
}
|
||||
|
||||
if (!reason) {
|
||||
|
@@ -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 { createReward as createChannelPointsReward } from "../../helpers/twitch";
|
||||
|
||||
async function createReward(
|
||||
channel: string,
|
||||
user: string,
|
||||
_user: string,
|
||||
message: string,
|
||||
msg: TwitchPrivateMessage
|
||||
): Promise<void> {
|
||||
const args = message.split(" ");
|
||||
|
||||
const title = args.shift();
|
||||
const cost = Math.max(1, parseInt(args.shift() ?? "0"));
|
||||
|
||||
if (!title || !cost) {
|
||||
await say(
|
||||
channel,
|
||||
"No se ha especificado el nombre de la recompensa o costo"
|
||||
);
|
||||
if (!title) {
|
||||
await sayError(channel, "Debes indicar el título de la recompensa");
|
||||
return;
|
||||
}
|
||||
|
||||
const minRewardPrice = 1;
|
||||
const cost = Math.max(minRewardPrice, parseInt(args.shift() ?? "0"));
|
||||
|
||||
try {
|
||||
await createChannelPointsReward(msg.channelId as string, {
|
||||
title,
|
||||
cost
|
||||
cost,
|
||||
});
|
||||
|
||||
say(
|
||||
saySuccess(
|
||||
channel,
|
||||
`✅ Creada recompensa de canal "${title}" con un costo de ${cost}`
|
||||
`Creada recompensa de canal "${title}" con un costo de ${cost}`
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
|
@@ -12,77 +12,14 @@ import { start } from "../helpers/miniDb";
|
||||
|
||||
let chatClient: ChatClient;
|
||||
|
||||
export { chatClient, connect, handleClientAction, say, LOG_PREFIX };
|
||||
|
||||
const LOG_PREFIX = "[ChatClient] ";
|
||||
|
||||
async function connect(channels: Array<any>): 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> {
|
||||
function onConnect(): Promise<void> {
|
||||
console.log(`${LOG_PREFIX}Connected`);
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
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 };
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { Action } from "../../interfaces/actions/Action";
|
||||
import { promises as fs } from "fs";
|
||||
import { handleClientAction } from "../chatClient";
|
||||
import { resolve } from "path";
|
||||
@@ -12,14 +13,70 @@ const FILES_BASE = resolve(process.cwd(), "./storage");
|
||||
const SCHEDULED_FILE = resolve(FILES_BASE, "./scheduled.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>> = {};
|
||||
|
||||
let checkingScheduled = false;
|
||||
let scheduledActionsInterval: NodeJS.Timeout;
|
||||
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> {
|
||||
if (scheduledActionsInterval) {
|
||||
return;
|
||||
@@ -35,10 +92,21 @@ async function start(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
scheduledActions.push.apply(scheduledActions, savedActions);
|
||||
scheduledActions.sort((a, b) => a.scheduledAt - b.scheduledAt);
|
||||
scheduledActions = [...scheduledActions, ...savedActions];
|
||||
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);
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
scheduledActionsInterval = setInterval(checkScheduledActions, CHECK_INTERVAL);
|
||||
|
||||
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() {
|
||||
try {
|
||||
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();
|
||||
|
||||
export { start, scheduledActions, save, vipUsers };
|
||||
|
@@ -5,12 +5,22 @@ import { resolve } from "path";
|
||||
const TOKENS_FILE = "tokens.json";
|
||||
const LOG_PREFIX = "[TokenData] ";
|
||||
|
||||
export { getTokenData, saveTokenData };
|
||||
|
||||
function getTokenDataFilePath(): string {
|
||||
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> {
|
||||
const tokenDataFilePath = getTokenDataFilePath();
|
||||
let buffer: Buffer;
|
||||
@@ -21,7 +31,10 @@ async function getTokenData(): Promise<AccessToken> {
|
||||
console.error(
|
||||
`${LOG_PREFIX}${TOKENS_FILE} not found on ${tokenDataFilePath}.`
|
||||
);
|
||||
process.exit(1);
|
||||
|
||||
const exitCode = 1;
|
||||
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
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`);
|
||||
}
|
||||
|
||||
function checkTokenData(tokenData: AccessToken): void {
|
||||
if (!tokenData.accessToken || !tokenData.refreshToken) {
|
||||
console.error(
|
||||
`${LOG_PREFIX}Missing refresh_token or access_token in ${TOKENS_FILE}.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
export { getTokenData, saveTokenData };
|
||||
|
@@ -2,7 +2,7 @@ import { AccessToken, RefreshingAuthProvider } from "@twurple/auth";
|
||||
import {
|
||||
ApiClient,
|
||||
HelixCreateCustomRewardData,
|
||||
UserIdResolvable
|
||||
UserIdResolvable,
|
||||
} from "@twurple/api";
|
||||
import { getTokenData, saveTokenData } from "./tokenData";
|
||||
|
||||
@@ -17,46 +17,49 @@ function getClientCredentials(): ClientCredentials {
|
||||
console.error(
|
||||
`${LOG_PREFIX}Missing environment parameters TWITCH_CLIENT_ID or TWITCH_CLIENT_SECRET`
|
||||
);
|
||||
process.exit(1);
|
||||
|
||||
const exitCode = 1;
|
||||
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
return {
|
||||
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> {
|
||||
console.log(`${LOG_PREFIX}Tokens refreshed`);
|
||||
|
||||
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> {
|
||||
const authProvider = await getAuthProvider();
|
||||
|
||||
return await new ApiClient({ authProvider });
|
||||
return new ApiClient({ authProvider });
|
||||
}
|
||||
|
||||
async function getUsernameFromId(userId: number): Promise<string | null> {
|
||||
@@ -123,5 +126,5 @@ export {
|
||||
getUsernameFromId,
|
||||
completeRewards,
|
||||
cancelRewards,
|
||||
createReward
|
||||
createReward,
|
||||
};
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { AddressInfo, Socket } from "net";
|
||||
import { IncomingMessage, Server } from "http";
|
||||
import { save, scheduledActions } from "./miniDb";
|
||||
|
||||
import { Action } from "../../interfaces/actions/Action";
|
||||
import { AddressInfo } from "net";
|
||||
import { Socket } from "net";
|
||||
import WebSocket from "ws";
|
||||
import express from "express";
|
||||
import { handleClientAction } from "../chatClient";
|
||||
@@ -17,72 +16,24 @@ const app = express();
|
||||
const sockets: Array<WebSocket> = [];
|
||||
|
||||
const wsServer = new WebSocket.Server({
|
||||
noServer: true
|
||||
noServer: true,
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
app.use(express.static(join(process.cwd(), "client")));
|
||||
|
||||
function listen() {
|
||||
if (server) {
|
||||
console.log(`${LOG_PREFIX_HTTP}Server is already running`);
|
||||
return;
|
||||
filteredSockets.forEach((s) => s.send(msg));
|
||||
}
|
||||
|
||||
server = app.listen(!isDevelopment ? 8080 : 8081, "0.0.0.0");
|
||||
|
||||
server.on("listening", onListening);
|
||||
server.on("upgrade", onUpgrade);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
async function onMessage(this: WebSocket, msg: string) {
|
||||
const data = JSON.parse(msg);
|
||||
|
||||
if (!data.actions || data.actions.length === 0) {
|
||||
broadcast(msg, socket);
|
||||
if (!data.actions) {
|
||||
broadcast(msg, this);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,6 +44,7 @@ async function onMessage(msg: string) {
|
||||
await handleClientAction(action);
|
||||
} else {
|
||||
scheduledActions.push(action);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
scheduledActions.sort((a: any, b: any) => a.scheduledAt - b.scheduledAt);
|
||||
save();
|
||||
}
|
||||
@@ -104,11 +56,63 @@ async function onMessage(msg: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
// @ts-ignore
|
||||
const socket: WebSocket = this as WebSocket;
|
||||
function onClose(this: WebSocket) {
|
||||
const socketIdx = sockets.indexOf(this);
|
||||
const deleteCount = 1;
|
||||
|
||||
const socketIdx = sockets.indexOf(socket);
|
||||
sockets.splice(socketIdx, 1);
|
||||
sockets.splice(socketIdx, deleteCount);
|
||||
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 };
|
||||
|
@@ -13,7 +13,7 @@ async function highlightMessage(
|
||||
}
|
||||
|
||||
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)) {
|
||||
console.log(`${LOG_PREFIX}Message contains a url`);
|
||||
@@ -27,8 +27,9 @@ async function highlightMessage(
|
||||
|
||||
try {
|
||||
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) {
|
||||
// user probably cannot be timed out
|
||||
}
|
||||
|
@@ -27,14 +27,18 @@ async function russianRoulette(
|
||||
gunsSafeShots[channelId] = maxSafeShots;
|
||||
}
|
||||
|
||||
const noShots = 0;
|
||||
|
||||
const win =
|
||||
gunsSafeShots[channelId] > 0 &&
|
||||
gunsSafeShots[channelId] > noShots &&
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
randomInt(gunsSafeShots[channelId]-- + 1) !== 0;
|
||||
|
||||
if (gunsSafeShots[channelId] < 0 || !win) {
|
||||
if (gunsSafeShots[channelId] < noShots || !win) {
|
||||
gunsSafeShots[channelId] = maxSafeShots;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
msg.message = win ? "" : "got shot";
|
||||
|
||||
const promises: Array<Promise<unknown>> = [];
|
||||
|
@@ -28,8 +28,9 @@ async function stealVip(
|
||||
const removeVipUser = msg.message.toLowerCase();
|
||||
const channelVips = vipUsers[channelId];
|
||||
|
||||
if (!channelVips.find(u => u.toLowerCase() === removeVipUser)) {
|
||||
if (!channelVips.find((u) => u.toLowerCase() === removeVipUser)) {
|
||||
const message =
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
channelVips.length === 0
|
||||
? "No hay nadie a quien puedas robar el VIP"
|
||||
: `Solo puedes robar el VIP de: "${channelVips.sort().join('", "')}"`;
|
||||
@@ -60,14 +61,16 @@ async function stealVip(
|
||||
}
|
||||
|
||||
const removeIdx = channelVips.findIndex(
|
||||
u => u.toLowerCase() === removeVipUser
|
||||
(u) => u.toLowerCase() === removeVipUser
|
||||
);
|
||||
|
||||
channelVips.splice(removeIdx);
|
||||
channelVips.push(addVipUser);
|
||||
save();
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
msg.message = `@${addVipUser} ha "tomado prestado" el VIP de @${removeVipUser}`;
|
||||
|
||||
await say(channel, msg.message);
|
||||
|
||||
return msg;
|
||||
|
@@ -27,6 +27,7 @@ async function timeoutFriend(
|
||||
try {
|
||||
await timeout(channel, msg.message, time, reason);
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
msg.message = `@${userDisplayName} ha expulsado a @${message} por ${time} segundos`;
|
||||
} catch (e) {
|
||||
// user can not be timed out
|
||||
|
@@ -2,7 +2,7 @@ import { PubSubClient, PubSubRedemptionMessage } from "@twurple/pubsub";
|
||||
import {
|
||||
cancelRewards,
|
||||
completeRewards,
|
||||
getAuthProvider
|
||||
getAuthProvider,
|
||||
} from "../helpers/twitch";
|
||||
|
||||
import { RedemptionIds } from "../../enums/Redemptions";
|
||||
@@ -20,17 +20,6 @@ import { timeoutFriend } from "./actions/timeoutFriend";
|
||||
|
||||
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) {
|
||||
console.log(
|
||||
`${LOG_PREFIX}Reward: "${message.rewardTitle}" (${message.rewardId}) redeemed by ${message.userDisplayName}`
|
||||
@@ -49,29 +38,32 @@ async function onRedemption(message: PubSubRedemptionMessage) {
|
||||
message: message.message,
|
||||
userId: message.userId,
|
||||
userDisplayName: message.userDisplayName,
|
||||
backgroundColor: raw.data.redemption.reward.background_color
|
||||
backgroundColor: raw.data.redemption.reward.background_color,
|
||||
};
|
||||
|
||||
let handledMessage: RedemptionMessage | undefined;
|
||||
|
||||
try {
|
||||
// TODO: extract to function
|
||||
// Returns the executor function, then call it inside a try/catch
|
||||
switch (msg.rewardId) {
|
||||
case RedemptionIds.RussianRoulette:
|
||||
handledMessage = await russianRoulette(msg);
|
||||
case RedemptionIds.GetVip:
|
||||
handledMessage = await getVip(msg);
|
||||
break;
|
||||
case RedemptionIds.TimeoutFriend:
|
||||
handledMessage = await timeoutFriend(msg);
|
||||
case RedemptionIds.Hidrate:
|
||||
handledMessage = await hidrate(msg);
|
||||
break;
|
||||
case RedemptionIds.HighlightMessage:
|
||||
handledMessage = await highlightMessage(msg);
|
||||
break;
|
||||
case RedemptionIds.GetVip:
|
||||
handledMessage = await getVip(msg);
|
||||
case RedemptionIds.RussianRoulette:
|
||||
handledMessage = await russianRoulette(msg);
|
||||
break;
|
||||
case RedemptionIds.StealVip:
|
||||
handledMessage = await stealVip(msg);
|
||||
break;
|
||||
case RedemptionIds.Hidrate:
|
||||
handledMessage = await hidrate(msg);
|
||||
case RedemptionIds.TimeoutFriend:
|
||||
handledMessage = await timeoutFriend(msg);
|
||||
break;
|
||||
default:
|
||||
console.log(`${LOG_PREFIX}Unhandled redemption ${msg.rewardId}`);
|
||||
@@ -79,6 +71,11 @@ async function onRedemption(message: PubSubRedemptionMessage) {
|
||||
handledMessage = msg;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
console.error(`${LOG_PREFIX}${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (handledMessage) {
|
||||
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 };
|
||||
|
@@ -5,5 +5,6 @@ export interface Action {
|
||||
channelId: string;
|
||||
userId: string;
|
||||
scheduledAt?: number;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: any;
|
||||
}
|
||||
|
Reference in New Issue
Block a user