Compare commits

...

45 Commits

Author SHA1 Message Date
812fc17c5a build: 升级版本为2.3.2
Some checks failed
Release Draft / build (push) Has been cancelled
2026-04-02 16:33:59 +08:00
ChrsiGray
e8a0d8a00c fix: support douban image uploads with referer
- use subject page referer for douban image requests\n- keep PicGo clipboard upload flow and await clipboard writes\n- add webp clipboard fallback for image decoding
2026-04-02 11:26:16 +08:00
ChrsiGray
318aabb21b fix: upload Douban images to PicGo via temp file 2026-04-02 11:26:16 +08:00
a8094d7575
Merge pull request #177
fix: All search type 403, undefined actor crash, and lazy login on startup
2026-03-11 23:27:55 +08:00
YuBai
7ba1a7be0c feat: implement lazy login to eliminate startup network request
Replace eager login verification on startup with assumeLoggedIn(),
which sets login state from saved credentials without any network call.
Real verification is deferred until sync (which needs the actual user ID)
or when the settings page is opened.

- UserComponent: add verified flag, assumeLoggedIn(), isVerified()
- main.ts: replace onLayoutReady(login) with assumeLoggedIn(); fix
  inverted condition bug in checkLogin() (!needLogin -> needLogin)
- DoubanSyncModal: remove redundant login check before checkLogin()
- LoginSettingsHelper: handle assumed-but-unverified state in constructLoginUI()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 20:38:48 +08:00
YuBai
4e8d3f1318 Merge branch 'fix-undefined-actor-error' into fix/search-all-api-and-login-init 2026-03-04 18:25:45 +08:00
YuBai
c375512903 fix: restore All search type and initialize login state on startup
- Switch AllPageSearchPageFetcher from m.douban.com/rexxar/api/v2/search
  (returns 403 need_login) to www.douban.com/j/search (works with cookies)
- Update AllFirstPageSearchResultPageParser and OtherAllPageSearchResultPageParser
  to use SearchParserHandler.parseSearchJson() matching the j/search response format,
  replacing the old Rexxar-specific SearchParserHandlerV2 parsing logic
- Add await this.userComponent.login() in onload() so login state is initialized
  on startup, fixing false isLogin()=false causing "need login for next page" errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:20:58 +08:00
YuBai
99d4170626 fix: 为其他 handler 添加数组空值检查
在 parseVariable 方法中为所有数组字段的 .map() 调用添加 (field || []) 保护,

防止当字段为 undefined 时调用 .map() 报错。
2026-02-02 11:26:47 +08:00
YuBai
297ccd33cf fix: 当 JSON-LD 解析失败时添加回退机制
豆瓣现在对未登录请求返回反爬虫验证页面,导致 JSON-LD 解析返回 undefined。

添加从 OG meta 标签提取基本信息的回退机制,防止代码崩溃。

同时给所有数组字段添加默认值 || []。
2026-02-02 11:25:56 +08:00
YuBai
a0eccf7370 fix: handle() 方法 catch 块返回 undefined 而不是错误对象
当 HTTP 请求失败时,返回错误对象会导致上层代码尝试访问错误对象的属性,

造成 TypeError。改为返回 undefined 让上层代码正确处理空值情况。
2026-02-02 11:24:07 +08:00
572907719b build: 升级版本为2.3.1
Some checks failed
Release Draft / build (push) Has been cancelled
2025-11-09 23:17:43 +08:00
ffc93a4a13 feat: 调整别名的转义 2025-11-09 23:16:34 +08:00
08749116f3 feat: 附件(图片)文件保存本地支持自定义
fix: 修复有些图片无法下问题
fix: 修复同步汇总文件中文件名和真实保存不一致问题
feat: 调整别名的转义
2025-11-09 22:43:17 +08:00
266cfb3901 docs: change home page header
Some checks failed
Deploy Jekyll site to Pages / build (push) Has been cancelled
Deploy Jekyll site to Pages / deploy (push) Has been cancelled
2025-10-10 18:05:36 +08:00
7e57846f32 docs: add documents website 2025-07-24 08:40:42 +08:00
16b9ff581d docs: add documents website
Some checks failed
Deploy Jekyll site to Pages / build (push) Has been cancelled
Deploy Jekyll site to Pages / deploy (push) Has been cancelled
2025-07-24 00:01:09 +08:00
dd1648c20a fix #151 笔记存放位置,无法使用自定义字段 2025-06-05 14:00:28 +08:00
df4c4956f1 upgrade to 2.3.0
Some checks failed
Release Draft / build (push) Has been cancelled
2025-03-11 17:07:09 +08:00
c09f96d3b8 fix #143 #132 #124
1. 增加游戏同步功能
2. 修复自定义参数配置问题
3. 增加搜索默认类型
4. 增加全部搜索为空时的提示
2025-03-11 14:45:14 +08:00
a7b81bd189 upgrade: 升级为2.2.4
Some checks failed
Release Draft / build (push) Has been cancelled
2025-03-10 09:03:46 +08:00
9403fef320 feature: #132 同步游戏 2025-03-10 09:02:54 +08:00
001cb5dc3e 调整说明
Some checks failed
Release Draft / build (push) Waiting to run
Deploy Jekyll site to Pages / build (push) Has been cancelled
Deploy Jekyll site to Pages / deploy (push) Has been cancelled
2025-03-09 16:11:22 +08:00
711744e065 调整说明 2025-03-09 13:48:25 +08:00
836c9c61b3 test 增加提交发布功能 2025-03-09 13:40:30 +08:00
f0b73c7d8b test 增加提交发布功能 2025-03-09 13:16:20 +08:00
8556de0501 test 增加提交发布功能 2025-03-09 13:11:55 +08:00
6481c69d81 test 增加提交发布功能 2025-03-09 13:06:14 +08:00
29322e0869 test 增加提交发布功能 2025-03-09 12:57:10 +08:00
431c1f2564 test 增加提交发布功能 2025-03-09 12:52:20 +08:00
89f43432d2 enhance 增加提交发布功能 2025-03-09 12:50:06 +08:00
20b6daf10e enhance 增加提交发布功能 2025-03-09 12:46:42 +08:00
208e0d2061 enhance 增加提交发布功能 2025-03-09 12:40:31 +08:00
449b96cbd1 enhance 增加提交发布功能 2025-03-09 12:39:49 +08:00
ee514a8c95 enhance 增加提交发布功能 2025-03-09 12:24:08 +08:00
161741b4af Update pages.yml 2025-03-09 12:04:02 +08:00
382e7c87dd
Update pages.yml 2025-03-09 11:58:55 +08:00
671b28f91a feature 游戏同步
Some checks are pending
Deploy Jekyll site to Pages / build (push) Waiting to run
Deploy Jekyll site to Pages / deploy (push) Blocked by required conditions
1. 增加游戏同步页面
2. 增加支持二维码
2025-03-09 00:13:21 +08:00
0aa0c15cc5 fix #139
1. 优化导入配置,增加更多导入时的选项
2. 优化导入结果的显示,现在会在结果文本中显示具体的条件
2025-03-06 18:55:39 +08:00
9fe0d1bfc9 fix #139
1. 优化导入配置,增加更多导入时的选项
2. 优化导入结果的显示,现在会在结果文本中显示具体的条件
2025-03-06 18:54:01 +08:00
ea700e481e fix #139 增加过滤条件:时间或条数限制 2025-03-06 18:54:01 +08:00
dc225f30f1 增加版本为2.2.0 2025-03-06 18:54:01 +08:00
a120e450e9 fix #139 #128
1. 优化配置将配置页面调整为tab
2. 优化文件夹选取方式避免卡顿
3. 优化模板文件选取方式避免卡顿
4. 增加设置的导入导出用以备份
5. 优化加载插件的卡顿问题,现在加载会非常快
6. 优化了配置的文本,简化了文本内容
2025-03-06 18:54:01 +08:00
c866c8be4c
Merge pull request #134 from vran-dev/fix/redundant_space
Fix: remove leading and and trailing whitespace of person name
2025-02-24 14:36:39 +08:00
vran
132e47f2b2 fix: remove leading and and trailing whitespace of person name 2025-01-22 13:36:14 +08:00
wanxuping
eeedf23e7c fix #94 移动端加载失败 2024-12-26 13:47:01 +08:00
122 changed files with 3340 additions and 10375 deletions

@ -55,7 +55,7 @@ jobs:
JEKYLL_ENV: production JEKYLL_ENV: production
- name: Upload artifact - name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by default # Automatically uploads an artifact from the './_site' directory by default
uses: actions/upload-pages-artifact@v1 uses: actions/upload-pages-artifact@v3
with: with:
path: "doc/_site/" path: "doc/_site/"
# Deployment job # Deployment job
@ -68,4 +68,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v2 uses: actions/deploy-pages@v4

97
.github/workflows/release-draft.yml vendored Normal file

@ -0,0 +1,97 @@
name: Release Draft
on:
push:
paths:
- 'manifest.json'
branches:
- "master"
workflow_dispatch:
permissions:
contents: write
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
environment: node18
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
- name: Extract version from package.json
id: extract_version
run: echo "VERSION=$(jq -r '.version' package.json)" >> $GITHUB_ENV
- name: Set Git user
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Create tag
run: git tag -a "${{ env.VERSION }}" -m "Release version ${{ env.VERSION }}"
- name: Push tag
run: git push origin "${{ env.VERSION }}"
- name: Create GitHub release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: "${{ env.VERSION }}"
release_name: "${{ env.VERSION }}"
draft: true
prerelease: false
generate_release_notes: true
- name: Upload release assets
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./main.js
asset_name: main.js
asset_content_type: application/javascript
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ env.VERSION }}
- name: Upload manifest.json
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./manifest.json
asset_name: manifest.json
asset_content_type: application/json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ env.VERSION }}
- name: Upload style.css
uses: actions/upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./styles.css
asset_name: styles.css
asset_content_type: text/css
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ env.VERSION }}
- name: Output release message
run: echo "发布预发布版本${{ env.VERSION }}正常"

2
.gitignore vendored

@ -20,3 +20,5 @@ data.json
# Exclude macOS Finder (System Explorer) View States # Exclude macOS Finder (System Explorer) View States
.DS_Store .DS_Store
doc/.vitepress/dist
doc/.vitepress/cache

@ -21,13 +21,13 @@
Bring your data from [Douban]() to Your [Obsidian](https://obsidian.md/) Bring your data from [Douban]() to Your [Obsidian](https://obsidian.md/)
Including your _Movie, Book, Music, Teleplay, Note, Game_ even your personal State and Comment Including your _Movie, Book, Music, Teleplay, Note, Game_ even your personal State and Comment
![background](./background.png) ![background](doc/background.png)
--- ---
If you want some features or have any questions about this plugin, create issues or join the development is welcome or ⭐Star If you want some features or have any questions about this plugin, create issues or join the development is welcome or ⭐Star
- [Bugs, Issues, & Feature Requests](https://github.com/Wanxp/obsidian-douban/issues) - [Bugs, Issues, & Feature Requests](https://github.com/Wanxp/obsidian-douban/issues)
- Read Other Languages: English | [简体中文](../README.md) - Read Other Languages: English | [简体中文](README.md)
## Target ## Target
- [x] Sync Personal Movie/TV/Book/Music - [x] Sync Personal Movie/TV/Book/Music
@ -36,23 +36,23 @@ If you want some features or have any questions about this plugin, create issues
- [x] Custom Variables - [x] Custom Variables
## 效果 ## 效果
1. 结合Timeline插件 __构建个人观影时间线__,请参照[结合timeline插件实现时间线效果](./Obsidian-Douban-TimeLine.md) 1. 结合Timeline插件 __构建个人观影时间线__,请参照[结合timeline插件实现时间线效果](doc/Obsidian-Douban-TimeLine.md)
![](./img/obsidian-douban-time-preview-example.gif) ![](doc/img/obsidian-douban-time-preview-example.gif)
<!--2. 结合DataView插件__构建个人电子书架书库数据__请参照[结合dateview插件实现个人书架效果](./doc/Obsidian-Douban-DataView.md))--> <!--2. 结合DataView插件__构建个人电子书架书库数据__请参照[结合dateview插件实现个人书架效果](./doc/Obsidian-Douban-DataView.md))-->
2. 结合主题 __构建类豆瓣网页效果__,请参照[结合Blue Topaz实现网页效果](./Obsidian-Douban-BlueTopaz.md) 2. 结合主题 __构建类豆瓣网页效果__,请参照[结合Blue Topaz实现网页效果](doc/Obsidian-Douban-BlueTopaz.md)
![](./background.png) ![](doc/background.png)
## How to use ## How to use
### Sync ### Sync
- Sync Data From Douban - Sync Data From Douban
Sync data from Douban (to learn how to use the TimeLine plugin to build a reading/movie watching timeline, please refer to [here](Obsidian-Douban-TimeLine.en.md)). Sync data from Douban (to learn how to use the TimeLine plugin to build a reading/movie watching timeline, please refer to [here](Obsidian-Douban-TimeLine.en.md)).
![Sync Data From Douban](img/sync_data_from_douban.gif) ![Sync Data From Douban](doc/img/sync_data_from_douban.gif)
### Search ### Search
Use the following method: Enter <kbd>Ctrl</kbd> + <kbd>P</kbd>, enter "Douban", select search and use Use the following method: Enter <kbd>Ctrl</kbd> + <kbd>P</kbd>, enter "Douban", select search and use
- Search Data And Create Note - Search Data And Create Note
- Search Data By File Name - Search Data By File Name
- Search Movie By Input Text - Search Movie By Input Text
![Search Movie By File Name](img/search_and_create_note.gif) ![Search Movie By File Name](doc/img/search_and_create_note.gif)
## Support Field ## Support Field
@ -119,9 +119,7 @@ Use the following method: Enter <kbd>Ctrl</kbd> + <kbd>P</kbd>, enter "Douban",
7. Enjoy your develop 7. Enjoy your develop
## Community ## Community
<img src="img/obsidian-douban-qq-qr_code.svg" width="300px"> <img src="doc/img/obsidian-douban-qq-qr_code.svg" width="300px"> <img src="doc/img/wechat_group.png" width="245px">
<img src="https://picture-bed-public.wanxuping.com/obsidian-douban/wechat_group.png" width="245px">
## Disclaimer ## Disclaimer
1. This program does not crawl any content such as books and videos, and is only for technical research purposes. It does not violate the copyright of authors of books and videos or the official interests of Douban. If there is any infringement, please contact me to delete it. 1. This program does not crawl any content such as books and videos, and is only for technical research purposes. It does not violate the copyright of authors of books and videos or the official interests of Douban. If there is any infringement, please contact me to delete it.

@ -22,7 +22,7 @@
这是一款[Obsidian](https://obsidian.md/)插件支持在Obsidian中导入[豆瓣]()中的 _电影、书籍、音乐、电视剧、日记、游戏_ 这是一款[Obsidian](https://obsidian.md/)插件支持在Obsidian中导入[豆瓣]()中的 _电影、书籍、音乐、电视剧、日记、游戏_
甚至是 _你标记过的书影音_ , 包含你的评分、观看日期、评论、阅读状态等信息. 甚至是 _你标记过的书影音_ , 包含你的评分、观看日期、评论、阅读状态等信息.
访问[Get Started/指导手册](https://wanxp.github.io/obsidian-douban/) 获取更多 访问[Get Started/指导手册](https://obsidian-douban.wxp.hk/) 获取更多
[//]: # (访问[Get Started/指导手册]&#40;https://obsidian-douban.wanxuping.com/&#41; 获取更多 ) [//]: # (访问[Get Started/指导手册]&#40;https://obsidian-douban.wanxuping.com/&#41; 获取更多 )
![background](./doc/background.png) ![background](./doc/background.png)
@ -32,16 +32,17 @@
如果觉得喜欢或对您有帮助,欢迎一键三连-点亮 ⭐Star 如果觉得喜欢或对您有帮助,欢迎一键三连-点亮 ⭐Star
- [异常, 问题 & 新的想法](https://github.com/Wanxp/obsidian-douban/issues) - [异常, 问题 & 新的想法](https://github.com/Wanxp/obsidian-douban/issues)
- 阅读其它语言的介绍请点击 [English](./doc/README.en.md) | 简体中文 - 阅读其它语言的介绍请点击 [English](README.en.md) | 简体中文
## 功能 ## 功能
- ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记 - ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记
- ☑️ 同步个人听过/看过的电影、电视剧、书籍、音乐 - ☑️ 同步个人听过/看过的电影、电视剧、书籍、音乐、游戏
- ☑️ 导入个人的评论,评论时间,阅读状态,个人评分 - ☑️ 导入个人的评论,评论时间,阅读状态,个人评分
- ☑️ 支持保存封面至本地/图床 - ☑️ 支持保存封面至本地/图床
- ⬜ 支持图床自定义 - ⬜ 支持图床自定义
- ☑️ 支持自定义参数 - ☑️ 支持自定义参数
- ☑️ 支持移动端导入 - ☑️ 支持移动端导入
- ⬜ 支持使用AI大模型ChatGPT、Deepseek、Ollama分析导入
## 效果 ## 效果
1. 结合Timeline插件 __构建个人观影时间线__,请参照[结合timeline插件实现时间线效果](./doc/Obsidian-Douban-TimeLine.md) 1. 结合Timeline插件 __构建个人观影时间线__,请参照[结合timeline插件实现时间线效果](./doc/Obsidian-Douban-TimeLine.md)
@ -120,7 +121,7 @@
3. 在obsidian插件中心开启当前插件功能 3. 在obsidian插件中心开启当前插件功能
## 如何开发调试 ## 如何开发调试
### 开发
1. 进入你的Obsidian测试文档文件夹下的`/.obsidian/plugins/` 1. 进入你的Obsidian测试文档文件夹下的`/.obsidian/plugins/`
2. 克隆代码 2. 克隆代码
`git clone git@github.com:Wanxp/obsidian-douban.git` `git clone git@github.com:Wanxp/obsidian-douban.git`
@ -134,6 +135,14 @@
`npm run dev` `npm run dev`
7. 进入Obsidian插件中心重新加载当前插件 7. 进入Obsidian插件中心重新加载当前插件
8. 享受开发吧 8. 享受开发吧
#### 文档
```shell
npm run docs:dev
```
## 支持开发者
如果觉得插件对你有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
![support_pay](./doc/img/support_pay_2.png)
## 交流社群 ## 交流社群
<img src="doc/img/obsidian-douban-qq-qr_code.svg" width="300px"> <img src="doc/img/wechat_group.png" width="245px"> <img src="doc/img/obsidian-douban-qq-qr_code.svg" width="300px"> <img src="doc/img/wechat_group.png" width="245px">
@ -164,7 +173,7 @@
| 同步书影音数据 | 以上所有 | 以上所有 | 以上所有 | | 同步书影音数据 | 以上所有 | 以上所有 | 以上所有 |
| 同步书影音数据 | `替换同名文档`值为勾选 | 已经存在 **同路径同文档名** ,直接覆盖 | 如已经存在在`data/Movie/蝙蝠侠.md`,配置`笔记名称`值为`/data/{{type}}/{{title}}`, 同步书影音记录时勾选 `替换同名文档`, 则`data/Movie/蝙蝠侠.md`会被替换成最新 | | 同步书影音数据 | `替换同名文档`值为勾选 | 已经存在 **同路径同文档名** ,直接覆盖 | 如已经存在在`data/Movie/蝙蝠侠.md`,配置`笔记名称`值为`/data/{{type}}/{{title}}`, 同步书影音记录时勾选 `替换同名文档`, 则`data/Movie/蝙蝠侠.md`会被替换成最新 |
[//]: # (## 鸣谢)
[//]: # (### IDE支持) [//]: # (### IDE支持)

@ -0,0 +1,11 @@
编写github action ,完成以下目标:
1. 当文件mainfest.json文件发生变更时才运行
2. 提供node18 环境
3. 执行npm install
4. 执行npm run build
5. 提取 package.json中的版本
6. 将当前master创建为版本的tag
7. 发布到github release 预发布版本,且版本的名称为当前版本
8. 发布的信息从commit中提取
9. 发布的内容为生成的main.js、mainfest.json、style.css 三个文件
10. 输出内容: 发布预发布版本{版本}正常

56
doc/.vitepress/config.mts Normal file

@ -0,0 +1,56 @@
import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Obsidian Douban",
description: "Plugin for obsidian to manage your douban data",
themeConfig: {
lang: 'zh-CN',
search: {
provider: 'local'
},
// https://vitepress.dev/reference/default-theme-config
logo: '/obsidian-douban-logo.png',
nav: [
{ text: '首页', link: '/' },
{ text: '特殊效果', items: [
{ text: '时间线效果', link: '/Obsidian-Douban-TimeLine' },
{ text: '类网页效果', link: '/Obsidian-Douban-BlueTopaz' },
{ text: '书架效果', link: 'Obsidian-Douban-DataView-Jump' }
] },
{ text: '作者', link: 'https://wxp.hk' },
],
sidebar: [
{
text: '如何安装', link: '/10_install'
},
{ text: '使用说明', link: '/20_howtouse_10_detail' },
{ text: '登录方式', link: '/20_howtouse_25_setting_login_douban_cookie' },
{ text: '图床配置', link: '/20_howtouse_30_picturebed' },
{ text: '功能支持', link: '/30_function_10' },
{ text: '可用参数', link: '/30_function_20_support_variables' },
{ text: '时间线效果', link: '/Obsidian-Douban-TimeLine' },
{ text: '类网页效果', link: '/Obsidian-Douban-BlueTopaz' },
{ text: '书架效果', link: 'Obsidian-Douban-DataView-Jump' },
{ text: '数据影响', link: '/80_others_20_effect' },
{ text: '免责声明', link: '/80_others_disclaimer' },
{ text: '开发调试', link: '/70_develop' },
{ text: '反馈建议', link: '/97_issues' },
{ text: '支持作者', link: '/99_support' },
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/Wanxp/obsidian-douban' },
// { icon: 'blog', link: 'https://github.com/Wanxp/obsidian-douban' }
],
lastUpdated: {
text: '最后更新于',
formatOptions: {
dateStyle: 'full',
timeStyle: 'medium'
}
}
}
})

@ -0,0 +1,15 @@
:root {
--vp-c-brand-1: #646cff;
--vp-c-brand-2: #747bff;
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #34fe48, #bd34fe);
--vp-home-hero-image-background-image: linear-gradient( 135deg, #34fe48 10%, #bd34fe 100%);
--vp-home-hero-image-filter: blur(80px);
/*--vp-home-hero-image-background-image: linear-gradient(*/
/* -45deg,*/
/* #34fe48 50%,*/
/* #bd34fe 50%*/
/*);*/
/*--vp-home-hero-image-filter: blur(44px);*/
}

@ -0,0 +1,5 @@
import DefaultTheme from 'vitepress/theme'
import './custom.css'
export default DefaultTheme

@ -1,17 +1,15 @@
--- ---
title: 如何安装 title: 如何安装
layout: default
nav_order: 200 nav_order: 200
--- ---
# 如何安装
## 如何安装 ## 从Obsidian插件中心
### 从Obsidian插件中心
1. 进入Obsidian插件中心 1. 进入Obsidian插件中心
2. 搜索obsidian-douban 2. 搜索obsidian-douban
3. 安装 3. 安装
4. 开启插件 4. 开启插件
### 手动安装 ## 手动安装
1. 从[Github release](https://github.com/Wanxp/obsidian-douban/releases) 页面下载 `main.js`, `manifest.json`, `styles.css` 1. 从[Github release](https://github.com/Wanxp/obsidian-douban/releases) 页面下载 `main.js`, `manifest.json`, `styles.css`
2. 将下载的文件复制到你的Obsidian文档根目录下的`/.obsidian/plugins/obsidian-douban`路径,若不存在则新建文件夹(注意.obsidian文件夹可能是个隐藏为文件夹) 2. 将下载的文件复制到你的Obsidian文档根目录下的`/.obsidian/plugins/obsidian-douban`路径,若不存在则新建文件夹(注意.obsidian文件夹可能是个隐藏为文件夹)

@ -1,11 +1,10 @@
--- ---
title: 使用说明 title: 使用说明
layout: default
nav_order: 300 nav_order: 300
parent: 如何使用 parent: 如何使用
--- ---
## 如何使用 # 如何使用
## 搜索 # 搜索
使用方式: 输入<kbd>Ctrl</kbd> + <kbd>P</kbd>输入“豆瓣”或“Douban”选择搜索并使用 使用方式: 输入<kbd>Ctrl</kbd> + <kbd>P</kbd>输入“豆瓣”或“Douban”选择搜索并使用
- 搜索数据并创建笔记 - 搜索数据并创建笔记
- 通过当前文件名搜索 - 通过当前文件名搜索
@ -13,6 +12,6 @@ parent: 如何使用
![search_and_create](img/search_and_create_note.gif) ![search_and_create](img/search_and_create_note.gif)
## 同步 # 同步
- 同步个人的观影、观剧、阅读、游戏、音乐记录 - 同步个人的观影、观剧、阅读、游戏、音乐记录
![Sync Data From Douban](img/sync_data_from_douban.gif) ![Sync Data From Douban](img/sync_data_from_douban.gif)

@ -1,11 +1,10 @@
--- ---
title: 设置 title: 设置
layout: default
nav_order: 350 nav_order: 350
parent: 如何使用 parent: 如何使用
--- ---
## 设置 # 设置
- 设置豆瓣账号(可选,可使用少部分功能) - 设置豆瓣账号(可选,可使用少部分功能)
- 设置导入模板(可选,不设置的情况下使用默认模板) - 设置导入模板(可选,不设置的情况下使用默认模板)
- 设置导入路径(可选,不设置的情况下使用默认路径) - 设置导入路径(可选,不设置的情况下使用默认路径)

@ -1,10 +1,17 @@
--- ---
title: Cookie登录Douban title: Cookie登录Douban
layout: default
nav_order: 350 nav_order: 350
parent: 如何使用 parent: 如何使用
--- ---
# 登录方式
Obsidian-Douban插件提供了两种登录方式扫码登录和Cookie登录。扫码登录是推荐的方式但如果扫码登录失败可以使用Cookie登录。
## 扫码登录
扫码登录是Obsidian-Douban插件的默认登录方式适用于大多数用户。扫码登录的步骤如下
1. 在Obsidian-Douban插件设置中点击`登录按钮`
2. 弹出扫码登录窗口
3. 使用手机或其他设备的豆瓣APP 扫描二维码
4. 在手机上确认登录
5. 登录成功后Obsidian-Douban插件会自动获取您的豆瓣账号信息
## Cookie登录Douban ## Cookie登录Douban
此方式仅在Obsidian-Douban中点击`登录按钮`,弹窗后,扫码登录失败的用户 此方式仅在Obsidian-Douban中点击`登录按钮`,弹窗后,扫码登录失败的用户
### 操作 ### 操作

@ -1,22 +1,21 @@
--- ---
title: 图床 title: 图床
layout: default
nav_order: 380 nav_order: 380
parent: 如何使用 parent: 如何使用
--- ---
## 图床 # 图床
### PicGo ## PicGo
#### 设置步骤 ### 设置步骤
1. 安装并下载PicGo图床软件 1. 安装并下载PicGo图床软件
2. 设置PicGo图床 2. 设置PicGo图床
3. 由于Obsidian-Douban是通过剪贴板上传图片的需要在PicGo设置中开启剪贴板上传 3. 由于Obsidian-Douban是通过剪贴板上传图片的需要在PicGo设置中开启剪贴板上传
4. 需要设置Server开启并设置 端口36677 4. 需要设置Server开启并设置 端口36677
5. 设置完成之后可以尝试点击PicGo主界面的`剪贴板上传`按钮,验证是否可以上传图片 5. 设置完成之后可以尝试点击PicGo主界面的`剪贴板上传`按钮,验证是否可以上传图片
6. 若在Obsidian-Douban设置中使用PicGo上传图片至图床则每次导入书影音数据前需要保证提前打开了PicGo软件 6. 若在Obsidian-Douban设置中使用PicGo上传图片至图床则每次导入书影音数据前需要保证提前打开了PicGo软件
#### 注意事项 ### 注意事项
Obsidian-Douban插件使用PicGo上传图片至图床仅在Linux系统下测试通过其他系统未测试其它系统有问题欢迎及时反馈 Obsidian-Douban插件使用PicGo上传图片至图床仅在Linux系统下测试通过其他系统未测试其它系统有问题欢迎及时反馈
##### Linux #### Linux
1. x11图形界面下还需要安装xclip软件否则无法使用剪贴板上传图片 1. x11图形界面下还需要安装xclip软件否则无法使用剪贴板上传图片
2. wayland图形界面下, 还需要安装wl-clipboard软件否则无法使用剪贴板上传图片 2. wayland图形界面下, 还需要安装wl-clipboard软件否则无法使用剪贴板上传图片
3. 若无法上传图片可尝试开启PicGo软件设置中的`使用内置剪贴板上传`选项 3. 若无法上传图片可尝试开启PicGo软件设置中的`使用内置剪贴板上传`选项

@ -1,12 +1,14 @@
--- ---
title: 基础功能 title: 基础功能
layout: default
nav_order: 400 nav_order: 400
parent: 功能 parent: 功能
--- ---
## 功能 # 功能
- ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记 - ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记
- ☑️ 同步个人听过/看过的电影、电视剧、书籍、音乐 - ☑️ 同步个人听过/看过的电影、电视剧、书籍、音乐、游戏
- ☑️ 导入个人的评论,评论时间,阅读状态,个人评分 - ☑️ 导入个人的评论,评论时间,阅读状态,个人评分
- ☑️ 支持保存封面至本地 - ☑️ 支持保存封面至本地/图床
- ⬜ 支持图床自定义
- ☑️ 支持自定义参数 - ☑️ 支持自定义参数
- ☑️ 支持移动端导入
- ⬜ 支持使用AI大模型ChatGPT、Deepseek、Ollama分析导入

@ -1,11 +1,10 @@
--- ---
title: 支持的参数 title: 支持的参数
layout: default
nav_order: 500 nav_order: 500
parent: 功能 parent: 功能
--- ---
## 支持的字段 # 支持的字段
(若有缺少想导入的字段, 欢迎提issues反馈) (若有缺少想导入的字段, 欢迎提issues反馈)
| 字段 | 电影 | 电视剧 | 书籍 | 音乐 | 日记 | 游戏 | 人物 | | 字段 | 电影 | 电视剧 | 书籍 | 音乐 | 日记 | 游戏 | 人物 |

@ -1,11 +1,10 @@
--- ---
title: 效果介绍 title: 效果介绍
layout: default
nav_order: 450 nav_order: 450
parent: 特殊效果 parent: 特殊效果
--- ---
## 效果 # 效果
1. 结合Timeline插件 __构建个人观影时间线__,请参照[结合timeline插件实现时间线效果](Obsidian-Douban-TimeLine) 1. 结合Timeline插件 __构建个人观影时间线__,请参照[结合timeline插件实现时间线效果](Obsidian-Douban-TimeLine)
![](./img/obsidian-douban-time-preview-example.gif) ![](./img/obsidian-douban-time-preview-example.gif)
<!--2. 结合DataView插件__构建个人电子书架书库数据__请参照[结合dateview插件实现个人书架效果](Obsidian-Douban-DataView.md))--> <!--2. 结合DataView插件__构建个人电子书架书库数据__请参照[结合dateview插件实现个人书架效果](Obsidian-Douban-DataView.md))-->

@ -1,10 +1,9 @@
--- ---
title: 开发 title: 开发
layout: default
nav_order: 700 nav_order: 700
--- ---
## 如何开发调试 # 如何开发调试
1. 进入你的Obsidian测试文档文件夹下的`/.obsidian/plugins/` 1. 进入你的Obsidian测试文档文件夹下的`/.obsidian/plugins/`
2. 克隆代码 2. 克隆代码

@ -1,11 +1,10 @@
--- ---
title: 数据影响 title: 数据影响
layout: default
nav_order: 800 nav_order: 800
parent: 其它 parent: 其它
--- ---
## 数据影响 # 数据影响
注意: 除了在同步书影音数据时勾选 `替换同名文档` 有可能会修改同路径同文档名的笔记外,其余操作均不会修改已有笔记。 注意: 除了在同步书影音数据时勾选 `替换同名文档` 有可能会修改同路径同文档名的笔记外,其余操作均不会修改已有笔记。
| 操作 | 条件 | 影响 | 举例 | | 操作 | 条件 | 影响 | 举例 |

@ -1,9 +1,8 @@
--- ---
title: 鸣谢 title: 鸣谢
layout: default
nav_order: 810 nav_order: 810
parent: 其它 parent: 其它
--- ---
## 鸣谢 # 鸣谢
### IDE支持 ## IDE支持
[<image src="img/jb_beam.svg"> </image>](https://www.jetbrains.com/?from=obsidian-douban) [<image src="img/jb_beam.svg"> </image>](https://www.jetbrains.com/?from=obsidian-douban)

@ -1,11 +1,10 @@
--- ---
title: 免责声明 title: 免责声明
layout: default
nav_order: 820 nav_order: 820
parent: 其它 parent: 其它
--- ---
## 免责声明 # 免责声明
1. 建议使用本插件前,一定要至少有一种方式备份你的数据,以防万一。 1. 建议使用本插件前,一定要至少有一种方式备份你的数据,以防万一。
2. 本程序没有爬取任何书影音等内容,只供技术研究使用。没有侵犯书影音作者版权和豆瓣官方利益。如有任何侵权行为,请联系我删除。 2. 本程序没有爬取任何书影音等内容,只供技术研究使用。没有侵犯书影音作者版权和豆瓣官方利益。如有任何侵权行为,请联系我删除。
3. 本程序仅供学习交流使用。 3. 本程序仅供学习交流使用。

@ -1,10 +1,9 @@
--- ---
title: 反馈与建议 title: 反馈与建议
layout: default
nav_order: 950 nav_order: 950
--- ---
## 反馈与建议 # 反馈与建议
如果你有任何问题或建议,欢迎在提交[Issues](https://github.com/Wanxp/obsidian-douban/issues) 如果你有任何问题或建议,欢迎在提交[Issues](https://github.com/Wanxp/obsidian-douban/issues)

@ -1,15 +1,17 @@
--- ---
title: 支持 title: 支持
layout: default
nav_order: 1000 nav_order: 1000
--- ---
## 支持 # 支持
愿世界充满爱与和平! 愿世界充满爱与和平!
如果觉得喜欢或对您有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
![support_pay](./img/support_pay_2.png)
## 交流社群 ## 交流社群
<img src="img/obsidian-douban-qq-qr_code.svg" width="300px"> <img src="/img/obsidian-douban-qq-qr_code.svg" width="300px" style="display: inline-block;"><img src="/img/wechat_group.png" width="245px" style="display: inline-block;">
<img src="https://picture-bed-public.wanxuping.com/obsidian-douban/wechat_group.png" width="245px">
[邮件联系我](mailto:977741432@qq.com)
或者[邮件联系我](mailto:977741432@qq.com)

@ -1,9 +1,9 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem "jekyll", "~> 4.3.3" # installed by `gem jekyll` gem "jekyll" # installed by `gem jekyll`
# gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2 # gem "webrick" # required when using Ruby >= 3 and Jekyll <= 4.2.2
gem "just-the-docs", "0.8.2" # pinned to the current release gem "just-the-docs" # pinned to the current release
# gem "just-the-docs" # always download the latest release # gem "just-the-docs" # always download the latest release
gem 'json' gem 'json'

@ -1,6 +1,5 @@
--- ---
title: 类豆瓣网页显示 title: 类豆瓣网页显示
layout: default
nav_order: 455 nav_order: 455
parent: 特殊效果 parent: 特殊效果
render_with_liquid: false render_with_liquid: false

@ -0,0 +1,3 @@
## 书架效果
点击以下链接跳转至少数派
[使用Obsidian打造个人图书馆](https://sspai.com/post/85574)

@ -1,70 +0,0 @@
## 效果如下
![](./img/obsidian-douban-time-preview-example.gif)
## 适用人群
1. 在豆瓣有标记/评论/评分的习惯的人
比如看完电影,会在豆瓣进行评分或评论。或者阅读完的书籍,进行评分或评论。支持包含:电影、书籍、电视剧、音乐、游戏
## 实现步骤
1. 安装[Timeline](https://github.com/Darakah/obsidian-timelines)插件
2. 安装[Obsidian-Douban](https://github.com/Wanxp/obsidian-douban)插件(本插件)
3. 在Obsidian-Douban插件配置中登录Douban
4. 配置同步需要的模板 电影/书籍的模板中的frontmatter在frontmatter中 **增加** 特定tags根据自己的需要指定用于需要过滤成为timeline的笔记如增加tags`我看过的电影`
```markdown
---
tags: 我看过的电影
---
````
5. 同时,在电影/书籍... 模板中的 **最后增加** timeline插件需要的html标签如下:
```html
<span class='ob-timelines' data-date='{{myCollectionDate}}'
data-title='{{title}}' data-img='{{image}}'
data-class = "custom-my-movie-time-line">{{myComment}} |简介: {{desc}}
</span>
```
6. 选择上述模板导入 电影/书籍...操作方式是打开obsidian命令窗口输入豆瓣找到导入功能在导入界面配置 选择模板进行导入
7. 导入需要一定时间每条内容导入需要15-30s左右所有有导入完成后会有导入汇总
8. 导入完成后新建一个笔记笔记内容加入timeline的代码块代码块的内容就是你上面指定的tags的内容如`我看过的电影`,代码块如下:
````markdown
```timeline
我看过的电影
```
````
9. 预览这个笔记就能看出已经出现了时间线
## 模板参考
### 电影
````markdown
---
doubanId: {{id}}
title: {{title}}
type: {{type}}
score: {{score}}
myRate: {{myRate}}
originalTitle: {{originalTitle}}
genre: {{genre}}
datePublished: {{datePublished}}
director: {{director}}
actor: {{actor}}
author: {{author}}
tags: {{type}}, 我看过的电影, {{myTags}}
state: {{myState}}
url: {{url}}
createTime: {{currentDate}} {{currentTime}}
collectionDate: {{myCollectionDate}}
desc: {{desc}}
---
![image]({{image}})
Comment:
---
{{myComment}}
<span class='ob-timelines' data-date='{{myCollectionDate}}'
data-title='{{title}}' data-img='{{image}}'
data-class = "custom-my-movie-time-line">{{myComment}} |简介: {{desc}}
</span>
````
### 书籍、电视剧、音乐、游戏
请参照电影模板
## 更多
参照讨论 [结合timeline插件的妙用](https://github.com/Wanxp/obsidian-douban/issues/19#issuecomment-1428307130)

@ -1,6 +1,5 @@
--- ---
title: 看剧时间线 title: 看剧时间线
layout: default
nav_order: 456 nav_order: 456
parent: 特殊效果 parent: 特殊效果
render_with_liquid: false render_with_liquid: false

BIN
doc/img/support_pay_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

BIN
doc/img/wanxp-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

@ -1,47 +1,34 @@
--- ---
title: 简介 # https://vitepress.dev/reference/default-theme-home-page
layout: home layout: home
nav_order: 10
hero:
name: "Obsidian Douban"
text: "obsidian插件\n同步你的豆瓣书影音"
tagline:
image:
src: /obsidian-douban-logo.png
alt: Obsidian Douban Logo
actions:
- theme: brand
text: 开始使用
link: /20_howtouse_10_detail
- theme: alt
text: 安装
link: /10_install
features:
- title: 搜索导入影音
icon: 📘
details: 搜索豆瓣中您喜欢的电影、电视剧、书籍、音乐、游戏等信息结构化并导入到Obsidian中
link: /20_howtouse_10_detail
- title: 同步个人数据
icon: 🙋
details: 登录后可同步个人的观影、观剧、阅读、游戏、音乐记录到您的Obsidian中
link: /20_howtouse_10_detail
- title: 建立个人书架
icon: 📚
details: 结合DataView生成整合你的书籍建立个人书架统一管理
link: /Obsidian-Douban-DataView-Jump
--- ---
这是一款[Obsidian](https://obsidian.md/)的插件, 用于导入[豆瓣](https://www.douban.com/)中的 _电影、书籍、音乐、电视剧、日记、游戏
甚至是你标记过的书影音, 包含你的评分、观看日期、评论、阅读状态等信息.
![background](./background.png)
## 基本功能
- ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记
- ☑️ 同步个人听过/看过的电影、电视剧、书籍、音乐
- ☑️ 导入个人的评论,评论时间,阅读状态,个人评分
- ☑️ 支持保存封面至本地或图床
- ☑️ 支持自定义参数
## 交流社群
<img src="img/obsidian-douban-qq-qr_code.svg" width="300px"> <img src="https://picture-bed-public.wanxuping.com/obsidian-douban/wechat_group.png" width="245px">
<p align="center">
<a href="https://github.com/Wanxp/obsidian-douban/releases/latest">
<img src="https://img.shields.io/github/manifest-json/v/Wanxp/obsidian-douban?color=blue">
</a>
<img src="https://img.shields.io/github/release-date/Wanxp/obsidian-douban">
<a href="https://github.com/Wanxp/obsidian-douban/blob/master/License">
<img src="https://img.shields.io/github/license/Wanxp/obsidian-douban">
</a>
<img src="https://img.shields.io/github/downloads/Wanxp/obsidian-douban/total">
<a href="https://github.com/Wanxp/obsidian-douban/issues">
<img src="https://img.shields.io/github/issues/Wanxp/obsidian-douban">
</a>
<br>
<img src="https://img.shields.io/tokei/lines/github/Wanxp/obsidian-douban">
<a href="https://www.codefactor.io/repository/github/wanxp/obsidian-douban">
<img src="https://www.codefactor.io/repository/github/wanxp/obsidian-douban/badge" alt="CodeFactor" />
</a>
</p>
[Just the Docs repo]: https://github.com/Wanxp/obsidian-douban

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

@ -1,7 +1,7 @@
{ {
"id": "obsidian-douban-plugin", "id": "obsidian-douban-plugin",
"name": "Douban", "name": "Douban",
"version": "2.1.0", "version": "2.3.2",
"minAppVersion": "0.12.0", "minAppVersion": "0.12.0",
"description": "This is a plugin that can import movies/books/musics/notes/games info data from Douban for Obsidian .", "description": "This is a plugin that can import movies/books/musics/notes/games info data from Douban for Obsidian .",
"author": "Wanxp", "author": "Wanxp",

9428
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,13 +1,16 @@
{ {
"name": "obsidian-douban-plugin", "name": "obsidian-douban-plugin",
"version": "2.1.0", "version": "2.3.2",
"description": "This is a plugin for Obsidian (https://obsidian.md) that can import data from Douban (https://www.douban.com/).", "description": "This is a plugin for Obsidian (https://obsidian.md) that can import data from Douban (https://www.douban.com/).",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json", "version": "node version-bump.mjs && git add manifest.json versions.json",
"test": "jest" "test": "jest",
"docs:dev": "vitepress dev doc",
"docs:build": "vitepress build doc",
"docs:preview": "vitepress preview doc"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -23,7 +26,8 @@
"obsidian": "latest", "obsidian": "latest",
"ts-jest": "^28.0.5", "ts-jest": "^28.0.5",
"tslib": "2.3.1", "tslib": "2.3.1",
"typescript": "^4.7.2" "typescript": "^4.7.2",
"vitepress": "^1.6.3"
}, },
"dependencies": { "dependencies": {
"@notable/html2markdown": "^1.1.3", "@notable/html2markdown": "^1.1.3",

@ -101,15 +101,34 @@ export enum TemplateKey {
} }
export enum SupportType { export enum SupportType {
ALL = "all", all = "all",
MOVIE = 'movie', movie = 'movie',
BOOK = 'book', book = 'book',
MUSIC = 'music', music = 'music',
NOTE = 'note', note = 'note',
GAME = 'game', game = 'game',
TELEPLAY = 'teleplay', teleplay = 'teleplay',
THEATER = 'theater', theater = 'theater',
} }
export const SupportTypeMap:object = {
"all": SupportType.all,
"movie": SupportType.movie,
"book": SupportType.book,
"music": SupportType.music,
"note": SupportType.note,
"game": SupportType.game,
"teleplay": SupportType.teleplay,
"theater": SupportType.theater,
"ALL": SupportType.all,
"MOVIE": SupportType.movie,
"BOOK": SupportType.book,
"MUSIC": SupportType.music,
"NOTE": SupportType.note,
"GAME": SupportType.game,
"TELEPLAY": SupportType.teleplay,
"THEATER": SupportType.theater,
}
export enum PropertyName { export enum PropertyName {
//base //base
@ -183,13 +202,13 @@ export enum PropertyName {
*/ */
// @ts-ignore // @ts-ignore
export const SearchTypeRecords: { [key in SupportType]: string } = { export const SearchTypeRecords: { [key in SupportType]: string } = {
[SupportType.ALL]: i18nHelper.getMessage('ALL'), [SupportType.all]: i18nHelper.getMessage('ALL'),
[SupportType.MOVIE]: i18nHelper.getMessage('MOVIE_AND_TELEPLAY'), [SupportType.movie]: i18nHelper.getMessage('MOVIE_AND_TELEPLAY'),
[SupportType.BOOK]: i18nHelper.getMessage('BOOK'), [SupportType.book]: i18nHelper.getMessage('BOOK'),
[SupportType.MUSIC]: i18nHelper.getMessage('MUSIC'), [SupportType.music]: i18nHelper.getMessage('MUSIC'),
[SupportType.NOTE]: i18nHelper.getMessage('NOTE'), [SupportType.note]: i18nHelper.getMessage('NOTE'),
[SupportType.GAME]: i18nHelper.getMessage('GAME'), [SupportType.game]: i18nHelper.getMessage('GAME'),
// [SupportType.THEATER]: i18nHelper.getMessage('THEATER'), [SupportType.theater]: i18nHelper.getMessage('THEATER'),
} }
/** /**
@ -204,6 +223,7 @@ export const PersonNameModeRecords: { [key in PersonNameMode]: string } = {
export enum SyncType { export enum SyncType {
movie = 'movie', movie = 'movie',
book = 'book', book = 'book',
game = 'game',
broadcast = 'broadcast', broadcast = 'broadcast',
note = 'note', note = 'note',
music = 'music', music = 'music',
@ -216,20 +236,23 @@ export const SyncTypeUrlDomain: Map<SyncType, string> = new Map([
[SyncType.broadcast , 'broadcast'], [SyncType.broadcast , 'broadcast'],
[SyncType.note , 'note'], [SyncType.note , 'note'],
[SyncType.music , 'music'], [SyncType.music , 'music'],
[SyncType.teleplay , 'movie']] [SyncType.teleplay , 'movie'],
[SyncType.game, 'games'],
]
) )
/** /**
* *
*/ */
// @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'),
// [SyncType.broadcast]: i18nHelper.getMessage('504104'), // [SyncType.broadcast]: i18nHelper.getMessage('504104'),
// [SyncType.note]: i18nHelper.getMessage('504105'), // [SyncType.note]: i18nHelper.getMessage('504105'),
[SyncType.music]: i18nHelper.getMessage('504106'), [SyncType.music]: i18nHelper.getMessage('504106'),
[SyncType.game]: i18nHelper.getMessage('504108'),
} }
/** /**
@ -257,6 +280,7 @@ export enum SyncItemStatus {
replace = 'replace', replace = 'replace',
create = 'create', create = 'create',
fail = 'fail', fail = 'fail',
failByDiffType = 'failByDiffType',
unHandle = 'unHandle', unHandle = 'unHandle',
} }
@ -371,13 +395,13 @@ sec-ch-ua-platform: "Windows"
export const ONLINE_SETTING_DEFAULT: DoubanPluginOnlineSettings = { export const ONLINE_SETTING_DEFAULT: DoubanPluginOnlineSettings = {
properties: [ properties: [
{ {
type: SupportType.BOOK, type: SupportType.book,
name: PropertyName.comment, name: PropertyName.comment,
selectors: ['#interest_sect_level > div > span:nth-child(7)' selectors: ['#interest_sect_level > div > span:nth-child(7)'
] ]
}, },
{ {
type: SupportType.MOVIE, type: SupportType.movie,
name: PropertyName.comment, name: PropertyName.comment,
selectors: ['#interest_sect_level > div > span:nth-child(8)', selectors: ['#interest_sect_level > div > span:nth-child(8)',
'#interest_sect_level > div > span:nth-child(7)', '#interest_sect_level > div > span:nth-child(7)',
@ -452,3 +476,75 @@ export const PictureBedSetting_PicGo ={
export const PictureBedTypeRecords: { [key in PictureBedType]: string } = { export const PictureBedTypeRecords: { [key in PictureBedType]: string } = {
[PictureBedType.PicGo]: PictureBedType.PicGo [PictureBedType.PicGo]: PictureBedType.PicGo
} }
export enum SyncConditionType {
/**
*
*/
ALL = "all",
// /**
// * 最近新变动
// */
// LAST_UPDATE = "lastUpdate",
/**
* 10
*/
LAST_THIRTY = "lastThirty",
/**
*
*/
CUSTOM_TIME = "customTime",
/**
*
*/
CUSTOM_ITEM = "customItem",
}
/**
*
*/
// @ts-ignore
export const SyncConditionTypeRecords: { [key in SyncConditionType|string]: string } = {
[SyncConditionType.ALL]: i18nHelper.getMessage('110071'),
// [SyncConditionType.LAST_UPDATE]: i18nHelper.getMessage('110072'),
[SyncConditionType.LAST_THIRTY]: i18nHelper.getMessage('110075'),
[SyncConditionType.CUSTOM_ITEM]: i18nHelper.getMessage('110076'),
[SyncConditionType.CUSTOM_TIME]: i18nHelper.getMessage('110074'),
}
export const DoubanSearchResultSubject_EMPTY: DoubanSearchResultSubject = {
id: '',
title: i18nHelper.getMessage('150107'),
score: null,
cast: '',
type: 'navigate',
desc: '-',
url: 'https://www.douban.com',
image: "",
imageUrl: "",
publisher: "",
datePublished: undefined,
genre: []
};
export const DoubanSearchResultSubject_TIP_EMPTY: DoubanSearchResultSubject = {
id: '',
title: i18nHelper.getMessage('150108'),
score: null,
cast: '',
type: 'navigate',
desc: '-',
url: 'https://www.douban.com',
image: "",
imageUrl: "",
publisher: "",
datePublished: undefined,
genre: []
};

@ -38,25 +38,29 @@ export const DEFAULT_SETTINGS: DoubanPluginSetting = {
statusBar: true, statusBar: true,
debugMode: false, debugMode: false,
customProperties: [ customProperties: [
{name: 'myType', value: 'movie', field: SupportType.MOVIE}, {name: 'myType', value: 'movie', field: SupportType.movie},
{name: 'myType', value: 'book', field: SupportType.BOOK}, {name: 'myType', value: 'book', field: SupportType.book},
{name: 'myType', value: 'music', field: SupportType.MUSIC}, {name: 'myType', value: 'music', field: SupportType.music},
{name: 'myType', value: 'note', field: SupportType.NOTE}, {name: 'myType', value: 'note', field: SupportType.note},
{name: 'myType', value: 'game', field: SupportType.GAME}, {name: 'myType', value: 'game', field: SupportType.game},
{name: 'myType', value: 'teleplay', field: SupportType.TELEPLAY}, {name: 'myType', value: 'teleplay', field: SupportType.teleplay},
{name: 'myType', value: 'theater', field: SupportType.theater},
], ],
loginCookiesContent: '', loginCookiesContent: '',
loginHeadersContent: '', loginHeadersContent: '',
cacheImage: true, cacheImage: true,
cacheHighQuantityImage: true, cacheHighQuantityImage: true,
attachmentPath: 'assets', attachmentPath: 'assets',
attachmentFileName: "{{title}}",
syncHandledDataArray: [], syncHandledDataArray: [],
// syncLastUpdateTime: new Map<string, string>(),
scoreSetting: { scoreSetting: {
starFull: '⭐', starFull: '⭐',
starEmpty: '☆', starEmpty: '☆',
displayStarEmpty: false, displayStarEmpty: false,
maxStar: 5, maxStar: 5,
} },
searchDefaultType: SupportType.all,
} }

@ -78,6 +78,10 @@ desc: {{desc}}
--- ---
![image]({{image}}) ![image]({{image}})
---
Menu:
{{menu}}
`, `,
noteTemplateFileContent: `--- noteTemplateFileContent: `---
doubanId: {{id}} doubanId: {{id}}
@ -240,8 +244,12 @@ desc: {{desc}}
![image]({{image}}) ![image]({{image}})
Comment:
--- ---
Menu:
{{menu}}
---
Comment:
{{myComment}} {{myComment}}
`, `,

@ -10,3 +10,7 @@ export const doubanHeaders = {
export const doubanSubjectSyncListUrl = function(subjectType:string, userId:string, doType:string, start:number):string { export const doubanSubjectSyncListUrl = function(subjectType:string, userId:string, doType:string, start:number):string {
return `https://${subjectType}.douban.com/people/${userId}/${doType}?start=${start}&sort=time&rating=all&filter=all&mode=list`; return `https://${subjectType}.douban.com/people/${userId}/${doType}?start=${start}&sort=time&rating=all&filter=all&mode=list`;
} }
export const doubanGameSubjectSyncListUrl = function(subjectType:string, userId:string, doType:string, start:number):string {
return `https://douban.com/people/${userId}/games?start=${start}&sort=time&rating=all&filter=all&mode=list${doType != 'all' ? '&action='+doType : ''}`;
}

@ -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',
@ -66,14 +66,14 @@ export const DoubanSubjectStateRecords_THEATER: { [key in DoubanSubjectState]: s
} }
export const DoubanSubjectStateRecords: { [key in SupportType]: Record<DoubanSubjectState, string> } = { export const DoubanSubjectStateRecords: { [key in SupportType]: Record<DoubanSubjectState, string> } = {
[SupportType.ALL]:DoubanSubjectStateRecords_ALL, [SupportType.all]:DoubanSubjectStateRecords_ALL,
[SupportType.MOVIE]:DoubanSubjectStateRecords_MOVIE, [SupportType.movie]:DoubanSubjectStateRecords_MOVIE,
[SupportType.BOOK]:DoubanSubjectStateRecords_BOOK, [SupportType.book]:DoubanSubjectStateRecords_BOOK,
[SupportType.MUSIC]:DoubanSubjectStateRecords_MUSIC, [SupportType.music]:DoubanSubjectStateRecords_MUSIC,
[SupportType.NOTE]:DoubanSubjectStateRecords_NOTE, [SupportType.note]:DoubanSubjectStateRecords_NOTE,
[SupportType.GAME]:DoubanSubjectStateRecords_GAME, [SupportType.game]:DoubanSubjectStateRecords_GAME,
[SupportType.TELEPLAY]:DoubanSubjectStateRecords_TELEPLAY, [SupportType.teleplay]:DoubanSubjectStateRecords_TELEPLAY,
[SupportType.THEATER]:DoubanSubjectStateRecords_THEATER, [SupportType.theater]:DoubanSubjectStateRecords_THEATER,
} }
@ -106,6 +106,15 @@ export const DoubanSubjectStateRecords_BOOK_SYNC: { [key in DoubanSubjectState]:
[DoubanSubjectState.collect]: i18nHelper.getMessage('500304'), [DoubanSubjectState.collect]: i18nHelper.getMessage('500304'),
} }
// @ts-ignore
export const DoubanSubjectStateRecords_GAME_SYNC: { [key in DoubanSubjectState]: string } = {
// @ts-ignore
// [ALL]: i18nHelper.getMessage('500004'),
[DoubanSubjectState.wish]: i18nHelper.getMessage('500602'),
[DoubanSubjectState.do]: i18nHelper.getMessage('500603'),
[DoubanSubjectState.collect]: i18nHelper.getMessage('500604'),
}
export const DoubanSubjectStateRecords_BROADCAST_SYNC: { [key :string]: string } = { export const DoubanSubjectStateRecords_BROADCAST_SYNC: { [key :string]: string } = {
[ALL]: i18nHelper.getMessage('500004'), [ALL]: i18nHelper.getMessage('500004'),
} }
@ -123,33 +132,51 @@ 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],
['我想看这部电视剧', SupportType.TELEPLAY], ['我想看这部电视剧', SupportType.teleplay],
['我在看这部电视剧', SupportType.TELEPLAY], ['我在看这部电视剧', SupportType.teleplay],
['我最近在看这部电视剧', SupportType.TELEPLAY], ['我最近在看这部电视剧', SupportType.teleplay],
['我最近看过这部电影', SupportType.MOVIE], ['我最近看过这部电影', SupportType.movie],
['我看过这部电影', SupportType.MOVIE], ['我看过这部电影', SupportType.movie],
['我想看这部电影', SupportType.MOVIE], ['我想看这部电影', SupportType.movie],
['我读过这本书', SupportType.BOOK], ['我读过这本书', SupportType.book],
['我想读这本书', SupportType.BOOK], ['我想读这本书', SupportType.book],
['我在读这本书', SupportType.BOOK], ['我在读这本书', SupportType.book],
['我最近在读这本书', SupportType.BOOK], ['我最近在读这本书', SupportType.book],
['我最近听过这张唱片', SupportType.MUSIC], ['我最近听过这张唱片', SupportType.music],
['我听过这张唱片', SupportType.MUSIC], ['我听过这张唱片', SupportType.music],
['我想听这张唱片', SupportType.MUSIC], ['我想听这张唱片', SupportType.music],
['我在听这张唱片', SupportType.MUSIC], ['我在听这张唱片', SupportType.music],
['我最近在听这张唱片', SupportType.MUSIC], ['我最近在听这张唱片', SupportType.music],
['我最近玩过这个游戏', SupportType.GAME], ['我最近玩过这个游戏', SupportType.game],
['我玩过这个游戏', SupportType.GAME], ['我玩过这个游戏', SupportType.game],
['我想玩这个游戏', SupportType.GAME], ['我想玩这个游戏', SupportType.game],
['我在玩这个游戏', SupportType.GAME], ['我在玩这个游戏', SupportType.game],
['我最近在玩这个游戏', SupportType.GAME],] ['我最近在玩这个游戏', SupportType.game],
['我最近看过这部电影', SupportType.movie],
['我看过这部电影', SupportType.movie],
['我想看这部电影', SupportType.movie],
]
) )

@ -0,0 +1,92 @@
import {CheerioAPI} from 'cheerio';
import DoubanPlugin from "../../../main";
import SchemaOrg from "src/org/wanxp/utils/SchemaOrg";
import {DataValueType, PropertyName, SupportType} from "../../../constant/Constsant";
import {moment} from "obsidian";
import {TITLE_ALIASES_SPECIAL_CHAR_REG_G} from "../../../utils/YamlUtil";
import {DataField} from "../../../utils/model/DataField";
import DoubanAbstractLoadHandler from "../../data/handler/DoubanAbstractLoadHandler";
import DoubanTheaterSubject from "../../data/model/DoubanTheaterSubject";
import HandleContext from "../../data/model/HandleContext";
import DoubanSubject from "../../data/model/DoubanSubject";
import {UserStateSubject} from "../../data/model/UserStateSubject";
export default class DoubanTheaterAiLoadHandler extends DoubanAbstractLoadHandler<DoubanTheaterSubject> {
constructor(doubanPlugin: DoubanPlugin) {
super(doubanPlugin);
}
getSupportType(): SupportType {
return SupportType.theater;
}
getHighQuantityImageUrl(fileName: string): string {
return `https://img9.doubanio.com/view/photo/l/public/${fileName}`;
}
getSubjectUrl(id:string):string{
return `https://www.douban.com/location/drama/${id}/`;
}
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanTheaterSubject, context: HandleContext): void {
variableMap.set("director", new DataField(
"director",
DataValueType.array,
extract.director,
(extract.director || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("actor", new DataField(
"actor",
DataValueType.array,
extract.actor,
(extract.actor || []).map(SchemaOrg.getPersonName).filter(c => c)
));
variableMap.set("author", new DataField(
"author",
DataValueType.array,
extract.author,
(extract.author || []).map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
));
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases,
(extract.aliases || []).map(a=>a
.trim()
// .replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
// //replase multiple _ to single _
// .replace(/_+/g, '_')
// .replace(/^_/, '')
// .replace(/_$/, '')
.replace(/:\s+/g, ':')
)));
// super.parseAliases(beforeContent, variableMap, extract, context);
}
support(extract: DoubanSubject): boolean {
return extract && extract.type && (extract.type.contains("舞台剧") || extract.type.contains("舞剧") || extract.type.contains("Theater") || extract.type.contains("theater"));
}
analysisUser(html: CheerioAPI, context: HandleContext): { data: CheerioAPI, userState: UserStateSubject } {
const rate = html('input#n_rating').val();
const tagsStr = html('div#interest_sect_level > div.a_stars > span.color_gray').text().trim();
const tags = tagsStr ? tagsStr.replace('标签:', '').trim().split(' ') : null;
const stateWord = html('#interest_sect_level > h2').text().trim();
const collectionDateStr = html('div#interest_sect_level > div.a_stars > span.mr10 > span.collection_date').text().trim();
const userState1 = DoubanAbstractLoadHandler.getUserState(stateWord);
const component = this.getPropertyValue(html, PropertyName.comment);
const userState: UserStateSubject = {
tags: tags,
rate: rate ? Number(rate) : null,
state: userState1,
collectionDate: collectionDateStr ? moment(collectionDateStr, 'YYYY-MM-DD').toDate() : null,
comment: component
}
return {data: html, userState: userState};
}
parseSubjectFromHtml(html: CheerioAPI, context: HandleContext): DoubanTheaterSubject {
const obj: DoubanTheaterSubject = new DoubanTheaterSubject();
obj.id = this.getPropertyValue(html, PropertyName.id);
return obj;
}
}

@ -1,12 +1,18 @@
import {ButtonComponent, Modal} from "obsidian"; import {App, ButtonComponent, Modal} from "obsidian";
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {create} from "istanbul-reports";
import DoubanPlugin from "../../main";
import {logger} from "bs-logger";
import {log} from "../../utils/Logutil";
export class ConfirmDialogModal extends Modal { export class ConfirmDialogModal extends Modal {
private promise:Promise<any>; private promise:Promise<any>;
private message:string; private message:string;
private doubanPlugin: DoubanPlugin;
constructor(app: any, message:string, promise: Promise<any>) { constructor(doubanPlugin: DoubanPlugin, message:string, promise: Promise<any>) {
super(app); super(doubanPlugin.app);
this.doubanPlugin = doubanPlugin;
this.message = message; this.message = message;
this.promise = promise; this.promise = promise;
} }
@ -23,7 +29,12 @@ export class ConfirmDialogModal extends Modal {
.setButtonText(i18nHelper.getMessage('110152')) .setButtonText(i18nHelper.getMessage('110152'))
.setCta() .setCta()
.onClick(async () => { .onClick(async () => {
await this.promise; //临时特殊处理导入文件
if (this.message == i18nHelper.getMessage('125046')) {
createFileSelectModal(this.doubanPlugin);
}else {
await this.promise;
}
this.close(); this.close();
}).setClass( "obsidian_douban_search_button"); }).setClass( "obsidian_douban_search_button");
new ButtonComponent(controls) new ButtonComponent(controls)
@ -33,3 +44,25 @@ export class ConfirmDialogModal extends Modal {
}).setClass( "obsidian_douban_cancel_button"); }).setClass( "obsidian_douban_cancel_button");
} }
} }
function createFileSelectModal(doubanPlugin: DoubanPlugin) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.multiple = false;
input.onchange = async () => {
const file = input.files[0];
const reader = new FileReader();
reader.onload = async () => {
const settings:object = JSON.parse(reader.result as string);
try {
await doubanPlugin.settingsManager.loadAndSaveSettings(settings);
}catch (e) {
log.error(i18nHelper.getMessage('125043'), e);
}
log.notice(i18nHelper.getMessage('125044'))
};
reader.readAsText(file);
};
input.click();}

@ -0,0 +1,18 @@
/**
*
* TextComponent
*/
import {TextComponent} from 'obsidian';
export class DatePickComponent extends TextComponent {
constructor(container: HTMLElement, date: Date = new Date()) {
super(container);
this.inputEl.type = 'date';
this.inputEl.value = date.toISOString().substring(0, 10);
}
getValue(): string {
return this.inputEl.value;
}
}

@ -3,7 +3,7 @@ import { log } from 'src/org/wanxp/utils/Logutil';
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {DoubanSettingTab} from "../setting/DoubanSettingTab"; import {DoubanSettingTab} from "../setting/DoubanSettingTab";
import SettingsManager from "../setting/SettingsManager"; import SettingsManager from "../setting/SettingsManager";
import {constructDoubanTokenSettingsUI} from "../setting/BasicSettingsHelper"; import {constructDoubanTokenSettingsUI} from "../setting/LoginSettingsHelper";
// Credits go to zhaohongxuan's Weread Plugin : https://github.com/zhaohongxuan/obsidian-weread-plugin // Credits go to zhaohongxuan's Weread Plugin : https://github.com/zhaohongxuan/obsidian-weread-plugin

@ -3,7 +3,7 @@ import { log } from 'src/org/wanxp/utils/Logutil';
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {DoubanSettingTab} from "../setting/DoubanSettingTab"; import {DoubanSettingTab} from "../setting/DoubanSettingTab";
import SettingsManager from "../setting/SettingsManager"; import SettingsManager from "../setting/SettingsManager";
import {constructDoubanTokenSettingsUI} from "../setting/BasicSettingsHelper"; import {constructDoubanTokenSettingsUI} from "../setting/LoginSettingsHelper";
import StringUtil from "../../utils/StringUtil"; import StringUtil from "../../utils/StringUtil";
import {Integer} from "schema-dts"; import {Integer} from "schema-dts";

@ -1,7 +1,7 @@
import {DoubanSettingTab} from "../setting/DoubanSettingTab"; import {DoubanSettingTab} from "../setting/DoubanSettingTab";
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import SettingsManager from "../setting/SettingsManager"; import SettingsManager from "../setting/SettingsManager";
import {constructDoubanTokenSettingsUI, constructLoginSettingsUI} from "../setting/BasicSettingsHelper"; import {constructDoubanTokenSettingsUI, constructLoginSettingsUI} from "../setting/LoginSettingsHelper";
import {log} from "../../utils/Logutil"; import {log} from "../../utils/Logutil";
// Credits go to zhaohongxuan's Weread Plugin : https://github.com/zhaohongxuan/obsidian-weread-plugin // Credits go to zhaohongxuan's Weread Plugin : https://github.com/zhaohongxuan/obsidian-weread-plugin

@ -1,17 +1,23 @@
import { import {
App, App,
ButtonComponent, ButtonComponent, DropdownComponent,
Modal, SearchComponent, Setting, Modal, SearchComponent, Setting, TextComponent, ValueComponent,
} from "obsidian"; } from "obsidian";
import DoubanPlugin from "../../main"; import DoubanPlugin from "../../main";
import {i18nHelper} from "src/org/wanxp/lang/helper"; import {i18nHelper} from "src/org/wanxp/lang/helper";
import HandleContext from "../data/model/HandleContext"; import HandleContext from "../data/model/HandleContext";
import {SyncType, SyncTypeRecords} from "../../constant/Constsant";
import { import {
ALL, DEFAULT_SETTINGS_ARRAY_INPUT_SIZE,
SupportType, SyncConditionType,
SyncConditionTypeRecords,
SyncType,
SyncTypeRecords
} from "../../constant/Constsant";
import {
ALL, DoubanSubjectState, DoubanSubjectStateRecords,
DoubanSubjectStateRecords_BOOK_SYNC, DoubanSubjectStateRecords_BOOK_SYNC,
DoubanSubjectStateRecords_BROADCAST_SYNC, DoubanSubjectStateRecords_BROADCAST_SYNC, DoubanSubjectStateRecords_GAME_SYNC,
DoubanSubjectStateRecords_MOVIE_SYNC, DoubanSubjectStateRecords_MOVIE_SYNC,
DoubanSubjectStateRecords_MUSIC_SYNC, DoubanSubjectStateRecords_MUSIC_SYNC,
DoubanSubjectStateRecords_NOTE_SYNC, DoubanSubjectStateRecords_NOTE_SYNC,
@ -25,6 +31,12 @@ import {createFileSelectionSetting} from "../setting/TemplateSettingHelper";
import {FileSuggest} from "../setting/model/FileSuggest"; import {FileSuggest} from "../setting/model/FileSuggest";
import {getDefaultTemplateContent} from "../../constant/DefaultTemplateContent"; import {getDefaultTemplateContent} from "../../constant/DefaultTemplateContent";
import TimeUtil from "../../utils/TimeUtil"; import TimeUtil from "../../utils/TimeUtil";
import SettingsManager from "../setting/SettingsManager";
import {ArraySetting, DEFAULT_SETTINGS_ARRAY_NAME} from "../setting/model/ArraySetting";
import {arraySettingDisplay} from "../setting/ArrayDisplayTypeSettingsHelper";
import {DatePickComponent} from "./DatePickComponent";
import {NumberComponent} from "./NumberComponent";
import {log} from "../../utils/Logutil";
export class DoubanSyncModal extends Modal { export class DoubanSyncModal extends Modal {
plugin: DoubanPlugin; plugin: DoubanPlugin;
@ -96,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;
@ -105,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) {
@ -120,8 +150,14 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
cacheImage: ( settings.cacheImage == null) ? DEFAULT_SETTINGS.cacheImage : settings.cacheImage, cacheImage: ( settings.cacheImage == null) ? DEFAULT_SETTINGS.cacheImage : settings.cacheImage,
cacheHighQuantityImage: ( settings.cacheHighQuantityImage == null) ? DEFAULT_SETTINGS.cacheHighQuantityImage : settings.cacheHighQuantityImage, cacheHighQuantityImage: ( settings.cacheHighQuantityImage == null) ? DEFAULT_SETTINGS.cacheHighQuantityImage : settings.cacheHighQuantityImage,
attachmentPath: (settings.attachmentPath == '' || settings.attachmentPath == null) ? DEFAULT_SETTINGS.attachmentPath : settings.attachmentPath, attachmentPath: (settings.attachmentPath == '' || settings.attachmentPath == null) ? DEFAULT_SETTINGS.attachmentPath : settings.attachmentPath,
attachmentFileName: (settings.attachmentFileName == '' || settings.attachmentFileName == null) ? DEFAULT_SETTINGS.attachmentFileName : settings.attachmentFileName,
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.ALL,
syncConditionDateFromValue: TimeUtil.getLastMonth(),
syncConditionDateToValue: new Date(),
syncConditionCountFromValue: 1,
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");
@ -160,10 +196,10 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
private showConfigPan(contentEl: HTMLElement, config:SyncConfig, disable:boolean) { private showConfigPan(contentEl: HTMLElement, config:SyncConfig, disable:boolean) {
new Setting(contentEl); new Setting(contentEl);
this.showTypeDropdown(contentEl, config, disable); this.showTypeDropdown(contentEl, config, disable);
this.showCondition(contentEl, config, disable);
this.showOutputFolderSelections(contentEl, config, disable); // this.showOutputFolderSelections(contentEl, config, disable);
this.showOutiFleName(contentEl, config, disable); // this.showOutiFleName(contentEl, config, disable);
this.showAttachmentsFileConfig(contentEl, config, disable); // this.showAttachmentsFileConfig(contentEl, config, disable);
this.showUpdateAllConfig(contentEl, config, disable); this.showUpdateAllConfig(contentEl, config, disable);
this.showForceUpdateConfig(contentEl, config, disable); this.showForceUpdateConfig(contentEl, config, disable);
} }
@ -196,14 +232,17 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
case SyncType.teleplay: case SyncType.teleplay:
this.showScopeDropdown(contentEl, DoubanSubjectStateRecords_TELEPLAY_SYNC, config, disable); this.showScopeDropdown(contentEl, DoubanSubjectStateRecords_TELEPLAY_SYNC, config, disable);
break; break;
case SyncType.game:
config.scope = DoubanSubjectState.collect;
this.showScopeDropdown(contentEl, DoubanSubjectStateRecords_GAME_SYNC, config, disable);
break;
} }
} }
private showTypeDropdown(containerEl:HTMLElement, config: SyncConfig, disable:boolean) { private showTypeDropdown(containerEl:HTMLElement, config: SyncConfig, disable:boolean) {
const settings = new Setting(containerEl); const settings = new Setting(containerEl);
const scopeSelections = containerEl.createDiv("scope-selection"); const scopeSelections = containerEl.createDiv("scope-selection");
const templateFile:HTMLDivElement = containerEl.createDiv('template-file-path-selection'); // const templateFile:HTMLDivElement = containerEl.createDiv('template-file-path-selection');
settings settings
.setName(i18nHelper.getMessage('110030')) .setName(i18nHelper.getMessage('110030'))
.addDropdown((dropdown) => { .addDropdown((dropdown) => {
@ -213,11 +252,11 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
config.syncType = value; config.syncType = value;
config.templateFile = this.getDefaultTemplatePath(value); config.templateFile = this.getDefaultTemplatePath(value);
this.openScopeDropdown(scopeSelections, config, disable); this.openScopeDropdown(scopeSelections, config, disable);
this.showTemplateFileSelectionSetting(templateFile, config, disable); // this.showTemplateFileSelectionSetting(templateFile, config, disable);
}); });
}).setDisabled(disable); }).setDisabled(disable);
this.openScopeDropdown(scopeSelections, config, disable); this.openScopeDropdown(scopeSelections, config, disable);
this.showTemplateFileSelectionSetting(templateFile, config, disable); // this.showTemplateFileSelectionSetting(templateFile, config, disable);
} }
private getDefaultTemplatePath(value: string) { private getDefaultTemplatePath(value: string) {
@ -236,6 +275,9 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
case SyncType.teleplay: case SyncType.teleplay:
result = (settings.teleplayTemplateFile == '' || settings.teleplayTemplateFile == null) ? DEFAULT_SETTINGS.teleplayTemplateFile : settings.teleplayTemplateFile result = (settings.teleplayTemplateFile == '' || settings.teleplayTemplateFile == null) ? DEFAULT_SETTINGS.teleplayTemplateFile : settings.teleplayTemplateFile
break; break;
case SyncType.game:
result = (settings.gameTemplateFile == '' || settings.gameTemplateFile == null) ? DEFAULT_SETTINGS.gameTemplateFile : settings.gameTemplateFile
break;
} }
return result; return result;
} }
@ -383,6 +425,20 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
}); });
}) })
.setDisabled(disable); .setDisabled(disable);
new Setting(containerEl)
.setName( i18nHelper.getMessage('121452'))
.setDesc( i18nHelper.getMessage('121453'))
.addSearch(async (search: SearchComponent) => {
new FolderSuggest(this.plugin.app, search.inputEl);
// @ts-ignore
search.setValue(config.attachmentFileName)
// @ts-ignore
.setPlaceholder(i18nHelper.getMessage('121454'))
.onChange(async (value: string) => {
config.attachmentFileName = value;
});
})
.setDisabled(disable);
new Setting(containerEl) new Setting(containerEl)
.setName(i18nHelper.getMessage('121435')) .setName(i18nHelper.getMessage('121435'))
@ -412,4 +468,128 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
.setDisabled(disable); .setDisabled(disable);
} }
private showCondition(contentEl: HTMLElement, config: SyncConfig, disable: boolean) {
showConditionItem(contentEl.createDiv("sync-douban-condition"), this.plugin.settingsManager, config, disable);
}
}
function showConditionItem(containerEl: HTMLElement, manager: SettingsManager, config: SyncConfig, disable: boolean) {
containerEl.empty();
const condition = new Setting(containerEl).setName(i18nHelper.getMessage('110070'))
const conditionDesc = condition.descEl.createDiv('sync-douban-condition-desc');
new DropdownComponent(conditionDesc).addOptions(SyncConditionTypeRecords)
.setValue(config.syncConditionType)
.onChange((value) => {
config.syncConditionType = value;
showConditionItem(containerEl, manager, config, disable);
}).setDisabled(disable);
showConditionItemInput(conditionDesc, config, disable);
}
function showConditionItemInput(containerEl: HTMLElement, config: SyncConfig, disable: boolean) {
if (config.syncConditionType == SyncConditionType.CUSTOM_ITEM) {
showCustomInputCount(containerEl, config, disable);
}else if (config.syncConditionType == SyncConditionType.CUSTOM_TIME) {
showCustomInputTime(containerEl, config, disable);
}
}
function showCustomInputCount(containerEl: HTMLElement, config: SyncConfig, disable: boolean) {
containerEl.createEl('span', { text: ' ' })
containerEl.createEl('span', { text: i18nHelper.getMessage('110077') })
containerEl.createEl('span', { text: i18nHelper.getMessage('110078') })
const fromField = new TextComponent(containerEl);
fromField.setPlaceholder(i18nHelper.getMessage('110080'))
.setValue(config.syncConditionCountFromValue + '')
.onChange(async (value) => {
if (!value) {
config.syncConditionCountFromValue = 1;
return;
}
try {
config.syncConditionCountFromValue = parseInt(value);
}catch (e) {
log.notice(i18nHelper.getMessage('112080'))
}
}).setDisabled(disable);
let fromEl = fromField.inputEl;
fromEl.addClass('obsidian_douban_settings_input')
fromEl.style.width ='20%';
containerEl.appendChild(fromEl);
const lang = window.localStorage.getItem('language');
if (lang == 'zh') {
containerEl.createEl('span', {text: i18nHelper.getMessage('110073')})
}
containerEl.createEl('span', { text: i18nHelper.getMessage('110079') })
containerEl.createEl('span', { text: i18nHelper.getMessage('110078') })
const toField = new TextComponent(containerEl);
toField.setPlaceholder(i18nHelper.getMessage('110080'))
.setValue(config.syncConditionCountToValue + '')
.onChange(async (value) => {
if (!value) {
config.syncConditionCountToValue = 30;
return;
}
try {
config.syncConditionCountToValue = parseInt(value);
}catch (e) {
log.notice(i18nHelper.getMessage('112080'))
}
}).setDisabled(disable);
let toEl = toField.inputEl;
toEl.addClass('obsidian_douban_settings_input')
toEl.style.width ='20%';
containerEl.appendChild(toEl);
if (lang == 'zh') {
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) {
containerEl.createEl('span', { text: i18nHelper.getMessage('110077') })
const fromDateField = new TextComponent(containerEl);
const fromDateEl = fromDateField.inputEl;
fromDateEl.type = 'date';
fromDateEl.value = config.syncConditionDateFromValue ? config.syncConditionDateFromValue.toISOString().substring(0, 10) : TimeUtil.getLastMonth().toISOString().substring(0, 10);
fromDateField.setPlaceholder(i18nHelper.getMessage('110075'))
.setValue(config.syncConditionDateFromValue ? config.syncConditionDateFromValue.toISOString().substring(0, 10) : TimeUtil.getLastMonth().toISOString().substring(0, 10))
.onChange(async (value) => {
if (!value) {
return;
}
try {
config.syncConditionDateFromValue = new Date(value);
}catch (e) {
log.notice(i18nHelper.getMessage('110082'))
}
}).setDisabled(disable);
fromDateEl.addClass('obsidian_douban_settings_input')
containerEl.appendChild(fromDateEl);
containerEl.createEl('span', { text: i18nHelper.getMessage('110079') })
const toDateField = new TextComponent(containerEl);
let toDateEl = toDateField.inputEl;
toDateEl.type = 'date';
toDateEl.value = config.syncConditionDateToValue ? config.syncConditionDateToValue.toISOString().substring(0, 10) : new Date().toISOString().substring(0, 10);
toDateField.setPlaceholder(i18nHelper.getMessage('110075'))
.setValue(config.syncConditionDateToValue ? config.syncConditionDateToValue.toISOString().substring(0, 10) : new Date().toISOString().substring(0, 10))
.onChange(async (value) => {
if (!value) {
return;
}
try {
config.syncConditionDateToValue = new Date(value);
}catch (e) {
log.notice(i18nHelper.getMessage('110082'))
}
}).setDisabled(disable);
toDateEl.addClass('obsidian_douban_settings_input')
containerEl.appendChild(toDateEl);
new ButtonComponent(containerEl).setIcon('help').setTooltip(i18nHelper.getMessage('110095'))
} }

@ -0,0 +1,26 @@
/**
*
* TextComponent
*/
import {TextComponent} from 'obsidian';
export class NumberComponent extends TextComponent {
constructor(container: HTMLElement, value: number = 0) {
super(container);
this.inputEl.type = 'date';
this.inputEl.value = value.toString();
}
getValue(): string {
return this.inputEl.value;
}
//当输入框输入的内容不是数字时,则回退到之前的值
// onChanged() {
// if (isNaN(Number(this.inputEl.value))) {
// this.inputEl.value = this.inputEl.value.slice(0, -1);
// }
// }
}

@ -5,7 +5,7 @@ import {moment, Platform, TFile} 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 YamlUtil from "../../../utils/YamlUtil"; import YamlUtil, {TITLE_ALIASES_SPECIAL_CHAR_REG_G} from "../../../utils/YamlUtil";
import { import {
BasicConst, BasicConst,
DataValueType, DataValueType,
@ -47,36 +47,44 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
async parse(extract: T, context: HandleContext): Promise<HandleResult> { async parse(extract: T, context: HandleContext): Promise<HandleResult> {
const template: string = await this.getTemplate(extract, context); const template: string = await this.getTemplate(extract, context);
await this.saveImage(extract, context); const variableMap = this.buildVariableMap(extract, context);
this.parseUserInfo(template, variableMap, extract, context);
this.parseVariable(template, variableMap, extract, context);
await this.saveImage(extract, context, variableMap);
const frontMatterStart: number = template.indexOf(BasicConst.YAML_FRONT_MATTER_SYMBOL, 0); const frontMatterStart: number = template.indexOf(BasicConst.YAML_FRONT_MATTER_SYMBOL, 0);
const frontMatterEnd: number = template.indexOf(BasicConst.YAML_FRONT_MATTER_SYMBOL, frontMatterStart + 1); const frontMatterEnd: number = template.indexOf(BasicConst.YAML_FRONT_MATTER_SYMBOL, frontMatterStart + 1);
let frontMatter = ''; let frontMatter = '';
let frontMatterBefore = ''; let frontMatterBefore = '';
let frontMatterAfter = ''; let frontMatterAfter = '';
let result = ''; let result = '';
if (frontMatterStart > -1 && frontMatterEnd > -1) { if (frontMatterStart > -1 && frontMatterEnd > -1) {
frontMatterBefore = template.substring(0, frontMatterStart); frontMatterBefore = template.substring(0, frontMatterStart);
frontMatter = template.substring(frontMatterStart, frontMatterEnd + 3); frontMatter = template.substring(frontMatterStart, frontMatterEnd + 3);
frontMatterAfter = template.substring(frontMatterEnd + 3); frontMatterAfter = template.substring(frontMatterEnd + 3);
if (frontMatterBefore.length > 0) { if (frontMatterBefore.length > 0) {
frontMatterBefore = this.parsePartText(frontMatterBefore, extract, context); frontMatterBefore = this.parsePartText(frontMatterBefore, extract, context, variableMap);
} }
if (frontMatterAfter.length > 0) { if (frontMatterAfter.length > 0) {
frontMatterAfter = this.parsePartText(frontMatterAfter, extract, context); frontMatterAfter = this.parsePartText(frontMatterAfter, extract, context, variableMap);
} }
if (frontMatter.length > 0) { if (frontMatter.length > 0) {
frontMatter = this.parsePartText(frontMatter, extract, context, TemplateTextMode.YAML); frontMatter = this.parsePartYml(frontMatter, extract, context, variableMap);
} }
result = frontMatterBefore + frontMatter + frontMatterAfter; result = frontMatterBefore + frontMatter + frontMatterAfter;
} else { } else {
result = this.parsePartText(template, extract, context); result = this.parsePartText(template, extract, context, variableMap);
}
let filePath = '';
if (SearchHandleMode.FOR_CREATE == context.mode) {
filePath = this.parsePartPath(this.getFilePath(context), extract, context, variableMap);
} }
let fileName = ''; let fileName = '';
if (SearchHandleMode.FOR_CREATE == context.mode) { if (SearchHandleMode.FOR_CREATE == context.mode) {
fileName = this.parsePartText(this.getFileName(context), extract, context); fileName = this.parsePartPath(this.getFileName(context), extract, context, variableMap);
} }
return {content: result,filePath: filePath, fileName: fileName, subject:extract};
return {content: result, fileName: fileName, subject:extract};
} }
private getFileName(context: HandleContext): string { private getFileName(context: HandleContext): string {
@ -88,13 +96,22 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
return dataFileNamePath ? dataFileNamePath : DEFAULT_SETTINGS.dataFileNamePath; return dataFileNamePath ? dataFileNamePath : DEFAULT_SETTINGS.dataFileNamePath;
} }
private getFilePath(context: HandleContext): string {
const {syncConfig} = context;
if (syncConfig) {
return syncConfig.dataFilePath;
}
const {dataFilePath} = context.settings;
return dataFilePath ? dataFilePath : DEFAULT_SETTINGS.dataFilePath;
}
abstract getSupportType(): SupportType; abstract getSupportType(): SupportType;
abstract parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: T, context: HandleContext, textMode: TemplateTextMode): void; abstract parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: T, context: HandleContext): void;
abstract support(extract: DoubanSubject): boolean; abstract support(extract: DoubanSubject): boolean;
@ -125,7 +142,7 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
}else { }else {
context.syncStatusHolder?context.syncStatusHolder.syncStatus.handled(1):null; context.syncStatusHolder?context.syncStatusHolder.syncStatus.handled(1):null;
} }
return e; return undefined;
}); });
@ -207,7 +224,7 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
default: default:
resultName = name; resultName = name;
} }
return resultName; return resultName.trim();
} }
getTitleNameByMode(name: string, personNameMode: string, context: HandleContext): string { getTitleNameByMode(name: string, personNameMode: string, context: HandleContext): string {
@ -254,13 +271,25 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
return s; return s;
} }
private parsePartText(template: string, extract: T, context: HandleContext, textMode: TemplateTextMode = TemplateTextMode.NORMAL): string { private parsePartYml(template: string, extract: T, context: HandleContext, variableMap : Map<string, DataField>): string {
const variableMap:Map<string, DataField> = new Map(); return VariableUtil.replaceSubject(variableMap, template, this.getSupportType(), this.doubanPlugin.settingsManager, 'yml_text');
}
private parsePartText(template: string, extract: T, context: HandleContext, variableMap : Map<string, DataField>): string {
return VariableUtil.replaceSubject(variableMap, template, this.getSupportType(), this.doubanPlugin.settingsManager, 'text');
}
private parsePartPath(template: string, extract: T, context: HandleContext, variableMap : Map<string, DataField>): string {
return VariableUtil.replaceSubject(variableMap, template, this.getSupportType(), this.doubanPlugin.settingsManager, 'path');
}
private buildVariableMap(extract: T, context: HandleContext) {
const variableMap: Map<string, DataField> = new Map();
for (const [key, value] of Object.entries(extract)) { for (const [key, value] of Object.entries(extract)) {
if (!value) { if (!value) {
continue; continue;
} }
const type:DataValueType = VariableUtil.getType(value); const type: DataValueType = VariableUtil.getType(value);
if (key == 'score') { if (key == 'score') {
variableMap.set(DoubanParameterName.SCORE_STAR, new DataField( variableMap.set(DoubanParameterName.SCORE_STAR, new DataField(
DoubanParameterName.SCORE_STAR, DoubanParameterName.SCORE_STAR,
@ -308,14 +337,10 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
currentDate, currentDate,
moment(currentDate).format(context.settings.timeFormat) moment(currentDate).format(context.settings.timeFormat)
)); ));
return variableMap;
this.parseUserInfo(template, variableMap, extract, context, textMode);
this.parseVariable(template, variableMap, extract, context, textMode);
return VariableUtil.replaceSubject(variableMap, template, this.getSupportType(), this.doubanPlugin.settingsManager);
} }
private parseUserInfo(resultContent: string, variableMap:Map<string, DataField>, extract: T, context: HandleContext, textMode: TemplateTextMode) { private parseUserInfo(resultContent: string, variableMap:Map<string, DataField>, extract: T, context: HandleContext) {
const userState = extract.userState; const userState = extract.userState;
if ((resultContent.indexOf(DoubanUserParameter.MY_TAGS) >= 0 || if ((resultContent.indexOf(DoubanUserParameter.MY_TAGS) >= 0 ||
resultContent.indexOf(DoubanUserParameter.MY_RATING) >= 0 || resultContent.indexOf(DoubanUserParameter.MY_RATING) >= 0 ||
@ -389,22 +414,22 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
private getTemplateKey():TemplateKey { private getTemplateKey():TemplateKey {
let templateKey: TemplateKey; let templateKey: TemplateKey;
switch (this.getSupportType()) { switch (this.getSupportType()) {
case SupportType.MOVIE: case SupportType.movie:
templateKey = TemplateKey.movieTemplateFile; templateKey = TemplateKey.movieTemplateFile;
break; break;
case SupportType.BOOK: case SupportType.book:
templateKey = TemplateKey.bookTemplateFile; templateKey = TemplateKey.bookTemplateFile;
break; break;
case SupportType.MUSIC: case SupportType.music:
templateKey = TemplateKey.musicTemplateFile; templateKey = TemplateKey.musicTemplateFile;
break; break;
case SupportType.TELEPLAY: case SupportType.teleplay:
templateKey = TemplateKey.teleplayTemplateFile; templateKey = TemplateKey.teleplayTemplateFile;
break; break;
case SupportType.GAME: case SupportType.game:
templateKey = TemplateKey.gameTemplateFile; templateKey = TemplateKey.gameTemplateFile;
break; break;
case SupportType.NOTE: case SupportType.note:
templateKey = TemplateKey.noteTemplateFile; templateKey = TemplateKey.noteTemplateFile;
break; break;
default: default:
@ -497,43 +522,82 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
} }
} }
private async saveImage(extract: T, context: HandleContext) { private async saveImage(extract: T, context: HandleContext, variableMap : Map<string, DataField>) {
const {syncConfig} = context; const {syncConfig} = context;
if (!extract.image || (syncConfig && !syncConfig.cacheImage) || !context.settings.cacheImage) { if (!extract.image || (syncConfig && !syncConfig.cacheImage) || !context.settings.cacheImage) {
return; return;
} }
const image = extract.image; const image = extract.image;
const filename = image.split('/').pop();
let folder = syncConfig? syncConfig.attachmentPath : context.settings.attachmentPath; let folder = syncConfig? syncConfig.attachmentPath : context.settings.attachmentPath;
if (!folder) { if (!folder) {
folder = DEFAULT_SETTINGS.attachmentPath; folder = DEFAULT_SETTINGS.attachmentPath;
} }
folder = this.parsePartText(folder, extract, context) folder = this.parsePartPath(folder, extract, context, variableMap)
let fileName = syncConfig? syncConfig.attachmentFileName : context.settings.attachmentFileName;
const referHeaders = {'referer': image}; if (!fileName) {
fileName = DEFAULT_SETTINGS.attachmentFileName;
}
let fileNameSuffix = image ? image.substring(image.lastIndexOf('.')) : '.jpg';
if (fileNameSuffix && fileNameSuffix.length > 10) {
fileNameSuffix = '.jpg';
}
fileName = this.parsePartPath(fileName, extract, context, variableMap)
fileName = fileName + fileNameSuffix;
const imageReferer = (extract.id ? this.getSubjectUrl(extract.id) : '') || extract.url;
const referHeaders = HttpUtil.buildImageRequestHeaders(
context.plugin.settingsManager.getHeaders() as Record<string, any>,
imageReferer
);
if ((syncConfig ? syncConfig.cacheHighQuantityImage : context.settings.cacheHighQuantityImage) && context.userComponent.isLogin()) { if ((syncConfig ? syncConfig.cacheHighQuantityImage : context.settings.cacheHighQuantityImage) && context.userComponent.isLogin()) {
try { try {
const fileNameSpilt = filename.split('.'); const highImageFilename = this.getImageFilename(image);
const highFilename = fileNameSpilt.first() + '.jpg'; const highImage = this.getHighQuantityImageUrl(highImageFilename);
const highImageHeaders = HttpUtil.buildImageRequestHeaders(
const highImage = this.getHighQuantityImageUrl(highFilename); context.plugin.settingsManager.getHeaders() as Record<string, any>,
const resultValue = await this.handleImage(highImage, folder, highFilename, context, false, referHeaders); imageReferer
);
const resultValue = await this.handleImage(highImage, folder, fileName, context, false, highImageHeaders);
if (resultValue && resultValue.success) { if (resultValue && resultValue.success) {
extract.image = resultValue.filepath; extract.image = resultValue.filepath;
this.initImageVariableMap(extract, context, variableMap);
return; return;
} }
}catch (e) { }catch (e) {
console.error(e); console.error(e);
console.error('下载高清封面失败,将会使用普通封面') console.error('下载高清封面失败,将会使用普通封面')
} }
} }
const resultValue = await this.handleImage(image, folder, filename, context, true, referHeaders); const resultValue = await this.handleImage(image, folder, fileName, context, true, referHeaders);
if (resultValue && resultValue.success) { if (resultValue && resultValue.success) {
extract.image = resultValue.filepath; extract.image = resultValue.filepath;
this.initImageVariableMap(extract, context, variableMap);
} }
} }
private getImageFilename(image: string): string {
if (!image) {
return '';
}
const imageUrl = image.split('?').first() || image;
return imageUrl.substring(imageUrl.lastIndexOf('/') + 1);
}
private initImageVariableMap(extract: T, context: HandleContext, variableMap : Map<string, DataField>) {
variableMap.set(DoubanParameterName.IMAGE_URL, new DataField(
DoubanParameterName.IMAGE_URL,
DataValueType.url,
extract.imageUrl,
extract.imageUrl
));
variableMap.set(DoubanParameterName.IMAGE, new DataField(
DoubanParameterName.IMAGE,
DataValueType.path,
extract.image,
extract.image
));
}
private async handleImage(image: string, folder: string, filename: string, context: HandleContext, showError: boolean, headers?: any) { private async handleImage(image: string, folder: string, filename: string, context: HandleContext, showError: boolean, headers?: any) {
//只有在桌面版且开启了图片上传才会使用PicGo并且开启图床功能 //只有在桌面版且开启了图片上传才会使用PicGo并且开启图床功能
if (context.settings.pictureBedFlag && Platform.isDesktopApp) { if (context.settings.pictureBedFlag && Platform.isDesktopApp) {
@ -557,12 +621,20 @@ export default abstract class DoubanAbstractLoadHandler<T extends DoubanSubject>
handlePersonNameByMeta(html: CheerioAPI, movie: DoubanSubject, context: HandleContext, handlePersonNameByMeta(html: CheerioAPI, movie: DoubanSubject, context: HandleContext,
metaProperty:string, objectProperty:string) { metaProperty:string, objectProperty:string) {
if (!movie) {
return;
}
const metaProperties: string[] = html(`head > meta[property='${metaProperty}']`).get() const metaProperties: string[] = html(`head > meta[property='${metaProperty}']`).get()
.map((e) => { .map((e) => {
return html(e).attr('content'); return html(e).attr('content');
}); });
// @ts-ignore // @ts-ignore
movie[objectProperty] const currentArray = movie[objectProperty];
if (!Array.isArray(currentArray)) {
return;
}
// @ts-ignore
currentArray
// @ts-ignore // @ts-ignore
.filter((p:Person) => p.name) .filter((p:Person) => p.name)
// @ts-ignore // @ts-ignore

@ -17,7 +17,7 @@ export default class DoubanBookLoadHandler extends DoubanAbstractLoadHandler<Dou
} }
getSupportType(): SupportType { getSupportType(): SupportType {
return SupportType.BOOK; return SupportType.book;
} }
getHighQuantityImageUrl(fileName:string):string{ getHighQuantityImageUrl(fileName:string):string{
@ -28,11 +28,11 @@ export default class DoubanBookLoadHandler extends DoubanAbstractLoadHandler<Dou
return `https://book.douban.com/subject/${id}/`; return `https://book.douban.com/subject/${id}/`;
} }
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanBookSubject, context: HandleContext, textMode: TemplateTextMode): void { parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanBookSubject, context: HandleContext): void {
variableMap.set(DoubanBookParameter.author, new DataField(DoubanBookParameter.author, variableMap.set(DoubanBookParameter.author, new DataField(DoubanBookParameter.author,
DataValueType.array, extract.author, extract.author.map(this.handleSpecialAuthorName))); DataValueType.array, extract.author, (extract.author || []).map(this.handleSpecialAuthorName)));
variableMap.set(DoubanBookParameter.translator, new DataField(DoubanBookParameter.translator, variableMap.set(DoubanBookParameter.translator, new DataField(DoubanBookParameter.translator,
DataValueType.array, extract.translator, extract.translator.map(this.handleSpecialAuthorName))); DataValueType.array, extract.translator, (extract.translator || []).map(this.handleSpecialAuthorName)));
} }
support(extract: DoubanSubject): boolean { support(extract: DoubanSubject): boolean {

@ -9,6 +9,7 @@ import {UserStateSubject} from "../model/UserStateSubject";
import {moment} from "obsidian"; import {moment} from "obsidian";
import {TITLE_ALIASES_SPECIAL_CHAR_REG_G} from "../../../utils/YamlUtil"; import {TITLE_ALIASES_SPECIAL_CHAR_REG_G} from "../../../utils/YamlUtil";
import {DataField} from "../../../utils/model/DataField"; import {DataField} from "../../../utils/model/DataField";
import {b} from "@shikijs/engine-javascript/dist/shared/engine-javascript.BnuFKbIS";
export default class DoubanGameLoadHandler extends DoubanAbstractLoadHandler<DoubanGameSubject> { export default class DoubanGameLoadHandler extends DoubanAbstractLoadHandler<DoubanGameSubject> {
@ -17,7 +18,7 @@ export default class DoubanGameLoadHandler extends DoubanAbstractLoadHandler<Dou
} }
getSupportType(): SupportType { getSupportType(): SupportType {
return SupportType.GAME; return SupportType.game;
} }
getHighQuantityImageUrl(fileName:string):string{ getHighQuantityImageUrl(fileName:string):string{
return `https://img9.doubanio.com/lpic/${fileName}`; return `https://img9.doubanio.com/lpic/${fileName}`;
@ -28,7 +29,18 @@ export default class DoubanGameLoadHandler extends DoubanAbstractLoadHandler<Dou
} }
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanGameSubject, context: HandleContext): void { parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanGameSubject, context: HandleContext): void {
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases, extract.aliases.map(a=>a.replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')))); // super.parseAliases(beforeContent, variableMap, extract, context);
variableMap.set("aliases", new DataField("aliases", DataValueType.array, extract.aliases,
(extract.aliases || []).map(a=>a
.trim()
// .replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
// //replase multiple _ to single _
// .replace(/_+/g, '_')
// .replace(/^_/, '')
// .replace(/_$/, '')
.replace(/:\s+/g, ':')
)));
// super.parseAliases(beforeContent, variableMap, extract, context);
} }
support(extract: DoubanSubject): boolean { support(extract: DoubanSubject): boolean {

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

@ -16,7 +16,7 @@ export default class DoubanMusicLoadHandler extends DoubanAbstractLoadHandler<Do
} }
getSupportType(): SupportType { getSupportType(): SupportType {
return SupportType.MUSIC; return SupportType.music;
} }
getHighQuantityImageUrl(fileName:string):string{ getHighQuantityImageUrl(fileName:string):string{
@ -88,6 +88,9 @@ export default class DoubanMusicLoadHandler extends DoubanAbstractLoadHandler<Do
const idPattern = /(\d){5,10}/g; const idPattern = /(\d){5,10}/g;
const id = idPattern.exec(url); const id = idPattern.exec(url);
const trackItems = html('.track-list .track-items li');
const tracks = Array.from(trackItems).map(item => html(item).text().trim());
const result: DoubanMusicSubject = { const result: DoubanMusicSubject = {
image: image, image: image,
imageUrl: image, imageUrl: image,
@ -104,11 +107,10 @@ export default class DoubanMusicLoadHandler extends DoubanAbstractLoadHandler<Do
genre: valueMap.has('genre') ? [valueMap.get('genre')] : [""], genre: valueMap.has('genre') ? [valueMap.get('genre')] : [""],
albumType: valueMap.has('albumType') ? valueMap.get('albumType') : "", albumType: valueMap.has('albumType') ? valueMap.get('albumType') : "",
medium: valueMap.has('medium') ? valueMap.get('medium') : "", medium: valueMap.has('medium') ? valueMap.get('medium') : "",
barcode: valueMap.has('barcode') ? valueMap.get('barcode') : "" barcode: valueMap.has('barcode') ? valueMap.get('barcode') : "",
menu: tracks
}; };
return result; return result;
} }

@ -16,7 +16,7 @@ export default class DoubanNoteLoadHandler extends DoubanAbstractLoadHandler<Dou
} }
getSupportType(): SupportType { getSupportType(): SupportType {
return SupportType.NOTE; return SupportType.note;
} }
getHighQuantityImageUrl(fileName:string):string{ getHighQuantityImageUrl(fileName:string):string{

@ -12,8 +12,8 @@ import {DataField} from "../../../utils/model/DataField";
* *
*/ */
export default class DoubanOtherLoadHandler extends DoubanAbstractLoadHandler<DoubanSubject> { export default class DoubanOtherLoadHandler extends DoubanAbstractLoadHandler<DoubanSubject> {
getSupportType(): SupportType.ALL { getSupportType(): SupportType.all {
return SupportType.ALL; return SupportType.all;
} }
parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanSubject, context: HandleContext): void { parseVariable(beforeContent: string, variableMap:Map<string, DataField>, extract: DoubanSubject, context: HandleContext): void {

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

@ -17,7 +17,7 @@ export default class DoubanTheaterLoadHandler extends DoubanAbstractLoadHandler<
} }
getSupportType(): SupportType { getSupportType(): SupportType {
return SupportType.THEATER; return SupportType.theater;
} }
getHighQuantityImageUrl(fileName: string): string { getHighQuantityImageUrl(fileName: string): string {
@ -33,28 +33,36 @@ export default class DoubanTheaterLoadHandler extends DoubanAbstractLoadHandler<
"director", "director",
DataValueType.array, DataValueType.array,
extract.director, extract.director,
extract.director.map(SchemaOrg.getPersonName).filter(c => c) (extract.director || []).map(SchemaOrg.getPersonName).filter(c => c)
)); ));
variableMap.set("actor", new DataField( variableMap.set("actor", new DataField(
"actor", "actor",
DataValueType.array, DataValueType.array,
extract.actor, extract.actor,
extract.actor.map(SchemaOrg.getPersonName).filter(c => c) (extract.actor || []).map(SchemaOrg.getPersonName).filter(c => c)
)); ));
variableMap.set("author", new DataField( variableMap.set("author", new DataField(
"author", "author",
DataValueType.array, DataValueType.array,
extract.author, extract.author,
extract.author.map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c) (extract.author || []).map(SchemaOrg.getPersonName).map(name => super.getPersonName(name, context)).filter(c => c)
)); ));
variableMap.set("aliases", new DataField( variableMap.set("aliases", new DataField(
"aliases", "aliases",
DataValueType.array, DataValueType.array,
extract.aliases, extract.aliases,
extract.aliases.map(a => a.replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')) (extract.aliases || []).map(a => a
.trim()
.replace(TITLE_ALIASES_SPECIAL_CHAR_REG_G, '_')
//replace multiple _ to single _
.replace(/_+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
)
)); ));
} }

@ -6,4 +6,5 @@ export default class DoubanMusicSubject extends DoubanSubject {
medium: string; medium: string;
records: number; records: number;
barcode: string; barcode: string;
menu: string[];
} }

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

@ -2,6 +2,7 @@ import DoubanSubject from "./DoubanSubject";
export default interface HandleResult { export default interface HandleResult {
content:string content:string
filePath?:string
fileName?:string fileName?:string
subject?:DoubanSubject, subject?:DoubanSubject,
} }

@ -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 {
return new SearchPage(0, 1, 0, type, []);
constructor(total: number, pageNum: number, pageSize: number, type:SupportType, list: any[]) {
super(total, pageNum, pageSize, type);
this._list = list;
} }
public get list() { static emptyWithNoType() {
return this._list; return new SearchPage(0, 1, 0, null, []);
}
public static empty(type:SupportType):SearchPage {
return new SearchPage(0, 0, 0, type, []);
} }
} }

@ -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;
} }
@ -73,7 +77,7 @@ export class SearchPageInfo {
return this; return this;
} }
return new SearchPageInfo(this.total, this._pageNum - 1, return new SearchPageInfo(this.total, this._pageNum - 1,
this._pageSize, SupportType.ALL); this._pageSize, SupportType.all);
} }

@ -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, 1, 0, type, []);
}
static emptyWithNoType() {
return new SearchPageTypeOf(0, 1, 0, null, []);
}
}

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

@ -1,9 +1,13 @@
import { import {
DoubanSearchGroupPublishResultSubjectNextPage, DoubanSearchGroupPublishResultSubjectPreviousPage, DoubanSearchGroupPublishResultSubjectNextPage,
DoubanSearchGroupPublishResultSubjectPreviousPage,
DoubanSearchResultSubject_EMPTY, DoubanSearchResultSubject_TIP_EMPTY,
DoubanSearchResultSubjectNextPage, DoubanSearchResultSubjectNextPage,
DoubanSearchResultSubjectNextPageNeedLogin, DoubanSearchResultSubjectNextPageNeedLogin,
DoubanSearchResultSubjectPreviousPage, DoubanSearchResultSubjectPreviousPage,
NavigateType, SEARCH_ITEM_PAGE_SIZE, SupportType NavigateType,
SEARCH_ITEM_PAGE_SIZE,
SupportType
} from "../../../constant/Constsant"; } from "../../../constant/Constsant";
import {FuzzySuggestModal, RequestUrlParam, request} from "obsidian"; import {FuzzySuggestModal, RequestUrlParam, request} from "obsidian";
@ -27,7 +31,7 @@ class DoubanFuzzySuggester extends FuzzySuggestModal<DoubanSearchResultSubject>
private searchItem:string; private searchItem:string;
constructor(plugin: DoubanPlugin, context: HandleContext, searchItem:string) { constructor(plugin: DoubanPlugin, context: HandleContext, searchItem:string) {
super(app); super(plugin.app);
this.plugin = plugin; this.plugin = plugin;
this.context = context; this.context = context;
this.searchItem = searchItem; this.searchItem = searchItem;
@ -106,9 +110,18 @@ class DoubanFuzzySuggester extends FuzzySuggestModal<DoubanSearchResultSubject>
private initItems(searchPage: SearchPage) { private initItems(searchPage: SearchPage) {
const doubanList: DoubanSearchResultSubject[] = searchPage.list; const doubanList: DoubanSearchResultSubject[] = searchPage.list;
if (searchPage.type == SupportType.all && searchPage.pageNum == 1) {
if (doubanList.length == 0) {
// if (searchPage.list.length > 0) {
doubanList.push(DoubanSearchResultSubject_EMPTY);
}else if (searchPage.list.length < SEARCH_ITEM_PAGE_SIZE) {
doubanList.push(DoubanSearchResultSubject_TIP_EMPTY);
}
}
if (searchPage.hasNext) { if (searchPage.hasNext) {
if (this.plugin.userComponent.isLogin()) { if (this.plugin.userComponent.isLogin()) {
if (searchPage.type == SupportType.ALL && searchPage.pageNum == 1) { if (searchPage.type == SupportType.all && searchPage.pageNum == 1) {
doubanList.push(DoubanSearchGroupPublishResultSubjectNextPage) doubanList.push(DoubanSearchGroupPublishResultSubjectNextPage)
}else { }else {
doubanList.push(DoubanSearchResultSubjectNextPage) doubanList.push(DoubanSearchResultSubjectNextPage)
@ -118,7 +131,7 @@ class DoubanFuzzySuggester extends FuzzySuggestModal<DoubanSearchResultSubject>
} }
} }
if (searchPage.hasPrevious) { if (searchPage.hasPrevious) {
if (searchPage.type == SupportType.ALL && searchPage.pageNum == 2) { if (searchPage.type == SupportType.all && searchPage.pageNum == 2) {
doubanList.unshift(DoubanSearchGroupPublishResultSubjectPreviousPage) doubanList.unshift(DoubanSearchGroupPublishResultSubjectPreviousPage)
}else { }else {
doubanList.unshift(DoubanSearchResultSubjectPreviousPage); doubanList.unshift(DoubanSearchResultSubjectPreviousPage);

@ -8,16 +8,19 @@ import {sleep} from "../../../utils/TimeUtil";
export class DoubanSearchModal extends Modal { export class DoubanSearchModal extends Modal {
searchTerm: string; searchTerm: string;
searchType: SupportType = SupportType.ALL; searchType: SupportType = SupportType.all;
plugin: DoubanPlugin; plugin: DoubanPlugin;
context: HandleContext context: HandleContext
constructor(app: App, plugin: DoubanPlugin, context: HandleContext) { constructor(app: App, plugin: DoubanPlugin, context: HandleContext, type: SupportType) {
super(app); super(app);
this.plugin = plugin; this.plugin = plugin;
this.context = context; this.context = context;
this.searchType = type??SupportType.all;
} }
onOpen() { onOpen() {
let {contentEl} = this; let {contentEl} = this;
@ -42,7 +45,7 @@ export class DoubanSearchModal extends Modal {
const typeSelect = content.createDiv("type-select"); const typeSelect = content.createDiv("type-select");
const typeSelectInput = new DropdownComponent(typeSelect) const typeSelectInput = new DropdownComponent(typeSelect)
.addOptions(SearchTypeRecords) .addOptions(SearchTypeRecords)
.setValue(SupportType.ALL) .setValue(this.searchType)
.onChange((value:SupportType) => { .onChange((value:SupportType) => {
this.searchType = value; this.searchType = value;
}); });

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

@ -6,7 +6,7 @@ import SearchParserHandler from "../SearchParser";
export class NotAllPageSearchResultPageParser implements SearchResultPageParserInterface { export class NotAllPageSearchResultPageParser implements SearchResultPageParserInterface {
support(type:SupportType, pageNum:number):boolean { support(type:SupportType, pageNum:number):boolean {
return type != SupportType.ALL && !(pageNum ==1 && type == SupportType.NOTE); return type != SupportType.all && !(pageNum ==1 && type == SupportType.note);
} }
parse(source:string, type:SupportType, pageNum:number, pageSize:number):SearchPage { parse(source:string, type:SupportType, pageNum:number, pageSize:number):SearchPage {
log.debug("解析给多页面结果"); log.debug("解析给多页面结果");

@ -7,7 +7,7 @@ import DoubanSearchResultSubject from "../../model/DoubanSearchResultSubject";
export class NoteFirstPageSearchResultPageParser implements SearchResultPageParserInterface { export class NoteFirstPageSearchResultPageParser implements SearchResultPageParserInterface {
support(type:SupportType, pageNum:number):boolean { support(type:SupportType, pageNum:number):boolean {
return pageNum == 1 && type == SupportType.NOTE; return pageNum == 1 && type == SupportType.note;
} }
parse(source:string, type:SupportType, pageNum:number, pageSize:number):SearchPage { parse(source:string, type:SupportType, pageNum:number, pageSize:number):SearchPage {
const pageData = load(source); const pageData = load(source);

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

@ -17,8 +17,9 @@ export abstract class AbstractSearchPageFetcher implements SearchPageFetcherInte
support(type: SupportType, pageNum?:number): boolean { support(type: SupportType, pageNum?:number): boolean {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
fetch(keyword: string, pageNum: number, pageSize: number): Promise<string> {
fetch(keyword: string, pageNum: number, pageSize: number): Promise<string> {
const start:number = Math.floor((pageNum - 1) * pageSize); const start:number = Math.floor((pageNum - 1) * pageSize);
const url:string = this.getSearchUrl(keyword, start, pageSize); const url:string = this.getSearchUrl(keyword, start, pageSize);
if (!url) { if (!url) {
@ -27,7 +28,8 @@ export abstract class AbstractSearchPageFetcher implements SearchPageFetcherInte
return DoubanHttpUtil.httpRequestGet(url, this.settingsManager.getHeaders(), this.settingsManager) return DoubanHttpUtil.httpRequestGet(url, this.settingsManager.getHeaders(), this.settingsManager)
.catch(e => { .catch(e => {
throw log.error(i18nHelper.getMessage('130101').replace('{0}', e.toString()), e); throw log.error(i18nHelper.getMessage('130101').replace('{0}', e.toString()), e);
}); } });
}
getSearchUrl(keyword: string, start: number, pageSize: number):string { getSearchUrl(keyword: string, start: number, pageSize: number):string {
keyword = keyword.trim(); keyword = keyword.trim();

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

@ -6,7 +6,7 @@ export class BookPageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1001`; return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1001`;
} }
support(type: SupportType): boolean { support(type: SupportType): boolean {
return type == SupportType.BOOK; return type == SupportType.book;
} }

@ -6,7 +6,7 @@ export class GamePageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=3114`; return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=3114`;
} }
support(type: SupportType): boolean { support(type: SupportType): boolean {
return type == SupportType.GAME; return type == SupportType.game;
} }

@ -6,7 +6,7 @@ export class MoviePageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1002`; return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1002`;
} }
support(type: SupportType): boolean { support(type: SupportType): boolean {
return type == SupportType.MOVIE; return type == SupportType.movie;
} }

@ -6,7 +6,7 @@ export class MusicPageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1003`; return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1003`;
} }
support(type: SupportType): boolean { support(type: SupportType): boolean {
return type == SupportType.MUSIC; return type == SupportType.music;
} }

@ -6,7 +6,7 @@ export class NoteFirstPageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/search?cat=1015&q=${keyword}`; return `https://www.douban.com/search?cat=1015&q=${keyword}`;
} }
support(type: SupportType, pageNum:number): boolean { support(type: SupportType, pageNum:number): boolean {
return type == SupportType.NOTE && pageNum == 1; return type == SupportType.note && pageNum == 1;
} }

@ -6,7 +6,7 @@ export class NotePageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1015`; return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=1015`;
} }
support(type: SupportType, pageNum:number): boolean { support(type: SupportType, pageNum:number): boolean {
return type == SupportType.NOTE && pageNum > 1; return type == SupportType.note && pageNum > 1;
} }

@ -6,7 +6,7 @@ export class TheaterPageSearchPageFetcher extends AbstractSearchPageFetcher {
return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=3069`; return `https://www.douban.com/j/search?q=${keyword}&start=${start}&cat=3069`;
} }
support(type: SupportType): boolean { support(type: SupportType): boolean {
return type == SupportType.THEATER; return type == SupportType.theater;
} }

@ -8,31 +8,74 @@ import User from "../user/User";
import {createFolderSelectionSetting} from "./TemplateSettingHelper"; import {createFolderSelectionSetting} from "./TemplateSettingHelper";
import { log } from "../../utils/Logutil"; import { log } from "../../utils/Logutil";
import {ConfirmDialogModal} from "../component/ConfirmDialogModal"; import {ConfirmDialogModal} from "../component/ConfirmDialogModal";
import {DoubanSearchModal} from "../data/search/DoubanSearchModal";
import {DoubanPluginSetting} from "./model/DoubanPluginSetting";
import TimeUtil from "../../utils/TimeUtil";
export function constructAdvancedUI(containerEl: HTMLElement, manager: SettingsManager) { export function constructAdvancedUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', { text: i18nHelper.getMessage('1250') }); // containerEl.createEl('h3', { text: i18nHelper.getMessage('1250') });
containerEl.createEl('p', { text: i18nHelper.getMessage('1252') }); containerEl.createEl('p', { text: i18nHelper.getMessage('1252') });
const settings:Setting = new Setting(containerEl);
const advancedSettings = containerEl.createDiv('advanced-settings');
settings.setDesc(i18nHelper.getMessage('1251')).addExtraButton((extraButton) => {
extraButton
.setIcon('reset')
.setTooltip(i18nHelper.getMessage('121905'))
.onClick(async () => {
resetAdvanced(manager);
await manager.plugin.saveSettings();
showAdvancedSettings(advancedSettings, manager)
});
})
showAdvancedSettings(advancedSettings, manager);
// const settings:Setting = new Setting(containerEl);
const advancedSettings = containerEl.createDiv('advanced-settings');
// settings.setDesc(i18nHelper.getMessage('1251')).addExtraButton((extraButton) => {
// extraButton
// .setIcon('reset')
// .setTooltip(i18nHelper.getMessage('121905'))
// .onClick(async () => {
// resetAdvanced(manager);
// await manager.plugin.saveSettings();
// showAdvancedSettings(advancedSettings, manager)
// });
// })
showAdvancedSettings(advancedSettings, manager);
//
} }
function showAdvancedSettings(containerEl: HTMLElement, manager: SettingsManager) { function showAdvancedSettings(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.empty(); containerEl.empty();
const promise:Promise<any> = new Promise<any>((resolve, reject) => {resolve(null)}); const promise:Promise<any> = new Promise<any>((resolve, reject) => {resolve(null)});
//导出
new Setting(containerEl)
.setName(i18nHelper.getMessage('125034'))
.setDesc(i18nHelper.getMessage('125035'))
.addButton((buttonComponent) => {
buttonComponent
.setIcon('folder')
.setButtonText(i18nHelper.getMessage('125047'))
.onClick(async (value) => {
const settings = manager.getSettings()
const settingsString = JSON.stringify(settings, null, 2);
const blob = new Blob([settingsString], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `obsidian_douban_plugin_settings_${TimeUtil.formatDate(new Date(), 'yyyy-MM-dd_HH-mm-ss')}.json`;
a.click();
URL.revokeObjectURL(url);
});
});
//导入
new Setting(containerEl)
.setName(i18nHelper.getMessage('125036'))
.setDesc(i18nHelper.getMessage('125037'))
.addButton((buttonComponent) => {
buttonComponent
.setIcon('document')
.setButtonText(i18nHelper.getMessage('125039'))
.onClick(async (value) => {
showConfirmDialog(i18nHelper.getMessage('125046'), promise.then(() => {
}), manager);
});
});
new Setting(containerEl) new Setting(containerEl)
.setName(i18nHelper.getMessage('125001')) .setName(i18nHelper.getMessage('125001'))
.setDesc(i18nHelper.getMessage('125002')) .setDesc(i18nHelper.getMessage('125002'))
@ -79,6 +122,7 @@ function showAdvancedSettings(containerEl: HTMLElement, manager: SettingsManager
}), manager) }), manager)
}); });
}); });
new Setting(containerEl) new Setting(containerEl)
.setName(i18nHelper.getMessage('125031')) .setName(i18nHelper.getMessage('125031'))
.setDesc(i18nHelper.getMessage('125032')) .setDesc(i18nHelper.getMessage('125032'))
@ -92,6 +136,7 @@ function showAdvancedSettings(containerEl: HTMLElement, manager: SettingsManager
}), manager); }), manager);
}); });
}); });
} }
function resetAdvanced( manager: SettingsManager) { function resetAdvanced( manager: SettingsManager) {
@ -100,7 +145,7 @@ function resetAdvanced( manager: SettingsManager) {
} }
function showConfirmDialog(message:string, promise:Promise<any>, manager: SettingsManager) { function showConfirmDialog(message:string, promise:Promise<any>, manager: SettingsManager) {
new ConfirmDialogModal(manager.plugin.app, message, promise new ConfirmDialogModal(manager.plugin, message, promise
.then( () => { .then( () => {
manager.plugin.saveSettings(); manager.plugin.saveSettings();
}) })

@ -4,14 +4,15 @@ import {Setting, TextComponent} from "obsidian";
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {DEFAULT_SETTINGS} from "../../constant/DefaultSettings"; import {DEFAULT_SETTINGS} from "../../constant/DefaultSettings";
import {DEFAULT_SETTINGS_ARRAY_INPUT_SIZE} from "../../constant/Constsant"; import {DEFAULT_SETTINGS_ARRAY_INPUT_SIZE} from "../../constant/Constsant";
import DoubanPlugin from "../../main";
export function arraySettingDisplayUI(containerEl: HTMLElement, manager: SettingsManager) { export function arraySettingDisplayUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', {text: i18nHelper.getMessage('120601')}); // containerEl.createEl('h3', {text: i18nHelper.getMessage('120601')});
arraySettingDisplay(containerEl.createDiv('array-settings'), manager, false); arraySettingDisplay(containerEl.createDiv('array-settings'), manager, true);
} }
export function arraySettingDisplay(containerEl: HTMLElement, manager: SettingsManager, displayExtraListTypeFlag: boolean = false) { export function arraySettingDisplay(containerEl: HTMLElement, manager: SettingsManager, displayExtraListTypeFlag: boolean = true) {
containerEl.empty(); containerEl.empty();
const arraySet = new Setting(containerEl) const arraySet = new Setting(containerEl)
.setName(i18nHelper.getMessage('120601')) .setName(i18nHelper.getMessage('120601'))
@ -25,27 +26,28 @@ export function arraySettingDisplay(containerEl: HTMLElement, manager: SettingsM
arraySettingDisplay(containerEl, manager, true); arraySettingDisplay(containerEl, manager, true);
}); });
}); });
if (displayExtraListTypeFlag) { // if (displayExtraListTypeFlag) {
arraySettingDisplayItem(containerEl, manager, manager.getArraySetting(DEFAULT_SETTINGS_ARRAY_NAME)); new Setting(containerEl)
displayExtraListType(manager, containerEl); arraySettingDisplayItem(containerEl, manager, manager.getArraySetting(DEFAULT_SETTINGS_ARRAY_NAME));
arraySet.addButton((button) => { displayExtraListType(manager, containerEl);
button // arraySet.addButton((button) => {
.setIcon('down-chevron-glyph') // button
.setTooltip(i18nHelper.getMessage('120608')) // .setIcon('down-chevron-glyph')
.onClick(async () => { // .setTooltip(i18nHelper.getMessage('120608'))
arraySettingDisplay(containerEl, manager, false); // .onClick(async () => {
}); // arraySettingDisplay(containerEl, manager, false);
}); // });
}else { // });
arraySet.addButton((button) => { // }else {
button // arraySet.addButton((button) => {
.setIcon('right-chevron-glyph') // button
.setTooltip(i18nHelper.getMessage('120608')) // .setIcon('right-chevron-glyph')
.onClick(async () => { // .setTooltip(i18nHelper.getMessage('120608'))
arraySettingDisplay(containerEl, manager, true); // .onClick(async () => {
}); // arraySettingDisplay(containerEl, manager, true);
}); // });
} // });
// }
} }
function arraySettingDisplayItem(containerEl: HTMLElement, manager: SettingsManager, arraySetting:ArraySetting) { function arraySettingDisplayItem(containerEl: HTMLElement, manager: SettingsManager, arraySetting:ArraySetting) {

@ -8,12 +8,12 @@ import User from "../user/User";
import {createFolderSelectionSetting} from "./TemplateSettingHelper"; import {createFolderSelectionSetting} from "./TemplateSettingHelper";
import StringUtil from "../../utils/StringUtil"; import StringUtil from "../../utils/StringUtil";
import {log} from "../../utils/Logutil"; import {log} from "../../utils/Logutil";
import DoubanPlugin from "../../main";
import {SearchTypeRecords, SupportType, SupportTypeMap} from "../../constant/Constsant";
export function constructBasicUI(containerEl: HTMLElement, manager: SettingsManager) { export function constructBasicUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') }); // containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') });
containerEl.createDiv('login-setting', async (loginSettingEl) => {
constructDoubanTokenSettingsUI(loginSettingEl, manager);
});
new Setting(containerEl).setName(i18nHelper.getMessage('120501')).then((setting) => { new Setting(containerEl).setName(i18nHelper.getMessage('120501')).then((setting) => {
setting.addMomentFormat((mf) => { setting.addMomentFormat((mf) => {
@ -98,166 +98,19 @@ export function constructBasicUI(containerEl: HTMLElement, manager: SettingsMana
}); });
}); });
new Setting(containerEl)
} .setName(i18nHelper.getMessage('121410'))
.setDesc(i18nHelper.getMessage('121411'))
export function constructDoubanTokenSettingsUI(containerEl: HTMLElement, manager: SettingsManager) { .addDropdown((dropdown) => {
containerEl.empty(); dropdown
let login = manager.plugin.userComponent.isLogin(); .addOptions(SearchTypeRecords)
manager.debug(`配置界面:展示豆瓣状态:${login?'已登录':'未登录'}`) .setValue(manager.plugin.settings.searchDefaultType)
if (Platform.isDesktopApp) { .onChange(async (value) => {
if(login) { // @ts-ignore
constructHasLoginSettingsUI(containerEl, manager); manager.plugin.settings.searchDefaultType = SupportTypeMap[value];
}else { await manager.plugin.saveSettings();
constructLoginSettingsUI(containerEl, manager);
}
} else {
if(login) {
showMobileLogout(containerEl, manager);
}else {
showMobileLogin(containerEl, manager);
}
}
}
export function constructLoginSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
manager.debug(`配置界面:未登录-展示登录按钮`)
let loginSetting = containerEl.createDiv("login-button");
let loginCookie = containerEl.createDiv("login-button-cookie");
new Setting(loginSetting).setName(i18nHelper.getMessage('100131')).addButton((button) => {
return button
.setButtonText(i18nHelper.getMessage('100130'))
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:点击登录按钮`)
const loginModel = new DoubanLoginModel(containerEl, manager);
await loginModel.doLogin();
});
});
const loginCookieSetting:Setting = new Setting(loginSetting).setName(i18nHelper.getMessage('100133'));
// .setDesc(i18nHelper.getMessage('100134'))
loginCookieSetting.addButton((button) => {
loginCookieSetting.descEl.appendChild(
createFragment((frag) => {
frag.appendText(
i18nHelper.getMessage('100134')
);
frag.createEl(
'a',
{
text: i18nHelper.getMessage('100139'),
href: 'https://obsidian-douban.wanxuping.com/20_howtouse_25_setting_login_douban_cookie.html',
},
(a) => {
a.setAttr('target', '_blank');
}
);
frag.appendText(i18nHelper.getMessage('100138'));
})
);
return button
.setButtonText(i18nHelper.getMessage('100135'))
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:点击登录异常处理按钮`)
constructLoginCookieSettingsUI(loginCookie, containerEl, manager);
});
});
}
export function constructLoginCookieSettingsUI(containerEl: HTMLElement, parentContainerEl: HTMLElement, manager: SettingsManager) {
manager.debug(`配置界面:登录异常处理按钮-展示Cookie输入框`)
new Setting(containerEl).setName(i18nHelper.getMessage('100136'))
.setClass("obsidian_douban_settings_cookie_login").addTextArea((text) => {
text.onChange(value => manager.updateCookieTemp(value));
return text;
}).addExtraButton((button) => {
return button
.setIcon('check')
.onClick(async () => {
manager.debug(`配置界面:确认输入Cookie`);
const user:User = await manager.plugin.userComponent.loginCookie(manager.getCookieTemp())
if (!user || !user.id) {
log.notice(i18nHelper.getMessage('100137'))
return;
}
constructDoubanTokenSettingsUI(parentContainerEl, manager);
});
})
.addExtraButton((button) => {
return button
.setIcon('x')
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:取消输入Cookie`);
constructDoubanTokenSettingsUI(parentContainerEl, manager);
});
});
}
export function constructHasLoginSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
const user: User = manager.plugin.userComponent.getUser();
let userDom = new DocumentFragment();
userDom.createDiv().innerHTML =
`${i18nHelper.getMessage('100120')}<br>
${i18nHelper.getMessage('100123')}: <a href="https://www.douban.com/people/${user.id}/">${user.id}</a><br>
${i18nHelper.getMessage('100124')}: ${user.name}<br>
${i18nHelper.getMessage('100125')}`;
manager.debug(`配置界面:展示豆瓣登录信息:id:${StringUtil.confuse(user.id)}, 用户名:${StringUtil.confuse(user.name)}`)
new Setting(containerEl)
.setName(i18nHelper.getMessage('100126'))
.setDesc(userDom)
.addButton((button) => {
return button
.setButtonText(i18nHelper.getMessage('100128'))
.setCta()
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:点击退出登录按钮,准备退出登录`)
// manager.debug(`配置界面:登出界面退出登录请求检测成功,准备退出登录`)
manager.plugin.userComponent.logout();
manager.debug(`配置界面:退出登录成功`);
constructDoubanTokenSettingsUI(containerEl, manager);
// const loginModel = new DoubanLogoutModel(containerEl, manager);
// await loginModel.doLogout();
});
});
}
function showMobileLogin(containerEl: HTMLElement, manager: SettingsManager) {
new Setting(containerEl)
.setName(i18nHelper.getMessage('100126'))
.setDesc(i18nHelper.getMessage('100129'))
}
function showMobileLogout(containerEl: HTMLElement, manager: SettingsManager) {
const user: User = manager.plugin.userComponent.getUser();
let userDom = new DocumentFragment();
userDom.createDiv().innerHTML =
`${i18nHelper.getMessage('100120')}<br>
${i18nHelper.getMessage('100123')}: <a href="https://www.douban.com/people/${user.id}/">${user.id}</a><br>
${i18nHelper.getMessage('100124')}: ${user.name}<br>
${i18nHelper.getMessage('100125')}`;
new Setting(containerEl)
.setName(i18nHelper.getMessage('100126'))
.setDesc(userDom)
.addButton((button) => {
return button
.setButtonText(i18nHelper.getMessage('100128'))
.setCta()
.onClick(async () => {
button.setDisabled(true);
manager.updateSetting('loginCookiesContent', '');
manager.updateSetting('loginHeadersContent', '');
constructDoubanTokenSettingsUI(containerEl, manager);
}); });
}); });
} }

@ -2,10 +2,11 @@ import {i18nHelper} from "../../lang/helper";
import SettingsManager from "./SettingsManager"; import SettingsManager from "./SettingsManager";
import {CustomProperty} from "./model/CustomProperty"; import {CustomProperty} from "./model/CustomProperty";
import {ButtonComponent, DropdownComponent, ExtraButtonComponent, Setting, TextComponent} from "obsidian"; import {ButtonComponent, DropdownComponent, ExtraButtonComponent, Setting, TextComponent} from "obsidian";
import {SupportType} from "../../constant/Constsant"; import {SupportType, SupportTypeMap} from "../../constant/Constsant";
import DoubanPlugin from "../../main";
export function constructCustomPropertySettingsUI(containerEl: HTMLElement, manager: SettingsManager) { export function constructCustomPropertySettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', { text: i18nHelper.getMessage('1240') }); // containerEl.createEl('h3', { text: i18nHelper.getMessage('1240') });
containerEl.createEl('p', { text: i18nHelper.getMessage('1242') }); containerEl.createEl('p', { text: i18nHelper.getMessage('1242') });
const customProperties = manager.plugin.settings.customProperties; const customProperties = manager.plugin.settings.customProperties;
new Setting(containerEl) new Setting(containerEl)
@ -15,7 +16,7 @@ export function constructCustomPropertySettingsUI(containerEl: HTMLElement, mana
button.setTooltip(i18nHelper.getMessage('124101')); button.setTooltip(i18nHelper.getMessage('124101'));
button.setIcon('plus'); button.setIcon('plus');
button.onClick(async () => { button.onClick(async () => {
customProperties.push({name: '', value: '', field: SupportType.ALL}); customProperties.push({name: '', value: '', field: SupportType.all});
constructCustomPropertyUI(list, customProperties, manager); constructCustomPropertyUI(list, customProperties, manager);
}); });
}); });
@ -67,11 +68,15 @@ function addFilterInput(data: CustomProperty, el: HTMLElement, customProperties:
const fieldsDropdown = new DropdownComponent(el); const fieldsDropdown = new DropdownComponent(el);
for (const fieldSelect in SupportType) { for (const fieldSelect in SupportType) {
// @ts-ignore
fieldsDropdown.addOption(fieldSelect, i18nHelper.getMessage(fieldSelect)); fieldsDropdown.addOption(fieldSelect, i18nHelper.getMessage(fieldSelect));
} }
item.createEl('span', { text: i18nHelper.getMessage('124106') }); item.createEl('span', { text: i18nHelper.getMessage('124106') });
fieldsDropdown.setValue(data.field) let dataFieldValue = data.field;
if(typeof dataFieldValue === 'string') {
// @ts-ignore
dataFieldValue = SupportTypeMap[dataFieldValue];
}
fieldsDropdown.setValue(dataFieldValue)
.onChange(async (value: SupportType) => { .onChange(async (value: SupportType) => {
customProperties[idx].field = value; customProperties[idx].field = value;
await manager.plugin.saveSettings(); await manager.plugin.saveSettings();

@ -9,11 +9,15 @@ import { constructTemplateVariablesUI } from "./TemplateVariableSettingsHelper";
import {constructCustomPropertySettingsUI } from "./CustomPropertySettingsHelper"; import {constructCustomPropertySettingsUI } from "./CustomPropertySettingsHelper";
import { constructAdvancedUI } from "./AdvancedSettingsHelper"; import { constructAdvancedUI } from "./AdvancedSettingsHelper";
import {arraySettingDisplay, arraySettingDisplayUI} from "./ArrayDisplayTypeSettingsHelper"; import {arraySettingDisplay, arraySettingDisplayUI} from "./ArrayDisplayTypeSettingsHelper";
import {i18nHelper} from "../../lang/helper";
import {constructLoginUI} from "./LoginSettingsHelper";
/** /**
* *
* obsidian-kanban * obsidian-kanban
*/ */
export class DoubanSettingTab extends PluginSettingTab { export class DoubanSettingTab extends PluginSettingTab {
plugin: DoubanPlugin; plugin: DoubanPlugin;
settingsManager: SettingsManager; settingsManager: SettingsManager;
@ -25,18 +29,51 @@ export class DoubanSettingTab extends PluginSettingTab {
} }
display(): void { display(): void {
const {containerEl} = this; const {containerEl} = this;
containerEl.empty(); containerEl.empty();
containerEl.createEl("h2", {text: 'Obsidian Douban'}); containerEl.createEl("h2", {text: 'Obsidian Douban'});
new Setting(containerEl);
constructBasicUI(containerEl, this.settingsManager);
constructTemplateUI(containerEl, this.settingsManager);
constructOutUI(containerEl, this.settingsManager);
arraySettingDisplayUI(containerEl, this.settingsManager);
constructCustomPropertySettingsUI(containerEl, this.settingsManager);
constructTemplateVariablesUI(containerEl, this.settingsManager);
constructAdvancedUI(containerEl, this.settingsManager);
// Create tab container
const tabContainer = containerEl.createEl("div", {cls: "obsidian_douban_settings_tab_container"});
const tabHeaders = containerEl.createEl("div", {cls: "obsidian_douban_settings_tab_headers"});
const tabContents = containerEl.createEl("div", {cls: "obsidian_douban_settings_tab_contents"});
// Create tabs
const tabs = [
{name: i18nHelper.getMessage('1210'), construct: constructBasicUI},
{name: i18nHelper.getMessage('1203'), construct: constructTemplateUI},
{name: i18nHelper.getMessage('1260'), construct: constructLoginUI},
{name: i18nHelper.getMessage('1220'), construct: constructOutUI},
{name: i18nHelper.getMessage('120601'), construct: arraySettingDisplayUI},
{name: i18nHelper.getMessage('1240'), construct: constructCustomPropertySettingsUI},
{name: i18nHelper.getMessage('1230'), construct: constructTemplateVariablesUI},
{name: i18nHelper.getMessage('1250'), construct: constructAdvancedUI}
];
tabs.forEach((tab, index) => {
const tabHeader = tabHeaders.createEl("div", {cls: "obsidian_douban_settings_tab_header", text: tab.name});
const tabContent = tabContents.createEl("div", {cls: "obsidian_douban_settings_tab_content"});
tab.construct(tabContent, this.settingsManager);
// Show the first tab by default
if (index === 0) {
tabHeader.addClass("active");
tabContent.addClass("active");
}
tabHeader.addEventListener("click", () => {
// Remove active class from all headers and contents
tabHeaders.querySelectorAll(".obsidian_douban_settings_tab_header").forEach(header => header.removeClass("active"));
tabContents.querySelectorAll(".obsidian_douban_settings_tab_content").forEach(content => content.removeClass("active"));
// Add active class to the clicked header and corresponding content
tabHeader.addClass("active");
tabContent.addClass("active");
});
});
tabContainer.appendChild(tabHeaders);
tabContainer.appendChild(tabContents);
} }
hide(): void { hide(): void {

@ -0,0 +1,192 @@
import I18nHelper, {i18nHelper} from "../../lang/helper";
import {Platform, Setting} from "obsidian";
import SettingsManager from "./SettingsManager";
import DoubanLoginModel from "../component/DoubanLoginModel";
import User from "../user/User";
import StringUtil from "../../utils/StringUtil";
import {log} from "../../utils/Logutil";
export function constructLoginUI(containerEl: HTMLElement, manager: SettingsManager) {
// containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') });
const userComponent = manager.plugin.userComponent;
if (userComponent.isLogin() && !userComponent.isVerified()) {
// Assumed login — verify to get user ID/name for settings display
userComponent.login()
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
} else if (userComponent.needLogin()) {
// Has credentials but not yet logged in
userComponent.login()
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
} else {
constructDoubanLoginSettingsUI(containerEl, manager);
}
}
export function constructDoubanLoginSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createDiv('login-setting', async (loginSettingEl) => {
constructDoubanTokenSettingsUI(loginSettingEl, manager);
});
}
export function constructDoubanTokenSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.empty();
let login = manager.plugin.userComponent.isLogin();
manager.debug(`配置界面:展示豆瓣状态:${login?'已登录':'未登录'}`)
if (Platform.isDesktopApp) {
if(login) {
constructHasLoginSettingsUI(containerEl, manager);
}else {
constructLoginSettingsUI(containerEl, manager);
}
} else {
if(login) {
showMobileLogout(containerEl, manager);
}else {
showMobileLogin(containerEl, manager);
}
}
}
export function constructLoginSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
manager.debug(`配置界面:未登录-展示登录按钮`)
let loginSetting = containerEl.createDiv("login-button");
let loginCookie = containerEl.createDiv("login-button-cookie");
new Setting(loginSetting).setName(i18nHelper.getMessage('100131')).addButton((button) => {
return button
.setButtonText(i18nHelper.getMessage('100130'))
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:点击登录按钮`)
const loginModel = new DoubanLoginModel(containerEl, manager);
await loginModel.doLogin();
});
});
const loginCookieSetting:Setting = new Setting(loginSetting).setName(i18nHelper.getMessage('100133'));
// .setDesc(i18nHelper.getMessage('100134'))
loginCookieSetting.addButton((button) => {
loginCookieSetting.descEl.appendChild(
createFragment((frag) => {
frag.appendText(
i18nHelper.getMessage('100134')
);
frag.createEl(
'a',
{
text: i18nHelper.getMessage('100139'),
href: 'https://wanxp.github.io/obsidian-douban/20_howtouse_25_setting_login_douban_cookie.html',
},
(a) => {
a.setAttr('target', '_blank');
}
);
frag.appendText(i18nHelper.getMessage('100138'));
})
);
return button
.setButtonText(i18nHelper.getMessage('100135'))
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:点击登录异常处理按钮`)
constructLoginCookieSettingsUI(loginCookie, containerEl, manager);
});
});
}
export function constructLoginCookieSettingsUI(containerEl: HTMLElement, parentContainerEl: HTMLElement, manager: SettingsManager) {
manager.debug(`配置界面:登录异常处理按钮-展示Cookie输入框`)
new Setting(containerEl).setName(i18nHelper.getMessage('100136'))
.setClass("obsidian_douban_settings_cookie_login").addTextArea((text) => {
text.onChange(value => manager.updateCookieTemp(value));
return text;
}).addExtraButton((button) => {
return button
.setIcon('check')
.onClick(async () => {
manager.debug(`配置界面:确认输入Cookie`);
const user:User = await manager.plugin.userComponent.loginCookie(manager.getCookieTemp())
if (!user || !user.id) {
log.notice(i18nHelper.getMessage('100137'))
return;
}
constructDoubanTokenSettingsUI(parentContainerEl, manager);
});
})
.addExtraButton((button) => {
return button
.setIcon('x')
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:取消输入Cookie`);
constructDoubanTokenSettingsUI(parentContainerEl, manager);
});
});
}
export function constructHasLoginSettingsUI(containerEl: HTMLElement, manager: SettingsManager) {
const user: User = manager.plugin.userComponent.getUser();
let userDom = new DocumentFragment();
userDom.createDiv().innerHTML =
`${i18nHelper.getMessage('100120')}<br>
${i18nHelper.getMessage('100123')}: <a href="https://www.douban.com/people/${user.id}/">${user.id}</a><br>
${i18nHelper.getMessage('100124')}: ${user.name}<br>
${i18nHelper.getMessage('100125')}`;
manager.debug(`配置界面:展示豆瓣登录信息:id:${StringUtil.confuse(user.id)}, 用户名:${StringUtil.confuse(user.name)}`)
new Setting(containerEl)
.setName(i18nHelper.getMessage('100126'))
.setDesc(userDom)
.addButton((button) => {
return button
.setButtonText(i18nHelper.getMessage('100128'))
.setCta()
.onClick(async () => {
button.setDisabled(true);
manager.debug(`配置界面:点击退出登录按钮,准备退出登录`)
// manager.debug(`配置界面:登出界面退出登录请求检测成功,准备退出登录`)
manager.plugin.userComponent.logout();
manager.debug(`配置界面:退出登录成功`);
constructDoubanTokenSettingsUI(containerEl, manager);
});
});
}
function showMobileLogin(containerEl: HTMLElement, manager: SettingsManager) {
new Setting(containerEl)
.setName(i18nHelper.getMessage('100126'))
.setDesc(i18nHelper.getMessage('100129'))
}
function showMobileLogout(containerEl: HTMLElement, manager: SettingsManager) {
const user: User = manager.plugin.userComponent.getUser();
let userDom = new DocumentFragment();
userDom.createDiv().innerHTML =
`${i18nHelper.getMessage('100120')}<br>
${i18nHelper.getMessage('100123')}: <a href="https://www.douban.com/people/${user.id}/">${user.id}</a><br>
${i18nHelper.getMessage('100124')}: ${user.name}<br>
${i18nHelper.getMessage('100125')}`;
new Setting(containerEl)
.setName(i18nHelper.getMessage('100126'))
.setDesc(userDom)
.addButton((button) => {
return button
.setButtonText(i18nHelper.getMessage('100128'))
.setCta()
.onClick(async () => {
button.setDisabled(true);
manager.updateSetting('loginCookiesContent', '');
manager.updateSetting('loginHeadersContent', '');
constructDoubanTokenSettingsUI(containerEl, manager);
});
});
}

@ -1,6 +1,6 @@
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {Setting, TextComponent, ToggleComponent} from "obsidian"; import {Setting, TextComponent, ToggleComponent} from "obsidian";
import {createFolderSelectionSetting} from "./TemplateSettingHelper"; import {createFolderSelectionSetting, createFolderSelectionSettingInput} from "./TemplateSettingHelper";
import {DEFAULT_SETTINGS} from "../../constant/DefaultSettings"; import {DEFAULT_SETTINGS} from "../../constant/DefaultSettings";
import { import {
DEFAULT_SETTINGS_ARRAY_INPUT_SIZE, EXAMPLE_RATE, EXAMPLE_RATE_MAX, DEFAULT_SETTINGS_ARRAY_INPUT_SIZE, EXAMPLE_RATE, EXAMPLE_RATE_MAX,
@ -14,6 +14,7 @@ import NumberUtil from "../../utils/NumberUtil";
import {VariableUtil} from "../../utils/VariableUtil"; import {VariableUtil} from "../../utils/VariableUtil";
import {FileUtil} from "../../utils/FileUtil"; import {FileUtil} from "../../utils/FileUtil";
import {ScoreSetting} from "./model/ScoreSetting"; import {ScoreSetting} from "./model/ScoreSetting";
import DoubanPlugin from "../../main";
function showStarExample(containerEl: HTMLElement, manager: SettingsManager) { function showStarExample(containerEl: HTMLElement, manager: SettingsManager) {
@ -32,8 +33,8 @@ export function showFileExample(containerEl: HTMLElement, manager: SettingsManag
const document = new DocumentFragment(); const document = new DocumentFragment();
document.createDiv('file-path-example') document.createDiv('file-path-example')
.innerHTML = `${i18nHelper.getMessage('121604')}<a href="https://book.douban.com/subject/2253379/">《简爱》</a>: ${VariableUtil.replaceSubject(EXAMPLE_SUBJECT_MAP, .innerHTML = `${i18nHelper.getMessage('121604')}<a href="https://book.douban.com/subject/2253379/">《简爱》</a>: ${VariableUtil.replaceSubject(EXAMPLE_SUBJECT_MAP,
FileUtil.join(manager.plugin.settings.dataFilePath, manager.plugin.settings.dataFileNamePath + ".md"), SupportType.BOOK, FileUtil.join(manager.plugin.settings.dataFilePath, manager.plugin.settings.dataFileNamePath + ".md"), SupportType.book,
manager)}`; manager, 'path')}`;
new Setting(containerEl) new Setting(containerEl)
.setName(i18nHelper.getMessage('120603')) .setName(i18nHelper.getMessage('120603'))
@ -111,16 +112,21 @@ function scoreSettingDisplay(containerEl: HTMLElement, manager: SettingsManager)
} }
export function constructOutUI(containerEl: HTMLElement, manager: SettingsManager) { export function constructOutUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', { text: i18nHelper.getMessage('1220') }); // containerEl.createEl('h3', { text: i18nHelper.getMessage('1220') });
new Setting(containerEl); new Setting(containerEl);
const attachmentFileSetting = containerEl.createDiv({ cls: 'settings-item-attachment' }); const attachmentFileSetting = containerEl.createDiv({ cls: 'settings-item-attachment' });
constructAttachmentFileSettingsUI(attachmentFileSetting, manager); constructAttachmentFileSettingsUI(attachmentFileSetting, manager);
const folder = new Setting(containerEl); const folder = new Setting(containerEl);
const folderInput = new Setting(containerEl);
const outFolder = containerEl.createDiv({ cls: 'settings-item' }); const outFolder = containerEl.createDiv({ cls: 'settings-item' });
const filePathDisplayExample = containerEl.createDiv('filePath-display-example'); const filePathDisplayExample = containerEl.createDiv('filePath-display-example');
folder.then(createFolderSelectionSetting({name: '121501', desc: '121502', placeholder: '121503', key: 'dataFilePath', manager: manager}, filePathDisplayExample));
folder.then(createFolderSelectionSetting({containerEl: containerEl, name: '121501', desc: '121502', placeholder: null, key: null, manager: manager}, filePathDisplayExample));
folderInput.then(createFolderSelectionSettingInput({containerEl: containerEl, name: null, desc: null, placeholder: '121503', key: 'dataFilePath', manager: manager}, filePathDisplayExample));
constructOutputFileNameUI(outFolder, filePathDisplayExample, manager); constructOutputFileNameUI(outFolder, filePathDisplayExample, manager);
@ -215,7 +221,11 @@ export function constructAttachmentFileSettingsUI(containerEl: HTMLElement, mana
if (manager.plugin.settings.pictureBedFlag) { if (manager.plugin.settings.pictureBedFlag) {
constructAttachmentFilePictureBedSettingsUI(containerEl, manager); constructAttachmentFilePictureBedSettingsUI(containerEl, manager);
}else { }else {
new Setting(containerEl).then(createFolderSelectionSetting({name: '121432', desc: '121433', placeholder: '121434', key: 'attachmentPath', manager: manager})); new Setting(containerEl).then(createFolderSelectionSetting({containerEl: containerEl, name: '121432', desc: '121433', placeholder: null, key: null, manager: manager}));
new Setting(containerEl).then(createFolderSelectionSettingInput({containerEl: containerEl, name: null, desc: null, placeholder: '121434', key: 'attachmentPath', manager: manager}));
new Setting(containerEl).then(createFolderSelectionSetting({containerEl: containerEl, name: '121452', desc: '121453', placeholder: null, key: null, manager: manager}));
new Setting(containerEl).then(createFolderSelectionSettingInput({containerEl: containerEl, name: null, desc: null, placeholder: '121454', key: 'attachmentFileName', manager: manager}));
;
} }
new Setting(containerEl) new Setting(containerEl)

@ -17,6 +17,8 @@ import {
ArraySettingFieldName, ArraySettingFieldName,
DEFAULT_SETTINGS_ARRAY_NAME DEFAULT_SETTINGS_ARRAY_NAME
} from "./model/ArraySetting"; } from "./model/ArraySetting";
import {logger} from "bs-logger";
import {i18nHelper} from "../../lang/helper";
export default class SettingsManager { export default class SettingsManager {
app: App; app: App;
@ -228,4 +230,21 @@ export default class SettingsManager {
clearSyncCache() { clearSyncCache() {
this.settings.syncHandledDataArray = []; this.settings.syncHandledDataArray = [];
} }
async loadAndSaveSettings(config: object) {
this.validateSettings(config);
// @ts-ignore
this.settings = Object.assign({}, config);
await this.plugin.saveSettings();
}
private validateSettings(config: object) {
if (!config) {
this.innerLogger.warn(i18nHelper.getMessage('125040'));
}
}
getSettings() {
return this.settings;
}
} }

@ -7,43 +7,50 @@ import {getDefaultTemplateContent} from "../../constant/DefaultTemplateContent";
import {FolderSuggest} from "./model/FolderSuggest"; import {FolderSuggest} from "./model/FolderSuggest";
import SettingsManager from "./SettingsManager"; import SettingsManager from "./SettingsManager";
import {showFileExample} from "./OutputSettingsHelper"; import {showFileExample} from "./OutputSettingsHelper";
import {FileTreeSelectSuggest} from "./model/FileTreeSelectSuggest";
import DoubanPlugin from "../../main";
import {FolderTreeSelectSuggest} from "./model/FolderTreeSelectSuggest";
export function constructTemplateUI(containerEl: HTMLElement, manager: SettingsManager) { export function constructTemplateUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', { text: i18nHelper.getMessage('1203') }); // containerEl.createEl('h3', { text: i18nHelper.getMessage('1203') });
containerEl.createEl('p', { text: i18nHelper.getMessage('1204') }); containerEl.createEl('p', { text: i18nHelper.getMessage('1204') });
new Setting(containerEl).setDesc(i18nHelper.getMessage('1205')) new Setting(containerEl).setDesc(i18nHelper.getMessage('1205'))
new Setting(containerEl).then(createFileSelectionSetting({name: '120101', desc: '120102', placeholder: '121701', key: 'movieTemplateFile', manager: manager})); new Setting(containerEl).then(createFileSelectionSetting({containerEl: containerEl, name: '120101', desc: '120102', placeholder: '121701', key: 'movieTemplateFile', manager: manager}));
new Setting(containerEl).then(createFileSelectionSetting({name: '120201', desc: '120202', placeholder: '121701', key: 'bookTemplateFile', manager: manager})); new Setting(containerEl).then(createFileSelectionSetting({containerEl: containerEl, name: '120201', desc: '120202', placeholder: '121701', key: 'bookTemplateFile', manager: manager}));
new Setting(containerEl).then(createFileSelectionSetting({name: '120301', desc: '120302', placeholder: '121701', key: 'musicTemplateFile', manager: manager})); new Setting(containerEl).then(createFileSelectionSetting({containerEl: containerEl, name: '120301', desc: '120302', placeholder: '121701', key: 'musicTemplateFile', manager: manager}));
new Setting(containerEl).then(createFileSelectionSetting({name: '120401', desc: '120402', placeholder: '121701', key: 'noteTemplateFile', manager: manager})); new Setting(containerEl).then(createFileSelectionSetting({containerEl: containerEl, name: '120401', desc: '120402', placeholder: '121701', key: 'noteTemplateFile', manager: manager}));
new Setting(containerEl).then(createFileSelectionSetting({name: '121301', desc: '121302', placeholder: '121701', key: 'gameTemplateFile', manager: manager})); new Setting(containerEl).then(createFileSelectionSetting({containerEl: containerEl, name: '121301', desc: '121302', placeholder: '121701', key: 'gameTemplateFile', manager: manager}));
new Setting(containerEl).then(createFileSelectionSetting({name: '121801', desc: '121802', placeholder: '121701', key: 'teleplayTemplateFile', manager: manager})); new Setting(containerEl).then(createFileSelectionSetting({containerEl: containerEl, name: '121801', desc: '121802', placeholder: '121701', key: 'teleplayTemplateFile', manager: manager}));
} }
export function createFileSelectionSetting({name, desc, placeholder, key, manager export function createFileSelectionSetting({containerEl, name, desc, placeholder, key, manager
}: CreateTemplateSelectParams) { }: CreateTemplateSelectParams) {
return (setting: Setting) => { return (setting: Setting) => {
setting.controlEl.addClass('obsidian_douban_template_file_select');
// @ts-ignore // @ts-ignore
setting.setName(i18nHelper.getMessage(name)); setting.setName(i18nHelper.getMessage(name));
// @ts-ignore // settingDesc.setDesc(i18nHelper.getMessage(desc));
setting.setDesc(i18nHelper.getMessage(desc));
setting.addSearch(async (search: SearchComponent) => { setting.addSearch(async (search: SearchComponent) => {
const [oldValue, defaultVal] = manager.getSettingWithDefault(key); const [oldValue, defaultVal] = manager.getSettingWithDefault(key);
let v = defaultVal; let v = defaultVal;
if (oldValue) { if (oldValue) {
v = oldValue; v = oldValue;
} }
new FileSuggest(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
search.setPlaceholder(i18nHelper.getMessage(placeholder)); search.setPlaceholder(i18nHelper.getMessage(placeholder));
search.onChange(async (value: string) => { search.inputEl.addClass('obsidian_douban_template_file_select_input');
search.inputEl.style.width = '100%';
search.onChange(async (value: string) => {
manager.updateSetting(key, value); manager.updateSetting(key, value);
}); });
}); });
setting.addExtraButton((button) => { setting.addExtraButton((button) => {
button button
.setIcon('copy') .setIcon('copy')
@ -74,13 +81,25 @@ export function createFolderSelectionSetting({
setting.setName( i18nHelper.getMessage(name)); setting.setName( i18nHelper.getMessage(name));
// @ts-ignore // @ts-ignore
setting.setDesc( i18nHelper.getMessage(desc)); setting.setDesc( i18nHelper.getMessage(desc));
};
}
export function createFolderSelectionSettingInput({
name, desc, placeholder, key, manager,
}: CreateTemplateSelectParams, filePathDisplayExample?:HTMLDivElement) {
return (setting: Setting) => {
setting.controlEl.addClass('obsidian_douban_template_file_select');
setting.addSearch(async (search: SearchComponent) => { setting.addSearch(async (search: SearchComponent) => {
const [oldValue, defaultVal] = manager.getSettingWithDefault(key); const [oldValue, defaultVal] = manager.getSettingWithDefault(key);
let v = defaultVal; let v = defaultVal;
if (oldValue) { if (oldValue) {
v = oldValue; v = oldValue;
} }
new FolderSuggest(manager.app, search.inputEl); new FolderTreeSelectSuggest(manager.app, search.inputEl);
search.inputEl.addClass('obsidian_douban_template_file_select_input');
search.inputEl.style.width = '100%';
// @ts-ignore // @ts-ignore
search.setValue(v) search.setValue(v)
// @ts-ignore // @ts-ignore
@ -95,5 +114,3 @@ export function createFolderSelectionSetting({
}; };
} }

@ -1,9 +1,10 @@
import SettingsManager from "./SettingsManager"; import SettingsManager from "./SettingsManager";
import {i18nHelper} from "../../lang/helper"; import {i18nHelper} from "../../lang/helper";
import {Setting} from "obsidian"; import {Setting} from "obsidian";
import DoubanPlugin from "../../main";
export function constructTemplateVariablesUI(containerEl: HTMLElement, manager: SettingsManager) { export function constructTemplateVariablesUI(containerEl: HTMLElement, manager: SettingsManager) {
containerEl.createEl('h3', { text: i18nHelper.getMessage('1230') }); // containerEl.createEl('h3', { text: i18nHelper.getMessage('1230') });
containerEl.createEl('p', { text: i18nHelper.getMessage('122003') }); containerEl.createEl('p', { text: i18nHelper.getMessage('122003') });
const basicVariablesTable = new DocumentFragment(); const basicVariablesTable = new DocumentFragment();
@ -300,6 +301,16 @@ ${i18nHelper.getMessage('122004')}
<td>${i18nHelper.getMessage('310520')}</th> <td>${i18nHelper.getMessage('310520')}</th>
<td>${i18nHelper.getMessage('310620')}</th> <td>${i18nHelper.getMessage('310620')}</th>
<td>${i18nHelper.getMessage('310720')}</th> <td>${i18nHelper.getMessage('310720')}</th>
</tr>
<tr>
<td>${i18nHelper.getMessage('320111')}</th>
<td>${i18nHelper.getMessage('310122')}</th>
<td>${i18nHelper.getMessage('310222')}</th>
<td>${i18nHelper.getMessage('310322')}</th>
<td>${i18nHelper.getMessage('310422')}</th>
<td>${i18nHelper.getMessage('310522')}</th>
<td>${i18nHelper.getMessage('310622')}</th>
<td>${i18nHelper.getMessage('310722')}</th>
</tr> </tr>
</table>`; </table>`;

@ -3,6 +3,7 @@ import { DoubanPluginSetting } from "./DoubanPluginSetting";
export interface CreateTemplateSelectParams { export interface CreateTemplateSelectParams {
// @ts-ignore // @ts-ignore
containerEl: HTMLElement,
name:string, name:string,
desc:string, desc:string,
placeholder:string, placeholder:string,

@ -3,6 +3,7 @@ import {SyncHandledData} from "./SyncHandledData";
import {ArraySetting} from "./ArraySetting"; import {ArraySetting} from "./ArraySetting";
import {ScoreSetting} from "./ScoreSetting"; import {ScoreSetting} from "./ScoreSetting";
import PictureBedSetting from "./PictureBedSetting"; import PictureBedSetting from "./PictureBedSetting";
import {SupportType} from "../../../constant/Constsant";
export interface DoubanPluginSetting { export interface DoubanPluginSetting {
onlineSettingsFileName: string; onlineSettingsFileName: string;
@ -32,10 +33,13 @@ export interface DoubanPluginSetting {
cacheImage: boolean, cacheImage: boolean,
cacheHighQuantityImage: boolean, cacheHighQuantityImage: boolean,
attachmentPath: string, attachmentPath: string,
attachmentFileName: string,
pictureBedFlag: boolean pictureBedFlag: boolean
pictureBedType: string; pictureBedType: string;
pictureBedSetting: PictureBedSetting; pictureBedSetting: PictureBedSetting;
syncHandledDataArray: SyncHandledData[], syncHandledDataArray: SyncHandledData[],
// syncLastUpdateTime: Map<string, string>,
arraySettings: ArraySetting[], arraySettings: ArraySetting[],
scoreSetting: ScoreSetting, scoreSetting: ScoreSetting,
searchDefaultType: SupportType,
} }

@ -0,0 +1,90 @@
import {App, TAbstractFile, TFile, TFolder} from "obsidian";
import {TextInputSuggest} from "./TextInputSuggest";
import SettingsManager from "../SettingsManager";
export class FileTreeSelectSuggest extends TextInputSuggest<TAbstractFile> {
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[] {
const files: TAbstractFile[] = [];
if (inputStr.length == 0 || inputStr.trim().length == 0 || inputStr.trim() == '/') {
this.searchFiles(this.app.vault.getRoot(), "", files);
return files;
}
let parentSearchPath:string = null;
let currentName:string = null;
try {
const testFile = this.app.vault.getAbstractFileByPath(inputStr.trim())
if (testFile) {
if (testFile instanceof TFile && testFile.name.endsWith(".md")) {
files.push(testFile);
return files;
}
if (testFile instanceof TFolder) {
parentSearchPath = inputStr.trim();
currentName = "";
}
}
}catch (e) {
}
if (parentSearchPath == null) {
parentSearchPath = inputStr.lastIndexOf("/") > 0 ? inputStr.substring(0, inputStr.lastIndexOf("/")) : "/";
currentName = inputStr.lastIndexOf("/") > 0 ? inputStr.substring(inputStr.lastIndexOf("/") + 1) : inputStr;
currentName = currentName.trim();
}
if (currentName == null) {
currentName = "";
}
const root = this.app.vault.getAbstractFileByPath(parentSearchPath) as TFolder;
if (!root) {
return [];
}
const name = currentName.toLowerCase();
if (root) {
this.searchFiles(root, name, files);
}
return files;
}
searchFiles(folder: TFolder, name: string, files: TAbstractFile[]): void {
folder.children.forEach((file: TAbstractFile) => {
if (file.name.toLowerCase().contains(name)) {
files.push(file);
}
});
}
renderSuggestion(file: TAbstractFile, el: HTMLElement): void {
el.setText(file.path);
}
selectSuggestion(file: TAbstractFile): void {
this.inputEl.value = file.path;
this.parentPath = file.path;
// this.inputEl.addEventListener("change", () => {
// this.onInputChanged()
// })
if (file instanceof TFolder) {
this.inputEl.value += "/";
this.inputEl.trigger("input");
}else {
//@ts-ignore
this.manager.updateSetting(this.settingKey, file.path);
this.close();
}
}
}

@ -0,0 +1,78 @@
import {TAbstractFile, TFile, TFolder} from "obsidian";
import {TextInputSuggest} from "./TextInputSuggest";
export class FolderTreeSelectSuggest extends TextInputSuggest<TAbstractFile> {
parentPath: string = "/";
getSuggestions(inputStr: string): TAbstractFile[] {
const files: TAbstractFile[] = [];
if (inputStr.length == 0 || inputStr.trim().length == 0 || inputStr.trim() == '/') {
this.searchFiles(this.app.vault.getRoot(), "", files);
return files;
}
let parentSearchPath:string = null;
let currentName:string = null;
try {
const testFile = this.app.vault.getAbstractFileByPath(inputStr.trim())
if (testFile) {
if (testFile instanceof TFile) {
return files;
}
if (testFile instanceof TFolder) {
parentSearchPath = inputStr.trim();
currentName = "";
}
}
}catch (e) {
}
if (parentSearchPath == null) {
parentSearchPath = inputStr.lastIndexOf("/") > 0 ? inputStr.substring(0, inputStr.lastIndexOf("/")) : "/";
currentName = inputStr.lastIndexOf("/") > 0 ? inputStr.substring(inputStr.lastIndexOf("/") + 1) : inputStr;
currentName = currentName.trim();
}
if (currentName == null) {
currentName = "";
}
const root = this.app.vault.getAbstractFileByPath(parentSearchPath) as TFolder;
if (!root) {
return [];
}
const name = currentName.toLowerCase();
if (root) {
this.searchFiles(root, name, files);
}
return files;
}
searchFiles(folder: TFolder, name: string, files: TAbstractFile[]): void {
folder.children.filter(f => f instanceof TFolder).forEach((file: TAbstractFile) => {
if (file.name.toLowerCase().contains(name)) {
files.push(file);
}
});
}
renderSuggestion(file: TAbstractFile, el: HTMLElement): void {
el.setText(file.path);
}
selectSuggestion(file: TAbstractFile): void {
this.inputEl.value = file.path;
this.parentPath = file.path;
// this.inputEl.addEventListener("change", () => {
// this.onInputChanged()
// })
if (file instanceof TFolder) {
this.inputEl.value += "/";
this.inputEl.trigger("input");
}else {
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[]> {
let startDate = syncConfig.syncConditionDateFromValue
? new Date(syncConfig.syncConditionDateFromValue)
: null;
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()) {
break;
}
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[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) {
break;
}
} while (currentPage < lastPage);
}
leftPage = 1;
rightPage = lastPage;
currentPage = 1;
if (endDate != null) {
do {
if (!context.plugin.statusHolder.syncing()) {
break;
}
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;
}
async syncByTimeLimit(syncConfig: SyncConfig, context: HandleContext) {
const items = await this.getByTimeLimit(syncConfig, context);
if (!items || items.length == 0) {
return;
}
let subjectListItems = await this.removeExists(
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<SubjectListItem[]> {
const supportHandlers:DoubanListHandler[] = this.doubanListHandlers.filter((h) => h.support(syncConfig));
let items:SubjectListItem[] = [];
for(const handler of supportHandlers) {
if (!context.plugin.statusHolder.syncing()) {
return [];
}
const item = await handler.getAllPageList(context);
if (item) {
items = items.concat(item);
}
}
return items;
} }
private async removeExists(items:SubjectListItem[], syncConfig: SyncConfig, context: HandleContext):Promise<SubjectListItem[]> { 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()) { if (!context.plugin.statusHolder.syncing()) {
return []; return [];
} }
return items; return items;
} }
private async handleItems(items:SubjectListItem[], context:HandleContext):Promise<void> { private async handleItems(
searchPage: SearchPage,
items: SubjectListItem[],
context: HandleContext,
): Promise<void> {
if (!items || items.length == 0) { if (!items || items.length == 0) {
return ; return;
} }
const {syncStatus} = context.syncStatusHolder; const { syncStatus } = context.syncStatusHolder;
syncStatus.totalNum(items.length); syncStatus.totalNum(searchPage.total);
const needHandled:number = items.filter(item => syncStatus.shouldSync(item.id)).length; 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);
}
} }

@ -0,0 +1,40 @@
import {DoubanAbstractSyncHandler} from "./DoubanAbstractSyncHandler";
import {BasicConst, SyncType} from "../../../constant/Constsant";
import {SyncConfig} from "../model/SyncConfig";
import HandleContext from "../../data/model/HandleContext";
import DoubanSubjectLoadHandler from "../../data/handler/DoubanSubjectLoadHandler";
import DoubanMovieLoadHandler from "../../data/handler/DoubanMovieLoadHandler";
import DoubanMovieSubject from "../../data/model/DoubanMovieSubject";
import DoubanPlugin from "../../../main";
import {SubjectListItem} from "../../data/model/SubjectListItem";
import DoubanMovieCollectListHandler from "./list/DoubanMovieCollectListHandler";
import {DoubanListHandler} from "./list/DoubanListHandler";
import DoubanMovieWishListHandler from "./list/DoubanMovieWishListHandler";
import DoubanMovieDoListHandler from "./list/DoubanMovieDoListHandler";
import TimeUtil, {sleepRange} from "../../../utils/TimeUtil";
import {log} from "../../../utils/Logutil";
import DoubanGameLoadHandler from "../../data/handler/DoubanGameLoadHandler";
import DoubanGameCollectListHandler from "./list/DoubanGameCollectListHandler";
import DoubanGameWishListHandler from "./list/DoubanGameWishListHandler";
import DoubanGameDoListHandler from "./list/DoubanGameDoListHandler";
import DoubanGameSubject from "../../data/model/DoubanGameSubject";
//TODO will support in future version
export class DoubanGameSyncHandler extends DoubanAbstractSyncHandler<DoubanGameSubject>{
constructor(plugin:DoubanPlugin) {
super(plugin, new DoubanGameLoadHandler(plugin),[
new DoubanGameCollectListHandler(),
new DoubanGameWishListHandler(),
new DoubanGameDoListHandler()]);
}
getSyncType(): SyncType {
return SyncType.game;
}
}

@ -9,6 +9,11 @@ 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";
import {DoubanGameSyncHandler} from "./DoubanGameSyncHandler";
import {DataField} from "../../../utils/model/DataField";
import {VariableUtil} from "../../../utils/VariableUtil";
import {FileUtil} from "../../../utils/FileUtil";
export default class SyncHandler { export default class SyncHandler {
private app: App; private app: App;
@ -18,6 +23,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;
@ -32,12 +38,17 @@ export default class SyncHandler {
// new DoubanNoteSyncHandler(plugin), // new DoubanNoteSyncHandler(plugin),
new DoubanMusicSyncHandler(plugin), new DoubanMusicSyncHandler(plugin),
new DoubanTeleplaySyncHandler(plugin), new DoubanTeleplaySyncHandler(plugin),
new DoubanGameSyncHandler(plugin),
this.defaultSyncHandler this.defaultSyncHandler
]; ];
} }
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 +64,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'))}
|-----|----|----------------------------------| |-----|----|----------------------------------|
@ -74,13 +104,33 @@ export default class SyncHandler {
`; `;
}else { }else {
// @ts-ignore // @ts-ignore
details+= `${value.id}-[[${value.title}]]: ${i18nHelper.getMessage(value.status)} details+= `${value.id}-[[${FileUtil.replaceSpecialCharactersForFileName(value.title)}]]: ${i18nHelper.getMessage(value.status)}
`; `;
} }
} }
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[] = []; if (!context.plugin.statusHolder.syncing()) {
let pages:SubjectListItem[] = []; return SearchPage.emptyWithNoType();
let start = 0; }
do { let subjectListItemSearchPageTypeOf = await this.getPageList(url, context);
await sleepRange(BasicConst.CALL_DOUBAN_DELAY, if (subjectListItemSearchPageTypeOf) {
BasicConst.CALL_DOUBAN_DELAY + BasicConst.CALL_DOUBAN_DELAY_RANGE); context.plugin.statusHolder.syncStatus.setAllTotal(subjectListItemSearchPageTypeOf.total)
const url:string = this.getUrl(context, start); }
if (!context.plugin.statusHolder.syncing()) { return subjectListItemSearchPageTypeOf;
return [];
}
pages = await this.getPageList(url, context);
if (pages) {
all = all.concat(pages);
}
start = start + PAGE_SIZE;
} while (pages && pages.length > 0)
return all;
} }
async delay(ms: number) { async delay(ms: number) {}
protected 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;
} }
} }

@ -0,0 +1,11 @@
import { DoubanSubjectState} from "src/org/wanxp/constant/DoubanUserState";
import { DoubanMovieListHandler } from "./DoubanMovieListHandler";
import {DoubanGameListHandler} from "./DoubanGameListHandler";
export default class DoubanGameCollectListHandler extends DoubanGameListHandler{
getDoType(): string {
return DoubanSubjectState.collect;
}
}

@ -0,0 +1,11 @@
import { DoubanSubjectState} from "src/org/wanxp/constant/DoubanUserState";
import { DoubanMovieListHandler } from "./DoubanMovieListHandler";
import {DoubanGameListHandler} from "./DoubanGameListHandler";
export default class DoubanGameDoListHandler extends DoubanGameListHandler{
getDoType(): string {
return DoubanSubjectState.do;
}
}

@ -0,0 +1,105 @@
import DoubanAbstractListHandler from "./DoubanAbstractListHandler";
import {PAGE_SIZE, SubjectHandledStatus, SyncType} from "../../../../constant/Constsant";
import {CheerioAPI} from "cheerio";
import HandleContext from "../../../data/model/HandleContext";
import {SearchPageTypeOf} from "../../../data/model/SearchPageTypeOf";
import {SubjectListItem} from "../../../data/model/SubjectListItem";
import {log} from "../../../../utils/Logutil";
import {SearchPage} from "../../../data/model/SearchPage";
import {doubanGameSubjectSyncListUrl, doubanSubjectSyncListUrl} from "../../../../constant/Douban";
import {ALL, DoubanSubjectState} from "../../../../constant/DoubanUserState";
export abstract class DoubanGameListHandler extends DoubanAbstractListHandler {
getSyncType(): SyncType {
return SyncType.game;
}
abstract getDoType(): string;
protected getUrl(context: HandleContext, start: number) {
return doubanGameSubjectSyncListUrl(
this.getSyncTypeDomain(),
context.userComponent.getUserId(),
this.getDoType(),
start,
);
}
parseSubjectFromHtml(
dataHtml: CheerioAPI,
context: HandleContext,
): SearchPageTypeOf<SubjectListItem> {
const items = dataHtml(".common-item")
.get()
.map((i: any) => {
const item = dataHtml(i);
const linkValue: string = item
.find("div.title > a")
.attr("href");
const titleValue: string = item
.find("div.title > a")
.text()
.trim();
const updateDateStr: string = item.find("span.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 ececResult = idPattern.exec(linkValue);
return !ececResult ? null : {id: ececResult[0], url: linkValue, title: titleValue, updateDate: updateDate};
// return linkValue;
});
const total = this.getTotal(dataHtml, context);
return new SearchPage(
total,
Math.floor(context.syncOffset / PAGE_SIZE) + 1,
PAGE_SIZE,
null,
items,
);
}
private getTotal(dataHtml: CheerioAPI,
context: HandleContext):number {
const countDescs = dataHtml("div.tabs > a")
.get()
.map((i: any) => {
const item = dataHtml(i);
return item.text().trim();
});
const {syncConfig} = context;
const {scope} = syncConfig;
const wishCount = this.getCount(countDescs, '想玩');
const collectCount = this.getCount(countDescs, '玩过');
const doCount = this.getCount(countDescs, '在玩');
switch (scope) {
case DoubanSubjectState.wish:
return wishCount;
case DoubanSubjectState.collect:
return collectCount;
case DoubanSubjectState.do:
return doCount;
case ALL:
return wishCount + collectCount + doCount;
}
}
private getCount(countDescs:string[], keyword:string):number {
return countDescs.filter(desc => desc.includes(keyword)).map(desc => {
const pattern = /(\d+)/g;
const result = pattern.exec(desc);
return result ? parseInt(result[0], 10) : 0;
})[0];
}
}

Some files were not shown because too many files have changed in this diff Show More