Merge pull request #177

fix: All search type 403, undefined actor crash, and lazy login on startup
This commit is contained in:
wanxp 2026-03-11 23:27:55 +08:00 committed by GitHub
commit a8094d7575
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 183 additions and 85 deletions

@ -33,24 +33,24 @@ export default class DoubanTheaterAiLoadHandler extends DoubanAbstractLoadHandle
"director",
DataValueType.array,
extract.director,
extract.director.map(SchemaOrg.getPersonName).filter(c => c)
(extract.director || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("actor", new DataField(
"actor",
DataValueType.array,
extract.actor,
extract.actor.map(SchemaOrg.getPersonName).filter(c => c)
(extract.actor || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("author", new DataField(
"author",
DataValueType.array,
extract.author,
extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
(extract.author || []).map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
));
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases,
extract.aliases.map(a=>a
(extract.aliases || []).map(a=>a
.trim()
// .replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
// //replase multiple _ to single _

@ -169,9 +169,6 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
const syncButton = new ButtonComponent(controls)
.setButtonText(i18nHelper.getMessage('110007'))
.onClick(async () => {
if (!this.plugin.userComponent.isLogin()) {
await this.plugin.userComponent.login();
}
if(!await this.plugin.checkLogin(this.context)) {
return;
}

@ -142,7 +142,7 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
}else {
context.syncStatusHolder?context.syncStatusHolder.syncStatus.handled(1):null;
}
return e;
return undefined;
});
@ -608,12 +608,20 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
handlePersonNameByMeta(html: CheerioAPI, movie: DoubanSubject, context: HandleContext,
metaProperty:string, objectProperty:string) {
if (!movie) {
return;
}
const metaProperties: string[] = html(`head > meta[property='${metaProperty}']`).get()
.map((e) => {
return html(e).attr('content');
});
// @ts-ignore
movie[objectProperty]
const currentArray = movie[objectProperty];
if (!Array.isArray(currentArray)) {
return;
}
// @ts-ignore
currentArray
// @ts-ignore
.filter((p:Person) => p.name)
// @ts-ignore

@ -30,9 +30,9 @@ export default class DoubanBookLoadHandler extends DoubanAbstractLoadHandler<Dou
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanBookSubject, context: HandleContext): void {
variableMap.set(DoubanBookParameter.author, new DataField(DoubanBookParameter.author,
DataValueType.array, extract.author, extract.author.map(this.handleSpecialAuthorName)));
DataValueType.array, extract.author, (extract.author || []).map(this.handleSpecialAuthorName)));
variableMap.set(DoubanBookParameter.translator, new DataField(DoubanBookParameter.translator,
DataValueType.array, extract.translator, extract.translator.map(this.handleSpecialAuthorName)));
DataValueType.array, extract.translator, (extract.translator || []).map(this.handleSpecialAuthorName)));
}
support(extract: DoubanSubject): boolean {

@ -31,7 +31,7 @@ export default class DoubanGameLoadHandler extends DoubanAbstractLoadHandler<Dou
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanGameSubject, context: HandleContext): void {
// super.parseAliases(beforeContent, variableMap, extract, context);
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases,
extract.aliases.map(a=>a
(extract.aliases || []).map(a=>a
.trim()
// .replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
// //replase multiple _ to single _

@ -35,24 +35,24 @@ export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler<Do
"director",
DataValueType.array,
extract.director,
extract.director.map(SchemaOrg.getPersonName).filter(c => c)
(extract.director || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("actor", new DataField(
"actor",
DataValueType.array,
extract.actor,
extract.actor.map(SchemaOrg.getPersonName).filter(c => c)
(extract.actor || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("author", new DataField(
"author",
DataValueType.array,
extract.author,
extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
(extract.author || []).map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
));
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases,
extract.aliases.map(a=>a
(extract.aliases || []).map(a=>a
.trim()
// .replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
// //replase multiple _ to single _
@ -98,7 +98,7 @@ export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler<Do
}
parseSubjectFromHtml(html: CheerioAPI, context: HandleContext): DoubanMovieSubject {
const movie:DoubanMovieSubject = html('script')
let movie: DoubanMovieSubject | undefined = html('script')
.get()
.filter(scd => "application/ld+json" == html(scd).attr("type"))
.map(i => {
@ -108,7 +108,7 @@ export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler<Do
const idPattern = /(\d){5,10}/g;
const id = idPattern.exec(obj.url);
const name = obj.name;
const title = super.getTitleNameByMode(name, PersonNameMode.CH_NAME, context)??name;
const title = super.getTitleNameByMode(name, PersonNameMode.CH_NAME, context) ?? name;
const originalTitle = super.getTitleNameByMode(name, PersonNameMode.EN_NAME, context) ?? name;
const result: DoubanMovieSubject = {
@ -119,14 +119,14 @@ export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler<Do
originalTitle: originalTitle,
desc: obj.description,
url: "https://movie.douban.com" + obj.url,
director: obj.director,
author: obj.author,
actor: obj.actor,
director: obj.director || [],
author: obj.author || [],
actor: obj.actor || [],
aggregateRating: obj.aggregateRating,
datePublished: obj.datePublished ? new Date(obj.datePublished) : undefined,
image: obj.image,
imageUrl: obj.image,
genre: obj.genre,
genre: obj.genre || [],
publisher: '',
aliases: [""],
language: [""],
@ -136,10 +136,52 @@ export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler<Do
}
return result;
})[0];
// Fallback: if JSON-LD parsing failed (e.g., anti-bot page), extract from meta tags
if (!movie) {
const title = html(html("head > meta[property='og:title']").get(0)).attr("content") || '';
const image = html(html("head > meta[property='og:image']").get(0)).attr("content") || '';
const urlMeta = html(html("head > meta[property='og:url']").get(0)).attr("content") || '';
const desc = html(html("head > meta[property='og:description']").get(0)).attr("content") || '';
// Extract ID from URL
const idPattern = /(\d){5,10}/g;
const idMatch = idPattern.exec(urlMeta);
const id = idMatch ? idMatch[0] : '';
// Extract score from HTML
const scoreText = html("#interest_sectl strong[property='v:average']").text();
const score = scoreText ? parseFloat(scoreText) : undefined;
movie = {
id,
title,
type: this.getSupportType(),
score,
originalTitle: title,
desc,
url: urlMeta || (id ? `https://movie.douban.com/subject/${id}/` : ''),
director: [],
author: [],
actor: [],
aggregateRating: undefined,
datePublished: undefined,
image,
imageUrl: image,
genre: [],
publisher: '',
aliases: [],
language: [],
country: [],
time: null,
IMDb: null,
};
}
this.handlePersonNameByMeta(html, movie, context, 'video:actor', 'actor');
this.handlePersonNameByMeta(html, movie, context, 'video:director', 'director');
const desc:string = html("span[property='v:summary']").text();
const desc: string = html("span[property='v:summary']").text();
if (desc) {
movie.desc = desc;
}
@ -156,7 +198,7 @@ export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler<Do
// value = html(info.next.next).text().trim();
const vas = html(info.next).text().trim();
value = vas.split("/").map((v) => v.trim());
} else if(key.indexOf('片长') >= 0) {
} else if (key.indexOf('片长') >= 0) {
value = html(info.next.next).text().trim()
} else {
value = html(info.next).text().trim();

@ -25,22 +25,22 @@ export class DoubanTeleplayLoadHandler extends DoubanAbstractLoadHandler<DoubanT
}
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanTeleplaySubject, context: HandleContext): void {
variableMap.set("director", new DataField("director", DataValueType.array, extract.director,extract.director.map(SchemaOrg.getPersonName).filter(c => c)));
variableMap.set("director", new DataField("director", DataValueType.array, extract.director,(extract.director || []).map(SchemaOrg.getPersonName).filter(c => c)));
variableMap.set("actor", new DataField(
"actor",
DataValueType.array,
extract.actor,
extract.actor.map(SchemaOrg.getPersonName).filter(c => c)
(extract.actor || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("author", new DataField(
"author",
DataValueType.array,
extract.author,
extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
(extract.author || []).map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
));
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases,
extract.aliases.map(a=>a
(extract.aliases || []).map(a=>a
.trim()
// .replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
// //replase multiple _ to single _
@ -84,7 +84,7 @@ export class DoubanTeleplayLoadHandler extends DoubanAbstractLoadHandler<DoubanT
}
parseSubjectFromHtml(html: CheerioAPI, context: HandleContext): DoubanTeleplaySubject {
const teleplay:DoubanTeleplaySubject = html('script')
let teleplay: DoubanTeleplaySubject | undefined = html('script')
.get()
.filter(scd => "application/ld+json" == html(scd).attr("type"))
.map(i => {
@ -104,14 +104,14 @@ export class DoubanTeleplayLoadHandler extends DoubanAbstractLoadHandler<DoubanT
originalTitle: originalTitle,
desc: obj.description,
url: "https://movie.douban.com" + obj.url,
director: obj.director,
author: obj.author,
actor: obj.actor,
director: obj.director || [],
author: obj.author || [],
actor: obj.actor || [],
aggregateRating: obj.aggregateRating,
datePublished: obj.datePublished ? new Date(obj.datePublished) : undefined,
image: obj.image,
imageUrl: obj.image,
genre: obj.genre,
genre: obj.genre || [],
score: obj.aggregateRating ? obj.aggregateRating.ratingValue : undefined,
publisher: "",
aliases: [""],
@ -124,6 +124,46 @@ export class DoubanTeleplayLoadHandler extends DoubanAbstractLoadHandler<DoubanT
return result;
})[0];
// Fallback: if JSON-LD parsing failed, extract from meta tags
if (!teleplay) {
const title = html(html("head > meta[property='og:title']").get(0)).attr("content") || '';
const image = html(html("head > meta[property='og:image']").get(0)).attr("content") || '';
const urlMeta = html(html("head > meta[property='og:url']").get(0)).attr("content") || '';
const desc = html(html("head > meta[property='og:description']").get(0)).attr("content") || '';
const idPattern = /(\d){5,10}/g;
const idMatch = idPattern.exec(urlMeta);
const id = idMatch ? idMatch[0] : '';
const scoreText = html("#interest_sectl strong[property='v:average']").text();
const score = scoreText ? parseFloat(scoreText) : undefined;
teleplay = {
id,
title,
type: this.getSupportType(),
score,
originalTitle: title,
desc,
url: urlMeta || (id ? `https://movie.douban.com/subject/${id}/` : ''),
director: [],
author: [],
actor: [],
aggregateRating: undefined,
datePublished: undefined,
image,
imageUrl: image,
genre: [],
publisher: '',
aliases: [],
language: [],
country: [],
episode: null,
time: null,
IMDb: null,
};
}
this.handlePersonNameByMeta(html, teleplay, context, 'video:actor', 'actor');
this.handlePersonNameByMeta(html, teleplay, context, 'video:director', 'director');
const desc:string = html("span[property='v:summary']").text();

@ -33,28 +33,28 @@ export default class DoubanTheaterLoadHandler extends DoubanAbstractLoadHandler<
"director",
DataValueType.array,
extract.director,
extract.director.map(SchemaOrg.getPersonName).filter(c => c)
(extract.director || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("actor", new DataField(
"actor",
DataValueType.array,
extract.actor,
extract.actor.map(SchemaOrg.getPersonName).filter(c => c)
(extract.actor || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("author", new DataField(
"author",
DataValueType.array,
extract.author,
extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
(extract.author || []).map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
));
variableMap.set("aliases", new DataField(
"aliases",
DataValueType.array,
extract.aliases,
extract.aliases.map(a => a
(extract.aliases || []).map(a => a
.trim()
.replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
//replace multiple _ to single _

@ -3,8 +3,7 @@ import {
} from "../../../../constant/Constsant";
import {SearchResultPageParserInterface} from "./SearchResultPageParserInterface";
import {SearchPage} from "../../model/SearchPage";
import SearchParserHandlerV2 from "../SearchParserV2";
import StringUtil from "../../../../utils/StringUtil";
import SearchParserHandler from "../SearchParser";
import {log} from "../../../../utils/Logutil";
export class AllFirstPageSearchResultPageParser implements SearchResultPageParserInterface {
@ -12,22 +11,11 @@ export class AllFirstPageSearchResultPageParser implements SearchResultPageParse
return pageNum == 1 && type == SupportType.all;
}
parse(source:string, type:SupportType, pageNum:number, pageSize:number):SearchPage {
if (!source || StringUtil.notJsonString(source)) {
//TODO 国际化
log.notice("Obsidian-Douban:查询结果为空,无匹配结果,请尝试登录获取获取更多数据(已登录则忽略)");
return SearchPage.empty(type);
log.debug("解析给多页面结果");
if (!source) {
return new SearchPage(0, 0, 0, type, []);
}
const {subjects} = JSON.parse(source);
if (!subjects) {
return SearchPage.empty(type);
}
const {items} = subjects;
if (!items ||items.length == 0) {
return SearchPage.empty(type);
}
const doubanSearchResultSubjects = SearchParserHandlerV2.itemMapToSearchResult(items);
return new SearchPage(2000, pageNum, pageSize, type, doubanSearchResultSubjects);
return SearchParserHandler.parseSearchJson(source, type, pageNum);
}

@ -2,7 +2,7 @@ import {SupportType} from "../../../../constant/Constsant";
import {SearchResultPageParserInterface} from "./SearchResultPageParserInterface";
import {log} from "../../../../utils/Logutil";
import {SearchPage} from "../../model/SearchPage";
import SearchParserHandlerV2 from "../SearchParserV2";
import SearchParserHandler from "../SearchParser";
export class OtherAllPageSearchResultPageParser implements SearchResultPageParserInterface {
support(type:SupportType, pageNum:number):boolean {
@ -10,13 +10,10 @@ export class OtherAllPageSearchResultPageParser implements SearchResultPageParse
}
parse(source:string, type:SupportType, pageNum:number, pageSize:number):SearchPage {
log.debug("解析给多页面结果");
const {contents} = JSON.parse(source);
if (!contents) {
if (!source) {
return new SearchPage(0, 0, 0, type, []);
}
const data:{total:number, start:number, count:number, items:any[]} = contents;
const doubanSearchResultSubjects = SearchParserHandlerV2.itemMapToSearchResult(data.items);
return new SearchPage(data.total, pageNum, pageSize, type, doubanSearchResultSubjects);
return SearchParserHandler.parseSearchJson(source, type, pageNum);
}
}

@ -2,8 +2,8 @@ import {AbstractSearchPageFetcher} from "./AbstractSearchPageFetcher";
import { SupportType } from "src/org/wanxp/constant/Constsant";
export class AllPageSearchPageFetcher extends AbstractSearchPageFetcher {
getUrl(keyword: string, pageNum: number, pageSize: number): string {
return `https://m.douban.com/rexxar/api/v2/search?q=${keyword}&start=${pageNum}&count=${pageSize}`;
getUrl(keyword: string, start: number, pageSize: number): string {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}`;
}
support(type: SupportType): boolean {
return type == SupportType.all;

@ -10,17 +10,17 @@ export function constructLoginUI(containerEl: HTMLElement, manager: SettingsMana
// containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') });
const userComponent = manager.plugin.userComponent;
if (userComponent.needLogin()) {
try {
if (userComponent.isLogin() && !userComponent.isVerified()) {
// Assumed login — verify to get user ID/name for settings display
userComponent.login()
.then(() => {
constructDoubanLoginSettingsUI(containerEl, manager);
});
}catch (e) {
log.debug(i18nHelper.getMessage('100101'));
constructDoubanLoginSettingsUI(containerEl, manager);
}
}else {
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
} else if (userComponent.needLogin()) {
// Has credentials but not yet logged in
userComponent.login()
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
} else {
constructDoubanLoginSettingsUI(containerEl, manager);
}

@ -15,6 +15,7 @@ import {DoubanHttpUtil} from "../../utils/DoubanHttpUtil";
export default class UserComponent {
private settingsManager: SettingsManager;
private user: User;
private verified: boolean = false;
constructor(settingsManager: SettingsManager) {
this.settingsManager = settingsManager;
@ -39,11 +40,26 @@ export default class UserComponent {
this.user.login = false;
}
this.user = null;
this.verified = false;
this.settingsManager.updateSetting('loginCookiesContent', '');
this.settingsManager.updateSetting('loginHeadersContent', '');
}
assumeLoggedIn(): void {
const headers: any = this.settingsManager.getSetting('loginHeadersContent');
const cookies: any = this.settingsManager.getSetting('loginCookiesContent');
if (headers || cookies) {
this.user = new User();
this.user.login = true;
this.verified = false;
}
}
isVerified(): boolean {
return this.verified;
}
needLogin() {
@ -68,6 +84,7 @@ export default class UserComponent {
this.settingsManager.debug(`配置界面:loginCookie:豆瓣headers信息正常${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
});
if(this.user) {
this.verified = true;
this.settingsManager.updateSetting('loginHeadersContent', JSON.stringify(headers));
}
return this.user;
@ -139,6 +156,9 @@ export default class UserComponent {
this.user = user;
this.settingsManager.debug(`主界面:loginByCookie:豆瓣cookies信息正常${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
});
if (this.user && this.user.id) {
this.verified = true;
}
return this.user;
}
}

@ -286,6 +286,7 @@ export default class DoubanPlugin extends Plugin {
// this.fetchOnlineData(this.settingsManager);
this.userComponent = new UserComponent(this.settingsManager);
this.netFileHandler = new NetFileHandler(this.fileHandler);
this.userComponent.assumeLoggedIn();
this.settingTab = new DoubanSettingTab(this.app, this);
this.addSettingTab(this.settingTab);
@ -355,11 +356,16 @@ export default class DoubanPlugin extends Plugin {
async checkLogin(context: HandleContext):Promise<boolean> {
this.settingsManager.debug('主界面:同步时的登录状态检测');
if (!context.userComponent.needLogin()) {
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 无用户信息, 尝试获取用户信息');
await context.userComponent.login();
const uc = context.userComponent;
// If assumed-logged-in but not verified, verify now (sync needs real user ID)
if (uc.isLogin() && !uc.isVerified()) {
await uc.login();
}
if (!context.userComponent.isLogin()) {
// If has saved credentials but not logged in, try login
if (uc.needLogin()) {
await uc.login();
}
if (!uc.isLogin()) {
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 尝试获取用户信息失败');
new Notice(i18nHelper.getMessage('140303'));
return false;