Files
ChatVelocity/cloudflareworker.js
2024-12-13 00:02:57 +11:00

178 lines
4.4 KiB
JavaScript

// Constants
const CONFIG = {
CACHE_TTL: 86400,
TWITCH_API: {
TOKEN_URL: 'https://id.twitch.tv/oauth2/token',
USERS_URL: 'https://api.twitch.tv/helix/users',
EMOTES_URL: 'https://api.twitch.tv/helix/chat/emotes'
}
};
// Cache management
class TokenCache {
static token = null;
static timestamp = 0;
static isExpired() {
return !this.token || (Date.now() - this.timestamp) / 1000 > CONFIG.CACHE_TTL;
}
static update(token) {
this.token = token;
this.timestamp = Date.now();
}
static get() {
return this.token;
}
}
// API helpers
class TwitchAPI {
constructor(env) {
this.env = env;
}
async refreshToken() {
if (!TokenCache.isExpired()) {
return TokenCache.get();
}
const response = await fetch(CONFIG.TWITCH_API.TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: this.env.TWITCH_CLIENT_ID,
client_secret: this.env.TWITCH_CLIENT_SECRET,
grant_type: 'client_credentials'
})
});
const data = await this.handleResponse(response, 'Token refresh failed');
TokenCache.update(data.access_token);
return data.access_token;
}
async getUser(username) {
const token = await this.refreshToken();
const response = await fetch(
`${CONFIG.TWITCH_API.USERS_URL}?login=${encodeURIComponent(username)}`,
{
headers: this.getHeaders(token)
}
);
const data = await this.handleResponse(response, 'User fetch failed');
const user = data.data[0];
if (!user) {
throw new HttpError('User not found', 404);
}
return user;
}
async getEmotes(userId) {
const token = await this.refreshToken();
const response = await fetch(
`${CONFIG.TWITCH_API.EMOTES_URL}?broadcaster_id=${userId}`,
{
headers: this.getHeaders(token)
}
);
const data = await this.handleResponse(response, 'Emotes fetch failed');
return this.formatEmotes(data.data);
}
getHeaders(token) {
return {
'Client-ID': this.env.TWITCH_CLIENT_ID,
'Authorization': `Bearer ${token}`
};
}
async handleResponse(response, errorPrefix) {
if (!response.ok) {
const text = await response.text();
throw new HttpError(`${errorPrefix}: ${text}`, response.status);
}
return response.json();
}
formatEmotes(emotes) {
return emotes.reduce((acc, emote) => {
acc[emote.name] = emote.images.url_1x;
return acc;
}, {});
}
}
// Error handling
class HttpError extends Error {
constructor(message, status = 500) {
super(message);
this.status = status;
}
}
// Response helpers
class ResponseHandler {
static json(data, status = 200) {
return new Response(
JSON.stringify(data),
{
status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': '*'
}
}
);
}
static error(error) {
const status = error instanceof HttpError ? error.status : 500;
return this.json({ error: error.message }, status);
}
}
// Request handler
class RequestHandler {
static validateRequest(request) {
const url = new URL(request.url);
const username = url.searchParams.get('username');
if (url.pathname !== '/twitch/userdata' || !username) {
throw new HttpError('Invalid endpoint or missing username', 400);
}
return username;
}
}
// Main worker
export default {
async fetch(request, env) {
try {
const username = RequestHandler.validateRequest(request);
const api = new TwitchAPI(env);
const user = await api.getUser(username);
const twitchEmotes = await api.getEmotes(user.id);
return ResponseHandler.json({
userId: user.id,
twitchEmotes
});
} catch (error) {
console.error('Worker error:', error);
return ResponseHandler.error(error);
}
}
};