Files
re-AstarCupWeb/app/lib/osuAuth.ts
2026-02-05 12:05:12 +08:00

171 lines
5.4 KiB
TypeScript

// 这些环境变量只在服务器端可用
const OSU_CLIENT_ID = process.env.OSU_CLIENT_ID || '';
const OSU_CLIENT_SECRET = process.env.OSU_CLIENT_SECRET || '';
const OSU_REDIRECT_URI = process.env.OSU_REDIRECT_URI || 'http://localhost:3000/api/auth/callback/osu';
export function getOsuAuthUrl() {
if (!OSU_CLIENT_ID || !OSU_CLIENT_SECRET) {
throw new Error('OSU_CLIENT_ID or OSU_CLIENT_SECRET is not set in environment variables.');
}
const params = new URLSearchParams({
client_id: OSU_CLIENT_ID,
redirect_uri: OSU_REDIRECT_URI,
response_type: 'code',
scope: 'public identify',
});
return `https://osu.ppy.sh/oauth/authorize?${params.toString()}`;
}
export async function getOsuToken(code: string): Promise<{
access_token: string;
refresh_token: string;
expires_in: number;
}> {
try {
const response = await fetch('https://osu.ppy.sh/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: OSU_CLIENT_ID,
client_secret: OSU_CLIENT_SECRET,
code,
grant_type: 'authorization_code',
redirect_uri: OSU_REDIRECT_URI,
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error('Token exchange failed:', response.status, errorText);
throw new Error(`Failed to get access token: ${response.status} - ${errorText}`);
}
try {
const tokenData = await response.json();
return tokenData;
} catch (jsonError) {
console.error('JSON parsing error:', jsonError);
const responseText = await response.text();
console.error('Response text:', responseText);
throw new Error(`Failed to parse token response: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`);
}
} catch (error) {
console.error('Token exchange error:', error);
throw error;
}
}
export async function getOsuClientToken(): Promise<{
access_token: string;
expires_in: number;
token_type: string;
}> {
try {
if (!OSU_CLIENT_ID || !OSU_CLIENT_SECRET) {
throw new Error('OSU_CLIENT_ID and OSU_CLIENT_SECRET must be configured');
}
console.log('Requesting client token from osu API...');
const response = await fetch('https://osu.ppy.sh/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: OSU_CLIENT_ID,
client_secret: OSU_CLIENT_SECRET,
grant_type: 'client_credentials',
scope: 'public'
}),
});
console.log('Token request response status:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('Token request failed:', errorText);
throw new Error(`Failed to get client token: ${response.status} ${errorText}`);
}
const data = await response.json();
console.log('Token obtained successfully');
return data;
} catch (error) {
console.error('Error getting client token:', error);
throw error;
}
}
let clientToken: { token: string; expires: number } | null = null;
export async function getValidClientToken(): Promise<string> {
const now = Date.now() / 1000;
console.log('Checking client token cache...');
if (clientToken && clientToken.expires > now + 60) {
console.log('Using cached client token');
return clientToken.token;
}
const tokenData = await getOsuClientToken();
console.log('New token obtained, expires in:', tokenData.expires_in, 'seconds');
clientToken = {
token: tokenData.access_token,
expires: now + tokenData.expires_in,
};
return clientToken.token;
}
export async function getMyOsuInfo(accessToken: string): Promise<{
id: number;
username: string;
avatar_url: string;
country_code: string;
cover?: {
custom_url: string | null;
url: string;
id: string | null;
};
statistics?: {
pp: number;
global_rank: number | null;
country_rank: number | null;
country: string;
};
}> {
const response = await fetch('https://osu.ppy.sh/api/v2/me', {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error('Failed to get user info');
}
const userData = await response.json();
return {
id: userData.id,
username: userData.username,
avatar_url: userData.avatar_url,
country_code: userData.country_code,
cover: userData.cover ? {
custom_url: userData.cover.custom_url || null,
url: userData.cover.url || '',
id: userData.cover.id || null,
} : undefined,
statistics: userData.statistics ? {
pp: userData.statistics.pp,
global_rank: userData.statistics.global_rank,
country_rank: userData.statistics.country_rank,
country: userData.country_code || '',
} : undefined,
};
}