add easy setup

This commit is contained in:
Darius
2025-09-26 19:05:49 +02:00
parent 95693c0201
commit a0a76a8b75
18 changed files with 313 additions and 54 deletions

97
src/util/auth.ts Normal file
View File

@@ -0,0 +1,97 @@
import {
createServer,
type IncomingMessage,
type ServerResponse,
} from "node:http";
import { URL } from "node:url";
import { Config } from "../config/config.js";
export class TwitchAuth {
private readonly clientId: string;
private readonly redirectUri: string;
constructor(redirectUri: string = "http://localhost:3000") {
this.clientId = Config.client_id;
this.redirectUri = redirectUri;
}
getAuthorizationUrl(scopes: string[]): string {
const baseUrl = "https://id.twitch.tv/oauth2/authorize";
const params = new URLSearchParams({
response_type: "code",
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: scopes.join(" "),
});
return `${baseUrl}?${params.toString()}`;
}
startCallbackServer(port: number, timeoutS: number = 120): Promise<string> {
return new Promise((resolve, reject) => {
const server = createServer(
(req: IncomingMessage, res: ServerResponse) => {
if (!req.url) {
res.writeHead(400, { "Content-Type": "text/html" });
res.end("<h1>Bad Request</h1><p>Invalid request URL</p>");
return;
}
const url = new URL(req.url, `http://localhost:${port}`);
const code = url.searchParams.get("code");
const error = url.searchParams.get("error");
const errorDescription = url.searchParams.get("error_description");
if (error) {
res.writeHead(400, { "Content-Type": "text/html" });
res.end(`
<h1>Authorization Failed</h1>
<p>Error: ${error}</p>
<p>Description: ${errorDescription || "Unknown error"}</p>
<p>You can close this window.</p>
`);
server.close();
reject(
new Error(`Authorization failed: ${error} - ${errorDescription}`),
);
return;
}
if (code) {
res.writeHead(200, { "Content-Type": "text/html" });
res.end(`
<h1>Authorization Successful!</h1>
<p>You have successfully authorized the Twitch bot.</p>
<p>You can close this window and return to your terminal.</p>
`);
server.close();
resolve(code);
return;
}
res.writeHead(400, { "Content-Type": "text/html" });
res.end("<h1>Bad Request</h1><p>No authorization code received</p>");
},
);
server.listen(port, "localhost", () => {
console.log(
`Waiting for authorization callback on http://localhost:${port}`,
);
});
server.on("error", (err) => {
reject(new Error(`Server error: ${err.message}`));
});
const timeout = setTimeout(() => {
server.close();
reject(new Error("Authorization timeout. Please try again."));
}, timeoutS * 1000);
server.on("close", () => {
clearTimeout(timeout);
});
});
}
}

12
src/util/general.ts Normal file
View File

@@ -0,0 +1,12 @@
import { apiClient } from "../core/api-client";
import { logError, logInfo } from "./logger";
export async function getUserId(username: string): Promise<string | null> {
const user = await apiClient.users.getUserByName(username);
if (user) {
logInfo(`${user.name} => ${user.id}`);
return user.id;
}
logError(`no user with name ${username} found`);
return null;
}

17
src/util/logger.ts Normal file
View File

@@ -0,0 +1,17 @@
import chalk from "chalk";
export function logError(...args: unknown[]) {
console.log(chalk.red(args));
}
export function logWarning(...args: unknown[]) {
console.log(chalk.yellow(args));
}
export function logSuccess(...args: unknown[]) {
console.log(chalk.green(args));
}
export function logInfo(...args: unknown[]) {
console.log(chalk.cyan(args));
}