import DoubanPlugin from "main"; import DoubanSubject, {DoubanParameter} from '../model/DoubanSubject'; import DoubanSubjectLoadHandler from "./DoubanSubjectLoadHandler"; import {moment, request, RequestUrlParam, TFile} from "obsidian"; import {i18nHelper} from 'src/lang/helper'; import {log} from "src/utils/Logutil"; import {CheerioAPI, load} from "cheerio"; import YamlUtil from "../../../utils/YamlUtil"; import { BasicConst, PersonNameMode, SearchHandleMode, SupportType, TemplateKey, TemplateTextMode } from "../../../constant/Constsant"; import HandleContext from "@App/data/model/HandleContext"; import HandleResult from "@App/data/model/HandleResult"; import {getDefaultTemplateContent} from "../../../constant/DefaultTemplateContent"; import StringUtil from "../../../utils/StringUtil"; import {DEFAULT_SETTINGS} from "../../../constant/DefaultSettings"; import {DoubanUserParameter, UserStateSubject} from "@App/data/model/UserStateSubject"; import {DoubanSubjectState, DoubanSubjectStateRecords} from "../../../constant/DoubanUserState"; export default abstract class DoubanAbstractLoadHandler implements DoubanSubjectLoadHandler { public doubanPlugin: DoubanPlugin; constructor(doubanPlugin: DoubanPlugin) { this.doubanPlugin = doubanPlugin; } async parse(extract: T, context: HandleContext): Promise { let template: string = await this.getTemplate(extract, context); await this.saveImage(extract, context); let frontMatterStart: number = template.indexOf(BasicConst.YAML_FRONT_MATTER_SYMBOL, 0); let frontMatterEnd: number = template.indexOf(BasicConst.YAML_FRONT_MATTER_SYMBOL, frontMatterStart + 1); let frontMatter: string = ''; let frontMatterBefore: string = ''; let frontMatterAfter: string = ''; let result: string = ''; if (frontMatterStart > -1 && frontMatterEnd > -1) { frontMatterBefore = template.substring(0, frontMatterStart); frontMatter = template.substring(frontMatterStart, frontMatterEnd + 3); frontMatterAfter = template.substring(frontMatterEnd + 3); if (frontMatterBefore.length > 0) { frontMatterBefore = this.parsePartText(frontMatterBefore, extract, context); } if (frontMatterAfter.length > 0) { frontMatterAfter = this.parsePartText(frontMatterAfter, extract, context); } if (frontMatter.length > 0) { frontMatter = this.parsePartText(frontMatter, extract, context, TemplateTextMode.YAML); } result = frontMatterBefore + frontMatter + frontMatterAfter; } else { result = this.parsePartText(template, extract, context); } let fileName: string = ''; if (SearchHandleMode.FOR_CREATE == context.mode) { fileName = this.parsePartText(this.getFileName(context), extract, context); } return {content: result, fileName: fileName}; } private getFileName(context: HandleContext): string { const {dataFileNamePath} = context.settings; return dataFileNamePath ? dataFileNamePath : DEFAULT_SETTINGS.dataFileNamePath; } /** * 处理特殊字符 * @param text * @param textMode */ handleSpecialText(text: string, textMode: TemplateTextMode): string { let result = text; switch (textMode) { case TemplateTextMode.YAML: result = YamlUtil.handleText(text); break; } return result; } /** * 处理内容数组 * @param array * @param settings * @param textMode */ handleContentArray(array: any[], context: HandleContext, textMode: TemplateTextMode): string { let result; switch (textMode) { case TemplateTextMode.YAML: result = array.map(YamlUtil.handleText).join(', '); break; default: result = array.join(context.settings.arraySpilt); } return result; } /** * 处理特殊内容 * @param value * @param textMode * @param settings */ handleSpecialContent(value: any, textMode: TemplateTextMode = TemplateTextMode.NORMAL, context: HandleContext = null): string { let result; if (!value) { return ''; } if (value instanceof Array) { result = this.handleContentArray(value, context, textMode); } else if (value instanceof Number) { result = value.toString(); } else { result = this.handleSpecialText(value, textMode); } return result; } abstract getSupportType(): SupportType; abstract parseText(beforeContent: string, extract: T, context: HandleContext, textMode: TemplateTextMode): string; abstract support(extract: DoubanSubject): boolean; handle(url: string, context: HandleContext): void { let headers = JSON.parse(context.settings.searchHeaders); headers.Cookie = context.settings.loginCookiesContent; const requestUrlParam: RequestUrlParam = { url: url, method: "GET", headers: headers, throw: true }; request(requestUrlParam) .then(load) .then(data => this.analysisUserState(data, context)) .then(({data, userState}) => { let sub = this.parseSubjectFromHtml(data); sub.userState = userState; return sub; }) .then(content => this.toEditor(context, content)) // .then(content => content ? editor.replaceSelection(content) : content) .catch(e => log .error( i18nHelper.getMessage('130101') .replace('{0}', e.toString()) , e)); ; } abstract parseSubjectFromHtml(data: CheerioAPI): T | undefined; toEditor(context: HandleContext, extract: T): T { this.doubanPlugin.putToObsidian(context, extract); return extract; } getPersonName(name: string, context: HandleContext): string { return this.getPersonNameByMode(name, context.settings.personNameMode); } getPersonNameByMode(name: string, personNameMode: string): string { if (!name || !personNameMode) { return ""; } let resultName: string; let regValue: RegExpExecArray; switch (personNameMode) { case PersonNameMode.CH_NAME: regValue = /[\u4e00-\u9fa50-9\. \:\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]{2,20}/g.exec(name); resultName = regValue ? regValue[0] : name; break; case PersonNameMode.EN_NAME: regValue = /[0-9a-zA-Z.\s-]{2,50}/g.exec(name); resultName = regValue ? regValue[0] : name; break; default: resultName = name; } return resultName; } // html_encode(str: string): string { // let s = ""; // if (str.length == 0) return ""; // s = str.replace(/&/g, "&"); // s = s.replace(//g, ">"); // s = s.replace(/ /g, " "); // s = s.replace(/\'/g, "'"); // s = s.replace(/\"/g, """); // s = s.replace(/\n/g, "
"); // return s; // } html_decode(str: string): string { let s = ""; if (str.length == 0) return ""; s = str.replace(/&/g, "&"); s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/ /g, " "); s = s.replace(/'/g, "\'"); s = s.replace(/"/g, "\""); s = s.replace(//g, "\n"); return s; } private parsePartText(template: string, extract: T, context: HandleContext, textMode: TemplateTextMode = TemplateTextMode.NORMAL): string { let resultContent = this.handleCustomVariable(template, context); resultContent = resultContent.replaceAll(DoubanParameter.ID, extract.id) .replaceAll(DoubanParameter.TITLE, this.handleSpecialContent(this.getPersonName(extract.title, context), textMode)) .replaceAll(DoubanParameter.TYPE, extract.type) .replaceAll(DoubanParameter.SCORE, this.handleSpecialContent(extract.score)) .replaceAll(DoubanParameter.IMAGE, extract.image) .replaceAll(DoubanParameter.URL, extract.url) .replaceAll(DoubanParameter.DESC, this.handleSpecialContent(extract.desc, textMode)) .replaceAll(DoubanParameter.PUBLISHER, extract.publisher) .replaceAll(DoubanParameter.DATE_PUBLISHED, extract.datePublished ? moment(extract.datePublished).format(context.settings.dateFormat) : '') .replaceAll(DoubanParameter.TIME_PUBLISHED, extract.datePublished ? moment(extract.datePublished).format(context.settings.timeFormat) : '') .replaceAll(DoubanParameter.CURRENT_DATE, moment(new Date()).format(context.settings.dateFormat)) .replaceAll(DoubanParameter.CURRENT_TIME, moment(new Date()).format(context.settings.timeFormat)) .replaceAll(DoubanParameter.GENRE, this.handleSpecialContent(extract.genre, textMode, context)) ; resultContent = this.parseUserInfo(resultContent, extract, context, textMode); return this.parseText(resultContent, extract, context, textMode); } private parseUserInfo(resultContent: string, extract: T, context: HandleContext, textMode: TemplateTextMode) { const userState = extract.userState; if ((resultContent.indexOf(DoubanUserParameter.MY_TAGS) >= 0 || resultContent.indexOf(DoubanUserParameter.MY_RATING) >= 0 || resultContent.indexOf(DoubanUserParameter.MY_STATE) >= 0 || resultContent.indexOf(DoubanUserParameter.MY_COMMENT) >= 0 || resultContent.indexOf(DoubanUserParameter.MY_COLLECTION_DATE) >= 0 ) && !this.doubanPlugin.userComponent.isLogin()) { log.warn(i18nHelper.getMessage('100113')); return resultContent; } if (!userState || !userState.collectionDate) { return resultContent; } return resultContent.replaceAll(DoubanUserParameter.MY_TAGS, this.handleSpecialContent(userState.tags, textMode, context)) .replaceAll(DoubanUserParameter.MY_RATING, this.handleSpecialContent(userState.rate, textMode)) .replaceAll(DoubanUserParameter.MY_STATE, this.getUserStateName(userState.state)) .replaceAll(DoubanUserParameter.MY_COMMENT, this.handleSpecialContent(userState.comment, textMode)) .replaceAll(DoubanUserParameter.MY_COLLECTION_DATE, moment(userState.collectionDate).format(context.settings.dateFormat)) } /** * 处理自定义参数 * @param template * @param context * @private */ private handleCustomVariable(template: string, context: HandleContext): string { let customProperties = context.settings.customProperties; let resultContent = template; if (!customProperties) { return resultContent; } customProperties.filter(customProperty => customProperty.name && customProperty.field && (customProperty.field == SupportType.ALL || customProperty.field == this.getSupportType())).forEach(customProperty => { resultContent = resultContent.replaceAll(`{{${customProperty.name}}}`, customProperty.value); }); return resultContent; } private getTemplateKey():TemplateKey { let templateKey: TemplateKey; switch (this.getSupportType()) { case SupportType.MOVIE: templateKey = TemplateKey.movieTemplateFile; break; case SupportType.BOOK: templateKey = TemplateKey.bookTemplateFile; break; case SupportType.MUSIC: templateKey = TemplateKey.musicTemplateFile; break; case SupportType.TELEPLAY: templateKey = TemplateKey.teleplayTemplateFile; break; case SupportType.GAME: templateKey = TemplateKey.gameTemplateFile; break; case SupportType.NOTE: templateKey = TemplateKey.noteTemplateFile; break; default: templateKey = null; } return templateKey; } private async getTemplate(extract: T, context: HandleContext): Promise { const tempKey: TemplateKey = this.getTemplateKey(); const templatePath: string = context.settings[tempKey]; let useUserState:boolean = context.userComponent.isLogin() && extract.userState && extract.userState.collectionDate != null && extract.userState.collectionDate != undefined; useUserState = useUserState ? useUserState : false; // @ts-ignore if (!templatePath || StringUtil.isBlank(templatePath)) { return getDefaultTemplateContent(tempKey, useUserState); } const defaultContent = getDefaultTemplateContent(tempKey, useUserState); let firstLinkpathDest: TFile = this.doubanPlugin.app.metadataCache.getFirstLinkpathDest(templatePath, ''); if (!firstLinkpathDest) { return defaultContent; } else { const val = await this.doubanPlugin.fileHandler.getFileContent(firstLinkpathDest.path); return val ? val : defaultContent; } } analysisUserState(html: CheerioAPI, context: HandleContext): {data:CheerioAPI , userState: UserStateSubject} { if (!context.userComponent.isLogin()) { return {data: html, userState: null}; } if(!html('.nav-user-account')) { return {data: html, userState: null}; } let rate = html(html('input#n_rating').get(0)).val(); let tagsStr = html(html('div#interest_sect_level > div.a_stars > span.color_gray').get(0)).text().trim(); let tags = tagsStr.replace('标签:', '').split(' '); let stateWord = html(html('div#interest_sect_level > div.a_stars > span.mr10').get(0)).text().trim(); let collectionDateStr = html(html('div#interest_sect_level > div.a_stars > span.mr10 > span.collection_date').get(0)).text().trim(); let userState1 = DoubanAbstractLoadHandler.getUserState(stateWord); let component = html(html('div#interest_sect_level > div.a_stars > span.color_gray').get(0)).next().next().text().trim(); const userState: UserStateSubject = { tags: tags, rate: rate?Number(rate):null, state: userState1, collectionDate: collectionDateStr?moment(collectionDateStr, 'YYYY-MM-DD').toDate():null, comment: component } return {data: html, userState: userState}; } public static getUserState(stateWord:string):DoubanSubjectState { let state:DoubanSubjectState; if(!stateWord) { return null; } if(stateWord.indexOf('想')>=0 ) { state = DoubanSubjectState.wish; }else if(stateWord.indexOf('在')>=0) { state = DoubanSubjectState.do; }else if(stateWord.indexOf('过')>=0) { state = DoubanSubjectState.collect; }else { state = DoubanSubjectState.not; } return state; } private getUserStateName(state: DoubanSubjectState): string { if (!state) { return ''; } let v = DoubanSubjectStateRecords[this.getSupportType()]; switch (state) { case DoubanSubjectState.wish: return v.wish; case DoubanSubjectState.do: return v.do; case DoubanSubjectState.collect: return v.collect; case DoubanSubjectState.not: return v.not; default: return ''; } } private async saveImage(extract: T, context: HandleContext) { if (!extract.image || !context.settings.cacheImage) { return; } let image = extract.image; const filename = image.split('/').pop(); let folder = context.settings.attachmentPath; if (!folder) { folder = DEFAULT_SETTINGS.attachmentPath; } const {success, filepath} = await context.netFileHandler.downloadFile(image, folder, filename); if (success) { extract.image = filepath; } } }