修复emby用户名密码登录
This commit is contained in:
@@ -83,10 +83,12 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
// 如果使用用户名密码,先认证
|
// 如果使用用户名密码,先认证
|
||||||
let finalUserId: string | undefined = UserId; // 使用用户提供的 UserId
|
let finalUserId: string | undefined = UserId; // 使用用户提供的 UserId
|
||||||
|
let authToken: string | undefined;
|
||||||
if (!ApiKey && Username && Password) {
|
if (!ApiKey && Username && Password) {
|
||||||
try {
|
try {
|
||||||
const authResult = await client.authenticate(Username, Password);
|
const authResult = await client.authenticate(Username, Password);
|
||||||
finalUserId = authResult.User.Id;
|
finalUserId = authResult.User.Id;
|
||||||
|
authToken = authResult.AccessToken;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Emby 认证失败: ' + (error as Error).message },
|
{ error: 'Emby 认证失败: ' + (error as Error).message },
|
||||||
@@ -112,6 +114,7 @@ export async function POST(request: NextRequest) {
|
|||||||
Username: Username || undefined,
|
Username: Username || undefined,
|
||||||
Password: Password || undefined,
|
Password: Password || undefined,
|
||||||
UserId: finalUserId,
|
UserId: finalUserId,
|
||||||
|
AuthToken: authToken,
|
||||||
Libraries: Libraries || [],
|
Libraries: Libraries || [],
|
||||||
LastSyncTime: Date.now(),
|
LastSyncTime: Date.now(),
|
||||||
};
|
};
|
||||||
|
|||||||
+155
-7
@@ -6,6 +6,7 @@ interface EmbyConfig {
|
|||||||
Username?: string;
|
Username?: string;
|
||||||
Password?: string;
|
Password?: string;
|
||||||
UserId?: string;
|
UserId?: string;
|
||||||
|
AuthToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EmbyItem {
|
interface EmbyItem {
|
||||||
@@ -55,6 +56,8 @@ export class EmbyClient {
|
|||||||
private apiKey?: string;
|
private apiKey?: string;
|
||||||
private userId?: string;
|
private userId?: string;
|
||||||
private authToken?: string;
|
private authToken?: string;
|
||||||
|
private username?: string;
|
||||||
|
private password?: string;
|
||||||
|
|
||||||
constructor(config: EmbyConfig) {
|
constructor(config: EmbyConfig) {
|
||||||
let serverUrl = config.ServerURL.replace(/\/$/, '');
|
let serverUrl = config.ServerURL.replace(/\/$/, '');
|
||||||
@@ -65,6 +68,30 @@ export class EmbyClient {
|
|||||||
this.serverUrl = serverUrl;
|
this.serverUrl = serverUrl;
|
||||||
this.apiKey = config.ApiKey;
|
this.apiKey = config.ApiKey;
|
||||||
this.userId = config.UserId;
|
this.userId = config.UserId;
|
||||||
|
this.authToken = config.AuthToken;
|
||||||
|
this.username = config.Username;
|
||||||
|
this.password = config.Password;
|
||||||
|
|
||||||
|
console.log('[EmbyClient] constructor - ServerURL:', this.serverUrl);
|
||||||
|
console.log('[EmbyClient] constructor - ApiKey:', this.apiKey);
|
||||||
|
console.log('[EmbyClient] constructor - UserId:', this.userId);
|
||||||
|
console.log('[EmbyClient] constructor - AuthToken:', this.authToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureAuthenticated(): Promise<void> {
|
||||||
|
// 如果有 ApiKey,不需要认证
|
||||||
|
if (this.apiKey) return;
|
||||||
|
|
||||||
|
// 如果有 AuthToken,假设它是有效的
|
||||||
|
if (this.authToken) return;
|
||||||
|
|
||||||
|
// 如果有用户名和密码,自动认证
|
||||||
|
if (this.username && this.password) {
|
||||||
|
console.log('[EmbyClient] Auto-authenticating with username/password');
|
||||||
|
const authResult = await this.authenticate(this.username, this.password);
|
||||||
|
this.authToken = authResult.AccessToken;
|
||||||
|
this.userId = authResult.User.Id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHeaders(): Record<string, string> {
|
private getHeaders(): Record<string, string> {
|
||||||
@@ -82,17 +109,27 @@ export class EmbyClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(username: string, password: string): Promise<{ AccessToken: string; User: { Id: string } }> {
|
async authenticate(username: string, password: string): Promise<{ AccessToken: string; User: { Id: string } }> {
|
||||||
const response = await fetch(`${this.serverUrl}/Users/AuthenticateByName`, {
|
const url = `${this.serverUrl}/Users/AuthenticateByName`;
|
||||||
|
console.log('[EmbyClient] authenticate - URL:', url);
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
Username: username,
|
||||||
|
Pw: password,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
body: JSON.stringify({
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
Username: username,
|
'X-Emby-Authorization': 'MediaBrowser Client="LunaTV", Device="Web", DeviceId="lunatv-web", Version="1.0.0"',
|
||||||
Pw: password,
|
},
|
||||||
}),
|
body: params.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Emby 认证失败');
|
const errorText = await response.text();
|
||||||
|
console.error('[EmbyClient] authenticate - Error:', response.status, errorText);
|
||||||
|
throw new Error(`Emby 认证失败 (${response.status}): ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -129,6 +166,8 @@ export class EmbyClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getItems(params: GetItemsParams): Promise<EmbyItemsResult> {
|
async getItems(params: GetItemsParams): Promise<EmbyItemsResult> {
|
||||||
|
await this.ensureAuthenticated();
|
||||||
|
|
||||||
if (!this.userId) {
|
if (!this.userId) {
|
||||||
throw new Error('未配置 Emby 用户 ID,请在管理面板重新保存 Emby 配置');
|
throw new Error('未配置 Emby 用户 ID,请在管理面板重新保存 Emby 配置');
|
||||||
}
|
}
|
||||||
@@ -159,6 +198,26 @@ export class EmbyClient {
|
|||||||
|
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
// 如果是 401 错误且有用户名密码,尝试重新认证
|
||||||
|
if (response.status === 401 && this.username && this.password && !this.apiKey) {
|
||||||
|
console.log('[EmbyClient] Token expired, re-authenticating...');
|
||||||
|
const authResult = await this.authenticate(this.username, this.password);
|
||||||
|
this.authToken = authResult.AccessToken;
|
||||||
|
this.userId = authResult.User.Id;
|
||||||
|
|
||||||
|
// 重试请求
|
||||||
|
searchParams.set('X-Emby-Token', this.authToken);
|
||||||
|
const retryUrl = `${this.serverUrl}/Users/${this.userId}/Items?${searchParams.toString()}`;
|
||||||
|
const retryResponse = await fetch(retryUrl);
|
||||||
|
|
||||||
|
if (!retryResponse.ok) {
|
||||||
|
const errorText = await retryResponse.text();
|
||||||
|
throw new Error(`获取 Emby 媒体列表失败 (${retryResponse.status}): ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await retryResponse.json();
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
throw new Error(`获取 Emby 媒体列表失败 (${response.status}): ${errorText}`);
|
throw new Error(`获取 Emby 媒体列表失败 (${response.status}): ${errorText}`);
|
||||||
@@ -168,10 +227,35 @@ export class EmbyClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getItem(itemId: string): Promise<EmbyItem> {
|
async getItem(itemId: string): Promise<EmbyItem> {
|
||||||
|
await this.ensureAuthenticated();
|
||||||
|
|
||||||
|
if (!this.userId) {
|
||||||
|
throw new Error('未配置 Emby 用户 ID,请在管理面板重新保存 Emby 配置');
|
||||||
|
}
|
||||||
|
|
||||||
const token = this.apiKey || this.authToken;
|
const token = this.apiKey || this.authToken;
|
||||||
const url = `${this.serverUrl}/Users/${this.userId}/Items/${itemId}?Fields=MediaSources${token ? `&api_key=${token}` : ''}`;
|
const url = `${this.serverUrl}/Users/${this.userId}/Items/${itemId}?Fields=MediaSources${token ? `&api_key=${token}` : ''}`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
// 如果是 401 错误且有用户名密码,尝试重新认证
|
||||||
|
if (response.status === 401 && this.username && this.password && !this.apiKey) {
|
||||||
|
console.log('[EmbyClient] Token expired, re-authenticating...');
|
||||||
|
const authResult = await this.authenticate(this.username, this.password);
|
||||||
|
this.authToken = authResult.AccessToken;
|
||||||
|
this.userId = authResult.User.Id;
|
||||||
|
|
||||||
|
// 重试请求
|
||||||
|
const retryToken = this.authToken;
|
||||||
|
const retryUrl = `${this.serverUrl}/Users/${this.userId}/Items/${itemId}?Fields=MediaSources${retryToken ? `&api_key=${retryToken}` : ''}`;
|
||||||
|
const retryResponse = await fetch(retryUrl);
|
||||||
|
|
||||||
|
if (!retryResponse.ok) {
|
||||||
|
throw new Error('获取 Emby 媒体详情失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await retryResponse.json();
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('获取 Emby 媒体详情失败');
|
throw new Error('获取 Emby 媒体详情失败');
|
||||||
}
|
}
|
||||||
@@ -180,10 +264,36 @@ export class EmbyClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSeasons(seriesId: string): Promise<EmbyItem[]> {
|
async getSeasons(seriesId: string): Promise<EmbyItem[]> {
|
||||||
|
await this.ensureAuthenticated();
|
||||||
|
|
||||||
|
if (!this.userId) {
|
||||||
|
throw new Error('未配置 Emby 用户 ID,请在管理面板重新保存 Emby 配置');
|
||||||
|
}
|
||||||
|
|
||||||
const token = this.apiKey || this.authToken;
|
const token = this.apiKey || this.authToken;
|
||||||
const url = `${this.serverUrl}/Shows/${seriesId}/Seasons?userId=${this.userId}${token ? `&api_key=${token}` : ''}`;
|
const url = `${this.serverUrl}/Shows/${seriesId}/Seasons?userId=${this.userId}${token ? `&api_key=${token}` : ''}`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
// 如果是 401 错误且有用户名密码,尝试重新认证
|
||||||
|
if (response.status === 401 && this.username && this.password && !this.apiKey) {
|
||||||
|
console.log('[EmbyClient] Token expired, re-authenticating...');
|
||||||
|
const authResult = await this.authenticate(this.username, this.password);
|
||||||
|
this.authToken = authResult.AccessToken;
|
||||||
|
this.userId = authResult.User.Id;
|
||||||
|
|
||||||
|
// 重试请求
|
||||||
|
const retryToken = this.authToken;
|
||||||
|
const retryUrl = `${this.serverUrl}/Shows/${seriesId}/Seasons?userId=${this.userId}${retryToken ? `&api_key=${retryToken}` : ''}`;
|
||||||
|
const retryResponse = await fetch(retryUrl);
|
||||||
|
|
||||||
|
if (!retryResponse.ok) {
|
||||||
|
throw new Error('获取 Emby 季列表失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryData = await retryResponse.json();
|
||||||
|
return retryData.Items || [];
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('获取 Emby 季列表失败');
|
throw new Error('获取 Emby 季列表失败');
|
||||||
}
|
}
|
||||||
@@ -193,6 +303,12 @@ export class EmbyClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getEpisodes(seriesId: string, seasonId?: string): Promise<EmbyItem[]> {
|
async getEpisodes(seriesId: string, seasonId?: string): Promise<EmbyItem[]> {
|
||||||
|
await this.ensureAuthenticated();
|
||||||
|
|
||||||
|
if (!this.userId) {
|
||||||
|
throw new Error('未配置 Emby 用户 ID,请在管理面板重新保存 Emby 配置');
|
||||||
|
}
|
||||||
|
|
||||||
const token = this.apiKey || this.authToken;
|
const token = this.apiKey || this.authToken;
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
userId: this.userId!,
|
userId: this.userId!,
|
||||||
@@ -210,6 +326,38 @@ export class EmbyClient {
|
|||||||
const url = `${this.serverUrl}/Shows/${seriesId}/Episodes?${searchParams.toString()}`;
|
const url = `${this.serverUrl}/Shows/${seriesId}/Episodes?${searchParams.toString()}`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
// 如果是 401 错误且有用户名密码,尝试重新认证
|
||||||
|
if (response.status === 401 && this.username && this.password && !this.apiKey) {
|
||||||
|
console.log('[EmbyClient] Token expired, re-authenticating...');
|
||||||
|
const authResult = await this.authenticate(this.username, this.password);
|
||||||
|
this.authToken = authResult.AccessToken;
|
||||||
|
this.userId = authResult.User.Id;
|
||||||
|
|
||||||
|
// 重试请求
|
||||||
|
const retrySearchParams = new URLSearchParams({
|
||||||
|
userId: this.userId!,
|
||||||
|
Fields: 'MediaSources',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (seasonId) {
|
||||||
|
retrySearchParams.set('seasonId', seasonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.authToken) {
|
||||||
|
retrySearchParams.set('api_key', this.authToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryUrl = `${this.serverUrl}/Shows/${seriesId}/Episodes?${retrySearchParams.toString()}`;
|
||||||
|
const retryResponse = await fetch(retryUrl);
|
||||||
|
|
||||||
|
if (!retryResponse.ok) {
|
||||||
|
throw new Error('获取 Emby 集列表失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryData = await retryResponse.json();
|
||||||
|
return retryData.Items || [];
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('获取 Emby 集列表失败');
|
throw new Error('获取 Emby 集列表失败');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user