1. 优化导入配置,增加更多导入时的选项
2. 优化导入结果的显示,现在会在结果文本中显示具体的条件
This commit is contained in:
Wanxp 2025-03-06 18:44:31 +08:00
parent 8f405600a5
commit 4d3dc61d33
24 changed files with 855 additions and 180 deletions

@ -223,7 +223,7 @@ export const SyncTypeUrlDomain: Map<SyncType, string> = new Map([
* *
*/ */
// @ts-ignore // @ts-ignore
export const SyncTypeRecords: { [key in SyncType]: string } = { export const SyncTypeRecords: { [key in SyncType | string]: string } = {
[SyncType.movie]: i18nHelper.getMessage('504103'), [SyncType.movie]: i18nHelper.getMessage('504103'),
[SyncType.teleplay]: i18nHelper.getMessage('504107'), [SyncType.teleplay]: i18nHelper.getMessage('504107'),
[SyncType.book]: i18nHelper.getMessage('504102'), [SyncType.book]: i18nHelper.getMessage('504102'),
@ -257,6 +257,7 @@ export enum SyncItemStatus {
replace = 'replace', replace = 'replace',
create = 'create', create = 'create',
fail = 'fail', fail = 'fail',
failByDiffType = 'failByDiffType',
unHandle = 'unHandle', unHandle = 'unHandle',
} }
@ -459,15 +460,15 @@ export enum SyncConditionType {
* *
*/ */
ALL = "all", ALL = "all",
/** // /**
* // * 最近新变动
*/ // */
LAST_UPDATE = "lastUpdate", // LAST_UPDATE = "lastUpdate",
/** /**
* 10 * 10
*/ */
LAST_TEN = "lastTen", LAST_THIRTY = "lastThirty",
/** /**
* *
@ -485,10 +486,10 @@ export enum SyncConditionType {
* *
*/ */
// @ts-ignore // @ts-ignore
export const SyncConditionTypeRecords: { [key in SyncConditionType]: string } = { export const SyncConditionTypeRecords: { [key in SyncConditionType|string]: string } = {
[SyncConditionType.ALL]: i18nHelper.getMessage('110071'), [SyncConditionType.ALL]: i18nHelper.getMessage('110071'),
[SyncConditionType.LAST_UPDATE]: i18nHelper.getMessage('110072'), // [SyncConditionType.LAST_UPDATE]: i18nHelper.getMessage('110072'),
[SyncConditionType.LAST_TEN]: i18nHelper.getMessage('110075'), [SyncConditionType.LAST_THIRTY]: i18nHelper.getMessage('110075'),
[SyncConditionType.CUSTOM_ITEM]: i18nHelper.getMessage('110076'), [SyncConditionType.CUSTOM_ITEM]: i18nHelper.getMessage('110076'),
[SyncConditionType.CUSTOM_TIME]: i18nHelper.getMessage('110074'), [SyncConditionType.CUSTOM_TIME]: i18nHelper.getMessage('110074'),

@ -51,6 +51,7 @@ export const DEFAULT_SETTINGS: DoubanPluginSetting = {
cacheHighQuantityImage: true, cacheHighQuantityImage: true,
attachmentPath: 'assets', attachmentPath: 'assets',
syncHandledDataArray: [], syncHandledDataArray: [],
// syncLastUpdateTime: new Map<string, string>(),
scoreSetting: { scoreSetting: {
starFull: '⭐', starFull: '⭐',
starEmpty: '☆', starEmpty: '☆',

@ -1,5 +1,5 @@
import {i18nHelper} from "../lang/helper"; import {i18nHelper} from "../lang/helper";
import {SupportType} from "./Constsant"; import {SupportType, SyncType} from "./Constsant";
export enum DoubanSubjectState { export enum DoubanSubjectState {
not = 'not', not = 'not',
@ -123,6 +123,19 @@ export const DoubanSubjectStateRecords_MUSIC_SYNC: { [key in DoubanSubjectState]
[DoubanSubjectState.collect]: i18nHelper.getMessage('500404'), [DoubanSubjectState.collect]: i18nHelper.getMessage('500404'),
} }
// @ts-ignore
export const DoubanSubjectStateRecords_SYNC: { [key in SyncType]: Record<DoubanSubjectState, string> } = {
[SyncType.movie]:DoubanSubjectStateRecords_MOVIE_SYNC,
[SyncType.book]:DoubanSubjectStateRecords_BOOK_SYNC,
[SyncType.music]:DoubanSubjectStateRecords_MUSIC_SYNC,
// [SyncType.note]:DoubanSubjectStateRecords_NOTE_SYNC,
// [SyncType.game]:DoubanSubjectStateRecords_GAME_SYNC,
[SyncType.teleplay]:DoubanSubjectStateRecords_TELEPLAY_SYNC,
// [SyncType.theater]:DoubanSubjectStateRecords_THEATER_SYNC,
}
export const DoubanSubjectStateRecords_KEY_WORD_TYPE: Map<string, SupportType> = new Map<string, SupportType> ( export const DoubanSubjectStateRecords_KEY_WORD_TYPE: Map<string, SupportType> = new Map<string, SupportType> (
[['我看过这部电视剧', SupportType.TELEPLAY], [['我看过这部电视剧', SupportType.TELEPLAY],
['我最近看过这部电视剧', SupportType.TELEPLAY], ['我最近看过这部电视剧', SupportType.TELEPLAY],

@ -15,7 +15,7 @@ import {
SyncTypeRecords SyncTypeRecords
} from "../../constant/Constsant"; } from "../../constant/Constsant";
import { import {
ALL, ALL, DoubanSubjectState, DoubanSubjectStateRecords,
DoubanSubjectStateRecords_BOOK_SYNC, DoubanSubjectStateRecords_BOOK_SYNC,
DoubanSubjectStateRecords_BROADCAST_SYNC, DoubanSubjectStateRecords_BROADCAST_SYNC,
DoubanSubjectStateRecords_MOVIE_SYNC, DoubanSubjectStateRecords_MOVIE_SYNC,
@ -36,6 +36,7 @@ import {ArraySetting, DEFAULT_SETTINGS_ARRAY_NAME} from "../setting/model/ArrayS
import {arraySettingDisplay} from "../setting/ArrayDisplayTypeSettingsHelper"; import {arraySettingDisplay} from "../setting/ArrayDisplayTypeSettingsHelper";
import {DatePickComponent} from "./DatePickComponent"; import {DatePickComponent} from "./DatePickComponent";
import {NumberComponent} from "./NumberComponent"; import {NumberComponent} from "./NumberComponent";
import {log} from "../../utils/Logutil";
export class DoubanSyncModal extends Modal { export class DoubanSyncModal extends Modal {
plugin: DoubanPlugin; plugin: DoubanPlugin;
@ -107,7 +108,16 @@ export class DoubanSyncModal extends Modal {
progress.innerHTML = `<p> progress.innerHTML = `<p>
<label for="file">${i18nHelper.getMessage('110033')}</label> <label for="file">${i18nHelper.getMessage('110033')}</label>
<progress class="obsidian_douban_sync_slider" max="${syncStatus.getTotal() == 0 ? 1:syncStatus.getTotal()}" value="${syncStatus.getHasHandle()}"> </progress> <span> ${syncStatus.getHasHandle()}/${syncStatus.getTotal()}:${i18nHelper.getMessage('110036')} </span> <progress class="obsidian_douban_sync_slider" max="${syncStatus.getTotal() == 0 ? 1:syncStatus.getTotal()}" value="${syncStatus.getHasHandle()}"> </progress> <span> ${syncStatus.getHasHandle()}/${syncStatus.getTotal()}:${i18nHelper.getMessage('110036')} </span>
</p>` </p>
<p>
<label for="file">${i18nHelper.getMessage('110092')}</label>
<span>${i18nHelper.getMessage('110090', syncStatus.getTypeName(), syncStatus.getScopeName(), syncStatus.getAllTotal(), syncStatus.getTotal())}</span>
</p>
<p>
<label for="file">${i18nHelper.getMessage('110091')}</label>
<span>${syncStatus.getMessage()}</span>
</p>
`
backgroundButton.setDisabled(true); backgroundButton.setDisabled(true);
stopButton.setButtonText(i18nHelper.getMessage('110036')) stopButton.setButtonText(i18nHelper.getMessage('110036'))
return; return;
@ -116,7 +126,16 @@ export class DoubanSyncModal extends Modal {
<label for="file">${i18nHelper.getMessage('110033')}</label> <label for="file">${i18nHelper.getMessage('110033')}</label>
<progress class="obsidian_douban_sync_slider" max="${syncStatus.getTotal() == 0 ? 1:syncStatus.getTotal()}" value="${syncStatus.getHasHandle()}"> </progress> <span> ${syncStatus.getTotal() == 0 ? i18nHelper.getMessage('110043') : syncStatus.getHasHandle() + '/' + syncStatus.getTotal()} <progress class="obsidian_douban_sync_slider" max="${syncStatus.getTotal() == 0 ? 1:syncStatus.getTotal()}" value="${syncStatus.getHasHandle()}"> </progress> <span> ${syncStatus.getTotal() == 0 ? i18nHelper.getMessage('110043') : syncStatus.getHasHandle() + '/' + syncStatus.getTotal()}
${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + TimeUtil.estimateTimeMsg(syncStatus.getNeedHandled()-syncStatus.getHandle(), syncStatus.getOverSize())} </span> ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + TimeUtil.estimateTimeMsg(syncStatus.getNeedHandled()-syncStatus.getHandle(), syncStatus.getOverSize())} </span>
</p>`} </p>
<p>
<label for="file">${i18nHelper.getMessage('110092')}</label>
<span>${i18nHelper.getMessage('110090', syncStatus.getTypeName(), syncStatus.getScopeName(), syncStatus.getAllTotal(), syncStatus.getTotal())}</span>
</p>
<p>
<label for="file">${i18nHelper.getMessage('110091')}</label>
<span>${syncStatus.getMessage()}</span>
</p>
`}
private showSyncConfig(contentEl: HTMLElement) { private showSyncConfig(contentEl: HTMLElement) {
if (this.timer != null) { if (this.timer != null) {
@ -133,11 +152,11 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
attachmentPath: (settings.attachmentPath == '' || settings.attachmentPath == null) ? DEFAULT_SETTINGS.attachmentPath : settings.attachmentPath, attachmentPath: (settings.attachmentPath == '' || settings.attachmentPath == null) ? DEFAULT_SETTINGS.attachmentPath : settings.attachmentPath,
templateFile: (settings.movieTemplateFile == '' || settings.movieTemplateFile == null) ? DEFAULT_SETTINGS.movieTemplateFile : settings.movieTemplateFile, templateFile: (settings.movieTemplateFile == '' || settings.movieTemplateFile == null) ? DEFAULT_SETTINGS.movieTemplateFile : settings.movieTemplateFile,
incrementalUpdate: true, incrementalUpdate: true,
syncConditionType: SyncConditionType.LAST_UPDATE, syncConditionType: SyncConditionType.ALL,
syncConditionDateFromValue: null, syncConditionDateFromValue: TimeUtil.getLastMonth(),
syncConditionDateToValue: null, syncConditionDateToValue: new Date(),
syncConditionCountFromValue: null, syncConditionCountFromValue: 1,
syncConditionCountToValue: null syncConditionCountToValue: 30
}; };
this.showConfigPan(contentEl.createDiv('config'), syncConfig, false); this.showConfigPan(contentEl.createDiv('config'), syncConfig, false);
const controls = contentEl.createDiv("controls"); const controls = contentEl.createDiv("controls");
@ -149,6 +168,9 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
const syncButton = new ButtonComponent(controls) const syncButton = new ButtonComponent(controls)
.setButtonText(i18nHelper.getMessage('110007')) .setButtonText(i18nHelper.getMessage('110007'))
.onClick(async () => { .onClick(async () => {
if (!this.plugin.userComponent.isLogin()) {
await this.plugin.userComponent.login();
}
if(!await this.plugin.checkLogin(this.context)) { if(!await this.plugin.checkLogin(this.context)) {
return; return;
} }
@ -461,12 +483,17 @@ function showCustomInputCount(containerEl: HTMLElement, config: SyncConfig, disa
containerEl.createEl('span', { text: i18nHelper.getMessage('110078') }) containerEl.createEl('span', { text: i18nHelper.getMessage('110078') })
const fromField = new TextComponent(containerEl); const fromField = new TextComponent(containerEl);
fromField.setPlaceholder(i18nHelper.getMessage('110080')) fromField.setPlaceholder(i18nHelper.getMessage('110080'))
.setValue(config.syncConditionCountFromValue) .setValue(config.syncConditionCountFromValue + '')
.onChange(async (value) => { .onChange(async (value) => {
if (!value) { if (!value) {
config.syncConditionCountFromValue = 1;
return; return;
} }
config.syncConditionCountFromValue = value; try {
config.syncConditionCountFromValue = parseInt(value);
}catch (e) {
log.notice(i18nHelper.getMessage('112080'))
}
}).setDisabled(disable); }).setDisabled(disable);
let fromEl = fromField.inputEl; let fromEl = fromField.inputEl;
fromEl.addClass('obsidian_douban_settings_input') fromEl.addClass('obsidian_douban_settings_input')
@ -481,12 +508,17 @@ function showCustomInputCount(containerEl: HTMLElement, config: SyncConfig, disa
containerEl.createEl('span', { text: i18nHelper.getMessage('110078') }) containerEl.createEl('span', { text: i18nHelper.getMessage('110078') })
const toField = new TextComponent(containerEl); const toField = new TextComponent(containerEl);
toField.setPlaceholder(i18nHelper.getMessage('110080')) toField.setPlaceholder(i18nHelper.getMessage('110080'))
.setValue(config.syncConditionCountToValue) .setValue(config.syncConditionCountToValue + '')
.onChange(async (value) => { .onChange(async (value) => {
if (!value) { if (!value) {
config.syncConditionCountToValue = 30;
return; return;
} }
config.syncConditionCountToValue = value; try {
config.syncConditionCountToValue = parseInt(value);
}catch (e) {
log.notice(i18nHelper.getMessage('112080'))
}
}).setDisabled(disable); }).setDisabled(disable);
let toEl = toField.inputEl; let toEl = toField.inputEl;
toEl.addClass('obsidian_douban_settings_input') toEl.addClass('obsidian_douban_settings_input')
@ -495,6 +527,9 @@ function showCustomInputCount(containerEl: HTMLElement, config: SyncConfig, disa
if (lang == 'zh') { if (lang == 'zh') {
containerEl.createEl('span', {text: i18nHelper.getMessage('110073')}) containerEl.createEl('span', {text: i18nHelper.getMessage('110073')})
} }
containerEl.createEl('span', {text: ' '})
const buttopn = new ButtonComponent(containerEl).setIcon('help').setTooltip(i18nHelper.getMessage('110095'))
containerEl.appendChild(buttopn.buttonEl);
} }
function showCustomInputTime(containerEl: HTMLElement, config: SyncConfig, disable: boolean) { function showCustomInputTime(containerEl: HTMLElement, config: SyncConfig, disable: boolean) {
@ -502,14 +537,18 @@ function showCustomInputTime(containerEl: HTMLElement, config: SyncConfig, disab
const fromDateField = new TextComponent(containerEl); const fromDateField = new TextComponent(containerEl);
const fromDateEl = fromDateField.inputEl; const fromDateEl = fromDateField.inputEl;
fromDateEl.type = 'date'; fromDateEl.type = 'date';
fromDateEl.value = config.syncConditionDateFromValue??TimeUtil.getLastMonth().toISOString().substring(0, 10); fromDateEl.value = config.syncConditionDateFromValue ? config.syncConditionDateFromValue.toISOString().substring(0, 10) : TimeUtil.getLastMonth().toISOString().substring(0, 10);
fromDateField.setPlaceholder(i18nHelper.getMessage('110075')) fromDateField.setPlaceholder(i18nHelper.getMessage('110075'))
.setValue(config.syncConditionDateFromValue) .setValue(config.syncConditionDateFromValue ? config.syncConditionDateFromValue.toISOString().substring(0, 10) : TimeUtil.getLastMonth().toISOString().substring(0, 10))
.onChange(async (value) => { .onChange(async (value) => {
if (!value) { if (!value) {
return; return;
} }
config.syncConditionDateFromValue = value; try {
config.syncConditionDateFromValue = new Date(value);
}catch (e) {
log.notice(i18nHelper.getMessage('110082'))
}
}).setDisabled(disable); }).setDisabled(disable);
fromDateEl.addClass('obsidian_douban_settings_input') fromDateEl.addClass('obsidian_douban_settings_input')
containerEl.appendChild(fromDateEl); containerEl.appendChild(fromDateEl);
@ -518,16 +557,21 @@ function showCustomInputTime(containerEl: HTMLElement, config: SyncConfig, disab
const toDateField = new TextComponent(containerEl); const toDateField = new TextComponent(containerEl);
let toDateEl = toDateField.inputEl; let toDateEl = toDateField.inputEl;
toDateEl.type = 'date'; toDateEl.type = 'date';
toDateEl.value = config.syncConditionDateToValue??new Date().toISOString().substring(0, 10); toDateEl.value = config.syncConditionDateToValue ? config.syncConditionDateToValue.toISOString().substring(0, 10) : new Date().toISOString().substring(0, 10);
toDateField.setPlaceholder(i18nHelper.getMessage('110075')) toDateField.setPlaceholder(i18nHelper.getMessage('110075'))
.setValue(config.syncConditionDateFromValue) .setValue(config.syncConditionDateToValue ? config.syncConditionDateToValue.toISOString().substring(0, 10) : new Date().toISOString().substring(0, 10))
.onChange(async (value) => { .onChange(async (value) => {
if (!value) { if (!value) {
return; return;
} }
config.syncConditionDateFromValue = value; try {
config.syncConditionDateToValue = new Date(value);
}catch (e) {
log.notice(i18nHelper.getMessage('110082'))
}
}).setDisabled(disable); }).setDisabled(disable);
toDateEl.addClass('obsidian_douban_settings_input') toDateEl.addClass('obsidian_douban_settings_input')
containerEl.appendChild(toDateEl); containerEl.appendChild(toDateEl);
new ButtonComponent(containerEl).setIcon('help').setTooltip(i18nHelper.getMessage('110095'))
} }

@ -26,5 +26,6 @@ export default interface HandleContext {
syncActive?:boolean; syncActive?:boolean;
searchPage?:SearchPageInfo; searchPage?:SearchPageInfo;
syncOffset?:number;
} }

@ -1,21 +1,14 @@
import {SearchPageInfo} from "./SearchPageInfo"; import { SearchPageInfo } from "./SearchPageInfo";
import {SupportType} from "../../../constant/Constsant"; import { SupportType } from "../../../constant/Constsant";
import {SearchPageTypeOf} from "./SearchPageTypeOf";
export class SearchPage extends SearchPageInfo{ export class SearchPage extends SearchPageTypeOf<any> {
private _list:any[]; public static empty(type: SupportType): SearchPage {
constructor(total: number, pageNum: number, pageSize: number, type:SupportType, list: any[]) {
super(total, pageNum, pageSize, type);
this._list = list;
}
public get list() {
return this._list;
}
public static empty(type:SupportType):SearchPage {
return new SearchPage(0, 0, 0, type, []); return new SearchPage(0, 0, 0, type, []);
} }
static emptyWithNoType() {
return new SearchPage(0, 0, 0, null, []);
}
} }

@ -56,6 +56,10 @@ export class SearchPageInfo {
return this._total; return this._total;
} }
public set total(total: number) {
this._total = total;
}
get pageSize(): number { get pageSize(): number {
return this._pageSize; return this._pageSize;
} }

@ -0,0 +1,29 @@
import { SearchPageInfo } from "./SearchPageInfo";
import { SupportType } from "../../../constant/Constsant";
export class SearchPageTypeOf<T> extends SearchPageInfo {
private _list: T[];
constructor(
total: number,
pageNum: number,
pageSize: number,
type: SupportType,
list: T[],
) {
super(total, pageNum, pageSize, type);
this._list = list;
}
public get list() {
return this._list;
}
public static empty(type: SupportType): SearchPageTypeOf<any> {
return new SearchPageTypeOf(0, 0, 0, type, []);
}
static emptyWithNoType() {
return new SearchPageTypeOf(0, 0, 0, null, []);
}
}

@ -2,4 +2,5 @@ export interface SubjectListItem {
id:string; id:string;
url:string; url:string;
title:string; title:string;
updateDate: Date | null;
} }

@ -38,7 +38,7 @@ export function createFileSelectionSetting({containerEl, name, desc, placeholder
if (oldValue) { if (oldValue) {
v = oldValue; v = oldValue;
} }
new FileTreeSelectSuggest(manager.app, search.inputEl); const fileTreeSelectSuggest = new FileTreeSelectSuggest(manager.app, search.inputEl, manager, key);
// @ts-ignore // @ts-ignore
search.setValue(v); search.setValue(v);
// @ts-ignore // @ts-ignore
@ -48,6 +48,7 @@ export function createFileSelectionSetting({containerEl, name, desc, placeholder
search.onChange(async (value: string) => { search.onChange(async (value: string) => {
manager.updateSetting(key, value); manager.updateSetting(key, value);
}); });
}); });
setting.addExtraButton((button) => { setting.addExtraButton((button) => {

@ -36,6 +36,7 @@ export interface DoubanPluginSetting {
pictureBedType: string; pictureBedType: string;
pictureBedSetting: PictureBedSetting; pictureBedSetting: PictureBedSetting;
syncHandledDataArray: SyncHandledData[], syncHandledDataArray: SyncHandledData[],
// syncLastUpdateTime: Map<string, string>,
arraySettings: ArraySetting[], arraySettings: ArraySetting[],
scoreSetting: ScoreSetting, scoreSetting: ScoreSetting,
} }

@ -1,8 +1,17 @@
import {TAbstractFile, TFile, TFolder} from "obsidian"; import {App, TAbstractFile, TFile, TFolder} from "obsidian";
import {TextInputSuggest} from "./TextInputSuggest"; import {TextInputSuggest} from "./TextInputSuggest";
import SettingsManager from "../SettingsManager";
export class FileTreeSelectSuggest extends TextInputSuggest<TAbstractFile> { export class FileTreeSelectSuggest extends TextInputSuggest<TAbstractFile> {
parentPath: string = "/"; parentPath: string = "/";
settingKey: string = "";
manager: SettingsManager;
constructor(app: App, inputEl: HTMLInputElement, manager: SettingsManager, settingKey:string) {
super(app, inputEl);
this.manager = manager;
this.settingKey = settingKey;
}
getSuggestions(inputStr: string): TAbstractFile[] { getSuggestions(inputStr: string): TAbstractFile[] {
const files: TAbstractFile[] = []; const files: TAbstractFile[] = [];
@ -71,6 +80,8 @@ export class FileTreeSelectSuggest extends TextInputSuggest<TAbstractFile> {
this.inputEl.value += "/"; this.inputEl.value += "/";
this.inputEl.trigger("input"); this.inputEl.trigger("input");
}else { }else {
//@ts-ignore
this.manager.updateSetting(this.settingKey, file.path);
this.close(); this.close();
} }

@ -1,25 +1,67 @@
import DoubanPlugin from "../../../main"; import DoubanPlugin from "../../../main";
import {BasicConst, SubjectHandledStatus, SyncType} from "../../../constant/Constsant"; import {
import {DoubanSyncHandler} from "./DoubanSyncHandler"; BasicConst,
import {SyncConfig} from "../model/SyncConfig"; PAGE_SIZE,
SyncConditionType,
SyncType,
} from "../../../constant/Constsant";
import { DoubanSyncHandler } from "./DoubanSyncHandler";
import { SyncConfig } from "../model/SyncConfig";
import HandleContext from "../../data/model/HandleContext"; import HandleContext from "../../data/model/HandleContext";
import {SubjectListItem} from "../../data/model/SubjectListItem"; import { SubjectListItem } from "../../data/model/SubjectListItem";
import {sleepRange} from "../../../utils/TimeUtil"; import { sleepRange } from "../../../utils/TimeUtil";
import DoubanSubjectLoadHandler from "../../data/handler/DoubanSubjectLoadHandler"; import DoubanSubjectLoadHandler from "../../data/handler/DoubanSubjectLoadHandler";
import {DoubanListHandler} from "./list/DoubanListHandler"; import { DoubanListHandler } from "./list/DoubanListHandler";
import DoubanSubject from "../../data/model/DoubanSubject"; import DoubanSubject from "../../data/model/DoubanSubject";
import {log} from "../../../utils/Logutil"; import { log } from "../../../utils/Logutil";
import {i18nHelper} from "../../../lang/helper"; import { i18nHelper } from "../../../lang/helper";
import { SearchPage } from "../../data/model/SearchPage";
import {SearchPageTypeOf} from "../../data/model/SearchPageTypeOf";
export abstract class DoubanAbstractSyncHandler<T extends DoubanSubject> implements DoubanSyncHandler{ function toDateList(dataList: SubjectListItem[]): Date[] {
const dateList = dataList
.map((item) => item.updateDate)
.sort((a, b) => {
try {
return a.getTime() - b.getTime();
} catch (e) {
}
return 0;
});
return dateList;
}
function testTouchEndCondition(searchPage: SearchPage, context: HandleContext) {
const { syncConfig } = context;
if (!syncConfig) {
return false;
}
switch (syncConfig.syncConditionType) {
case SyncConditionType.ALL:
return false;
case SyncConditionType.LAST_THIRTY:
return searchPage.pageNum >= 0;
case SyncConditionType.CUSTOM_ITEM:
const syncConditionCountToValue = syncConfig.syncConditionCountToValue? syncConfig.syncConditionCountToValue : searchPage.total;
return searchPage.start + PAGE_SIZE - 1 >= syncConditionCountToValue;
case SyncConditionType.CUSTOM_TIME:
return true;
}
return false;
}
export abstract class DoubanAbstractSyncHandler<T extends DoubanSubject>
implements DoubanSyncHandler
{
private plugin: DoubanPlugin; private plugin: DoubanPlugin;
private doubanSubjectLoadHandler:DoubanSubjectLoadHandler<T>; private doubanSubjectLoadHandler: DoubanSubjectLoadHandler<T>;
private doubanListHandlers:DoubanListHandler[]; private doubanListHandlers: DoubanListHandler[];
constructor(plugin: DoubanPlugin, constructor(
doubanSubjectLoadHandler:DoubanSubjectLoadHandler<T>, plugin: DoubanPlugin,
doubanListHandlers:DoubanListHandler[]) { doubanSubjectLoadHandler: DoubanSubjectLoadHandler<T>,
doubanListHandlers: DoubanListHandler[],
) {
this.plugin = plugin; this.plugin = plugin;
this.doubanSubjectLoadHandler = doubanSubjectLoadHandler; this.doubanSubjectLoadHandler = doubanSubjectLoadHandler;
this.doubanListHandlers = doubanListHandlers; this.doubanListHandlers = doubanListHandlers;
@ -31,61 +73,387 @@ export abstract class DoubanAbstractSyncHandler<T extends DoubanSubject> implem
abstract getSyncType(): SyncType; abstract getSyncType(): SyncType;
async sync(syncConfig: SyncConfig, context: HandleContext): Promise<void>{ async sync(syncConfig: SyncConfig, context: HandleContext): Promise<void> {
return Promise.resolve() if (syncConfig.syncConditionType == SyncConditionType.CUSTOM_TIME) {
.then(() => this.getItems(syncConfig, context)) await this.syncByTimeLimit(syncConfig, context);
.then(items => this.removeExists(items , syncConfig, context)) } else if (syncConfig.syncConditionType == SyncConditionType.CUSTOM_ITEM) {
.then(items => this.handleItems(items, context)); await this.syncByCountLimit(syncConfig, context);
}else if (syncConfig.syncConditionType == SyncConditionType.ALL) {
await this.syncAll(syncConfig, context);
}else if (syncConfig.syncConditionType == SyncConditionType.LAST_THIRTY) {
await this.syncLastThirty(syncConfig, context);
}else {
log.warn(i18nHelper.getMessage("110083"));
}
} }
async getByTimeLimit(syncConfig: SyncConfig, context: HandleContext): Promise<SubjectListItem[]> {
private async getItems(syncConfig:SyncConfig, context:HandleContext):Promise<SubjectListItem[]> { let startDate = syncConfig.syncConditionDateFromValue
const supportHandlers:DoubanListHandler[] = this.doubanListHandlers.filter((h) => h.support(syncConfig)); ? new Date(syncConfig.syncConditionDateFromValue)
let items:SubjectListItem[] = []; : null;
for(const handler of supportHandlers) { let endDate = syncConfig.syncConditionDateToValue
? new Date(syncConfig.syncConditionDateToValue)
: null;
if (!startDate && !endDate) {
log.warn(i18nHelper.getMessage("110081"));
return;
}
const cacheList = new Map<number, SearchPageTypeOf<SubjectListItem>>();
let searchPage = await this.getItems(syncConfig, context);
if (!searchPage) {
return;
}
const total = searchPage.total;
const lastPage = total / PAGE_SIZE + 1;
if (lastPage == 1) {
return searchPage.list;
}
let leftPage = 1;
let startPage = 1;
let rightPage = lastPage;
let endPage = lastPage;
let currentPage = 1;
cacheList.set(currentPage, searchPage);
if (startDate != null) {
do {
if (!context.plugin.statusHolder.syncing()) { if (!context.plugin.statusHolder.syncing()) {
return []; break;
} }
const item = await handler.getAllPageList(context); let page = cacheList.get(currentPage);
if (item) { if (!page) {
items = items.concat(item); page = await this.getItems(syncConfig, context);
if (!page) {
break;
} }
cacheList.set(currentPage, page);
} }
return items; const pageItems = page.list;
const pageDateList = toDateList(pageItems);
if (pageDateList[pageDateList.length - 1] >= startDate) {
leftPage = currentPage;
endPage = currentPage;
currentPage = Math.ceil((leftPage + rightPage) / 2);
}else {
rightPage = currentPage;
endPage = currentPage;
currentPage = Math.floor((leftPage + rightPage) / 2);
} }
if (currentPage == leftPage || currentPage == rightPage) {
private async removeExists(items:SubjectListItem[], syncConfig: SyncConfig, context: HandleContext):Promise<SubjectListItem[]> { break;
}
} while (currentPage < lastPage);
}
leftPage = 1;
rightPage = lastPage;
currentPage = 1;
if (endDate != null) {
do {
if (!context.plugin.statusHolder.syncing()) { if (!context.plugin.statusHolder.syncing()) {
return []; break;
} }
return items; let page = cacheList.get(currentPage);
if (!page) {
page = await this.getItems(syncConfig, context);
if (!page) {
break;
} }
cacheList.set(currentPage, page);
}
const pageItems = page.list;
const pageDateList = toDateList(pageItems);
if (pageDateList[0] <= endDate) {
rightPage = currentPage;
startPage = currentPage;
currentPage = Math.ceil((leftPage + rightPage) / 2);
}else {
leftPage = currentPage;
startPage = currentPage;
currentPage = Math.floor((leftPage + rightPage) / 2);
}
if (currentPage == leftPage || currentPage == rightPage) {
break;
}
} while (currentPage < lastPage);
}
let needHandleItems:SubjectListItem[] = [];
for (let pageNum = startPage; pageNum <= endPage; pageNum++) {
if (!context.plugin.statusHolder.syncing()) {
break;
}
let page = cacheList.get(pageNum);
if (!page) {
page = await this.getItems(syncConfig, context);
if (!page) {
break;
}
cacheList.set(pageNum, page);
}
const pageItems = page.list;
needHandleItems = needHandleItems.concat(pageItems
.filter((item) => {
const itemDate = item.updateDate;
return (!startDate || itemDate >= startDate) && (!endDate || itemDate <= endDate);
}
));
}
return needHandleItems;
private async handleItems(items:SubjectListItem[], context:HandleContext):Promise<void> { }
async syncByTimeLimit(syncConfig: SyncConfig, context: HandleContext) {
const items = await this.getByTimeLimit(syncConfig, context);
if (!items || items.length == 0) { if (!items || items.length == 0) {
return ; return;
} }
const {syncStatus} = context.syncStatusHolder;
syncStatus.totalNum(items.length); let subjectListItems = await this.removeExists(
const needHandled:number = items.filter(item => syncStatus.shouldSync(item.id)).length; items,
syncConfig,
context,
);
const searchPage = new SearchPageTypeOf<SubjectListItem>(subjectListItems.length,
1,
subjectListItems.length,
null,subjectListItems);
await this.handleItems(searchPage, subjectListItems, context);
}
private async getItems(
syncConfig: SyncConfig,
context: HandleContext,
): Promise<SearchPageTypeOf<SubjectListItem>> {
const supportHandlers: DoubanListHandler[] =
this.doubanListHandlers.filter((h) => h.support(syncConfig));
const handler = supportHandlers[0];
if (!context.plugin.statusHolder.syncing()) {
return SearchPage.emptyWithNoType();
}
const item = await handler.getPageData(context);
if (!context.plugin.statusHolder.syncing()) {
return SearchPage.emptyWithNoType();
}
return item;
}
private async removeExists(
items: SubjectListItem[],
syncConfig: SyncConfig,
context: HandleContext,
): Promise<SubjectListItem[]> {
if (!context.plugin.statusHolder.syncing()) {
return [];
}
return items;
}
private async handleItems(
searchPage: SearchPage,
items: SubjectListItem[],
context: HandleContext,
): Promise<void> {
if (!items || items.length == 0) {
return;
}
const { syncStatus } = context.syncStatusHolder;
syncStatus.totalNum(searchPage.total);
const needHandled: number =
syncStatus.getTotal() - syncStatus.getHasHandle();
syncStatus.setNeedHandled(needHandled); syncStatus.setNeedHandled(needHandled);
for (const item of items) { for (const item of items) {
if (!context.plugin.statusHolder.syncing()) { if (!context.plugin.statusHolder.syncing()) {
return; return;
} }
try { try {
if(syncStatus.shouldSync(item.id)) { if (syncStatus.shouldSync(item.id)) {
let subject: DoubanSubject = await this.doubanSubjectLoadHandler.handle(item.id, context); let subject: DoubanSubject =
await sleepRange(BasicConst.CALL_DOUBAN_DELAY, BasicConst.CALL_DOUBAN_DELAY + BasicConst.CALL_DOUBAN_DELAY_RANGE); await this.doubanSubjectLoadHandler.handle(
}else { item.id,
context,
);
await sleepRange(
BasicConst.CALL_DOUBAN_DELAY,
BasicConst.CALL_DOUBAN_DELAY +
BasicConst.CALL_DOUBAN_DELAY_RANGE,
);
} else {
syncStatus.unHandle(item.id, item.title); syncStatus.unHandle(item.id, item.title);
} }
}catch (e) { } catch (e) {
log.notice(i18nHelper.getMessage('130120')) log.notice(i18nHelper.getMessage("130120"));
} }
} }
} }
private async syncByCountLimit(syncConfig: SyncConfig, context: HandleContext) {
const {syncConditionCountFromValue, syncConditionCountToValue} = syncConfig;
const startOffset = Math.floor((syncConditionCountFromValue - 1)/ PAGE_SIZE) * PAGE_SIZE;
context.syncOffset = startOffset;
//结束点是第几条
let endOffsetNumberForCustom = 0;
let needHandleTotalCustomItem = 0;
let isFirstStep = true;
let handleCount = 0;
do {
let searchPage = await this.getItems(syncConfig, context);
if (!context.plugin.statusHolder.syncing()) {
break;
}
const {list, total} = searchPage;
if (
!searchPage ||
!list ||
list.length == 0
) {
break;
}
if (syncConditionCountFromValue > total) {
context.syncStatusHolder.syncStatus.setMessage(i18nHelper.getMessage("130121", total));
break;
}
if (endOffsetNumberForCustom == 0) {
endOffsetNumberForCustom = Math.min(syncConditionCountToValue?syncConditionCountToValue:searchPage.total, searchPage.total);
needHandleTotalCustomItem = endOffsetNumberForCustom - syncConditionCountFromValue + 1;
}
let subjectListItems = [];
//在开始和结束同一页
if (Math.floor((syncConditionCountFromValue - 1) / PAGE_SIZE) == Math.floor((endOffsetNumberForCustom - 1) / PAGE_SIZE)) {
const startIndex = Math.floor((syncConditionCountFromValue - 1) % PAGE_SIZE);
const endIndex = Math.floor((endOffsetNumberForCustom - 1) % PAGE_SIZE);
subjectListItems = await this.removeExists(
list.slice(startIndex, endIndex + 1),
syncConfig,
context,
);
handleCount += (endIndex - startIndex + 1);
//第一页
} else if (isFirstStep) {
const startIndex = (syncConditionCountFromValue - 1) % PAGE_SIZE;
handleCount += (list.length - startIndex);
subjectListItems = await this.removeExists(
list.slice(startIndex),
syncConfig,
context,
);
isFirstStep = false;
}
//最后一页
else if (needHandleTotalCustomItem - handleCount <= PAGE_SIZE) {
const endIndex = needHandleTotalCustomItem - handleCount;
subjectListItems = await this.removeExists(
list.slice(0, endIndex),
syncConfig,
context,
);
handleCount += endIndex;
//中间页
} else {
subjectListItems = await this.removeExists(
list,
syncConfig,
context,
);
handleCount += PAGE_SIZE;
}
if (!subjectListItems || subjectListItems.length == 0) {
await sleepRange(
BasicConst.CALL_DOUBAN_DELAY,
BasicConst.CALL_DOUBAN_DELAY +
BasicConst.CALL_DOUBAN_DELAY_RANGE,
);
continue;
}
searchPage.total = needHandleTotalCustomItem;
//处理
await this.handleItems(searchPage, subjectListItems, context);
context.syncOffset = context.syncOffset + PAGE_SIZE;
await sleepRange(
BasicConst.CALL_DOUBAN_DELAY,
BasicConst.CALL_DOUBAN_DELAY +
BasicConst.CALL_DOUBAN_DELAY_RANGE,
);
} while (handleCount < needHandleTotalCustomItem);
}
private async syncAll(syncConfig: SyncConfig, context: HandleContext) {
//最多100000条
context.syncOffset = 0;
let handleCount = 0;
let totalForHandle = 0;
let isFirstStep = true;
do {
let searchPage = await this.getItems(syncConfig, context);
if (!context.plugin.statusHolder.syncing()) {
break;
}
const {list, total} = searchPage;
if (
!searchPage ||
!list ||
list.length == 0
) {
break;
}
if (isFirstStep) {
totalForHandle = total;
isFirstStep = false;
}
handleCount += list.length;
let subjectListItems = await this.removeExists(
list,
syncConfig,
context,
);
if (!subjectListItems || subjectListItems.length == 0) {
await sleepRange(
BasicConst.CALL_DOUBAN_DELAY,
BasicConst.CALL_DOUBAN_DELAY +
BasicConst.CALL_DOUBAN_DELAY_RANGE,
);
continue;
}
await this.handleItems(searchPage, subjectListItems, context);
context.syncOffset = context.syncOffset + PAGE_SIZE;
await sleepRange(
BasicConst.CALL_DOUBAN_DELAY,
BasicConst.CALL_DOUBAN_DELAY +
BasicConst.CALL_DOUBAN_DELAY_RANGE,
);
} while (handleCount <= totalForHandle);
}
private async syncLastThirty(syncConfig: SyncConfig, context: HandleContext) {
context.syncOffset = 0;
let searchPage = await this.getItems(syncConfig, context);
if (!context.plugin.statusHolder.syncing()) {
return;
}
const {list, total} = searchPage;
if (
!searchPage ||
!list ||
list.length == 0
) {
return;
}
let subjectListItems = await this.removeExists(
list,
syncConfig,
context,
);
if (!subjectListItems || subjectListItems.length == 0) {
return;
}
searchPage.total = Math.min(list.length, total);
await this.handleItems(searchPage, subjectListItems, context);
}
} }

@ -9,6 +9,7 @@ import { DoubanMusicSyncHandler } from "./DoubanMusicSyncHandler";
import { DoubanBookSyncHandler } from "./DoubanBookSyncHandler"; import { DoubanBookSyncHandler } from "./DoubanBookSyncHandler";
import {i18nHelper} from "../../../lang/helper"; import {i18nHelper} from "../../../lang/helper";
import {DoubanTeleplaySyncHandler} from "./DoubanTeleplaySyncHandler"; import {DoubanTeleplaySyncHandler} from "./DoubanTeleplaySyncHandler";
import {SyncConditionType} from "../../../constant/Constsant";
export default class SyncHandler { export default class SyncHandler {
private app: App; private app: App;
@ -18,6 +19,7 @@ export default class SyncHandler {
private syncHandlers: DoubanSyncHandler[]; private syncHandlers: DoubanSyncHandler[];
private defaultSyncHandler: DoubanSyncHandler; private defaultSyncHandler: DoubanSyncHandler;
constructor(app: App, plugin: DoubanPlugin, syncConfig: SyncConfig, context: HandleContext) { constructor(app: App, plugin: DoubanPlugin, syncConfig: SyncConfig, context: HandleContext) {
this.app = app; this.app = app;
this.plugin = plugin; this.plugin = plugin;
@ -38,6 +40,10 @@ export default class SyncHandler {
async sync() { async sync() {
if (this.syncConfig && this.syncConfig.syncType && this.syncConfig.scope) { if (this.syncConfig && this.syncConfig.syncType && this.syncConfig.scope) {
if(this.checkSyncConfig()) {
this.context.syncStatusHolder.syncStatus.setMessage(this.checkSyncConfig());
return;
}
let syncHandler = this.syncHandlers.find(handler => handler.support(this.syncConfig.syncType)); let syncHandler = this.syncHandlers.find(handler => handler.support(this.syncConfig.syncType));
if (syncHandler) { if (syncHandler) {
await syncHandler.sync(this.syncConfig, this.context); await syncHandler.sync(this.syncConfig, this.context);
@ -53,6 +59,25 @@ export default class SyncHandler {
const {syncStatus} = syncStatusHolder; const {syncStatus} = syncStatusHolder;
const {statusHandleMap} = syncStatus; const {statusHandleMap} = syncStatus;
const {syncResultMap} = syncStatus; const {syncResultMap} = syncStatus;
const {syncConfig} = this;
let extendCondition = '';
if (syncConfig.syncConditionType == SyncConditionType.CUSTOM_ITEM) {
extendCondition = `${syncConfig.syncConditionCountFromValue} - ${syncConfig.syncConditionCountToValue}`;
} else if (syncConfig.syncConditionType == SyncConditionType.CUSTOM_TIME) {
extendCondition = `${syncConfig.syncConditionDateFromValue.toISOString().substring(0, 10)} - ${syncConfig.syncConditionDateToValue.toISOString().substring(0, 10)}`;
}
const condition = `
1. ${i18nHelper.getMessage('110030')} \`${syncStatus.getTypeName()}\`
2. ${i18nHelper.getMessage('110032')} \`${syncStatus.getScopeName()}\`
3. ${i18nHelper.getMessage('110070')} \`${syncStatus.getSyncConditionName()}\`${extendCondition? (' => ' + extendCondition) : ''}
4. ${i18nHelper.getMessage('110039')} \`${syncConfig.incrementalUpdate?i18nHelper.getMessage('110055'):i18nHelper.getMessage('110056')}\`
5. ${i18nHelper.getMessage('110031')} \`${syncConfig.force?i18nHelper.getMessage('110055'):i18nHelper.getMessage('110056')}\`
`
let summary:string let summary:string
= `${i18nHelper.getMessage('110053', i18nHelper.getMessage('110050'), i18nHelper.getMessage('110051'), i18nHelper.getMessage('110052'))} = `${i18nHelper.getMessage('110053', i18nHelper.getMessage('110050'), i18nHelper.getMessage('110051'), i18nHelper.getMessage('110052'))}
|-----|----|----------------------------------| |-----|----|----------------------------------|
@ -79,8 +104,28 @@ export default class SyncHandler {
} }
} }
const result : string = i18nHelper.getMessage('110037', summary, details); const result : string = i18nHelper.getMessage('110037', condition, summary, details);
const resultFileName = `${i18nHelper.getMessage('110038')}_${moment(new Date()).format('YYYYMMDDHHmmss')}` const resultFileName = `${i18nHelper.getMessage('110038')}_${moment(new Date()).format('YYYYMMDDHHmmss')}`
await this.plugin.fileHandler.createNewNoteWithData(`${this.syncConfig.dataFilePath}/${resultFileName}`, result, true); await this.plugin.fileHandler.createNewNoteWithData(`${this.syncConfig.dataFilePath}/${resultFileName}`, result, true);
} }
private checkSyncConfig() {
const {syncConfig} = this;
switch (syncConfig.syncConditionType) {
case SyncConditionType.CUSTOM_ITEM:
if (syncConfig.syncConditionCountFromValue && syncConfig.syncConditionCountToValue) {
if (syncConfig.syncConditionCountFromValue > syncConfig.syncConditionCountToValue) {
return i18nHelper.getMessage('110044');
}
}
break;
case SyncConditionType.CUSTOM_TIME:
if (syncConfig.syncConditionDateToValue && syncConfig.syncConditionDateFromValue) {
if (syncConfig.syncConditionDateFromValue > syncConfig.syncConditionDateToValue) {
return i18nHelper.getMessage('110045');
}
}
}
}
} }

@ -1,88 +1,124 @@
import { request, RequestUrlParam} from "obsidian"; import { request, RequestUrlParam } from "obsidian";
import {i18nHelper} from 'src/org/wanxp/lang/helper'; import { i18nHelper } from "src/org/wanxp/lang/helper";
import {log} from "src/org/wanxp/utils/Logutil"; import { log } from "src/org/wanxp/utils/Logutil";
import {CheerioAPI, load} from "cheerio"; import { CheerioAPI, load } from "cheerio";
import HandleContext from "../../../data/model/HandleContext"; import HandleContext from "../../../data/model/HandleContext";
import {doubanSubjectSyncListUrl} from "../../../../constant/Douban"; import { doubanSubjectSyncListUrl } from "../../../../constant/Douban";
import {BasicConst, PAGE_SIZE, SyncType, SyncTypeUrlDomain} from "../../../../constant/Constsant"; import {
import {SubjectListItem} from "../../../data/model/SubjectListItem"; BasicConst,
import {DoubanListHandler} from "./DoubanListHandler"; PAGE_SIZE,
import {SyncConfig} from "../../model/SyncConfig"; SyncType,
import { sleepRange} from "../../../../utils/TimeUtil"; SyncTypeUrlDomain,
import {ALL} from "../../../../constant/DoubanUserState"; } from "../../../../constant/Constsant";
import { SubjectListItem } from "../../../data/model/SubjectListItem";
import { DoubanListHandler } from "./DoubanListHandler";
import { SyncConfig } from "../../model/SyncConfig";
import { sleepRange } from "../../../../utils/TimeUtil";
import { ALL } from "../../../../constant/DoubanUserState";
import HttpUtil from "../../../../utils/HttpUtil"; import HttpUtil from "../../../../utils/HttpUtil";
import {DoubanHttpUtil} from "../../../../utils/DoubanHttpUtil"; import { DoubanHttpUtil } from "../../../../utils/DoubanHttpUtil";
import { SearchPage } from "../../../data/model/SearchPage";
import {SearchPageTypeOf} from "../../../data/model/SearchPageTypeOf";
export default abstract class DoubanAbstractListHandler implements DoubanListHandler{ export default abstract class DoubanAbstractListHandler
implements DoubanListHandler
{
async getPageData(context: HandleContext): Promise<SearchPage> {
let all: SubjectListItem[] = [];
let pages: SearchPage = SearchPage.emptyWithNoType();
async getAllPageList(context: HandleContext):Promise<SubjectListItem[]>{ const url: string = this.getUrl(context, context.syncOffset);
let all:SubjectListItem[] = [];
let pages:SubjectListItem[] = [];
let start = 0;
do {
await sleepRange(BasicConst.CALL_DOUBAN_DELAY,
BasicConst.CALL_DOUBAN_DELAY + BasicConst.CALL_DOUBAN_DELAY_RANGE);
const url:string = this.getUrl(context, start);
if (!context.plugin.statusHolder.syncing()) { if (!context.plugin.statusHolder.syncing()) {
return []; return SearchPage.emptyWithNoType();
} }
pages = await this.getPageList(url, context); let subjectListItemSearchPageTypeOf = await this.getPageList(url, context);
if (pages) { if (subjectListItemSearchPageTypeOf) {
all = all.concat(pages); context.plugin.statusHolder.syncStatus.setAllTotal(subjectListItemSearchPageTypeOf.total)
} }
start = start + PAGE_SIZE; return subjectListItemSearchPageTypeOf;
} while (pages && pages.length > 0)
return all;
} }
async delay(ms: number) { async delay(ms: number) {}
private getUrl(context: HandleContext, start: number) {
return doubanSubjectSyncListUrl(
this.getSyncTypeDomain(),
context.userComponent.getUserId(),
this.getDoType(),
start,
);
} }
private getUrl(context: HandleContext, start:number) { abstract getDoType(): string;
return doubanSubjectSyncListUrl(this.getSyncTypeDomain(), context.userComponent.getUserId(), this.getDoType(), start);
}
abstract getDoType():string; abstract getSyncType(): SyncType;
abstract getSyncType():SyncType; getSyncTypeDomain(): string {
getSyncTypeDomain():string {
return SyncTypeUrlDomain.get(this.getSyncType()); return SyncTypeUrlDomain.get(this.getSyncType());
} }
async getPageList(url: string, context: HandleContext):Promise<SubjectListItem[]> { async getPageList(
return DoubanHttpUtil.httpRequestGet(url, context.plugin.settingsManager.getHeaders(), context.plugin.settingsManager) url: string,
context: HandleContext,
): Promise<SearchPageTypeOf<SubjectListItem>> {
return DoubanHttpUtil.httpRequestGet(
url,
context.plugin.settingsManager.getHeaders(),
context.plugin.settingsManager,
)
.then(load) .then(load)
.then(data => this.parseSubjectFromHtml(data, context)) .then((data) => this.parseSubjectFromHtml(data, context))
.catch(e => log .catch((e) =>
.error( log.error(
i18nHelper.getMessage('130101') i18nHelper
.replace('{0}', e.toString()) .getMessage("130101")
, e)); .replace("{0}", e.toString()),
; e,
),
);
} }
parseSubjectFromHtml(
dataHtml: CheerioAPI,
context: HandleContext,
): SearchPageTypeOf<SubjectListItem> {
parseSubjectFromHtml(dataHtml: CheerioAPI, context: HandleContext):SubjectListItem[] { const items = dataHtml(".item-show")
return dataHtml('.item-show')
.get() .get()
.map((i: any) => { .map((i: any) => {
const item = dataHtml(i); const item = dataHtml(i);
const linkValue:string = item.find('div.title > a').attr('href'); const linkValue: string = item
const titleValue:string = item.find('div.title > a').text().trim(); .find("div.title > a")
.attr("href");
const titleValue: string = item
.find("div.title > a")
.text()
.trim();
const updateDateStr: string = item.find("div.date").text().trim();
let updateDate = null;
try {
updateDate = new Date(updateDateStr);
}catch (e) {
console.error(e);
log.info("parse date error:" + titleValue);
}
let idPattern = /(\d){5,10}/g; let idPattern = /(\d){5,10}/g;
let ececResult = idPattern.exec(linkValue); let ececResult = idPattern.exec(linkValue);
return ececResult?{id: ececResult[0], url: linkValue, title: titleValue}:null; return !ececResult ? null : {id: ececResult[0], url: linkValue, title: titleValue, updateDate: updateDate};
// return linkValue; // return linkValue;
}) });
const subjectNumText = dataHtml(".subject-num").text().trim();
const totalNumMatch = subjectNumText.match(/\/\s*(\d+)/);
const totalNum = totalNumMatch ? parseInt(totalNumMatch[1], 10) : 0;
return new SearchPage(
totalNum,
Math.floor(context.syncOffset / PAGE_SIZE) + 1,
PAGE_SIZE,
null,
items,
);
} }
support(config: SyncConfig): boolean { support(config: SyncConfig): boolean {
return this.getDoType() == config.scope || ALL == config.scope; return this.getDoType() == config.scope || ALL == config.scope;
} }
} }

@ -1,10 +1,10 @@
import HandleContext from "../../../data/model/HandleContext"; import HandleContext from "../../../data/model/HandleContext";
import {SubjectListItem} from "../../../data/model/SubjectListItem";
import {SyncConfig} from "../../model/SyncConfig"; import {SyncConfig} from "../../model/SyncConfig";
import {SearchPage} from "../../../data/model/SearchPage";
export interface DoubanListHandler { export interface DoubanListHandler {
getAllPageList(context: HandleContext):Promise<SubjectListItem[]>; getPageData(context: HandleContext):Promise<SearchPage>;
support(config:SyncConfig):boolean; support(config:SyncConfig):boolean;
} }

@ -1,10 +1,10 @@
export interface SyncConfig { export interface SyncConfig {
syncType: string, syncType: string,
syncConditionType: string, syncConditionType: string,
syncConditionCountFromValue: string, syncConditionCountFromValue: number,
syncConditionCountToValue: string, syncConditionCountToValue: number,
syncConditionDateFromValue: string, syncConditionDateFromValue: Date,
syncConditionDateToValue: string, syncConditionDateToValue: Date,
scope: string, scope: string,
force: boolean, force: boolean,
dataFilePath: string; dataFilePath: string;

@ -1,7 +1,7 @@
import {SyncItemStatus} from "../../../constant/Constsant"; import { SyncItemStatus } from "../../../constant/Constsant";
export interface SyncItemResult { export interface SyncItemResult {
id:string, id: string;
title:string, title: string;
status:SyncItemStatus, status: SyncItemStatus;
} }

@ -1,7 +1,18 @@
import {SyncConfig} from "./SyncConfig"; import {SyncConfig} from "./SyncConfig";
import {SyncItemResult} from "./SyncItemResult"; import {SyncItemResult} from "./SyncItemResult";
import {BasicConst, SyncItemStatus} from "../../../constant/Constsant"; import {
BasicConst,
SupportType,
SyncConditionTypeRecords,
SyncItemStatus,
SyncTypeRecords
} from "../../../constant/Constsant";
import {SyncHandledData} from "../../setting/model/SyncHandledData"; import {SyncHandledData} from "../../setting/model/SyncHandledData";
import {
DoubanSubjectState,
DoubanSubjectStateRecords,
DoubanSubjectStateRecords_SYNC
} from "../../../constant/DoubanUserState";
export default class SyncStatusHolder { export default class SyncStatusHolder {
@ -12,15 +23,20 @@ export default class SyncStatusHolder {
[SyncItemStatus.replace, 0], [SyncItemStatus.replace, 0],
[SyncItemStatus.create, 0], [SyncItemStatus.create, 0],
[SyncItemStatus.fail, 0], [SyncItemStatus.fail, 0],
[SyncItemStatus.failByDiffType, 0],
[SyncItemStatus.unHandle, 0], [SyncItemStatus.unHandle, 0],
]); ]);
private key: string; private key: string;
//不管处不处理的总数比如会包含已经存在的或者条件没覆盖的部分比如过滤条件选择了1-2条但总共其实有10条
private allTotal: number;
public syncConfig: SyncConfig; public syncConfig: SyncConfig;
//处理的总数
private total:number; private total:number;
private handle:number; private handle:number;
private needHandled:number; private needHandled:number;
private message:string = '';
constructor(syncConfig: SyncConfig) { constructor(syncConfig: SyncConfig) {
this.syncConfig = syncConfig; this.syncConfig = syncConfig;
@ -46,6 +62,16 @@ export default class SyncStatusHolder {
return this.handle; return this.handle;
} }
getAllTotal():number {
return this.allTotal;
}
setAllTotal(allTotal:number) {
this.allTotal = allTotal;
}
/** /**
* *
*/ */
@ -86,6 +112,10 @@ export default class SyncStatusHolder {
this.updateResult(id, title, SyncItemStatus.fail); this.updateResult(id, title, SyncItemStatus.fail);
} }
public failByDiffType(id:string, title:string) {
this.updateResult(id, title, SyncItemStatus.failByDiffType);
}
private updateResult(id:string, title:string, status:SyncItemStatus) { private updateResult(id:string, title:string, status:SyncItemStatus) {
this.syncResultMap.set(id, {id: id,title:title,status:status}); this.syncResultMap.set(id, {id: id,title:title,status:status});
this.statusHandleMap.set(status, this.statusHandleMap.get(status) + 1); this.statusHandleMap.set(status, this.statusHandleMap.get(status) + 1);
@ -161,4 +191,26 @@ export default class SyncStatusHolder {
return false; return false;
} }
public setMessage(s: string) {
this.message = s;
}
public getMessage() {
return this.message;
}
public getScopeName():string {
//@ts-ignore
return DoubanSubjectStateRecords_SYNC[this.syncConfig.syncType][this.syncConfig.scope];
}
public getTypeName():string {
return SyncTypeRecords[this.syncConfig.syncType];
}
public getSyncConditionName() {
return SyncConditionTypeRecords[this.syncConfig.syncConditionType];
}
} }

@ -11,19 +11,34 @@ const locale = localeMap[lang || 'en'];
export default class I18nHelper { export default class I18nHelper {
public getMessage(str: keyof typeof en, ...params: any[]): string { public getMessage(str: keyof typeof en | string, ...params: any[]): string {
if (!locale) { if (!locale) {
console.error('Error: obsidian douban locale not found', lang); console.error('Error: obsidian douban locale not found', lang);
} }
// @ts-ignore
let val:string = (locale && locale[str]) || en[str]; let val:string = (locale && locale[str]) || en[str];
if (params) { if (params) {
for (let i:number = 0;i < params.length;i++) { for (let i:number = 0;i < params.length;i++) {
val = val.replaceAll(`{${i}}`, params[i]) val = this.replaceAll(i, val, params[i])
} }
} }
return val; return val;
} }
private replaceAll(index: number, message: string, replace: string): string {
const placeholderRegex = new RegExp(`\\{${index}:([^}]+)\\}`);
const match = message.match(placeholderRegex);
if (!match) {
return message.replaceAll(`{${index}}`, replace);
}
const defaultValue = match ? match[1] : '';
// If replace is undefined or null, use the default value
const replacement = (replace === undefined || replace === null) ? defaultValue : replace;
// Replace the specific placeholder with the replacement value
return message.replace(placeholderRegex, replacement);
}
} }
export const i18nHelper: I18nHelper = new I18nHelper(); export const i18nHelper: I18nHelper = new I18nHelper();

@ -23,12 +23,15 @@ export default {
'110035': `FileName: (Tip:Support Variables And Path)`, '110035': `FileName: (Tip:Support Variables And Path)`,
'110036': `Complete`, '110036': `Complete`,
'110037': ` '110037': `
### Summary ### Condition
{0} {0}
### Details ### Summary
{1} {1}
### Details
{2}
--- ---
PS: This file could be delete if you want to. PS: This file could be delete if you want to.
`, `,
@ -38,16 +41,20 @@ PS: This file could be delete if you want to.
'110041': `IncrementalSync`, '110041': `IncrementalSync`,
'110042': `EstimateTime`, '110042': `EstimateTime`,
'110043': `Loading Menu`, '110043': `Loading Menu`,
'110044': `Custom Count Input End Must Bigger Than Start`,
'110045': `Custom Time Input End Must Bigger Than Start`,
'110050': `Type`, '110050': `Type`,
'110051': `Number`, '110051': `Number`,
'110052': `Description`, '110052': `Description`,
'110152': `Confirm`, '110152': `Confirm`,
'110055': `Enable`,
'110056': `Disable`,
'110070': `Sync Scope:`, '110070': `Sync Scope:`,
'110071': `All`, '110071': `All`,
'110072': `Recent Changes`, '110072': `Recent Changes`,
'110075': `Last 10 Items`, '110075': `Last 30 Items`,
'110074': `Custom Time`, '110074': `Custom Time`,
'110076': `Custom Count`, '110076': `Custom Count`,
'110077': `From`, '110077': `From`,
@ -55,12 +62,21 @@ PS: This file could be delete if you want to.
'110079': `To`, '110079': `To`,
'110073': `Items`, '110073': `Items`,
'110080': `Number`, '110080': `Number`,
'112080': `Custom Count Input not a number`,
'110081': `Custom Time must input both start and end or only one`,
'110082': `Custom Time must input format: YYYY-MM-DD`,
'110083': `Not Support`,
'110090': `The total number of {0} for status {1} is {2}, and the number that needs to be synchronized is {3}`,
'110091': `Error`,
'110095': `The smaller the number, the closer to now. For example, the latest update on Douban is the first one, and the earliest update is the last one.`,
'110096': `This is the update time of our entry on Douban`,
'exists':`[exists]`, 'exists':`[exists]`,
'unHandle':`[unHandle]`, 'unHandle':`[unHandle]`,
'replace':`[replace]`, 'replace':`[replace]`,
'create':`[create]`, 'create':`[create]`,
'fail':`[fail]`, 'fail':`[fail]`,
'failByDiffType': `[fail by diff type]`,
'syncall':`[summary]`, 'syncall':`[summary]`,
'notsync':`[notsync]`, 'notsync':`[notsync]`,
@ -69,6 +85,7 @@ PS: This file could be delete if you want to.
'replace_desc': `replace`, 'replace_desc': `replace`,
'create_desc': `create`, 'create_desc': `create`,
'fail_desc': `fail`, 'fail_desc': `fail`,
'failByDiffType_desc': `fail because of different type`,
'notsync_desc': `notsync`, 'notsync_desc': `notsync`,
'syncall_desc': `syncall`, 'syncall_desc': `syncall`,
@ -309,6 +326,7 @@ PS: This file could be delete if you want to.
'130107': `Can not find array setting for {1} in {0} , Please add it in array settings`, '130107': `Can not find array setting for {1} in {0} , Please add it in array settings`,
'130108': `Redirect times too much, please check your network or proxy`, '130108': `Redirect times too much, please check your network or proxy`,
'130120': `An error occurred during Sync, but Sync will continue. Error item is {}`, '130120': `An error occurred during Sync, but Sync will continue. Error item is {}`,
'130121': `An error occurred during Sync, Begin number is bigger than total number {0}, will not sync this item`,
'140201': `[OB-Douban]: searching '{0}'...`, '140201': `[OB-Douban]: searching '{0}'...`,
'140202': `[OB-Douban]: result {0} rows`, '140202': `[OB-Douban]: result {0} rows`,

@ -28,22 +28,38 @@ export default {
'110070': `同步范围:`, '110070': `同步范围:`,
'110071': `全部`, '110071': `全部`,
'110072': `最新变动`, '110072': `最新变动`,
'110075': `最近10条`, '110075': `最近30条`,
'110074': `自定义时间`, '110074': `自定义时间`,
'110076': `自定义条数`, '110076': `自定义条数`,
'110077': ``, '110077': ``,
'110078': ``, '110078': ``,
'110079': ``, '110079': ``,
'110073': ``, '110073': ``,
'110095': `数字越小代表越接近现在,比如在豆瓣上我们最新更新一条内容为第一条,反之最早更新的就是最后一条了`,
'110096': `这是我们在豆瓣上条目的更新时间`,
'110080': `数字`, '110080': `数字`,
'112080': `自定义的数量输入不是个数字`,
'110081': `开始时间和结束时间必须填写一个或都填写`,
'110082': `时间录入格式必须是YYYY-MM-DD`,
'110083': `不支持当前同步条件`,
'110090': `[{0:未知}]状态为[{1:未知}]的总数为{2:未知},当前需要同步数为{3::未知}`,
'110091': `异常:`,
'110092': `情况:`,
'110037': ` '110037': `
### ###
{0} {0}
###
{1}
### ###
{1} {1}
@ -52,20 +68,26 @@ export default {
`, `,
'110038': `豆瓣同步结果`, '110038': `豆瓣同步结果`,
'110039': `增量同步:`, '110039': `增量同步:`,
'110040': `仅同步上面'笔记存放位置'目录中最近新增/同步失败/未同步的部分,不同步已同步的内容.若关闭则会全量同步.增量同步会更快,适合之前同步中途失败停止或最近有豆瓣新增了内容,全量同步适合修改了模板或修改了存放路径情况下进行.`, '110040': `仅同步最近新增/同步失败/未同步的部分,增量适合之前同步中途失败停止或最近有豆瓣新增了内容,全量同步适合修改了模板或修改了存放路径情况下进行.`,
'110041': `使用增量`, '110041': `使用增量`,
'110042': `预计处理时间`, '110042': `预计处理时间`,
'110043': `正在获取需要同步的列表, 请稍后`, '110043': `正在获取需要同步的列表, 请稍后`,
'110044': `自定义条数的录入结束数量不能比开始数量小`,
'110045': `自定义时间的录入结束时间不能比开始时间小`,
'110050': `类型`, '110050': `类型`,
'110051': `数量`, '110051': `数量`,
'110052': `说明`, '110052': `说明`,
'110055': `开启`,
'110056': `关闭`,
'unHandle':`[已忽略]`, 'unHandle':`[已忽略]`,
'exists': `[未替换]`, 'exists': `[未替换]`,
'replace': `[已替换]`, 'replace': `[已替换]`,
'create': `[已创建]`, 'create': `[已创建]`,
'fail': `[已失败]`, 'fail': `[已失败]`,
'failByDiffType': `[类型不匹配]`,
'syncall': `[总数]`, 'syncall': `[总数]`,
'notsync': `[未进行]`, 'notsync': `[未进行]`,
@ -74,6 +96,7 @@ export default {
'replace_desc': `已经存在的同名文档,这次已被替换`, 'replace_desc': `已经存在的同名文档,这次已被替换`,
'create_desc': `之前不存在的文档,这次直接新增`, 'create_desc': `之前不存在的文档,这次直接新增`,
'fail_desc': `处理过程钟出现错误,未能成功`, 'fail_desc': `处理过程钟出现错误,未能成功`,
'failByDiffType_desc': `失败,类型不匹配,比如想要同步的是电影,但是实际获取到的是电视剧`,
'notsync_desc': `因异常中断或提前终止导致还未处理`, 'notsync_desc': `因异常中断或提前终止导致还未处理`,
'syncall_desc': `您此次同步条件在豆瓣中的条目总数`, 'syncall_desc': `您此次同步条件在豆瓣中的条目总数`,
@ -308,6 +331,7 @@ export default {
'130106': `请尝试在Douban插件中登录后操作. 若还是无效果则尝试于12小时或24小时后再重试或重置你的网络(如重新拨号或更换网络) `, '130106': `请尝试在Douban插件中登录后操作. 若还是无效果则尝试于12小时或24小时后再重试或重置你的网络(如重新拨号或更换网络) `,
'130107': `参数{0}中指定的数组输出类型{1}不存在,请前往配置进行设置`, '130107': `参数{0}中指定的数组输出类型{1}不存在,请前往配置进行设置`,
'130120': `同步时发生错误,但同步将会继续。错误项目是 {}。`, '130120': `同步时发生错误,但同步将会继续。错误项目是 {}。`,
'130121': `总数只有{0}, 选择的开始比总数还要大,将不会同步。`,
'140201': `[OB-Douban]: 开始搜索'{0}'...`, '140201': `[OB-Douban]: 开始搜索'{0}'...`,
'140202': `[OB-Douban]: 搜索条数{0}条`, '140202': `[OB-Douban]: 搜索条数{0}条`,

@ -57,7 +57,12 @@ export default class DoubanPlugin extends Plugin {
} }
if (context.syncActive && extract.guessType && extract.guessType != extract.type) { if (context.syncActive && extract.guessType && extract.guessType != extract.type) {
extract.handledStatus = SubjectHandledStatus.syncTypeDiffAbort; extract.handledStatus = SubjectHandledStatus.syncTypeDiffAbort;
if (Action.Sync == context.action) {
this.showStatus(i18nHelper.getMessage('140207', syncStatus.getHasHandle(), syncStatus.getTotal(), extract.title));
syncStatus.failByDiffType(extract.id, extract.title);
}else {
console.log(i18nHelper.getMessage('140102', extract.type, extract.title, extract.guessType)); console.log(i18nHelper.getMessage('140102', extract.type, extract.title, extract.guessType));
}
return; return;
} }
if (Action.Sync == context.action) { if (Action.Sync == context.action) {

@ -1,3 +1,15 @@
/* 获取主题颜色并定义新的颜色 */
:root {
--obsidian-primary-color: var(--text-normal);
--obsidian-secondary-color: var(--background-primary);
/* 定义新的颜色,稍微调整亮度或饱和度 */
--plugin-primary-color: hsl(var(--obsidian-primary-color-h), var(--obsidian-primary-color-s), calc(var(--obsidian-primary-color-l) + 10%));
--plugin-secondary-color: hsl(var(--obsidian-secondary-color-h), var(--obsidian-secondary-color-s), calc(var(--obsidian-secondary-color-l) - 10%));
}
.obsidian_douban_settings_area { .obsidian_douban_settings_area {
margin-left: 5px; margin-left: 5px;
margin-right: 5px; margin-right: 5px;
@ -140,17 +152,17 @@
} }
.obsidian_douban_settings_tab_header.active { .obsidian_douban_settings_tab_header.active {
background-color: #282828; background-color: var(--plugin-primary-color);
font-weight: bold; font-weight: bold;
border: 1px solid #323232; border: 1px solid var(--plugin-secondary-color);
border-radius: 8px 8px 0 0; /* Rounded corners for the top */ border-radius: 8px 8px 0 0; /* Rounded corners for the top */
} }
.obsidian_douban_settings_tab_content.active { .obsidian_douban_settings_tab_content.active {
background-color: #282828; background-color: var(--plugin-primary-color);
display: block; display: block;
padding: 10px; padding: 10px;
border: 1px solid #323232; border: 1px solid var(--plugin-secondary-color);
border-radius: 0 8px 8px 8px; /* Rounded corners for the bottom */ border-radius: 0 8px 8px 8px; /* Rounded corners for the bottom */
} }