From 0fb3be63f4c163f39c54f4f5f82b370a34f46c9b Mon Sep 17 00:00:00 2001 From: hughwan <977741432@qq.com> Date: Tue, 23 Aug 2022 18:23:59 +0800 Subject: [PATCH] add douban broadcast --- main.ts | 26 +- src/douban/Douban.ts | 124 + src/douban/DoubanSettingTab.ts | 291 ++ src/douban/ResponseHandle.ts | 13 + .../handler/DoubanBroadcastAbstractHandler.ts | 12 + .../handler/DoubanBroadcastMovieHandler.ts | 14 + .../handler/DoubanPageBroadcastTransformer.ts | 31 + .../handler/DoubanPageBroadcatLoadHandler.ts | 79 + .../model/DoubanBroadcastMoveSubject.ts | 4 + .../broadcast/model/DoubanBroadcastSubject.ts | 12 + .../model/DoubanPageBroadcastSubject.ts | 9 + .../data/handler/DoubanAbstractLoadHandler.ts | 93 + .../data/handler/DoubanBookLoadHandler.ts | 116 + .../data/handler/DoubanMovieLoadHandler.ts | 77 + .../data/handler/DoubanMusicLoadHandler.ts | 102 + .../data/handler/DoubanNoteLoadHandler.ts | 68 + .../data/handler/DoubanOtherLoadHandler.ts | 29 + .../handler/DoubanSearchChooseItemHandler.ts | 69 + .../data/handler/DoubanSubjectLoadHandler.ts | 14 + .../data/handler/DoubanTeleplayLoadHandler.ts | 79 + src/douban/data/model/DoubanBookSubject.ts | 22 + src/douban/data/model/DoubanMovieSubject.ts | 15 + src/douban/data/model/DoubanMusicSubject.ts | 16 + src/douban/data/model/DoubanNoteSubject.ts | 11 + .../data/model/DoubanSearchResultSubject.ts | 6 + src/douban/data/model/DoubanSubject.ts | 7 + .../data/model/DoubanTeleplaySubject.ts | 14 + .../search/DoubanSearchFuzzySuggestModal.ts | 53 + src/douban/data/search/DoubanSearchModal.ts | 65 + src/douban/data/search/Search.ts | 24 + src/douban/data/search/SearchParser.ts | 28 + src/lang/helper.ts | 23 + src/lang/locale/en.ts | 79 + src/lang/locale/zh-cn.ts | 84 + src/typings/tiny-network.d.ts | 6 + src/utils/HttpUtil.ts | 34 + src/utils/Logutil.ts | 35 + src/utils/SchemaOrg.ts | 25 + test/broadcast_abstract.txt | 118 + test/broadcast_movie.txt | 111 + test/broadcast_transformer.txt | 3677 +++++++++++++++++ versions.json | 4 +- 42 files changed, 5708 insertions(+), 11 deletions(-) create mode 100644 src/douban/Douban.ts create mode 100644 src/douban/DoubanSettingTab.ts create mode 100644 src/douban/ResponseHandle.ts create mode 100644 src/douban/broadcast/handler/DoubanBroadcastAbstractHandler.ts create mode 100644 src/douban/broadcast/handler/DoubanBroadcastMovieHandler.ts create mode 100644 src/douban/broadcast/handler/DoubanPageBroadcastTransformer.ts create mode 100644 src/douban/broadcast/handler/DoubanPageBroadcatLoadHandler.ts create mode 100644 src/douban/broadcast/model/DoubanBroadcastMoveSubject.ts create mode 100644 src/douban/broadcast/model/DoubanBroadcastSubject.ts create mode 100644 src/douban/broadcast/model/DoubanPageBroadcastSubject.ts create mode 100644 src/douban/data/handler/DoubanAbstractLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanBookLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanMovieLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanMusicLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanNoteLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanOtherLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanSearchChooseItemHandler.ts create mode 100644 src/douban/data/handler/DoubanSubjectLoadHandler.ts create mode 100644 src/douban/data/handler/DoubanTeleplayLoadHandler.ts create mode 100644 src/douban/data/model/DoubanBookSubject.ts create mode 100644 src/douban/data/model/DoubanMovieSubject.ts create mode 100644 src/douban/data/model/DoubanMusicSubject.ts create mode 100644 src/douban/data/model/DoubanNoteSubject.ts create mode 100644 src/douban/data/model/DoubanSearchResultSubject.ts create mode 100644 src/douban/data/model/DoubanSubject.ts create mode 100644 src/douban/data/model/DoubanTeleplaySubject.ts create mode 100644 src/douban/data/search/DoubanSearchFuzzySuggestModal.ts create mode 100644 src/douban/data/search/DoubanSearchModal.ts create mode 100644 src/douban/data/search/Search.ts create mode 100644 src/douban/data/search/SearchParser.ts create mode 100644 src/lang/helper.ts create mode 100644 src/lang/locale/en.ts create mode 100644 src/lang/locale/zh-cn.ts create mode 100644 src/typings/tiny-network.d.ts create mode 100644 src/utils/HttpUtil.ts create mode 100644 src/utils/Logutil.ts create mode 100644 src/utils/SchemaOrg.ts create mode 100644 test/broadcast_abstract.txt create mode 100644 test/broadcast_movie.txt create mode 100644 test/broadcast_transformer.txt diff --git a/main.ts b/main.ts index 0126de0..14e4f87 100644 --- a/main.ts +++ b/main.ts @@ -1,15 +1,14 @@ -import { DEFAULT_SETTINGS, DoubanPluginSettings } from "./douban/Douban"; +import { DEFAULT_SETTINGS, DoubanPluginSettings } from "./src/douban/Douban"; import { Editor, Plugin } from "obsidian"; -import { DoubanFuzzySuggester } from "douban/search/DoubanSearchFuzzySuggestModal"; -import DoubanMovieSubject from "douban/model/DoubanMovieSubject"; -import { DoubanSearchChooseItemHandler } from "douban/handler/DoubanSearchChooseItemHandler"; -import { DoubanSearchModal } from "douban/search/DoubanSearchModal"; -import { DoubanSettingTab } from "douban/DoubanSettingTab"; -import DoubanSubject from "douban/model/DoubanSubject"; -import Searcher from "douban/search/Search"; -import { i18nHelper } from './lang/helper'; -import { log } from "utils/Logutil"; +import { DoubanFuzzySuggester } from "src/douban/data/search/DoubanSearchFuzzySuggestModal"; +import { DoubanSearchChooseItemHandler } from "src/douban/data/handler/DoubanSearchChooseItemHandler"; +import { DoubanSearchModal } from "src/douban/data/search/DoubanSearchModal"; +import { DoubanSettingTab } from "src/douban/DoubanSettingTab"; +import DoubanSubject from "src/douban/data/model/DoubanSubject"; +import Searcher from "src/douban/data/search/Search"; +import { i18nHelper } from './src/lang/helper'; +import { log } from "src/utils/Logutil"; export default class DoubanPlugin extends Plugin { public settings: DoubanPluginSettings; @@ -66,6 +65,13 @@ export default class DoubanPlugin extends Plugin { editorCallback: (editor: Editor) => this.geDoubanTextForSearchTerm(editor), }); + + this.addCommand({ + id: "sync-douban-broadcast-by-user-id", + name: i18nHelper.getMessage("sync douban broadcast by douban user id"), + editorCallback: (editor: Editor) => + this.geDoubanTextForSearchTerm(editor), + }); this.addSettingTab(new DoubanSettingTab(this.app, this)); } diff --git a/src/douban/Douban.ts b/src/douban/Douban.ts new file mode 100644 index 0000000..211a6cf --- /dev/null +++ b/src/douban/Douban.ts @@ -0,0 +1,124 @@ +import { i18nHelper } from "src/lang/helper"; +import { type } from "os"; + +export interface DoubanPluginSettings { + movieTemplate:string, + bookTemplate:string, + musicTemplate:string, + noteTemplate:string + dateFormat:string, + dateTimeFormat:string, + searchUrl:string, + arraySpilt:string, + searchHeaders?:string, + personNameMode:PersonNameMode, +} + +export enum PersonNameMode { + CH_NAME = "CH", + EN_NAME = "EN", + CH_EN_NAME = "CH_EN", +} + + +export const doubanHeadrs = { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36", +}; + +export const DEFAULT_SETTINGS:DoubanPluginSettings = { + movieTemplate: +`--- +doubanId: {{id}} +title: {{title}} +originalTitle: {{originalTitle}} +type: {{type}} +score: {{score}} +genre: {{genre}} +datePublished: {{datePublished}} +director: {{director}} +actor: {{actor}} +author: {{author}} +url: {{url}} +desc: {{desc}} +--- + +![image]({{image}}) +`, + bookTemplate: +`--- +doubanId: {{id}} +title: {{title}} +subTitle: {{subTitle}} +originalTitle: {{originalTitle}} +type: {{type}} +author: {{author}} +score: {{score}} +datePublished: {{datePublished}} +translator: {{translator}} +publish: {{publish}} +isbn: {{isbn}} +url: {{url}} +totalPage: {{totalPage}} +price: {{price}} +tags: Book +desc: {{desc}} +--- + +![image|150]({{image}}) +`, + musicTemplate: +`--- +doubanId: {{id}} +title: {{title}} +type: {{type}} +actor: {{actor}} +score: {{score}} +genre: {{genre}} +medium: {{medium}} +albumType: {{albumType}} +datePublished: {{datePublished}} +publish: {{publish}} +barcode: {{barcode}} +url: {{url}} +numberOfRecords: {{numberOfRecords}} +tags: Music +desc: {{desc}} +--- + +![image|150]({{image}}) +`, +noteTemplate: +`--- +doubanId: {{id}} +title: {{title}} +type: {{type}} +author: [{{author}}]({{authorUrl}}) +timePublished: {{timePublished}} +url: {{url}} +tags: Article +desc: {{desc}} +--- + +- content +{{content}} +`, +// totalWord: {{totalWord}} + + searchUrl: 'https://www.douban.com/search?q=', + searchHeaders: JSON.stringify(doubanHeadrs), + dateFormat: "yyyy-MM-DD", + dateTimeFormat: "yyyy-MM-DD HH:mm:ss", + arraySpilt: ", ", + personNameMode: PersonNameMode.CH_NAME + +} + +export const personNameModeRecords: {[key in PersonNameMode]: string} = { + [PersonNameMode.CH_NAME]: i18nHelper.getMessage("Chinese Name"), + [PersonNameMode.EN_NAME]: i18nHelper.getMessage("English Name"), + [PersonNameMode.CH_EN_NAME]: i18nHelper.getMessage("Chinese And English Name"), + } + + diff --git a/src/douban/DoubanSettingTab.ts b/src/douban/DoubanSettingTab.ts new file mode 100644 index 0000000..36596a1 --- /dev/null +++ b/src/douban/DoubanSettingTab.ts @@ -0,0 +1,291 @@ +import { App, PluginSettingTab, Setting } from "obsidian"; +import { DEFAULT_SETTINGS, PersonNameMode, personNameModeRecords } from "./Douban"; + +import DoubanPlugin from "main"; +import { i18nHelper } from "src/lang/helper"; +import { log } from "src/utils/Logutil"; + +export class DoubanSettingTab extends PluginSettingTab { + plugin: DoubanPlugin; + + constructor(app: App, plugin: DoubanPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + let { containerEl } = this; + + containerEl.empty(); + + containerEl.createEl("h2", { text: "Obsidian Douban" }); + + new Setting(containerEl).setName(i18nHelper.getMessage('douban search url')) + .then((setting) => { + setting.addText((textField) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText(i18nHelper.getMessage('douban search url desc 1')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('douban search url desc 2')); + frag.createEl( + 'a', + { + text: i18nHelper.getMessage('Douban'), + href: 'https://www.douban.com', + }, + (a) => { + a.setAttr('target', '_blank'); + } + ); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('douban search url desc 3')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('douban search url desc 4')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('douban search url desc 5')); + frag.createEl('br'); + }) + ); + textField.inputEl.addClass("settings_textField"); + textField + .setPlaceholder(DEFAULT_SETTINGS.searchUrl) + .setValue(this.plugin.settings.searchUrl) + .onChange(async (value) => { + this.plugin.settings.searchUrl = value; + await this.plugin.saveSettings(); + }); + + }); + }); + + new Setting(containerEl).setName(i18nHelper.getMessage("movie content template")).then((setting) => { + setting.addTextArea((textarea) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText(i18nHelper.getMessage('movie content template desc 1')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('movie content template desc 2')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('movie content template desc 3')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('movie content template desc 4')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('movie content template desc 5')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('movie content template desc 6')); + frag.createEl('br'); + }) + ); + textarea.inputEl.addClass("settings_area"); + textarea.inputEl.setAttr("rows", 10); + textarea.setPlaceholder(DEFAULT_SETTINGS.movieTemplate) + .setValue(this.plugin.settings.movieTemplate) + .onChange(async (value) => { + this.plugin.settings.movieTemplate = value; + await this.plugin.saveSettings(); + }); + }); + }); + + new Setting(containerEl).setName(i18nHelper.getMessage("book content template")).then((setting) => { + setting.addTextArea((textarea) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText(i18nHelper.getMessage('book content template desc 1')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('book content template desc 2')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('book content template desc 3')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('book content template desc 4')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('book content template desc 5')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('book content template desc 6')); + frag.createEl('br'); + }) + ); + textarea.inputEl.addClass("settings_area"); + textarea.inputEl.setAttr("rows", 10); + textarea.setPlaceholder(DEFAULT_SETTINGS.bookTemplate) + .setValue(this.plugin.settings.bookTemplate) + .onChange(async (value) => { + this.plugin.settings.bookTemplate = value; + await this.plugin.saveSettings(); + }); + }); + }); + + new Setting(containerEl).setName(i18nHelper.getMessage("music content template")).then((setting) => { + setting.addTextArea((textarea) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText(i18nHelper.getMessage('music content template desc 1')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('music content template desc 2')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('music content template desc 3')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('music content template desc 4')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('music content template desc 5')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('music content template desc 6')); + frag.createEl('br'); + }) + ); + textarea.inputEl.addClass("settings_area"); + textarea.inputEl.setAttr("rows", 10); + textarea.setPlaceholder(DEFAULT_SETTINGS.musicTemplate) + .setValue(this.plugin.settings.musicTemplate) + .onChange(async (value) => { + this.plugin.settings.musicTemplate = value; + await this.plugin.saveSettings(); + }); + }); + }); + + new Setting(containerEl).setName(i18nHelper.getMessage("note content template")).then((setting) => { + setting.addTextArea((textarea) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText(i18nHelper.getMessage('note content template desc 1')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('note content template desc 2')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('note content template desc 3')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('note content template desc 4')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('note content template desc 5')); + frag.createEl('br'); + + }) + ); + textarea.inputEl.addClass("settings_area"); + textarea.inputEl.setAttr("rows", 10); + textarea.setPlaceholder(DEFAULT_SETTINGS.noteTemplate) + .setValue(this.plugin.settings.noteTemplate) + .onChange(async (value) => { + this.plugin.settings.noteTemplate = value; + await this.plugin.saveSettings(); + }); + }); + }); + + new Setting(containerEl).setName(i18nHelper.getMessage("Person Name Language Mode")).then((setting) => { + setting.addDropdown((dropdwon) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText(i18nHelper.getMessage('options:')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('Chinese Name mode, only show Chinese name')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('English Name mode, only show English name')); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('Chinese English Name mode, show Chinese English name both')); + frag.createEl('br'); + }) + ); + // dropdwon.inputEl.addClass("settings_area"); + // dropdwon.inputEl.setAttr("rows", 10); + dropdwon.addOption(PersonNameMode.CH_NAME, personNameModeRecords.CH) + dropdwon.addOption(PersonNameMode.EN_NAME, personNameModeRecords.EN) + dropdwon.addOption(PersonNameMode.CH_EN_NAME, personNameModeRecords.CH_EN) + dropdwon.setValue(this.plugin.settings.personNameMode) + .onChange(async (value:string) => { + this.plugin.settings.personNameMode = value as PersonNameMode; + await this.plugin.saveSettings(); + }); + }); + }); + + + + + new Setting(containerEl).setName(i18nHelper.getMessage('Date format')).then((setting) => { + setting.addMomentFormat((mf) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText( + i18nHelper.getMessage('This format will be used when available template variables contain date.') + ); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('For more syntax, refer to') + ' '); + frag.createEl( + 'a', + { + text: i18nHelper.getMessage('format reference'), + href: 'https://momentjs.com/docs/#/displaying/format/', + }, + (a) => { + a.setAttr('target', '_blank'); + } + ); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('Your current syntax looks like this') + ': '); + mf.setSampleEl(frag.createEl('b', { cls: 'u-pop' })); + frag.createEl('br'); + }) + ); + mf.setPlaceholder(DEFAULT_SETTINGS.dateFormat); + mf.setValue(this.plugin.settings.dateFormat) + mf.onChange(async (value) => { + this.plugin.settings.dateFormat = value; + await this.plugin.saveSettings(); + }); + + }); + }); + + new Setting(containerEl).setName(i18nHelper.getMessage('DateTime format')).then((setting) => { + setting.addMomentFormat((mf) => { + setting.descEl.appendChild( + createFragment((frag) => { + frag.appendText( + i18nHelper.getMessage('This format will be used when available template variables contain dateTime.') + ); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('For more syntax, refer to') + ' '); + frag.createEl( + 'a', + { + text: i18nHelper.getMessage('format reference'), + href: 'https://momentjs.com/docs/#/displaying/format/', + }, + (a) => { + a.setAttr('target', '_blank'); + } + ); + frag.createEl('br'); + frag.appendText(i18nHelper.getMessage('Your current syntax looks like this') + ': '); + mf.setSampleEl(frag.createEl('b', { cls: 'u-pop' })); + frag.createEl('br'); + }) + ); + mf.setPlaceholder(DEFAULT_SETTINGS.dateTimeFormat); + mf.setValue(this.plugin.settings.dateTimeFormat) + mf.onChange(async (value) => { + this.plugin.settings.dateTimeFormat = value; + await this.plugin.saveSettings(); + }); + + }); + }); + + + new Setting(containerEl) + .setName(i18nHelper.getMessage("Array Spilt String")) + .setDesc(i18nHelper.getMessage(`string to join between array type, such as author, actor`)) + .addText((textField) => { + textField.setPlaceholder(DEFAULT_SETTINGS.arraySpilt) + .setValue(this.plugin.settings.arraySpilt) + .onChange(async (value) => { + this.plugin.settings.arraySpilt = value; + await this.plugin.saveSettings(); + }); + }); + + } + } \ No newline at end of file diff --git a/src/douban/ResponseHandle.ts b/src/douban/ResponseHandle.ts new file mode 100644 index 0000000..cbef1d2 --- /dev/null +++ b/src/douban/ResponseHandle.ts @@ -0,0 +1,13 @@ +import { Notice } from "obsidian"; + +export const ensureStatusCode = (expected:any) => { + if (!Array.isArray(expected)) + expected = [expected]; + return (res:any) => { + const { statusCode } = res; + if(!expected.includes(statusCode)) { + new Notice(`Request Douban failed, Status code must be "${expected}" but actually "${statusCode}"`) + } + return res; + }; + }; \ No newline at end of file diff --git a/src/douban/broadcast/handler/DoubanBroadcastAbstractHandler.ts b/src/douban/broadcast/handler/DoubanBroadcastAbstractHandler.ts new file mode 100644 index 0000000..be5dc3e --- /dev/null +++ b/src/douban/broadcast/handler/DoubanBroadcastAbstractHandler.ts @@ -0,0 +1,12 @@ +import { CheerioAPI } from "cheerio"; +import DoubanBroadcastSubject from "../model/DoubanBroadcastSubject"; + +export abstract class DoubanBroadcastAbstractHandler { + + abstract support(t:string):boolean; + + abstract transform(data:Element, source:CheerioAPI):T; + +} + + diff --git a/src/douban/broadcast/handler/DoubanBroadcastMovieHandler.ts b/src/douban/broadcast/handler/DoubanBroadcastMovieHandler.ts new file mode 100644 index 0000000..3a9d311 --- /dev/null +++ b/src/douban/broadcast/handler/DoubanBroadcastMovieHandler.ts @@ -0,0 +1,14 @@ +import { CheerioAPI } from "cheerio"; +import { DoubanBroadcastAbstractHandler } from "./DoubanBroadcastAbstractHandler"; +import DoubanBroadcastMovieSubject from "../model/DoubanBroadcastMoveSubject"; + +export class DoubanBroadcastMovieHandler extends DoubanBroadcastAbstractHandler{ + support(t: string): boolean { + throw new Error("Method not implemented."); + } + transform(data: Element, source:CheerioAPI): DoubanBroadcastMovieSubject { + throw new Error("Method not implemented."); + } + + +} diff --git a/src/douban/broadcast/handler/DoubanPageBroadcastTransformer.ts b/src/douban/broadcast/handler/DoubanPageBroadcastTransformer.ts new file mode 100644 index 0000000..ff3b194 --- /dev/null +++ b/src/douban/broadcast/handler/DoubanPageBroadcastTransformer.ts @@ -0,0 +1,31 @@ +import { CheerioAPI } from "cheerio"; +import { DoubanBroadcastAbstractHandler } from "./DoubanBroadcastAbstractHandler"; +import { DoubanBroadcastMovieHandler } from "./DoubanBroadcastMovieHandler"; +import DoubanBroadcastSubject from "../model/DoubanBroadcastSubject"; +import DoubanPageBroadcastSubject from "../model/DoubanPageBroadcastSubject"; + +export class DoubanPageBroadcastTransformer { + + private handlers:DoubanBroadcastAbstractHandler[]; + + + constructor() { + this.handlers = [ + new DoubanBroadcastMovieHandler(), + ] + + } + + public transform(data: CheerioAPI):DoubanPageBroadcastSubject { + var doubanBroadcastSubjects:DoubanBroadcastSubject[] = data('.new-status .status-wrapper') + .get() + .map(i => this.transformElement(i, data)); + return new DoubanPageBroadcastSubject (); + } + + public transformElement(element:any, source:CheerioAPI):DoubanBroadcastSubject { + var targetType:string = element.innerHTML; + return this.handlers.filter(h => h.support(targetType))[0].transform(element, source); + } + +} diff --git a/src/douban/broadcast/handler/DoubanPageBroadcatLoadHandler.ts b/src/douban/broadcast/handler/DoubanPageBroadcatLoadHandler.ts new file mode 100644 index 0000000..7cd6725 --- /dev/null +++ b/src/douban/broadcast/handler/DoubanPageBroadcatLoadHandler.ts @@ -0,0 +1,79 @@ +import { CheerioAPI } from 'cheerio'; +import DoubanAbstractLoadHandler from 'src/douban/data/handler/DoubanAbstractLoadHandler'; +import DoubanNoteSubject from '../model/DoubanPageBroadcastSubject'; +import DoubanPageBroadcastSubject from '../model/DoubanPageBroadcastSubject'; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import DoubanSubject from 'src/douban/data/model/DoubanSubject'; +import html2markdown from '@notable/html2markdown'; +import { moment } from "obsidian"; + +export default class DoubanPageBroadcatLoadHandler extends DoubanAbstractLoadHandler { + + parseText(extract: DoubanNoteSubject, settings:DoubanPluginSettings): string { + return settings.bookTemplate ? null + // settings.noteTemplate + // .replaceAll("{{id}}", extract.id) + // .replaceAll("{{type}}", extract.type ? extract.type : "") + // .replaceAll("{{title}}", extract.title ? extract.title : "") + // .replaceAll("{{desc}}", extract.desc ? extract.desc : "") + // .replaceAll("{{image}}", extract.image ? extract.image : "") + // .replaceAll("{{timePublished}}", extract.timePublished ? moment(extract.timePublished).format(settings.dateTimeFormat) : "") + // .replaceAll("{{url}}", extract.url ? extract.url : "") + // .replaceAll("{{content}}", extract.content ? extract.content : "") + // .replaceAll("{{url}}", extract.url ? extract.url : "") + // .replaceAll("{{authorUrl}}", extract.authorUrl ? extract.authorUrl : "") + // .replaceAll("{{author}}", extract.author ? extract.author : "") + + + : undefined; + } + support(extract: DoubanSubject): boolean { + return extract && extract.type && (extract.type.contains("广播") || extract.type.contains("Broadcast")); + } + + + + + + constructor(doubanPlugin:DoubanPlugin) { + super(doubanPlugin); + } + + parseSubjectFromHtml(html: CheerioAPI): DoubanPageBroadcastSubject { + // return html('.new-status .status-wrapper') + // .get() + // .map(i -> { + // var div = html(i); + // div.find('') + // }); + + var title = html(html("head > meta[property= 'og:title']").get(0)).attr("content"); + var desc = html(html("head > meta[property= 'og:description']").get(0)).attr("content"); + var url = html(html("head > meta[property= 'og:url']").get(0)).attr("content"); + var image = html(html("head > meta[property= 'og:image']").get(0)).attr("content"); + var type = html(html("head > meta[property= 'og:type']").get(0)).attr("content"); + var authorA = html(html("a.note-author").get(0)); + var timePublished = html(html(".pub-date").get(0)).text(); + var content = html(html(".note").get(1)); + var idPattern = /(\d){5,10}/g; + var id = idPattern.exec(url); + + // const result:DoubanNoteSubject = { + // image: image, + // timePublished: timePublished ? new Date(timePublished) : null, + // content: content ? html2markdown(content.toString()): "", + // id: id ? id[0] : "", + // type: "Article", + // title: title, + // desc: desc, + // url: url, + // author: authorA ? authorA.text() : null, + // authorUrl: authorA ? authorA.attr("href") : null, + // }; + // return result; + return null; +} + + +} diff --git a/src/douban/broadcast/model/DoubanBroadcastMoveSubject.ts b/src/douban/broadcast/model/DoubanBroadcastMoveSubject.ts new file mode 100644 index 0000000..31eddf0 --- /dev/null +++ b/src/douban/broadcast/model/DoubanBroadcastMoveSubject.ts @@ -0,0 +1,4 @@ +import DoubanBroadcastSubject from "./DoubanBroadcastSubject"; + +export default class DoubanBroadcastMovieSubject extends DoubanBroadcastSubject{ +} diff --git a/src/douban/broadcast/model/DoubanBroadcastSubject.ts b/src/douban/broadcast/model/DoubanBroadcastSubject.ts new file mode 100644 index 0000000..53a82d6 --- /dev/null +++ b/src/douban/broadcast/model/DoubanBroadcastSubject.ts @@ -0,0 +1,12 @@ +export default class DoubanBroadcastSubject { + id: string; + type: string; + title: string; + desc: string; + url: string; + author:string; + authorUrl:string; + timePublished:Date; + image:string; + content:string; +} diff --git a/src/douban/broadcast/model/DoubanPageBroadcastSubject.ts b/src/douban/broadcast/model/DoubanPageBroadcastSubject.ts new file mode 100644 index 0000000..49d3cca --- /dev/null +++ b/src/douban/broadcast/model/DoubanPageBroadcastSubject.ts @@ -0,0 +1,9 @@ +import {AggregateRating, Person, WithContext} from 'schema-dts'; + +import DoubanSubject from 'src/douban/data/model/DoubanSubject'; + +export default class DoubanPageBroadcastSubject { + pageNumber:number; + broadcast:DoubanPageBroadcastSubject[]; + pageSize:number; +} diff --git a/src/douban/data/handler/DoubanAbstractLoadHandler.ts b/src/douban/data/handler/DoubanAbstractLoadHandler.ts new file mode 100644 index 0000000..75477b3 --- /dev/null +++ b/src/douban/data/handler/DoubanAbstractLoadHandler.ts @@ -0,0 +1,93 @@ +import { CheerioAPI, load } from 'cheerio'; +import { DoubanPluginSettings, PersonNameMode } from "src/douban/Douban"; +import { get, readStream } from "tiny-network"; + +import DoubanPlugin from "main"; +import DoubanSubject from '../model/DoubanSubject'; +import DoubanSubjectLoadHandler from "./DoubanSubjectLoadHandler"; +import { Editor } from "obsidian"; +import { i18nHelper } from 'src/lang/helper'; +import { log } from "src/utils/Logutil"; + +export default abstract class DoubanAbstractLoadHandler implements DoubanSubjectLoadHandler { + + + public doubanPlugin:DoubanPlugin; + + constructor(doubanPlugin:DoubanPlugin) { + this.doubanPlugin = doubanPlugin; + } + + abstract parseText(extract: T, settings:DoubanPluginSettings): string; + + abstract support(extract: DoubanSubject): boolean; + + handle(url:string, editor:Editor):void { + Promise.resolve().then(() => get(log.traceN("GET URL", url + "/"), log.traceN("GET HEAD", JSON.parse(this.doubanPlugin.settings.searchHeaders)))) + .then(readStream) + .then(a => {log.trace(a.toString()); return a;}) + .then(load) + .then(this.parseSubjectFromHtml) + .then(content => this.toEditor(editor, content)) + // .then(content => content ? editor.replaceSelection(content) : content) + .catch(e => log.error(i18nHelper.getMessage("Fetch Data Error"))) + ; + + } + + abstract parseSubjectFromHtml(data:CheerioAPI):T | undefined; + + toEditor(editor:Editor, extract: T):T { + this.doubanPlugin.putToEditor(editor, extract); + return extract; + } + + getPersonName(name:string, settings:DoubanPluginSettings):string { + if(!name || !settings || !settings.personNameMode) { + return ""; + } + var resultName = ""; + switch(settings.personNameMode) { + case PersonNameMode.CH_NAME: + var regValue = /[\u4e00-\u9fa5]{2,20}/g.exec(name); + resultName = regValue?regValue[0]:name; + break; + case PersonNameMode.EN_NAME: + var regValue = /[a-zA-Z.\s\-]{2,50}/g.exec(name); + resultName = regValue?regValue[0]:name; + break; + default: + resultName = name; + } + return resultName; + } + + html_encode(str:string):string + { + var 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 + { + var 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; + } + +} \ No newline at end of file diff --git a/src/douban/data/handler/DoubanBookLoadHandler.ts b/src/douban/data/handler/DoubanBookLoadHandler.ts new file mode 100644 index 0000000..f9966cf --- /dev/null +++ b/src/douban/data/handler/DoubanBookLoadHandler.ts @@ -0,0 +1,116 @@ +import { Editor, moment, renderResults } from "obsidian"; + +import { CheerioAPI } from 'cheerio'; +import DoubanAbstractLoadHandler from "./DoubanAbstractLoadHandler"; +import DoubanBookSubject from "../model/DoubanBookSubject"; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; + +export default class DoubanBookLoadHandler extends DoubanAbstractLoadHandler { + + parseText(extract: DoubanBookSubject, settings:DoubanPluginSettings): string { + return settings.bookTemplate ? settings.bookTemplate.replaceAll("{{id}}", extract.id) + .replaceAll("{{type}}", extract.type ? extract.type : "") + .replaceAll("{{title}}", extract.title ? extract.title : "") + .replaceAll("{{desc}}", extract.desc ? extract.desc : "") + .replaceAll("{{image}}", extract.image ? extract.image : "") + .replaceAll("{{author}}", extract.author ? extract.author.join(settings.arraySpilt) : "") + .replaceAll("{{datePublished}}", extract.datePublished ? moment(extract.datePublished).format(settings.dateFormat) : "") + .replaceAll("{{url}}", extract.url ? extract.url : "") + .replaceAll("{{score}}", extract.score && extract.score ? extract.score + "" : "") + .replaceAll("{{translator}}", extract.translator ? extract.translator.join(settings.arraySpilt) : "") + .replaceAll("{{totalWord}}", extract.totalWord ? extract.totalWord+"" : "") + .replaceAll("{{isbn}}", extract.isbn ? extract.isbn : "") + .replaceAll("{{publish}}", extract.publish ? extract.publish : "") + .replaceAll("{{originalTitle}}", extract.originalTitle ? extract.originalTitle : "") + .replaceAll("{{subTitle}}", extract.subTitle ? extract.subTitle : "") + .replaceAll("{{totalPage}}", extract.totalPage ? extract.totalPage + "" : "") + .replaceAll("{{menu}}", extract.menu ? extract.menu.join(settings.arraySpilt) : "") + .replaceAll("{{price}}", extract.price ? extract.price + "" : "") + .replaceAll("{{labels}}", extract.labels ? extract.labels.join(settings.arraySpilt) : "") + + : undefined; + } + support(extract: DoubanSubject): boolean { + return extract && extract.type && (extract.type.contains("书籍") || extract.type.contains("Book") || extract.type.contains("book")); + } + + + + + + constructor(doubanPlugin:DoubanPlugin) { + super(doubanPlugin); + } + + parseSubjectFromHtml(html: CheerioAPI): DoubanBookSubject { + var title = html(html("head > meta[property= 'og:title']").get(0)).attr("content"); + var desc = html(html("head > meta[property= 'og:description']").get(0)).attr("content"); + var url = html(html("head > meta[property= 'og:url']").get(0)).attr("content"); + var image = html(html("head > meta[property= 'og:image']").get(0)).attr("content"); + var type = html(html("head > meta[property= 'og:type']").get(0)).attr("content"); + var author = html(html("head > meta[property= 'book:author']").get(0)).attr("content"); + var isbn = html(html("head > meta[property= 'book:isbn']").get(0)).attr("content"); + var score = html(html("#interest_sectl > div > div.rating_self.clearfix > strong[property= 'v:average']").get(0)).text(); + var detailDom = html(html("#info").get(0)); + var publish = detailDom.find("span.pl"); + + var valueMap = new Map(); + + publish.map((index, info) => { + let key = html(info).text().trim(); + let value = '' + if(key.indexOf('作者') >= 0 || key.indexOf('丛书') >= 0 || key.indexOf('译者') >= 0 || key.indexOf('出版社') >= 0){ + value = html(info.next.next).text().trim(); + }else{ + value = html(info.next).text().trim(); + } + valueMap.set(BookKeyValueMap.get(key), value); + }) + + var idPattern = /(\d){5,10}/g; + var id = idPattern.exec(url); + + const result:DoubanBookSubject = { + author: [author], + translator: [valueMap.get('translator')], + bookType: "", + image: image, + datePublished: valueMap.has('datePublished')?new Date(valueMap.get('datePublished')) : null, + totalWord: valueMap.has('totalWord') ? Number(valueMap.get('totalWord')) : null, + isbn: isbn, + publish: valueMap.has('publish') ? valueMap.get('publish') : "", + score: Number(score), + originalTitle: valueMap.has('originalTitle') ? valueMap.get('originalTitle') : "", + subTitle: "", + totalPage: valueMap.has('originalTitle') ? Number(valueMap.get('totalPage')) : null, + belong: "", + menu: [], + price: valueMap.has('price') ? Number(valueMap.get('price').replace('元', '')) : null, + labels: [], + id: id ? id[0]:"", + type: "Book", + title: title, + desc: desc, + url: url + }; + return result; +} + + +} + + +const BookKeyValueMap:Map = new Map( + [['作者', 'author'], + ['出版社:', 'publish'], + ['原作名:', 'originalTitle'], + ['出版年:', 'datePublished'], + ['页数:', 'totalPage'], + ['定价:', 'price'], + ['装帧:', 'binding'], + ['丛书:', 'bush'], + ['ISBN:', 'isbn'], + ['译者', 'translator'], + ] +); \ No newline at end of file diff --git a/src/douban/data/handler/DoubanMovieLoadHandler.ts b/src/douban/data/handler/DoubanMovieLoadHandler.ts new file mode 100644 index 0000000..23e3f82 --- /dev/null +++ b/src/douban/data/handler/DoubanMovieLoadHandler.ts @@ -0,0 +1,77 @@ +import { CheerioAPI } from 'cheerio'; +import DoubanAbstractLoadHandler from "./DoubanAbstractLoadHandler"; +import DoubanMovieSubject from '../model/DoubanBookSubject'; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import SchemaOrg from "src/utils/SchemaOrg"; +import { moment } from "obsidian"; + +export default class DoubanMovieLoadHandler extends DoubanAbstractLoadHandler { + + parseText(extract: DoubanMovieSubject, settings:DoubanPluginSettings): string { + return settings.movieTemplate ? settings.movieTemplate.replaceAll("{{id}}", extract.id) + .replaceAll("{{type}}", extract.type ? extract.type : "") + .replaceAll("{{title}}", extract.title ? extract.title : "") + .replaceAll("{{originalTitle}}", extract.originalTitle ? extract.originalTitle : "") + .replaceAll("{{desc}}", extract.desc ? extract.desc : "") + .replaceAll("{{image}}", extract.image ? extract.image : "") + .replaceAll("{{director}}", extract.director ? extract.director.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, settings)).filter(c => c).join(settings.arraySpilt) : "") + .replaceAll("{{actor}}", extract.actor ? extract.actor.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, settings)).filter(c => c).join(settings.arraySpilt) : "") + .replaceAll("{{author}}", extract.author ? extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, settings)).filter(c => c).join(settings.arraySpilt) : "") + .replaceAll("{{datePublished}}", extract.datePublished ? moment(extract.datePublished).format(settings.dateFormat) : "") + .replaceAll("{{url}}", extract.url ? extract.url : "") + .replaceAll("{{score}}", extract.aggregateRating && extract.aggregateRating.ratingValue ? extract.aggregateRating.ratingValue + "" : "") + .replaceAll("{{genre}}", extract.genre ? extract.genre.join(settings.arraySpilt) : "") + + : undefined; } + support(extract: DoubanSubject): boolean { + return extract && extract.type && (extract.type.contains("电影") || extract.type.contains("Movie") || extract.type.contains("movie")); + } + + + + + + constructor(doubanPlugin:DoubanPlugin) { + super(doubanPlugin); + } + + parseSubjectFromHtml(data: CheerioAPI): DoubanMovieSubject { + return data('script') + .get() + .filter(scd => "application/ld+json" == data(scd).attr("type")) + .map(i => { + var item = data(i).text(); + item = super.html_decode(item); + var obj = JSON.parse(item.replace(/[\r\n\s+]/g, '')); + var idPattern = /(\d){5,10}/g; + var id = idPattern.exec(obj.url); + var name = obj.name; + var titleExec = /[\u4e00-\u9fa5]{2,20}/g.exec(name); + var title = titleExec?titleExec[0]:name; + + var originalTitleExec = /[a-zA-Z.\s\-]{2,50}/g.exec(name); + var originalTitle = originalTitleExec?originalTitleExec[0]:name; + + const result:DoubanMovieSubject = { + id: id?id[0]:'', + type: 'Movie', + title: title, + originalTitle: originalTitle, + desc: obj.description, + url: "https://movie.douban.com" + obj.url, + director: obj.director, + author: obj.author, + actor: obj.actor, + aggregateRating: obj.aggregateRating, + datePublished: obj.datePublished ? new Date(obj.datePublished) : undefined, + image:obj.image, + genre:obj.genre + } + return result; + })[0]; + } + +} + + diff --git a/src/douban/data/handler/DoubanMusicLoadHandler.ts b/src/douban/data/handler/DoubanMusicLoadHandler.ts new file mode 100644 index 0000000..7cf8ed0 --- /dev/null +++ b/src/douban/data/handler/DoubanMusicLoadHandler.ts @@ -0,0 +1,102 @@ +import { CheerioAPI } from 'cheerio'; +import DoubanAbstractLoadHandler from "./DoubanAbstractLoadHandler"; +import DoubanMusicSubject from '../model/DoubanMusicSubject'; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import { moment } from "obsidian"; + +export default class DoubanMusicLoadHandler extends DoubanAbstractLoadHandler { + + parseText(extract: DoubanMusicSubject, settings:DoubanPluginSettings): string { + return settings.bookTemplate ? settings.musicTemplate + .replaceAll("{{id}}", extract.id) + .replaceAll("{{type}}", extract.type ? extract.type : "") + .replaceAll("{{title}}", extract.title ? extract.title : "") + .replaceAll("{{desc}}", extract.desc ? extract.desc : "") + .replaceAll("{{image}}", extract.image ? extract.image : "") + .replaceAll("{{actor}}", extract.actor ? extract.actor.join(settings.arraySpilt) : "") + .replaceAll("{{datePublished}}", extract.datePublished ? moment(extract.datePublished).format(settings.dateFormat) : "") + .replaceAll("{{url}}", extract.url ? extract.url : "") + .replaceAll("{{score}}", extract.score && extract.score ? extract.score + "" : "") + .replaceAll("{{barcode}}", extract.barcode ? extract.barcode : "") + .replaceAll("{{publish}}", extract.publish ? extract.publish : "") + .replaceAll("{{genre}}", extract.genre ? extract.genre : "") + .replaceAll("{{medium}}", extract.medium ? extract.medium : "") + .replaceAll("{{albumType}}", extract.albumType ? extract.albumType : "") + .replaceAll("{{numberOfRecords}}", extract.numberOfRecords ? extract.numberOfRecords + "" : "") + : undefined; + } + support(extract: DoubanSubject): boolean { + return extract && extract.type && (extract.type.contains("音乐") || extract.type.contains("Music") || extract.type.contains("music")); + } + + + + + + constructor(doubanPlugin:DoubanPlugin) { + super(doubanPlugin); + } + + parseSubjectFromHtml(html: CheerioAPI): DoubanMusicSubject { + var title = html(html("head > meta[property= 'og:title']").get(0)).attr("content"); + var desc = html(html("head > meta[property= 'og:description']").get(0)).attr("content"); + var url = html(html("head > meta[property= 'og:url']").get(0)).attr("content"); + var image = html(html("head > meta[property= 'og:image']").get(0)).attr("content"); + var score = html(html("#interest_sectl > div > div.rating_self.clearfix > strong[property= 'v:average']").get(0)).text(); + var detailDom = html(html("#info").get(0)); + var publish = detailDom.find("span.pl"); + + var valueMap = new Map(); + + publish.map((index, info) => { + let key = html(info).text().trim(); + let value = '' + if(key.indexOf('表演者') >= 0){ + // value = html(info.next.next).text().trim(); + var vas:string[] = key.split("\n \n "); + value = vas && vas.length > 1? vas[1]:""; + key = vas && vas.length > 0? vas[0]:""; + }else{ + value = html(info.next).text().trim(); + } + valueMap.set(BookKeyValueMap.get(key), value); + }) + + var idPattern = /(\d){5,10}/g; + var id = idPattern.exec(url); + + const result:DoubanMusicSubject = { + image: image, + datePublished: valueMap.has('datePublished') ? new Date(valueMap.get('datePublished')) : null, + publish: valueMap.has('publish') ? valueMap.get('publish') : "", + score: Number(score), + numberOfRecords: valueMap.has('numberOfRecords') ? Number(valueMap.get('numberOfRecords')) : null, + id: id ? id[0] : "", + type: "Music", + title: title, + desc: desc, + url: url, + actor: [valueMap.has('actor') ? valueMap.get('actor') : null], + genre: valueMap.has('genre') ? valueMap.get('genre') : "", + albumType: valueMap.has('albumType') ? valueMap.get('albumType') : "", + medium: valueMap.has('medium') ? valueMap.get('medium') : "", + barcode: valueMap.has('barcode') ? valueMap.get('barcode') : "" + }; + return result; +} + + +} + + +const BookKeyValueMap:Map = new Map( + [['表演者:', 'actor'], + ['流派:', 'genre'], + ['发行时间:', 'datePublished'], + ['专辑类型:', 'albumType'], + ['介质:', 'medium'], + ['出版者:', 'publish'], + ['唱片数:', 'numberOfRecords'], + ['条形码:', 'barcode']] +); \ No newline at end of file diff --git a/src/douban/data/handler/DoubanNoteLoadHandler.ts b/src/douban/data/handler/DoubanNoteLoadHandler.ts new file mode 100644 index 0000000..23f9982 --- /dev/null +++ b/src/douban/data/handler/DoubanNoteLoadHandler.ts @@ -0,0 +1,68 @@ +import { CheerioAPI } from 'cheerio'; +import DoubanAbstractLoadHandler from "./DoubanAbstractLoadHandler"; +import DoubanNoteSubject from '../model/DoubanNoteSubject'; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import html2markdown from '@notable/html2markdown'; +import { moment } from "obsidian"; + +export default class DoubanNoteLoadHandler extends DoubanAbstractLoadHandler { + + parseText(extract: DoubanNoteSubject, settings:DoubanPluginSettings): string { + return settings.bookTemplate ? settings.noteTemplate + .replaceAll("{{id}}", extract.id) + .replaceAll("{{type}}", extract.type ? extract.type : "") + .replaceAll("{{title}}", extract.title ? extract.title : "") + .replaceAll("{{desc}}", extract.desc ? extract.desc : "") + .replaceAll("{{image}}", extract.image ? extract.image : "") + .replaceAll("{{timePublished}}", extract.timePublished ? moment(extract.timePublished).format(settings.dateTimeFormat) : "") + .replaceAll("{{url}}", extract.url ? extract.url : "") + .replaceAll("{{content}}", extract.content ? extract.content : "") + .replaceAll("{{url}}", extract.url ? extract.url : "") + .replaceAll("{{authorUrl}}", extract.authorUrl ? extract.authorUrl : "") + .replaceAll("{{author}}", extract.author ? extract.author : "") + + + : undefined; + } + support(extract: DoubanSubject): boolean { + return extract && extract.type && (extract.type.contains("日记") || extract.type.contains("Note") || extract.type.contains("Article")); + } + + + + + + constructor(doubanPlugin:DoubanPlugin) { + super(doubanPlugin); + } + + parseSubjectFromHtml(html: CheerioAPI): DoubanNoteSubject { + var title = html(html("head > meta[property= 'og:title']").get(0)).attr("content"); + var desc = html(html("head > meta[property= 'og:description']").get(0)).attr("content"); + var url = html(html("head > meta[property= 'og:url']").get(0)).attr("content"); + var image = html(html("head > meta[property= 'og:image']").get(0)).attr("content"); + var type = html(html("head > meta[property= 'og:type']").get(0)).attr("content"); + var authorA = html(html("a.note-author").get(0)); + var timePublished = html(html(".pub-date").get(0)).text(); + var content = html(html(".note").get(1)); + var idPattern = /(\d){5,10}/g; + var id = idPattern.exec(url); + + const result:DoubanNoteSubject = { + image: image, + timePublished: timePublished ? new Date(timePublished) : null, + content: content ? html2markdown(content.toString()): "", + id: id ? id[0] : "", + type: "Article", + title: title, + desc: desc, + url: url, + author: authorA ? authorA.text() : null, + authorUrl: authorA ? authorA.attr("href") : null, + }; + return result; +} + + +} diff --git a/src/douban/data/handler/DoubanOtherLoadHandler.ts b/src/douban/data/handler/DoubanOtherLoadHandler.ts new file mode 100644 index 0000000..f11510f --- /dev/null +++ b/src/douban/data/handler/DoubanOtherLoadHandler.ts @@ -0,0 +1,29 @@ +import { Editor, Notice } from "obsidian"; + +import { CheerioAPI } from "cheerio"; +import DoubanAbstractLoadHandler from "./DoubanAbstractLoadHandler"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import DoubanSubject from "../model/DoubanSubject"; +import { i18nHelper } from "src/lang/helper"; +import { log } from "src/utils/Logutil"; + +/** + * 默认的处理器 + */ +export default class DoubanOtherLoadHandler extends DoubanAbstractLoadHandler { + parseText(extract: DoubanSubject, settings:DoubanPluginSettings): string { + log.warn(i18nHelper.getMessage('current version not support type')); + return ""; + } + support(extract: DoubanSubject): boolean { + return false; + } + parseSubjectFromHtml(data: CheerioAPI): DoubanSubject { + return undefined; + } + + + + + +} \ No newline at end of file diff --git a/src/douban/data/handler/DoubanSearchChooseItemHandler.ts b/src/douban/data/handler/DoubanSearchChooseItemHandler.ts new file mode 100644 index 0000000..ad5af46 --- /dev/null +++ b/src/douban/data/handler/DoubanSearchChooseItemHandler.ts @@ -0,0 +1,69 @@ +import { App, Editor } from "obsidian"; + +import DoubanBookLoadHandler from "./DoubanBookLoadHandler"; +import DoubanMovieLoadHandler from "./DoubanMovieLoadHandler"; +import DoubanMusicLoadHandler from "./DoubanMusicLoadHandler"; +import DoubanNoteLoadHandler from "./DoubanNoteLoadHandler"; +import DoubanOtherLoadHandler from "./DoubanOtherLoadHandler"; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import DoubanSubject from "../model/DoubanSubject"; +import DoubanSubjectLoadHandler from "./DoubanSubjectLoadHandler"; +import { DoubanTeleplayLoadHandler } from "./DoubanTeleplayLoadHandler"; + +export class DoubanSearchChooseItemHandler { + + private _app:App; + private _doubanPlugin:DoubanPlugin; + private _doubanSubjectHandlers:DoubanSubjectLoadHandler[]; + private _doubanSubjectHandlerDefault:DoubanSubjectLoadHandler; + + + + constructor(app:App, doubanPlugin:DoubanPlugin) { + this._app = app; + this._doubanPlugin = doubanPlugin; + this._doubanSubjectHandlerDefault = new DoubanOtherLoadHandler(doubanPlugin); + this._doubanSubjectHandlers = [new DoubanMovieLoadHandler(doubanPlugin), new DoubanBookLoadHandler(doubanPlugin), + new DoubanTeleplayLoadHandler(doubanPlugin), + new DoubanMusicLoadHandler(doubanPlugin), + new DoubanNoteLoadHandler(doubanPlugin), + + this._doubanSubjectHandlerDefault]; + + } + + public handle(searchExtract:DoubanSubject, editor: Editor):void{ + if(!searchExtract) { + return; + } + var doubanSubjectHandlers:DoubanSubjectLoadHandler[] = this._doubanSubjectHandlers + .filter(h => h.support(searchExtract)); + if(doubanSubjectHandlers && doubanSubjectHandlers.length > 0) { + doubanSubjectHandlers[0].handle(searchExtract.url, editor); + }else { + this._doubanSubjectHandlerDefault.handle(searchExtract.url, editor); + } + } + + public parseText(extract:DoubanSubject, settings:DoubanPluginSettings):string { + if(!settings) { + return ""; + } + var doubanSubjectHandlers:DoubanSubjectLoadHandler[] = this._doubanSubjectHandlers + .filter(h => h.support(extract)); + if(doubanSubjectHandlers && doubanSubjectHandlers.length > 0) { + var result = doubanSubjectHandlers.map(h => h.parseText(extract, settings)); + if(result && result.length > 0) { + return result[0]; + }else { + return ""; + } + }else { + return this._doubanSubjectHandlerDefault.parseText(extract, settings); + } + + } + +} + diff --git a/src/douban/data/handler/DoubanSubjectLoadHandler.ts b/src/douban/data/handler/DoubanSubjectLoadHandler.ts new file mode 100644 index 0000000..8029dd3 --- /dev/null +++ b/src/douban/data/handler/DoubanSubjectLoadHandler.ts @@ -0,0 +1,14 @@ +import { DoubanPluginSettings } from "src/douban/Douban"; +import DoubanSubject from "../model/DoubanSubject"; +import { Editor } from "obsidian"; + +export default interface DoubanSubjectLoadHandler { + + parseText(extract: T, settings:DoubanPluginSettings): string; + + support(extract:DoubanSubject):boolean; + + handle(url:string, editor: Editor):void; + + +} \ No newline at end of file diff --git a/src/douban/data/handler/DoubanTeleplayLoadHandler.ts b/src/douban/data/handler/DoubanTeleplayLoadHandler.ts new file mode 100644 index 0000000..f410234 --- /dev/null +++ b/src/douban/data/handler/DoubanTeleplayLoadHandler.ts @@ -0,0 +1,79 @@ +import { CheerioAPI } from "cheerio"; +import DoubanAbstractLoadHandler from "./DoubanAbstractLoadHandler"; +import DoubanPlugin from "main"; +import { DoubanPluginSettings } from "src/douban/Douban"; +import DoubanTeleplaySubject from "../model/DoubanTeleplaySubject"; +import SchemaOrg from "src/utils/SchemaOrg"; +import { moment } from "obsidian"; + +/** + * teleplay + */ +export class DoubanTeleplayLoadHandler extends DoubanAbstractLoadHandler{ + + + + constructor(doubanPlugin:DoubanPlugin) { + super(doubanPlugin); + } + + + parseText(extract: DoubanTeleplaySubject, settings:DoubanPluginSettings): string { + return settings.movieTemplate ? settings.movieTemplate.replaceAll("{{id}}", extract.id) + .replaceAll("{{type}}", extract.type ? extract.type : "") + .replaceAll("{{title}}", extract.title ? extract.title : "") + .replaceAll("{{originalTitle}}", extract.originalTitle ? extract.originalTitle : "") + .replaceAll("{{desc}}", extract.desc ? extract.desc : "") + .replaceAll("{{image}}", extract.image ? extract.image : "") + .replaceAll("{{director}}", extract.director ? extract.director.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, settings)).filter(c => c).join(settings.arraySpilt) : "") + .replaceAll("{{actor}}", extract.actor ? extract.actor.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, settings)).filter(c => c).join(settings.arraySpilt) : "") + .replaceAll("{{author}}", extract.author ? extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, settings)).filter(c => c).join(settings.arraySpilt) : "") + .replaceAll("{{datePublished}}", extract.datePublished ? moment(extract.datePublished).format(settings.dateFormat) : "") + .replaceAll("{{url}}", extract.url ? extract.url : "") + .replaceAll("{{score}}", extract.aggregateRating && extract.aggregateRating.ratingValue ? extract.aggregateRating.ratingValue + "" : "") + .replaceAll("{{genre}}", extract.genre ? extract.genre.join(settings.arraySpilt) : "") + + : undefined; } + + support(extract: DoubanSubject): boolean { + return extract && extract.type && (extract.type.contains("电视剧") || extract.type.contains("Teleplay") || extract.type.contains("teleplay")); + } + + + parseSubjectFromHtml(data: CheerioAPI): DoubanTeleplaySubject { + return data('script') + .get() + .filter(scd => "application/ld+json" == data(scd).attr("type")) + .map(i => { + var item = data(i).text(); + item = super.html_decode(item); + var obj = JSON.parse(item.replace(/[\r\n\s+]/g, '')); + var idPattern = /(\d){5,10}/g; + var id = idPattern.exec(obj.url); + var name = obj.name; + var titleExec = /[\u4e00-\u9fa5]{2,20}/g.exec(name); + var title = titleExec?titleExec[0]:name; + + var originalTitleExec = /[a-zA-Z.\s\-]{2,50}/g.exec(name); + var originalTitle = originalTitleExec?originalTitleExec[0]:name; + + const result:DoubanTeleplaySubject = { + id: id?id[0]:'', + type: 'Movie', + title: title, + originalTitle: originalTitle, + desc: obj.description, + url: "https://movie.douban.com" + obj.url, + director: obj.director, + author: obj.author, + actor: obj.actor, + aggregateRating: obj.aggregateRating, + datePublished: obj.datePublished ? new Date(obj.datePublished) : undefined, + image:obj.image, + genre:obj.genre + } + return result; + })[0]; + } + +} \ No newline at end of file diff --git a/src/douban/data/model/DoubanBookSubject.ts b/src/douban/data/model/DoubanBookSubject.ts new file mode 100644 index 0000000..c0287c2 --- /dev/null +++ b/src/douban/data/model/DoubanBookSubject.ts @@ -0,0 +1,22 @@ +import {AggregateRating, Person, WithContext} from 'schema-dts'; + +import DoubanSubject from "./DoubanSubject"; + +export default class DoubanMovieSubject extends DoubanSubject { + author:string[]; + translator:string[]; + bookType:string; + image:string; + datePublished:Date; + totalWord:number; + isbn:string; + publish:string; + score:number; + originalTitle:string; + subTitle:string; + totalPage:number + belong:string; + menu:string[]; + price:number; + labels:string[]; +} diff --git a/src/douban/data/model/DoubanMovieSubject.ts b/src/douban/data/model/DoubanMovieSubject.ts new file mode 100644 index 0000000..0f827c2 --- /dev/null +++ b/src/douban/data/model/DoubanMovieSubject.ts @@ -0,0 +1,15 @@ +import {AggregateRating, Person, WithContext} from 'schema-dts'; + +import DoubanSubject from "./DoubanSubject"; + +export default class DoubanMovieSubject extends DoubanSubject { + director:Person[]; + author:Person[]; + actor:Person[]; + aggregateRating:AggregateRating; + datePublished:Date; + image:string; + genre:string[]; + originalTitle:string; + +} diff --git a/src/douban/data/model/DoubanMusicSubject.ts b/src/douban/data/model/DoubanMusicSubject.ts new file mode 100644 index 0000000..0101da5 --- /dev/null +++ b/src/douban/data/model/DoubanMusicSubject.ts @@ -0,0 +1,16 @@ +import {AggregateRating, Person, WithContext} from 'schema-dts'; + +import DoubanSubject from "./DoubanSubject"; + +export default class DoubanMusicSubject extends DoubanSubject { + actor:string[]; + datePublished:Date; + image:string; + genre:string; + albumType:string; + medium:string; + publish:string; + numberOfRecords:number; + barcode:string; + score:number; +} diff --git a/src/douban/data/model/DoubanNoteSubject.ts b/src/douban/data/model/DoubanNoteSubject.ts new file mode 100644 index 0000000..cd13513 --- /dev/null +++ b/src/douban/data/model/DoubanNoteSubject.ts @@ -0,0 +1,11 @@ +import {AggregateRating, Person, WithContext} from 'schema-dts'; + +import DoubanSubject from "./DoubanSubject"; + +export default class DoubanNoteSubject extends DoubanSubject { + author:string; + authorUrl:string; + timePublished:Date; + image:string; + content:string; +} diff --git a/src/douban/data/model/DoubanSearchResultSubject.ts b/src/douban/data/model/DoubanSearchResultSubject.ts new file mode 100644 index 0000000..dd31f9b --- /dev/null +++ b/src/douban/data/model/DoubanSearchResultSubject.ts @@ -0,0 +1,6 @@ +import DoubanSubject from "./DoubanSubject"; + +export default class DoubanSearchResultSubject extends DoubanSubject { + score:string; + cast:string; +} \ No newline at end of file diff --git a/src/douban/data/model/DoubanSubject.ts b/src/douban/data/model/DoubanSubject.ts new file mode 100644 index 0000000..9d67dd4 --- /dev/null +++ b/src/douban/data/model/DoubanSubject.ts @@ -0,0 +1,7 @@ +export default class DoubanExtract { + id: string; + type: string; + title: string; + desc: string; + url: string; +} diff --git a/src/douban/data/model/DoubanTeleplaySubject.ts b/src/douban/data/model/DoubanTeleplaySubject.ts new file mode 100644 index 0000000..dc7263e --- /dev/null +++ b/src/douban/data/model/DoubanTeleplaySubject.ts @@ -0,0 +1,14 @@ +import {AggregateRating, Person, WithContext} from 'schema-dts'; + +import DoubanSubject from "./DoubanSubject"; + +export default class DoubanTeleplaySubject extends DoubanSubject { + director:Person[]; + author:Person[]; + actor:Person[]; + aggregateRating:AggregateRating; + datePublished:Date; + image:string; + genre:string[]; + originalTitle:string; +} diff --git a/src/douban/data/search/DoubanSearchFuzzySuggestModal.ts b/src/douban/data/search/DoubanSearchFuzzySuggestModal.ts new file mode 100644 index 0000000..fc1e2e9 --- /dev/null +++ b/src/douban/data/search/DoubanSearchFuzzySuggestModal.ts @@ -0,0 +1,53 @@ +import { App, Editor, FuzzySuggestModal } from "obsidian"; + +import DoubanPlugin from "main"; +import DoubanSearchResultSubject from "../model/DoubanSearchResultSubject"; +import { log } from "src/utils/Logutil"; + +export {DoubanFuzzySuggester} + + +class DoubanFuzzySuggester extends FuzzySuggestModal { + + public editor: Editor; + private plugin: DoubanPlugin; + private doubanSearchResultExtract:DoubanSearchResultSubject[] + + constructor(plugin: DoubanPlugin, editor: Editor) { + super(app); + this.editor = editor; + this.plugin = plugin; + this.setPlaceholder("Choose an item..."); + + } + + + + getItems(): DoubanSearchResultSubject[] { + return this.doubanSearchResultExtract; + } + + getItemText(item: DoubanSearchResultSubject): string { + let text:string = item.type + "/" + item.score + "/" + item.title + "/" + item.cast; + return text; + } + + onChooseItem(item: DoubanSearchResultSubject, evt: MouseEvent | KeyboardEvent): void { + log.trace(`you chosen : ${JSON.stringify(item)}`) + this.plugin.doubanEtractHandler.handle(item, this.editor); + } + + public showSearchList(doubanSearchResultExtractList:DoubanSearchResultSubject[]) { + this.doubanSearchResultExtract = doubanSearchResultExtractList; + this.start(); + } + + public start(): void { + try { + this.open(); + } catch (e) { + log.error(e); + } + } + +} \ No newline at end of file diff --git a/src/douban/data/search/DoubanSearchModal.ts b/src/douban/data/search/DoubanSearchModal.ts new file mode 100644 index 0000000..3f90458 --- /dev/null +++ b/src/douban/data/search/DoubanSearchModal.ts @@ -0,0 +1,65 @@ +import { App, Editor, Modal, TextComponent } from "obsidian"; + +import DoubanPlugin from "main"; +import { i18nHelper } from "src/lang/helper"; +import { log } from "src/utils/Logutil"; + +export class DoubanSearchModal extends Modal { + searchTerm: string; + plugin: DoubanPlugin; + editor: Editor; + + constructor(app: App, plugin: DoubanPlugin, editor: Editor) { + super(app); + this.plugin = plugin; + this.editor = editor; + } + + onOpen() { + let { contentEl } = this; + + contentEl.createEl("h2", { text: i18nHelper.getMessage('Enter Search Term:') }); + + const inputs = contentEl.createDiv("inputs"); + const searchInput = new TextComponent(inputs).onChange((searchTerm) => { + this.searchTerm = searchTerm; + }); + searchInput.inputEl.addClass("search_input"); + + searchInput.inputEl.focus(); + searchInput.inputEl.addEventListener("keydown", (event) => { + if (event.key === "Enter") { + this.close(); + } + }); + + + + const controls = contentEl.createDiv("controls"); + const searchButton = controls.createEl("button", { + text: i18nHelper.getMessage('Search'), + cls: "mod-cta", + attr: { + autofocus: true, + }, + }); + searchButton.addClass("search_button"); + + searchButton.addEventListener("click", this.close.bind(this)); + const cancelButton = controls.createEl("button", { text: i18nHelper.getMessage('Cancel') }); + cancelButton.addEventListener("click", this.close.bind(this)); + cancelButton.addClass("search_button"); + + } + + + async onClose() { + let { contentEl } = this; + + contentEl.empty(); + if (this.searchTerm) { + await this.plugin.search(this.searchTerm, this.editor); + } + } + + } \ No newline at end of file diff --git a/src/douban/data/search/Search.ts b/src/douban/data/search/Search.ts new file mode 100644 index 0000000..a622440 --- /dev/null +++ b/src/douban/data/search/Search.ts @@ -0,0 +1,24 @@ +import { DoubanPluginSettings, doubanHeadrs } from 'src/douban/Douban'; +import cheerio, { load } from 'cheerio'; +import { get, readStream } from 'tiny-network'; + +import DoubanSearchResultSubject from 'src/douban/model/DoubanSearchResultSubject'; +import SearchParserHandler from './SearchParser'; +import { ensureStatusCode } from 'src/douban/ResponseHandle'; +import { log } from 'src/utils/Logutil'; + +export default class Searcher { + static search(searchItem:string, doubanSettings:DoubanPluginSettings):Promise { + // getData(); + // getData2(); + // return Promise.resolve(); + return Promise + .resolve() + .then(() => get(log.traceN("GET", doubanSettings.searchUrl + searchItem), JSON.parse(doubanSettings.searchHeaders))) + .then(ensureStatusCode(200)) + .then(readStream) + .then(load) + .then(SearchParserHandler.parseSearch) + .then(log.trace); + }; +} diff --git a/src/douban/data/search/SearchParser.ts b/src/douban/data/search/SearchParser.ts new file mode 100644 index 0000000..60fe283 --- /dev/null +++ b/src/douban/data/search/SearchParser.ts @@ -0,0 +1,28 @@ +import { CheerioAPI } from "cheerio"; +import DoubanSearchResultSubject from "src/douban/model/DoubanSearchResultSubject"; + +export default class SearchParserHandler { + static parseSearch(dataHtml:CheerioAPI):DoubanSearchResultSubject[] { + return dataHtml('.result') + .get() + .map((i:any) => { + const item = dataHtml(i); + var idPattern = /(\d){5,10}/g; + var urlPattern = /(https%3A%2F%2F)\S+(\d){5,10}/g; + var linkValue = item.find("div.content > div > h3 > a").attr("href"); + var ececResult = idPattern.exec(linkValue); + var urlResult = urlPattern.exec(linkValue); + var cast = item.find(".subject-cast").text(); + const result:DoubanSearchResultSubject = { + id: ececResult?ececResult[0]:'', + title: item.find("div.content > div > h3 > a").text(), + score: item.find(".rating_nums").text(), + cast: cast, + type: item.find("div.content > div > h3 > span").text(), + desc: item.find("div.content > p").text(), + url: urlResult?decodeURIComponent(urlResult[0]):'https://www.douban.com', + }; + return result; + }) + }; +} \ No newline at end of file diff --git a/src/lang/helper.ts b/src/lang/helper.ts new file mode 100644 index 0000000..1fe2c53 --- /dev/null +++ b/src/lang/helper.ts @@ -0,0 +1,23 @@ +import en from './locale/en'; +import zhCN from './locale/zh-cn'; + +const localeMap: { [k: string]: Partial } = { + en, + zh: zhCN, +}; + +const lang = window.localStorage.getItem('language'); +const locale = localeMap[lang || 'en']; + + +export default class I18nHelper { + public getMessage(str: keyof typeof en): string { + if (!locale) { + console.error('Error: obsidian douban locale not found', lang); + } + + return (locale && locale[str]) || en[str]; + } +} + +export const i18nHelper:I18nHelper = new I18nHelper(); \ No newline at end of file diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts new file mode 100644 index 0000000..3baa81d --- /dev/null +++ b/src/lang/locale/en.ts @@ -0,0 +1,79 @@ +//简体中文 +export default { + //main.ts + 'search douban by current file name':'search douban by current file name', + 'search douban and import to current file':'search douban and import to current file', + 'Enter Search Term:':`Enter Search Term:`, + 'Search':`Search`, + 'Cancel':`Cancel`, + "sync douban broadcast by douban user id": `sync douban broadcast ot Obsidian`, + + //DoubanSettingTab + 'douban search url': `Douban Search Url`, + 'douban search url desc 1': `Douban search page request address. `, + 'douban search url desc 2': `First go to:`, + 'douban search url desc 3': `Don't enter anything in the search input box, just click Search,`, + 'douban search url desc 4': `The redirected web page address is the search address,`, + 'douban search url desc 5': `Just copy the web address to the current input box.`, + + 'movie content template': `Movie Content Template`, + 'movie content template desc 1': `Set markdown Movie template for extract to be inserted.`, + 'movie content template desc 2': `Available template variables are :`, + 'movie content template desc 3': `{{id}}, {{type}}, {{title}}, {{originalTitle}},`, + 'movie content template desc 4': `{{score}}, {{datePublished}}, {{director}},`, + 'movie content template desc 5': `{{author}}, {{actor}}, {{desc}}, {{genre}},`, + 'movie content template desc 6': `{{image}}, {{url}}`, + + 'book content template': `Book Content Template`, + 'book content template desc 1': `Set markdown Book template for extract to be inserted.`, + 'book content template desc 2': `Available Book template variables are :`, + 'book content template desc 3': `{{id}}, {{title}}, {{originalTitle}}, {{subTitle}},`, + 'book content template desc 4': `{{score}}, {{author}}, {{datePublished}}, {{type}},`, + 'book content template desc 5': `{{publish}}, {{desc}}, {{translator}}, {{isbn}},`, + 'book content template desc 6': `{{image}}, {{url}}, {{price}}, {{desc}}, {{totalPage}}`, + + 'music content template': `Music Content Template`, + 'music content template desc 1': `Set markdown Music template for extract to be inserted.`, + 'music content template desc 2': `Available Music template variables are :`, + 'music content template desc 3': `{{id}}, {{title}}, {{actor}}, {{genre}},`, + 'music content template desc 4': `{{score}}, {{medium}}, {{datePublished}}, {{type}},`, + 'music content template desc 5': `{{publish}}, {{desc}}, {{albumType}}, {{barcode}},`, + 'music content template desc 6': `{{image}}, {{url}}, {{numberOfRecords}}, {{desc}}`, + + 'note content template': `Article Content Template`, + 'note content template desc 1': `Set markdown Article template for extract to be inserted.`, + 'note content template desc 2': `Available Article template variables are :`, + 'note content template desc 3': `{{id}}, {{title}}, {{author}}, {{authorUrl}},`, + 'note content template desc 4': `{{timePublished}}, {{url}}, {{desc}}, {{type}},`, + 'note content template desc 5': `{{content}}`, + + 'Date format': `Date Format`, + 'DateTime format': `DateTime Format`, + + 'This format will be used when available template variables contain date.': + `This format will be used when available template variables contain date.`, + 'This format will be used when available template variables contain dateTime.': + `This format will be used when available template variables contain dateTime.`, + 'For more syntax, refer to': `For more syntax, refer to`, + 'Your current syntax looks like this':`Your current syntax looks like this`, + 'format reference': `format reference`, + 'Array Spilt String':`Array Spilt String`, + 'string to join between array type, such as author, actor':`string to join between array type, such as author, actor`, + 'Douban Request Headers':`Douban Request Headers`, + 'current version not support type': `This type of import is not supported temporarily, please go to github to submit issues for help`, + 'Douban': `Douban`, + 'Person Name Language Mode':'Person Name Language Mode', + 'options:':"options:", + 'Chinese Name mode, only show Chinese name':'Chinese Name mode, person name only show Chinese name', + 'English Name mode, only show English name':'English Name mode, person name only show English name', + 'Chinese English Name mode, show Chinese English name both':'Chinese English Name mode, show Chinese and English name both', + 'Chinese Name': 'Chinese Name', + 'English Name': 'English Name', + 'Chinese And English Name': 'Chinese And English Name', + + + //error + "Fetch Data Error": "Fetch Data Error, You can go to Github add Issues", + "Obsidian Douban Plugin Error:": "Obsidian Douban Plugin Error: ", + "Obsidian Douban Plugin Warn:": "Obsidian Douban Plugin Warn: ", +} \ No newline at end of file diff --git a/src/lang/locale/zh-cn.ts b/src/lang/locale/zh-cn.ts new file mode 100644 index 0000000..a7124c4 --- /dev/null +++ b/src/lang/locale/zh-cn.ts @@ -0,0 +1,84 @@ +//简体中文 +export default { + //main.ts + 'search douban by current file name':'用当前文档名搜索豆瓣并写入当前文档', + 'search douban and import to current file':'在豆瓣搜索并写入到当前文档', + 'Enter Search Term:':`输入搜索内容:`, + 'Search':`搜索`, + 'Cancel':`取消`, + "sync douban broadcast by douban user id": `同步豆瓣广播至Obsidian`, + + //DoubanSettingTab + 'douban search url': `豆瓣搜索地址`, + 'douban search url desc 1': `豆瓣搜索页面请求地址, 通常是网页搜索的地址. `, + 'douban search url desc 2': `先访问:`, + 'douban search url desc 3': `然后在搜索输入框不输入任何内容,直接点击搜索,`, + 'douban search url desc 4': `所跳转的网页地址即是搜索地址,`, + 'douban search url desc 5': `将网页地址复制到当前输入框即可,`, + + 'movie content template': `电影文本模板`, + 'movie content template desc 1': `设置选择电影后导入的文本内容模板,`, + 'movie content template desc 2': `支持以下参数名称 :`, + 'movie content template desc 3': `{{id}}, {{type}}, {{title}}, {{originalTitle}},`, + 'movie content template desc 4': `{{score}}, {{datePublished}}, {{director}},`, + 'movie content template desc 5': `{{author}}, {{actor}}, {{desc}}, {{genre}},`, + 'movie content template desc 6': `{{image}}, {{url}}`, + + 'book content template': `书籍文本模板`, + 'book content template desc 1': `设置选择书籍后导入的文本内容模板,`, + 'book content template desc 2': `支持以下参数名称 :`, + 'book content template desc 3': `{{id}}, {{title}}, {{originalTitle}}, {{subTitle}},`, + 'book content template desc 4': `{{score}}, {{author}}, {{datePublished}}, {{type}},`, + 'book content template desc 5': `{{publish}}, {{desc}}, {{translator}}, {{isbn}},`, + 'book content template desc 6': `{{image}}, {{url}}, {{price}}, {{desc}}, {{totalPage}}`, + + 'music content template': `音乐文本模板`, + 'music content template desc 1': `设置选择音乐后导入的文本内容模板,`, + 'music content template desc 2': `支持以下参数名称 :`, + 'music content template desc 3': `{{id}}, {{title}}, {{actor}}, {{genre}},`, + 'music content template desc 4': `{{score}}, {{medium}}, {{datePublished}}, {{type}},`, + 'music content template desc 5': `{{publish}}, {{desc}}, {{albumType}}, {{barcode}},`, + 'music content template desc 6': `{{image}}, {{url}}, {{numberOfRecords}}, {{desc}}`, + + 'note content template': `日记文本模板`, + 'note content template desc 1': `设置选择日记后导入的文本内容模板,`, + 'note content template desc 2': `支持以下参数名称 :`, + 'note content template desc 3': `{{id}}, {{title}}, {{author}}, {{authorUrl}},`, + 'note content template desc 4': `{{timePublished}}, {{url}}, {{desc}}, {{type}},`, + 'note content template desc 5': `{{content}}`, + + + 'Date format': `参数日期格式`, + 'This format will be used when available template variables contain date.': + `这个格式是给上面获取到的参数进行格式化日期时显示的内容 .`, + + 'DateTime format': `参数时间格式`, + 'This format will be used when available template variables contain dateTime.': + `这个格式是给上面获取到的参数进行格式化时间时显示的内容 .`, + 'For more syntax, refer to': `详细介绍请参考`, + 'Your current syntax looks like this':`时间参数时间格式预览`, + 'format reference': `格式参考`, + 'Array Spilt String':`数组分割字符串`, + 'string to join between array type, such as author, actor':`当模板中的变量存在数组, 则需要设定数组元素中的分割符号,比如 演员列表等`, + 'Douban Request Headers':`豆瓣HTTP请求头`, + 'Douban Request Headers Desc': `如果豆瓣搜索或者获取数据失败,请尝试修改这个参数,\n + 参数获取方式为:\n + 1. 访问http://www.douban.com + 2. 复制请求头,仅复制以下请求头 `, + 'current version not support type': `暂时不支持该类型导入,请至github提交issuess获取帮助`, + 'Douban': `豆瓣网`, + + 'Person Name Language Mode':'人名显示模式', + 'options:':"可选项:", + 'Chinese Name mode, only show Chinese name':'中文名称模式, 人名只显示中文名', + 'English Name mode, only show English name':'英文名称模式, 人名只显示英文名', + 'Chinese English Name mode, show Chinese English name both':'中文和英文名称模式, 人名同时显示中文和英文名', + 'Chinese Name': '中文名', + 'English Name': '英文名', + 'Chinese And English Name': '中文名和英文名', + + "Fetch Data Error": "获取数据失败,您如有需要请至Github提交Issues", + "Obsidian Douban Plugin Error: ": "Obsidian Douban插件错误提示:", + "Obsidian Douban Plugin Warn: ": "Obsidian Douban插件异常提示:", + +} \ No newline at end of file diff --git a/src/typings/tiny-network.d.ts b/src/typings/tiny-network.d.ts new file mode 100644 index 0000000..03d3fb7 --- /dev/null +++ b/src/typings/tiny-network.d.ts @@ -0,0 +1,6 @@ +declare module 'tiny-network' { + export function get(url:string, headers:any): any; + export function get(url:string): any; + export function readStream(param:any): any; + export function ensureStatusCode(code:number): any; +} \ No newline at end of file diff --git a/src/utils/HttpUtil.ts b/src/utils/HttpUtil.ts new file mode 100644 index 0000000..5d2b583 --- /dev/null +++ b/src/utils/HttpUtil.ts @@ -0,0 +1,34 @@ +import * as https from 'https'; + +import { get } from 'tiny-network'; +import { log } from './Logutil'; + +export default class HttpUtil { + + static getHttps(url:string, options:any):Promise { + return new Promise( + function (resolve, reject) { + https.get(url + '/', options, (response) => { + console.log('url:', url + '/'); + console.log('statusCode:', response.statusCode); + console.log('headers:', response.headers); + if (response.statusCode === 200) { + response.on('data', (d) => { + resolve(d); + }); + } if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode == 303) { + resolve(response.headers.location); + } else { + reject(new Error(response.statusMessage)); + } + response.on('data', (d) => { + process.stdout.write(d); + }); + + }).on('error', (e) => { + reject(new Error('XMLHttpRequest Error: ' + e.message)); + }); + + }); + } +} \ No newline at end of file diff --git a/src/utils/Logutil.ts b/src/utils/Logutil.ts new file mode 100644 index 0000000..21e3793 --- /dev/null +++ b/src/utils/Logutil.ts @@ -0,0 +1,35 @@ +import { Notice } from "obsidian"; +import SchemaOrg from "./SchemaOrg"; +import { i18nHelper } from "src/lang/helper"; + +class Logger { + + public error(e:any):any { + new Notice(i18nHelper.getMessage("Obsidian Douban Plugin Error:") + e); + return e; + } + + public warn(e:any):any { + new Notice(i18nHelper.getMessage("Obsidian Douban Plugin Warn:") + e); + return e; + } + + public info(e:any):any { + // console.log(`Douban Plugin info:` + `${typeof e == 'string' ? e : JSON.stringify(e)}`); + return e; + } + + public trace(e:any):any { + // return e; + // console.log(`Douban Plugin trace:` + `${typeof e == 'string' ? e : JSON.stringify(e)}`); + return e; + } + + public traceN(notion:string, e:any):any { + // return e; + // console.log(`${notion} ${typeof e == 'string' ? e : JSON.stringify(e)}`); + return e; + } +} + +export const log:Logger = new Logger(); \ No newline at end of file diff --git a/src/utils/SchemaOrg.ts b/src/utils/SchemaOrg.ts new file mode 100644 index 0000000..50a1a82 --- /dev/null +++ b/src/utils/SchemaOrg.ts @@ -0,0 +1,25 @@ +import { Person } from "schema-dts"; + +export default class SchemaOrg { + public static getPersonName(p:Person):string { + if(isString(p)) { + return p; + }else { + let name: any = getProperty(p, 'name'); + return name + ""; + } + } + + +} + +function isString(s:any): s is string { + return typeof s === 'string'; + } + + +function getProperty(o: T, name: K): T[K] { + return o[name]; +} + + diff --git a/test/broadcast_abstract.txt b/test/broadcast_abstract.txt new file mode 100644 index 0000000..87c43ae --- /dev/null +++ b/test/broadcast_abstract.txt @@ -0,0 +1,118 @@ +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + +★★★★☆ + +

前半部分非常棒,逻辑细节都非常棒。比如喝医生讨价还价那段和我媳妇很像,因为做过小生意的人都会来那么一下。后半部分有点不太合理,主要是黄毛白送那一段,剧情有点无脑。 +总体非常棒。 +穷病 这回事看怎么理解,资源就那么多,需求那么大,总有个分配的方式,总有一部分人能拥有一部分无法得到。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

普通中年男子程勇(徐峥 饰)经营着一家保健品店,失意又失婚。不速之客吕受益(王... +

+
    +
  • 导演:文牧野 Muye Wen +
  • 主演:徐峥 Zheng Xu +
  • 类型:剧情 喜剧 +
+
+
+
+ + + + + + +
+ +2021年12月28日 + + + 回应 + + + + +   转发 + + +  删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
\ No newline at end of file diff --git a/test/broadcast_movie.txt b/test/broadcast_movie.txt new file mode 100644 index 0000000..bc94a9b --- /dev/null +++ b/test/broadcast_movie.txt @@ -0,0 +1,111 @@ +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + +★★★★☆ + +

前半部分非常棒,逻辑细节都非常棒。比如喝医生讨价还价那段和我媳妇很像,因为做过小生意的人都会来那么一下。后半部分有点不太合理,主要是黄毛白送那一段,剧情有点无脑。 +总体非常棒。 +穷病 这回事看怎么理解,资源就那么多,需求那么大,总有个分配的方式,总有一部分人能拥有一部分无法得到。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

普通中年男子程勇(徐峥 饰)经营着一家保健品店,失意又失婚。不速之客吕受益(王... +

+
    +
  • 导演:文牧野 Muye Wen +
  • 主演:徐峥 Zheng Xu +
  • 类型:剧情 喜剧 +
+
+
+
+ + + + + + +
+ +2021年12月28日 + + + 回应 + + + + +   转发 + + +  删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/test/broadcast_transformer.txt b/test/broadcast_transformer.txt new file mode 100644 index 0000000..9e4f771 --- /dev/null +++ b/test/broadcast_transformer.txt @@ -0,0 +1,3677 @@ + + + + + + + + + 我的广播 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + +
+ + +
+ + + + +
+
+ + w977741432 + +
+
+

+ 我的广播 +

+ +
+
+ + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 读过 + + + +
+ + + ★★★★☆ + +

自我的意义是独立。一本改变我认知的书,让我在很多时候变得更加自信且平静。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 自我 + + 8.6 +
+ +

我自己到底是谁;如何对他人和环境做出思考和反应;如何才能最好地了解自己;如何... +

+
    +
  • 作者:[美]乔纳森·布朗(Jonathon D. Brown) 玛格丽特·布朗(Margaret A. Brown) + +
  • 出版:人民邮电出版社 + +
+
+
+
+ + + + + + +
+ + 1月18日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 在读 + + + +
+ + + ★★★★☆ + +

每当我读鼠疫的时候我都会打开手机,查看下美国新冠疫情数据和中国的新冠疫情数据。历史总是惊人的相似。之前买了本 加缪文集,正好读到鼠疫。 +当厄运来临时,我们不会过分夸赞支持者的行为,因为这会这种行为显得难能可贵。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 鼠疫 + + 9.1 +
+ +

阿尔贝•加缪(1913—1960)是法国声名卓著的小说家、散文家和剧作家,“存在主... +

+
    +
  • 作者:[法] 阿尔贝·加缪 + +
  • 出版:上海译文出版社 + +
+
+ + + + 豆瓣图书Top250 NO.65 + +
+
+ + + + + + +
+ + 1月14日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + + ★★★☆☆ + +

转场很好看,剧情比较简单,最终也是没看懂,三星。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

近未来,为了治疗现代人类越来越多、越来越严重的精神疾病,位于东京的精神医疗综... +

+
    +
  • 导演:今敏 Satoshi Kon +
  • 主演:林原惠美 Megumi Hayashibara +
  • 类型:动画 悬疑 科幻 惊悚 +
+
+
+
+ + + + + + +
+ + 1月14日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想看 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

近未来,为了治疗现代人类越来越多、越来越严重的精神疾病,位于东京的精神医疗综... +

+
    +
  • 导演:今敏 Satoshi Kon +
  • 主演:林原惠美 Megumi Hayashibara +
  • 类型:动画 悬疑 科幻 惊悚 +
+
+
+
+ + + + + + +
+ + 1月14日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + + ★★★★☆ + +

分不清哪个是真哪个是假,就像时间,人的记忆和生活,彷忽之间跳跃了。看到最后我都不知道那个是真哪个是假。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

当红三人少女偶像团体Charming Bird面临解散,核心成员雾越未麻在事务所的安排下不... +

+
    +
  • 导演:今敏 Satoshi Kon +
  • 主演:岩男润子 Junko Iwao +
  • 类型:动画 奇幻 惊悚 +
+
+
+
+ + + + + + +
+ + 1月14日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想看 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 黑冰‎ (2001) + + 8.0 +
+ +

一桩桩案件惊现海州市,引起了相关部门的注意。因这一起起案件的交集点都在一个叫... +

+
    +
  • 导演:王冀邢 Jixing Wang +
  • 主演:王志文 Zhiwen Wang +
  • 类型:剧情 悬疑 +
+
+
+
+ + + + + + +
+ + 1月1日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + + ★★★★☆ + +

前半部分非常棒,逻辑细节都非常棒。比如喝医生讨价还价那段和我媳妇很像,因为做过小生意的人都会来那么一下。后半部分有点不太合理,主要是黄毛白送那一段,剧情有点无脑。 +总体非常棒。 +穷病 这回事看怎么理解,资源就那么多,需求那么大,总有个分配的方式,总有一部分人能拥有一部分无法得到。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

普通中年男子程勇(徐峥 饰)经营着一家保健品店,失意又失婚。不速之客吕受益(王... +

+
    +
  • 导演:文牧野 Muye Wen +
  • 主演:徐峥 Zheng Xu +
  • 类型:剧情 喜剧 +
+
+
+
+ + + + + + +
+ + 2021年12月28日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + + ★★★☆☆ + +

女主太绿茶了,明知男主喜欢还要利用,支持男主的做法。女主这么做,可怜之人必有可恨之处。

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

讲述了梦想成为网络漫画家的男主人公偶然得知自己仰慕的校花在从事援助交际而发生... +

+
    +
  • 导演:Deok-pyo Hong +
  • 主演:Yeong-gi Jeong +
  • 类型:动画 +
+
+
+
+ + + + + + +
+ + 2021年12月25日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想看 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

本片是80多岁高龄的德国电影大师爱德嘉·莱兹对其史诗系列作品的总结,亦是其电影... +

+
    +
  • 导演:爱德嘉·莱兹 Edgar Reitz +
  • 主演:扬·迪特尔·施耐德 Jan Dieter Schneider +
  • 类型:剧情 历史 +
+
+
+
+ + + + + + +
+ + 2021年12月25日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想看 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

意大利南部小镇,古灵精怪的小男孩托托(萨瓦特利·卡西欧 饰)喜欢看电影,更喜欢... +

+
    +
  • 导演:朱塞佩·托纳多雷 Giuseppe Tornatore +
  • 主演:菲利普·努瓦雷 Philippe Noiret +
  • 类型:剧情 爱情 +
+
+
+
+ + + + + + +
+ + 2021年12月25日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想看 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

米尔德雷德(弗兰西斯·麦克多蒙德 Frances McDormand 饰)的女儿在外出时惨遭奸杀... +

+
    +
  • 导演:马丁·麦克唐纳 Martin McDonagh +
  • 主演:弗兰西斯·麦克多蒙德 Frances McDormand +
  • 类型:剧情 犯罪 +
+
+
+
+ + + + + + +
+ + 2021年12月25日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + + ★★☆☆☆ + +

好多处都不符合逻辑,特别是频频送人头的行为

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

2022年地球收到一份来自未来的召唤:30年后的地球遭遇未知物种的攻击,面临灭种危... +

+
    +
  • 导演:克里斯·麦凯 Chris McKay +
  • 主演:克里斯·帕拉特 Chris Pratt +
  • 类型:动作 科幻 冒险 +
+
+
+
+ + + + + + +
+ + 2021年12月12日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 在读 + + + +
+ + + ★★★☆☆ + +

尊严与道德伦理讲的比较多 居住环境与尊严 一篇讲的挺好的

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 沉默的大多数 + + 8.9 +
+ +

☆ 用沉默影响世界! +☆ 恢复《积极的结论》《论战与道德》《个人尊严》《域外杂谈... +

+
    +
  • 作者:王小波 + +
  • 出版:北京十月文艺出版社 + +
+
+
+
+ + + + + + +
+ + 2021年12月10日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 在读 + + + +
+ + + ★★★☆☆ + +

尊严与道德伦理讲的比较多 居住环境与尊严 一篇讲的挺好的

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 沉默的大多数 + + 9.1 +
+ +

这本杂文随笔集包括思想文化方面的文章,涉及知识分子的处境及思考,社会道德伦理... +

+
    +
  • 作者:王小波 + +
  • 出版:中国青年出版社 + +
+
+ + + + 豆瓣图书Top250 NO.20 + +
+
+ + + + + + +
+ + 2021年12月10日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想读 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

本书结合设计实例从面向对象的设计中精选出23个设计模式, 总结了面向对象设计中*有... +

+
    +
  • 作者:[美] Erich Gamma + +
  • 出版:机械工业出版社 + +
+
+
+
+ + + + + + +
+ + 2021年11月28日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想读 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

克里斯托弗·亚力山大是美国杰出的建筑理论家。由他领衔撰写的《建筑模式语言》一... +

+
    +
  • 作者:[美] C. 亚历山大 等 + +
  • 出版:知识产权出版社 + +
+
+
+
+ + + + + + +
+ + 2021年11月28日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想读 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 正义之心 + + 8.6 +
+ +

[内容简介] +☆ 一个你认为颇有价值的决定在别人眼里可能一文不值,一个人眼中的恐... +

+
    +
  • 作者:[美] 乔纳森•海特(Jonathan Haidt) + +
  • 出版:浙江人民出版社 + +
+
+
+
+ + + + + + +
+ + 2021年11月28日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 看过 + + + +
+ + + ★★★☆☆ + +

搞笑片,带点推理,权当娱乐

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+ + +

月黑风高之夜,一群电影人被秘密召集到一起,欲将轰动一时的血案翻拍成电影,借此... +

+
    +
  • 导演:刘循子墨 Xunzimo Liu +
  • 主演:尹正 Zheng Yin +
  • 类型:剧情 喜剧 悬疑 +
+
+
+
+ + + + + + +
+ + 2021年11月26日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 读过 + + + +
+ + + ★★★★☆ + +

诙谐幽默,谈的观点有局限但前卫

+
+ +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 我的精神家园 + + 9.1 +
+ +

一九九七年六月,王小波逝世两个月后,他的杂文自选集《我的精神家园》出版。这是... +

+
    +
  • 作者:王小波 + +
  • 出版:文化艺术出版社 + +
+
+
+
+ + + + + + +
+ + 2021年11月20日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+ +
+ + + + +
+ + + w977741432 + +
+ +
+ w977741432 + 想看 + + + + +
+ +
+ +
+ + + + + + +
+
+ +
+
+
+ 沙丘 Dune‎ (2021) + + 7.7 +
+ +

电影《沙丘》为观众呈现了一段神秘而感人至深的英雄之旅。天赋异禀的少年保罗·厄... +

+
    +
  • 导演:丹尼斯·维伦纽瓦 Denis Villeneuve +
  • 主演:蒂莫西·柴勒梅德 Timothée Chalamet +
  • 类型:剧情 科幻 冒险 +
+
+
+
+ + + + + + +
+ + 2021年11月17日 + + + 回应 + + + + +   转发 + + +   删除 +
+ + +
+
+
+ +
+
+ +
+
+
+ + + + + + + + +
+ + + + + + + + +
+ + + + +
+ + + <前页 + + + + + + 1 + + 2 + + 3 + + + 4 + + + 5 + + + 6 + + + 7 + + + 8 + + + 9 + + ... + + + 后页> + + +
+ + +
+
+ + +

> 我的广播的隐私设置

+ + + + + + + + +
+
+ w977741432 +
+
+ w977741432 + (上海) +

far away from target

+
+
+
+
+ + + + + + + + + + +
+
+ +
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/versions.json b/versions.json index a1eceb0..2d3fcd1 100644 --- a/versions.json +++ b/versions.json @@ -4,6 +4,8 @@ "1.1.0": "0.12.0", "1.2.0": "0.12.0", "1.3.0": "0.12.0", - "1.4.0": "0.12.0" + "1.4.0": "0.12.0", + "1.4.1": "0.12.0", + "1.4.2": "0.12.0" }