mirror of
https://github.com/Wanxp/obsidian-douban.git
synced 2026-04-06 01:58:48 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 812fc17c5a | |||
|
|
e8a0d8a00c | ||
|
|
318aabb21b | ||
| a8094d7575 | |||
|
|
7ba1a7be0c | ||
|
|
4e8d3f1318 | ||
|
|
c375512903 | ||
|
|
99d4170626 | ||
|
|
297ccd33cf | ||
|
|
a0eccf7370 | ||
| 572907719b | |||
| ffc93a4a13 | |||
| 08749116f3 | |||
| 266cfb3901 | |||
| 7e57846f32 | |||
| 16b9ff581d | |||
| dd1648c20a |
2
.gitignore
vendored
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
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
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)
|
||||||

|

|
||||||
<!--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)
|
||||||

|

|
||||||
|
|
||||||
## 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)).
|
||||||

|

|
||||||
### 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
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 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.
|
||||||
12
README.md
12
README.md
@ -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/指导手册](https://obsidian-douban.wanxuping.com/) 获取更多 )
|
[//]: # (访问[Get Started/指导手册](https://obsidian-douban.wanxuping.com/) 获取更多 )
|
||||||

|

|
||||||
@ -32,7 +32,7 @@
|
|||||||
如果觉得喜欢或对您有帮助,欢迎一键三连-点亮 ⭐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) | 简体中文
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
- ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记
|
- ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记
|
||||||
@ -42,6 +42,7 @@
|
|||||||
- ⬜ 支持图床自定义
|
- ⬜ 支持图床自定义
|
||||||
- ☑️ 支持自定义参数
|
- ☑️ 支持自定义参数
|
||||||
- ☑️ 支持移动端导入
|
- ☑️ 支持移动端导入
|
||||||
|
- ⬜ 支持使用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,7 +135,10 @@
|
|||||||
`npm run dev`
|
`npm run dev`
|
||||||
7. 进入Obsidian插件中心重新加载当前插件
|
7. 进入Obsidian插件中心重新加载当前插件
|
||||||
8. 享受开发吧
|
8. 享受开发吧
|
||||||
|
#### 文档
|
||||||
|
```shell
|
||||||
|
npm run docs:dev
|
||||||
|
```
|
||||||
## 支持开发者
|
## 支持开发者
|
||||||
如果觉得插件对你有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
|
如果觉得插件对你有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
|
||||||
|
|
||||||
|
|||||||
11
ai/other/ACTION_提示词.md
Normal file
11
ai/other/ACTION_提示词.md
Normal file
@ -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
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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
15
doc/.vitepress/theme/custom.css
Normal file
15
doc/.vitepress/theme/custom.css
Normal file
@ -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);*/
|
||||||
|
}
|
||||||
|
|
||||||
5
doc/.vitepress/theme/index.js
Normal file
5
doc/.vitepress/theme/index.js
Normal file
@ -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: 如何使用
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 同步
|
# 同步
|
||||||
- 同步个人的观影、观剧、阅读、游戏、音乐记录
|
- 同步个人的观影、观剧、阅读、游戏、音乐记录
|
||||||

|

|
||||||
|
|||||||
@ -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)
|
||||||

|

|
||||||
<!--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,18 +1,17 @@
|
|||||||
---
|
---
|
||||||
title: 支持
|
title: 支持
|
||||||
layout: default
|
|
||||||
nav_order: 1000
|
nav_order: 1000
|
||||||
---
|
---
|
||||||
|
|
||||||
## 支持
|
# 支持
|
||||||
愿世界充满爱与和平!
|
愿世界充满爱与和平!
|
||||||
如果觉得喜欢或对您有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
|
如果觉得喜欢或对您有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 交流社群
|
## 交流社群
|
||||||
<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="img/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
|
||||||
|
|||||||
3
doc/Obsidian-Douban-DataView-Jump.md
Normal file
3
doc/Obsidian-Douban-DataView-Jump.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## 书架效果
|
||||||
|
点击以下链接跳转至少数派
|
||||||
|
[使用Obsidian打造个人图书馆!](https://sspai.com/post/85574)
|
||||||
@ -1,70 +0,0 @@
|
|||||||
## 效果如下
|
|
||||||

|
|
||||||
## 适用人群
|
|
||||||
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}}
|
|
||||||
---
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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/wanxp-logo.png
Normal file
BIN
doc/img/wanxp-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
79
doc/index.md
79
doc/index.md
@ -1,53 +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/)中的 _电影、书籍、音乐、电视剧、日记、游戏
|
|
||||||
甚至是你标记过的书影音, 包含你的评分、观看日期、评论、阅读状态等信息.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 基本功能
|
|
||||||
- ☑️ 导入电影、电视剧、书籍、音乐、游戏、日记
|
|
||||||
- ☑️ 同步个人听过/看过的电影、电视剧、书籍、音乐、游戏
|
|
||||||
- ☑️ 导入个人的评论,评论时间,阅读状态,个人评分
|
|
||||||
- ☑️ 支持保存封面至本地/图床
|
|
||||||
- ⬜ 支持图床自定义
|
|
||||||
- ☑️ 支持自定义参数
|
|
||||||
- ☑️ 支持移动端导入
|
|
||||||
|
|
||||||
## 支持
|
|
||||||
如果觉得喜欢或对您有帮助,欢迎请我喝杯咖啡,让我有更多的动力去维护和更新插件
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 交流社群
|
|
||||||
<img src="img/obsidian-douban-qq-qr_code.svg" width="300px">
|
|
||||||
<img src="img/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
|
|
||||||
|
|||||||
BIN
doc/obsidian-douban-logo.png
Normal file
BIN
doc/obsidian-douban-logo.png
Normal file
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.3.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
9428
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-douban-plugin",
|
"name": "obsidian-douban-plugin",
|
||||||
"version": "2.3.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",
|
||||||
|
|||||||
@ -208,7 +208,7 @@ export const SearchTypeRecords: { [key in SupportType]: string } = {
|
|||||||
[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'),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -44,12 +44,14 @@ export const DEFAULT_SETTINGS: DoubanPluginSetting = {
|
|||||||
{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>(),
|
// syncLastUpdateTime: new Map<string, string>(),
|
||||||
scoreSetting: {
|
scoreSetting: {
|
||||||
|
|||||||
@ -78,6 +78,10 @@ desc: {{desc}}
|
|||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
---
|
||||||
|
Menu:
|
||||||
|
{{menu}}
|
||||||
`,
|
`,
|
||||||
noteTemplateFileContent: `---
|
noteTemplateFileContent: `---
|
||||||
doubanId: {{id}}
|
doubanId: {{id}}
|
||||||
@ -240,8 +244,12 @@ desc: {{desc}}
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Comment:
|
|
||||||
---
|
---
|
||||||
|
Menu:
|
||||||
|
{{menu}}
|
||||||
|
|
||||||
|
---
|
||||||
|
Comment:
|
||||||
{{myComment}}
|
{{myComment}}
|
||||||
|
|
||||||
`,
|
`,
|
||||||
|
|||||||
@ -171,7 +171,12 @@ export const DoubanSubjectStateRecords_KEY_WORD_TYPE: Map<string, SupportType> =
|
|||||||
['我玩过这个游戏', 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -150,6 +150,7 @@ ${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,
|
syncConditionType: SyncConditionType.ALL,
|
||||||
@ -168,9 +169,6 @@ ${syncStatus.getHandle() == 0? '...' : i18nHelper.getMessage('110042') + ':' + T
|
|||||||
const syncButton = new ButtonComponent(controls)
|
const syncButton = new ButtonComponent(controls)
|
||||||
.setButtonText(i18nHelper.getMessage('110007'))
|
.setButtonText(i18nHelper.getMessage('110007'))
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
if (!this.plugin.userComponent.isLogin()) {
|
|
||||||
await this.plugin.userComponent.login();
|
|
||||||
}
|
|
||||||
if(!await this.plugin.checkLogin(this.context)) {
|
if(!await this.plugin.checkLogin(this.context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -427,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'))
|
||||||
|
|||||||
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -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 ||
|
||||||
@ -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
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -35,29 +35,33 @@ 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,
|
|
||||||
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 {
|
||||||
@ -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,7 +108,7 @@ 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 = {
|
||||||
@ -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];
|
||||||
|
|
||||||
|
// 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:actor', 'actor');
|
||||||
this.handlePersonNameByMeta(html, movie, context, 'video:director', 'director');
|
this.handlePersonNameByMeta(html, movie, context, 'video:director', 'director');
|
||||||
|
|
||||||
const desc:string = html("span[property='v:summary']").text();
|
const desc: string = html("span[property='v:summary']").text();
|
||||||
if (desc) {
|
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();
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,27 +25,31 @@ export class DoubanTeleplayLoadHandler extends DoubanAbstractLoadHandler<DoubanT
|
|||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@ -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[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,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;
|
||||||
|
|||||||
@ -3,8 +3,7 @@ import {
|
|||||||
} from "../../../../constant/Constsant";
|
} 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 {
|
||||||
@ -12,22 +11,11 @@ export class AllFirstPageSearchResultPageParser implements SearchResultPageParse
|
|||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ 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 {
|
||||||
@ -10,13 +10,10 @@ export class OtherAllPageSearchResultPageParser implements SearchResultPageParse
|
|||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ 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;
|
||||||
|
|||||||
@ -10,17 +10,17 @@ export function constructLoginUI(containerEl: HTMLElement, manager: SettingsMana
|
|||||||
// containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') });
|
// containerEl.createEl('h3', { text: i18nHelper.getMessage('1210') });
|
||||||
|
|
||||||
const userComponent = manager.plugin.userComponent;
|
const userComponent = manager.plugin.userComponent;
|
||||||
if (userComponent.needLogin()) {
|
if (userComponent.isLogin() && !userComponent.isVerified()) {
|
||||||
try {
|
// Assumed login — verify to get user ID/name for settings display
|
||||||
userComponent.login()
|
userComponent.login()
|
||||||
.then(() => {
|
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
|
||||||
constructDoubanLoginSettingsUI(containerEl, manager);
|
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
|
||||||
});
|
} else if (userComponent.needLogin()) {
|
||||||
}catch (e) {
|
// Has credentials but not yet logged in
|
||||||
log.debug(i18nHelper.getMessage('100101'));
|
userComponent.login()
|
||||||
constructDoubanLoginSettingsUI(containerEl, manager);
|
.then(() => constructDoubanLoginSettingsUI(containerEl, manager))
|
||||||
}
|
.catch(() => constructDoubanLoginSettingsUI(containerEl, manager));
|
||||||
}else {
|
} else {
|
||||||
constructDoubanLoginSettingsUI(containerEl, manager);
|
constructDoubanLoginSettingsUI(containerEl, manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export function showFileExample(containerEl: HTMLElement, manager: SettingsManag
|
|||||||
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'))
|
||||||
@ -223,7 +223,8 @@ export function constructAttachmentFileSettingsUI(containerEl: HTMLElement, mana
|
|||||||
}else {
|
}else {
|
||||||
new Setting(containerEl).then(createFolderSelectionSetting({containerEl: containerEl, name: '121432', desc: '121433', placeholder: null, key: null, 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(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}));
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -301,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>`;
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ 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;
|
||||||
|
|||||||
@ -11,6 +11,9 @@ import {i18nHelper} from "../../../lang/helper";
|
|||||||
import {DoubanTeleplaySyncHandler} from "./DoubanTeleplaySyncHandler";
|
import {DoubanTeleplaySyncHandler} from "./DoubanTeleplaySyncHandler";
|
||||||
import {SyncConditionType} from "../../../constant/Constsant";
|
import {SyncConditionType} from "../../../constant/Constsant";
|
||||||
import {DoubanGameSyncHandler} from "./DoubanGameSyncHandler";
|
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;
|
||||||
@ -101,7 +104,7 @@ 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)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export interface SyncConfig {
|
|||||||
|
|
||||||
cacheHighQuantityImage:boolean;
|
cacheHighQuantityImage:boolean;
|
||||||
attachmentPath: string;
|
attachmentPath: string;
|
||||||
|
attachmentFileName: string;
|
||||||
templateFile: string;
|
templateFile: string;
|
||||||
incrementalUpdate: boolean;
|
incrementalUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {DoubanHttpUtil} from "../../utils/DoubanHttpUtil";
|
|||||||
export default class UserComponent {
|
export default class UserComponent {
|
||||||
private settingsManager: SettingsManager;
|
private settingsManager: SettingsManager;
|
||||||
private user: User;
|
private user: User;
|
||||||
|
private verified: boolean = false;
|
||||||
|
|
||||||
constructor(settingsManager: SettingsManager) {
|
constructor(settingsManager: SettingsManager) {
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
@ -39,11 +40,26 @@ export default class UserComponent {
|
|||||||
this.user.login = false;
|
this.user.login = false;
|
||||||
}
|
}
|
||||||
this.user = null;
|
this.user = null;
|
||||||
|
this.verified = false;
|
||||||
this.settingsManager.updateSetting('loginCookiesContent', '');
|
this.settingsManager.updateSetting('loginCookiesContent', '');
|
||||||
this.settingsManager.updateSetting('loginHeadersContent', '');
|
this.settingsManager.updateSetting('loginHeadersContent', '');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assumeLoggedIn(): void {
|
||||||
|
const headers: any = this.settingsManager.getSetting('loginHeadersContent');
|
||||||
|
const cookies: any = this.settingsManager.getSetting('loginCookiesContent');
|
||||||
|
if (headers || cookies) {
|
||||||
|
this.user = new User();
|
||||||
|
this.user.login = true;
|
||||||
|
this.verified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isVerified(): boolean {
|
||||||
|
return this.verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
needLogin() {
|
needLogin() {
|
||||||
@ -68,6 +84,7 @@ export default class UserComponent {
|
|||||||
this.settingsManager.debug(`配置界面:loginCookie:豆瓣headers信息正常,${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
|
this.settingsManager.debug(`配置界面:loginCookie:豆瓣headers信息正常,${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
|
||||||
});
|
});
|
||||||
if(this.user) {
|
if(this.user) {
|
||||||
|
this.verified = true;
|
||||||
this.settingsManager.updateSetting('loginHeadersContent', JSON.stringify(headers));
|
this.settingsManager.updateSetting('loginHeadersContent', JSON.stringify(headers));
|
||||||
}
|
}
|
||||||
return this.user;
|
return this.user;
|
||||||
@ -139,6 +156,9 @@ export default class UserComponent {
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
this.settingsManager.debug(`主界面:loginByCookie:豆瓣cookies信息正常,${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
|
this.settingsManager.debug(`主界面:loginByCookie:豆瓣cookies信息正常,${user&&user.id?'获取用户信息成功id:'+ StringUtil.confuse(user.id) + ',用户名:'+ StringUtil.confuse(user.name) :'获取用户信息失败'}`);
|
||||||
});
|
});
|
||||||
|
if (this.user && this.user.id) {
|
||||||
|
this.verified = true;
|
||||||
|
}
|
||||||
return this.user;
|
return this.user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -248,12 +248,16 @@ PS: This file could be delete if you want to.
|
|||||||
'121410': `Search Default Type`,
|
'121410': `Search Default Type`,
|
||||||
'121411': `Search defuault type when open command palette 'search douban and create file'`,
|
'121411': `Search defuault type when open command palette 'search douban and create file'`,
|
||||||
|
|
||||||
'121430': `Save Attachment File`,
|
'121430': `Save Attachment(Picture) File`,
|
||||||
'121431': `Save attachment file to local disk, such as image ? If you do not enable this feature, it will not show cover image in note`,
|
'121431': `Save attachment file to local disk, such as image ? If you do not enable this feature, it will not show cover image in note`,
|
||||||
'121432': `Attachment folder`,
|
'121432': `Attachment(Picture) folder`,
|
||||||
'121433': `Attachment file created from Obsidian-Douban will be placed in this folder,
|
'121433': `Attachment file created from Obsidian-Douban will be placed in this folder,
|
||||||
If blank, they will be created by default name. support all basic template variables. example: {{type}}/assets`,
|
If blank, they will be created by default name. support all basic template variables. example: {{type}}/assets`,
|
||||||
'121434': `assets`,
|
'121434': `assets`,
|
||||||
|
'121452': `Attachment(Picture) File Name`,
|
||||||
|
'121453': `Attachment file name, If blank, they will be created by default name '{{title}}'. support all basic template variables. example: {{type}}-{{title}}`,
|
||||||
|
|
||||||
|
'121454': `{{title}}`,
|
||||||
'121435': `Save High Definition Cover`,
|
'121435': `Save High Definition Cover`,
|
||||||
'121436': `High Definition Cover looks better but it will take more space, and you must login douban in this plugin`,
|
'121436': `High Definition Cover looks better but it will take more space, and you must login douban in this plugin`,
|
||||||
'121437': `Please login first, Then this function could be enable`,
|
'121437': `Please login first, Then this function could be enable`,
|
||||||
@ -330,6 +334,10 @@ PS: This file could be delete if you want to.
|
|||||||
'140102': `subject type is different, will not sync this, chosen sync type is {0} but this {1} subject type is {2}`,
|
'140102': `subject type is different, will not sync this, chosen sync type is {0} but this {1} subject type is {2}`,
|
||||||
'130105': `Can not use Douban this time, Please try again after 12 hour or 24 hour. Or you can reset your connection `,
|
'130105': `Can not use Douban this time, Please try again after 12 hour or 24 hour. Or you can reset your connection `,
|
||||||
'130106': `Can not use Douban this time, Please try Login In Douban Plugin. If not working please again after 12 hour or 24 hour. Or you can reset your connection `,
|
'130106': `Can not use Douban this time, Please try Login In Douban Plugin. If not working please again after 12 hour or 24 hour. Or you can reset your connection `,
|
||||||
|
'130404': `404 Url Not Found`,
|
||||||
|
'130109': `Downloaded image is empty`,
|
||||||
|
'130110': `Clipboard write failed`,
|
||||||
|
|
||||||
|
|
||||||
'130107': `Can not find array setting for {1} in {0} , Please add it in array settings`,
|
'130107': `Can not find array setting for {1} in {0} , Please add it in array settings`,
|
||||||
'130108': `Redirect times too much, please check your network or proxy`,
|
'130108': `Redirect times too much, please check your network or proxy`,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
//简体中文
|
//简体中文
|
||||||
|
//简体中文
|
||||||
|
|
||||||
import {SyncItemStatus} from "../../constant/Constsant";
|
import {SyncItemStatus} from "../../constant/Constsant";
|
||||||
|
|
||||||
@ -302,9 +303,12 @@ export default {
|
|||||||
|
|
||||||
'121430': `保存图片附件`,
|
'121430': `保存图片附件`,
|
||||||
'121431': `导入数据会同步保存图片附件到本地文件夹, 如电影封面,书籍封面。如果需要显示封面,请保持开启该功能。`,
|
'121431': `导入数据会同步保存图片附件到本地文件夹, 如电影封面,书籍封面。如果需要显示封面,请保持开启该功能。`,
|
||||||
'121432': `附件存放位置`,
|
'121432': `附件(图片)存放位置`,
|
||||||
'121433': `保存的附件将会存放至该文件夹中. 如果为空, 笔记将会存放到默认位置(assets), 且支持所有'通用'的参数。如:{{myType}}/attachments`,
|
'121433': `保存的附件将会存放至该文件夹中. 如果为空, 笔记将会存放到默认位置(assets), 且支持所有'通用'的参数。如:{{myType}}/attachments`,
|
||||||
|
'121452': `附件(图片)文件名`,
|
||||||
|
'121453': `附件的文件名模板, 支持所有'通用'的参数作为名称(如:{{type}}-{{title}}),且支持路径, 比如: '{{myType}}/附件-{{title}}'。如果为空, 则使用默认名称{{title}}`,
|
||||||
'121434': `assets`,
|
'121434': `assets`,
|
||||||
|
'121454': `{{title}}`,
|
||||||
'121435': `保存高清封面`,
|
'121435': `保存高清封面`,
|
||||||
'121436': `高清封面图片质量更高清晰度更好, 需要您在此插件 登录豆瓣 才能生效, 若未登录则默认使用低精度版本封面`,
|
'121436': `高清封面图片质量更高清晰度更好, 需要您在此插件 登录豆瓣 才能生效, 若未登录则默认使用低精度版本封面`,
|
||||||
'121437': `登录后此功能才会生效`,
|
'121437': `登录后此功能才会生效`,
|
||||||
@ -346,6 +350,8 @@ export default {
|
|||||||
'130105': `由于多次频繁请求数据,豆瓣当前暂时不可用. 请于12小时或24小时后再重试,或重置你的网络(如重新拨号或更换网络) `,
|
'130105': `由于多次频繁请求数据,豆瓣当前暂时不可用. 请于12小时或24小时后再重试,或重置你的网络(如重新拨号或更换网络) `,
|
||||||
'130106': `请尝试在Douban插件中登录后操作. 若还是无效果则尝试于12小时或24小时后再重试,或重置你的网络(如重新拨号或更换网络) `,
|
'130106': `请尝试在Douban插件中登录后操作. 若还是无效果则尝试于12小时或24小时后再重试,或重置你的网络(如重新拨号或更换网络) `,
|
||||||
'130107': `参数{0}中指定的数组输出类型{1}不存在,请前往配置进行设置`,
|
'130107': `参数{0}中指定的数组输出类型{1}不存在,请前往配置进行设置`,
|
||||||
|
'130109': `下载图片内容为空`,
|
||||||
|
'130110': `写入剪贴板失败`,
|
||||||
'130120': `同步时发生错误,但同步将会继续。错误项目是 {}。`,
|
'130120': `同步时发生错误,但同步将会继续。错误项目是 {}。`,
|
||||||
'130121': `总数只有{0}, 选择的开始比总数还要大,将不会同步。`,
|
'130121': `总数只有{0}, 选择的开始比总数还要大,将不会同步。`,
|
||||||
|
|
||||||
@ -444,6 +450,7 @@ export default {
|
|||||||
'310118': `producer:出品方`,
|
'310118': `producer:出品方`,
|
||||||
'310130': `出版年份`,
|
'310130': `出版年份`,
|
||||||
'310121': `封面URL`,
|
'310121': `封面URL`,
|
||||||
|
'310122': `menu:目录`,
|
||||||
|
|
||||||
|
|
||||||
//电影
|
//电影
|
||||||
@ -469,6 +476,7 @@ export default {
|
|||||||
'310220': `-`,
|
'310220': `-`,
|
||||||
'310230': `上映年份`,
|
'310230': `上映年份`,
|
||||||
'310221': `封面URL`,
|
'310221': `封面URL`,
|
||||||
|
'310222': `-`,
|
||||||
|
|
||||||
//电视剧
|
//电视剧
|
||||||
'310301': `豆瓣ID`,
|
'310301': `豆瓣ID`,
|
||||||
@ -493,6 +501,7 @@ export default {
|
|||||||
'310320': `episode:集数`,
|
'310320': `episode:集数`,
|
||||||
'310330': `上映年份`,
|
'310330': `上映年份`,
|
||||||
'310321': `封面URL`,
|
'310321': `封面URL`,
|
||||||
|
'310322': `-`,
|
||||||
|
|
||||||
|
|
||||||
//音乐
|
//音乐
|
||||||
@ -516,6 +525,7 @@ export default {
|
|||||||
'310418': `-`,
|
'310418': `-`,
|
||||||
'310430': `发行年份`,
|
'310430': `发行年份`,
|
||||||
'310421': `封面URL`,
|
'310421': `封面URL`,
|
||||||
|
'310422': `menu:目录`,
|
||||||
|
|
||||||
|
|
||||||
//日记
|
//日记
|
||||||
@ -539,6 +549,7 @@ export default {
|
|||||||
'310518': `-`,
|
'310518': `-`,
|
||||||
'310530': `发布年份`,
|
'310530': `发布年份`,
|
||||||
'310521': `封面URL`,
|
'310521': `封面URL`,
|
||||||
|
'310522': `-`,
|
||||||
|
|
||||||
//游戏
|
//游戏
|
||||||
'310601': `豆瓣ID`,
|
'310601': `豆瓣ID`,
|
||||||
@ -561,6 +572,7 @@ export default {
|
|||||||
'310618': `-`,
|
'310618': `-`,
|
||||||
'310630': `发行年份`,
|
'310630': `发行年份`,
|
||||||
'310621': `封面URL`,
|
'310621': `封面URL`,
|
||||||
|
'310622': `-`,
|
||||||
|
|
||||||
|
|
||||||
//广播
|
//广播
|
||||||
@ -583,7 +595,8 @@ export default {
|
|||||||
'310717': `-`,
|
'310717': `-`,
|
||||||
'310718': `-`,
|
'310718': `-`,
|
||||||
'310730': `-`,
|
'310730': `-`,
|
||||||
'310721': `封面URL`,
|
'310721': `-`,
|
||||||
|
'310722': `-`,
|
||||||
|
|
||||||
'320101': `扩展1`,
|
'320101': `扩展1`,
|
||||||
'320102': `扩展2`,
|
'320102': `扩展2`,
|
||||||
|
|||||||
@ -105,12 +105,7 @@ export default class DoubanPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createFile(context: HandleContext, result: HandleResult) {
|
async createFile(context: HandleContext, result: HandleResult) {
|
||||||
let filePath = this.settings.dataFilePath;
|
let filePath = result.filePath?result.filePath:DEFAULT_SETTINGS.dataFilePath;
|
||||||
const {syncConfig} = context;
|
|
||||||
if (syncConfig) {
|
|
||||||
filePath = syncConfig.dataFilePath;
|
|
||||||
}
|
|
||||||
filePath = filePath?filePath:DEFAULT_SETTINGS.dataFilePath;
|
|
||||||
filePath = FileUtil.join(filePath, result.fileName);
|
filePath = FileUtil.join(filePath, result.fileName);
|
||||||
const syncStatus = context.syncStatusHolder && context.syncStatusHolder.syncStatus ? context.syncStatusHolder.syncStatus : null;
|
const syncStatus = context.syncStatusHolder && context.syncStatusHolder.syncStatus ? context.syncStatusHolder.syncStatus : null;
|
||||||
const {subject} = result;
|
const {subject} = result;
|
||||||
@ -287,10 +282,11 @@ export default class DoubanPlugin extends Plugin {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.settingsManager = new SettingsManager(app, this);
|
this.settingsManager = new SettingsManager(this.app, this);
|
||||||
// this.fetchOnlineData(this.settingsManager);
|
// this.fetchOnlineData(this.settingsManager);
|
||||||
this.userComponent = new UserComponent(this.settingsManager);
|
this.userComponent = new UserComponent(this.settingsManager);
|
||||||
this.netFileHandler = new NetFileHandler(this.fileHandler);
|
this.netFileHandler = new NetFileHandler(this.fileHandler);
|
||||||
|
this.userComponent.assumeLoggedIn();
|
||||||
|
|
||||||
this.settingTab = new DoubanSettingTab(this.app, this);
|
this.settingTab = new DoubanSettingTab(this.app, this);
|
||||||
this.addSettingTab(this.settingTab);
|
this.addSettingTab(this.settingTab);
|
||||||
@ -360,11 +356,16 @@ export default class DoubanPlugin extends Plugin {
|
|||||||
|
|
||||||
async checkLogin(context: HandleContext):Promise<boolean> {
|
async checkLogin(context: HandleContext):Promise<boolean> {
|
||||||
this.settingsManager.debug('主界面:同步时的登录状态检测');
|
this.settingsManager.debug('主界面:同步时的登录状态检测');
|
||||||
if (!context.userComponent.needLogin()) {
|
const uc = context.userComponent;
|
||||||
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 无用户信息, 尝试获取用户信息');
|
// If assumed-logged-in but not verified, verify now (sync needs real user ID)
|
||||||
await context.userComponent.login();
|
if (uc.isLogin() && !uc.isVerified()) {
|
||||||
|
await uc.login();
|
||||||
}
|
}
|
||||||
if (!context.userComponent.isLogin()) {
|
// If has saved credentials but not logged in, try login
|
||||||
|
if (uc.needLogin()) {
|
||||||
|
await uc.login();
|
||||||
|
}
|
||||||
|
if (!uc.isLogin()) {
|
||||||
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 尝试获取用户信息失败');
|
this.settingsManager.debug('主界面:同步时的登录状态检测完成: 尝试获取用户信息失败');
|
||||||
new Notice(i18nHelper.getMessage('140303'));
|
new Notice(i18nHelper.getMessage('140303'));
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -19,15 +19,28 @@ export default class NetFileHandler {
|
|||||||
const filePath:string = FileUtil.join(folder, filename);
|
const filePath:string = FileUtil.join(folder, filename);
|
||||||
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
if (response.status == 404) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130404'));
|
||||||
|
}
|
||||||
if (response.status == 403) {
|
if (response.status == 403) {
|
||||||
throw new Error(i18nHelper.getMessage('130106'));
|
throw new Error(i18nHelper.getMessage('130106'));
|
||||||
}
|
}
|
||||||
|
if (response.status == 418) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130105'));
|
||||||
|
}
|
||||||
return response.textArrayBuffer;
|
return response.textArrayBuffer;
|
||||||
})
|
})
|
||||||
.then((buffer) => {
|
.then((buffer) => {
|
||||||
|
if (!buffer || buffer.byteLength == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
this.fileHandler.creatAttachmentWithData(filePath, buffer);
|
this.fileHandler.creatAttachmentWithData(filePath, buffer);
|
||||||
}).then(() => {
|
return buffer.byteLength;
|
||||||
return {success: true, error: '', filepath: filePath};
|
}).then((size) => {
|
||||||
|
if (size == 0) {
|
||||||
|
return {success: false, size: size, error: '文件唯恐', filepath: null};
|
||||||
|
}
|
||||||
|
return {success: true, size: size, error: '', filepath: filePath};
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
if (showError) {
|
if (showError) {
|
||||||
@ -46,15 +59,23 @@ export default class NetFileHandler {
|
|||||||
async downloadDBUploadPicGoByClipboard(url: string, filename: string, context:HandleContext, showError:boolean, headers?:any): Promise<{ success: boolean, error:string, filepath: string }> {
|
async downloadDBUploadPicGoByClipboard(url: string, filename: string, context:HandleContext, showError:boolean, headers?:any): Promise<{ success: boolean, error:string, filepath: string }> {
|
||||||
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
return HttpUtil.httpRequestBuffer(url, headers, context.plugin.settingsManager)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
if (response.status == 404) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130404'));
|
||||||
|
}
|
||||||
if (response.status == 403) {
|
if (response.status == 403) {
|
||||||
throw new Error(i18nHelper.getMessage('130106'));
|
throw new Error(i18nHelper.getMessage('130106'));
|
||||||
}
|
}
|
||||||
|
if (response.status == 418) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130105'));
|
||||||
|
}
|
||||||
return response.textArrayBuffer;
|
return response.textArrayBuffer;
|
||||||
})
|
})
|
||||||
.then((buffer) => {
|
.then(async (buffer) => {
|
||||||
ClipboardUtil.writeImage(buffer);
|
if (!buffer || buffer.byteLength == 0) {
|
||||||
}).then(() => {
|
throw new Error(i18nHelper.getMessage('130109'));
|
||||||
return this.uploadClipboardFile(context);
|
}
|
||||||
|
await ClipboardUtil.writeImage(buffer);
|
||||||
|
return await this.uploadClipboardFile(context);
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
return {success: true, error: '', filepath: HttpUtil.extractURLFromString(data.result[0])};
|
return {success: true, error: '', filepath: HttpUtil.extractURLFromString(data.result[0])};
|
||||||
@ -98,4 +119,3 @@ export default class NetFileHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,80 @@
|
|||||||
|
|
||||||
|
|
||||||
export class ClipboardUtil {
|
import {i18nHelper} from "../lang/helper";
|
||||||
|
|
||||||
|
export class ClipboardUtil {
|
||||||
|
|
||||||
public static async writeImage(data:ArrayBuffer, options: ClipboardOptions = defaultClipboardOptions) {
|
public static async writeImage(data:ArrayBuffer, options: ClipboardOptions = defaultClipboardOptions) {
|
||||||
|
if (!data || data.byteLength == 0) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130110'));
|
||||||
|
}
|
||||||
const { clipboard, nativeImage } = require('electron');
|
const { clipboard, nativeImage } = require('electron');
|
||||||
|
const isWebp = this.isWebp(data);
|
||||||
await clipboard.writeImage(nativeImage.createFromBuffer(data));
|
let image = nativeImage.createFromBuffer(Buffer.from(data));
|
||||||
|
if ((!image || image.isEmpty()) && isWebp) {
|
||||||
|
image = await this.createNativeImageFromWebp(data);
|
||||||
|
}
|
||||||
|
if (!image || image.isEmpty()) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130110'));
|
||||||
|
}
|
||||||
|
clipboard.clear();
|
||||||
|
clipboard.writeImage(image);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
const clipboardImage = clipboard.readImage();
|
||||||
|
if (!clipboardImage || clipboardImage.isEmpty()) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130110'));
|
||||||
|
}
|
||||||
console.log(`Copied to clipboard as HTML`);
|
console.log(`Copied to clipboard as HTML`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static isWebp(data: ArrayBuffer): boolean {
|
||||||
|
const bytes = new Uint8Array(data);
|
||||||
|
if (bytes.length < 12) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return bytes[0] === 0x52
|
||||||
|
&& bytes[1] === 0x49
|
||||||
|
&& bytes[2] === 0x46
|
||||||
|
&& bytes[3] === 0x46
|
||||||
|
&& bytes[8] === 0x57
|
||||||
|
&& bytes[9] === 0x45
|
||||||
|
&& bytes[10] === 0x42
|
||||||
|
&& bytes[11] === 0x50;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async createNativeImageFromWebp(data: ArrayBuffer) {
|
||||||
|
const { nativeImage } = require('electron');
|
||||||
|
const imageElement = await this.loadImage(URL.createObjectURL(new Blob([data], {type: 'image/webp'})));
|
||||||
|
try {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = imageElement.naturalWidth || imageElement.width;
|
||||||
|
canvas.height = imageElement.naturalHeight || imageElement.height;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(i18nHelper.getMessage('130110'));
|
||||||
|
}
|
||||||
|
context.drawImage(imageElement, 0, 0);
|
||||||
|
return nativeImage.createFromDataURL(canvas.toDataURL('image/png'));
|
||||||
|
} finally {
|
||||||
|
imageElement.src = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static loadImage(url: string): Promise<HTMLImageElement> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.onload = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
resolve(image);
|
||||||
|
};
|
||||||
|
image.onerror = () => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
reject(new Error(i18nHelper.getMessage('130110')));
|
||||||
|
};
|
||||||
|
image.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -20,4 +86,3 @@ interface ClipboardOptions {
|
|||||||
const defaultClipboardOptions: ClipboardOptions = {
|
const defaultClipboardOptions: ClipboardOptions = {
|
||||||
contentType: 'text/plain',
|
contentType: 'text/plain',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -58,10 +58,15 @@ export const FileUtil = {
|
|||||||
/**
|
/**
|
||||||
* replace special characters for filename
|
* replace special characters for filename
|
||||||
*/
|
*/
|
||||||
replaceSpecialCharactersForFileName(fileName: string): string {
|
replaceSpecialCharactersForFileName(fileNameInput: string): string {
|
||||||
return fileName.replaceAll(/[\\/:*?"<>|]/g, '_');
|
let fileName = fileNameInput.replaceAll(/[\\/:*?"<>|]/g, '_');
|
||||||
|
fileName = fileName.replaceAll(/[\n\r\t]/g, '_');
|
||||||
|
fileName = fileName.replaceAll(/\s+/g, '_');
|
||||||
|
fileName = fileName.replaceAll(/^\.+/g, '_'); // remove leading dots
|
||||||
|
fileName = fileName.replaceAll(/\.+$/g, '_'); // remove trailing dots
|
||||||
|
fileName = fileName.replaceAll(/_+/g, '_'); // remove duplicate underscores
|
||||||
|
return fileName;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,19 @@ import {HttpResponse} from "./model/HttpResponse";
|
|||||||
|
|
||||||
export default class HttpUtil {
|
export default class HttpUtil {
|
||||||
|
|
||||||
|
private static readonly IMAGE_REQUEST_HEADERS_TO_DROP = new Set([
|
||||||
|
'authority',
|
||||||
|
'content-length',
|
||||||
|
'content-type',
|
||||||
|
'host',
|
||||||
|
'origin',
|
||||||
|
'referer',
|
||||||
|
'sec-fetch-dest',
|
||||||
|
'sec-fetch-mode',
|
||||||
|
'sec-fetch-site',
|
||||||
|
'sec-fetch-user',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +73,27 @@ export default class HttpUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static buildImageRequestHeaders(headers: Record<string, any> = {}, referer?: string): Record<string, string> {
|
||||||
|
const nextHeaders: Record<string, string> = {};
|
||||||
|
Object.entries(headers || {}).forEach(([key, value]) => {
|
||||||
|
if (value == null || value === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lowerKey = key.toLowerCase();
|
||||||
|
if (lowerKey === 'referer') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.IMAGE_REQUEST_HEADERS_TO_DROP.has(lowerKey) || lowerKey.startsWith('sec-ch-ua')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nextHeaders[key] = String(value);
|
||||||
|
});
|
||||||
|
if (referer) {
|
||||||
|
nextHeaders.Referer = referer;
|
||||||
|
}
|
||||||
|
return nextHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static parse(url: string): { protocol: string, host: string, port: string, path: string } {
|
public static parse(url: string): { protocol: string, host: string, port: string, path: string } {
|
||||||
const regex = /^(.*?):\/\/([^\/:]+)(?::(\d+))?([^?]*)$/;
|
const regex = /^(.*?):\/\/([^\/:]+)(?::(\d+))?([^?]*)$/;
|
||||||
|
|||||||
@ -117,6 +117,8 @@ export default class StringUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EscapeMap:Map< { [Symbol.replace](string: string, replaceValue: string): string; }, string> = new Map([
|
export const EscapeMap:Map< { [Symbol.replace](string: string, replaceValue: string): string; }, string> = new Map([
|
||||||
|
|||||||
@ -8,18 +8,26 @@ import {DataValueType, SupportType} from "../constant/Constsant";
|
|||||||
import {DataField} from "./model/DataField";
|
import {DataField} from "./model/DataField";
|
||||||
import {FieldVariable} from "./model/FieldVariable";
|
import {FieldVariable} from "./model/FieldVariable";
|
||||||
import {CustomProperty} from "../douban/setting/model/CustomProperty";
|
import {CustomProperty} from "../douban/setting/model/CustomProperty";
|
||||||
|
import {FileUtil} from "./FileUtil";
|
||||||
|
|
||||||
|
type TargetType = 'text' | 'path' | 'yml_text';
|
||||||
|
|
||||||
|
|
||||||
export class VariableUtil {
|
export class VariableUtil {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
*
|
*
|
||||||
* @param obj
|
* @param obj
|
||||||
* @param content
|
* @param content
|
||||||
|
* @param subjectType
|
||||||
* @param settingManager
|
* @param settingManager
|
||||||
|
* @param targetType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static replaceSubject(obj: any, content: string, subjectType: SupportType, settingManager:SettingsManager): string {
|
static replaceSubject(obj: any, content: string, subjectType: SupportType, settingManager:SettingsManager, targetType: TargetType): string {
|
||||||
if (!content || !obj) {
|
if (!content || !obj) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -28,12 +36,12 @@ export class VariableUtil {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
if (obj instanceof Map) {
|
if (obj instanceof Map) {
|
||||||
this.handleCustomVariable(subjectType, obj, settingManager)
|
this.handleCustomVariable(subjectType, obj, settingManager, 'text')
|
||||||
content = this.replaceMap(obj, allVariables, content, settingManager);
|
content = this.replaceMap(obj, allVariables, content, settingManager, targetType);
|
||||||
}else {
|
}else {
|
||||||
const map = this.objToMap(obj);
|
const map = this.objToMap(obj);
|
||||||
this.handleCustomVariable(subjectType, map, settingManager)
|
this.handleCustomVariable(subjectType, map, settingManager, 'text')
|
||||||
content = this.replaceMap(map, allVariables, content, settingManager);
|
content = this.replaceMap(map, allVariables, content, settingManager, targetType);
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -44,9 +52,10 @@ export class VariableUtil {
|
|||||||
* @param obj
|
* @param obj
|
||||||
* @param content
|
* @param content
|
||||||
* @param settingManager
|
* @param settingManager
|
||||||
|
* @param targetType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static replace(obj: any, content: string, settingManager:SettingsManager): string {
|
static replace(obj: any, content: string, settingManager:SettingsManager, targetType : TargetType): string {
|
||||||
if (!content || !obj) {
|
if (!content || !obj) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -55,10 +64,10 @@ export class VariableUtil {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
if (obj instanceof Map) {
|
if (obj instanceof Map) {
|
||||||
content = this.replaceMap(obj, allVariables, content, settingManager);
|
content = this.replaceMap(obj, allVariables, content, settingManager, targetType);
|
||||||
}else {
|
}else {
|
||||||
const map = this.objToMap(obj);
|
const map = this.objToMap(obj);
|
||||||
content = this.replaceMap(map, allVariables, content, settingManager); }
|
content = this.replaceMap(map, allVariables, content, settingManager, targetType); }
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -75,23 +84,24 @@ export class VariableUtil {
|
|||||||
* @param value
|
* @param value
|
||||||
* @param content
|
* @param content
|
||||||
* @param settingManager
|
* @param settingManager
|
||||||
|
* @param targetType
|
||||||
*/
|
*/
|
||||||
static replaceVariable(variable: FieldVariable, value: any, content: string, settingManager:SettingsManager): string {
|
static replaceVariable(variable: FieldVariable, value: any, content: string, settingManager:SettingsManager, targetType: TargetType): string {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
//根据value的类型,替换对应的变量
|
//根据value的类型,替换对应的变量
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
content = this.replaceArray(variable, value, content, settingManager);
|
content = this.replaceArray(variable, value, content, settingManager, targetType);
|
||||||
} else if(value instanceof DataField) {
|
} else if(value instanceof DataField) {
|
||||||
content = this.replaceDataField(variable, value, content, settingManager);
|
content = this.replaceDataField(variable, value, content, settingManager, targetType);
|
||||||
} else {
|
} else {
|
||||||
content = this.replaceString(variable, value, content, settingManager);
|
content = this.replaceString(variable, value, value, content, settingManager, targetType);
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
static replaceArray(variable: FieldVariable, value: any[], content: string, settingManager:SettingsManager): string {
|
static replaceArray(variable: FieldVariable, value: any[], content: string, settingManager:SettingsManager, targetType: TargetType): string {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -114,7 +124,7 @@ export class VariableUtil {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(v => v)
|
.filter(v => v)
|
||||||
.map(v => YamlUtil.handleText(v))
|
.map(v => this.handleText(v, targetType))
|
||||||
;
|
;
|
||||||
const arrayValue = StringUtil.handleArray(strValues, arraySettings);
|
const arrayValue = StringUtil.handleArray(strValues, arraySettings);
|
||||||
content = content.replaceAll(variableStr, arrayValue);
|
content = content.replaceAll(variableStr, arrayValue);
|
||||||
@ -125,19 +135,19 @@ export class VariableUtil {
|
|||||||
return `{{${key}}}`;
|
return `{{${key}}}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static replaceString(variable: FieldVariable, value: any, content: string, settingManager:SettingsManager): string {
|
static replaceString(variable: FieldVariable, valueField: DataField, value: any, content: string, settingManager:SettingsManager, targetType: TargetType): string {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
let strValue = value? value.toString() : "";
|
let strValue = value? value.toString() : "";
|
||||||
strValue = YamlUtil.handleText(strValue);
|
return content.replaceAll(variable.variable, this.handleText(strValue, targetType, valueField));
|
||||||
return content.replaceAll(variable.variable, strValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从key中提取 arrayName, 然后从settings中获取对应的ArraySetting
|
* 从key中提取 arrayName, 然后从settings中获取对应的ArraySetting
|
||||||
* @param key
|
|
||||||
* @private
|
* @private
|
||||||
|
* @param content
|
||||||
|
* @param settingManager
|
||||||
*/
|
*/
|
||||||
private static getAllVariables(content: string, settingManager:SettingsManager): FieldVariable[] {
|
private static getAllVariables(content: string, settingManager:SettingsManager): FieldVariable[] {
|
||||||
const reg =/\{\{[a-zA-Z-0-9_.]+([(a-zA-Z-0-9)]+)?}}/g
|
const reg =/\{\{[a-zA-Z-0-9_.]+([(a-zA-Z-0-9)]+)?}}/g
|
||||||
@ -161,6 +171,7 @@ export class VariableUtil {
|
|||||||
/**
|
/**
|
||||||
* 从key中提取 arrayName, 然后从settings中获取对应的ArraySetting
|
* 从key中提取 arrayName, 然后从settings中获取对应的ArraySetting
|
||||||
* @param outTypeName
|
* @param outTypeName
|
||||||
|
* @param settingManager
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private static getArraySetting(outTypeName: string, settingManager:SettingsManager): ArraySetting {
|
private static getArraySetting(outTypeName: string, settingManager:SettingsManager): ArraySetting {
|
||||||
@ -172,10 +183,10 @@ export class VariableUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static replaceMap(obj: Map<string, any>, allVariables:FieldVariable[], content: string, settingManager: SettingsManager) {
|
private static replaceMap(obj: Map<string, any>, allVariables:FieldVariable[], content: string, settingManager: SettingsManager, targetType: TargetType) {
|
||||||
allVariables.forEach(variable => {
|
allVariables.forEach(variable => {
|
||||||
const value = obj.get(variable.key);
|
const value = obj.get(variable.key);
|
||||||
content = this.replaceVariable(variable, value,content, settingManager);
|
content = this.replaceVariable(variable, value, content, settingManager, targetType);
|
||||||
});
|
});
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -199,7 +210,7 @@ export class VariableUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static replaceDataField(variable: FieldVariable, value: DataField, content: string, settingManager: SettingsManager) {
|
private static replaceDataField(variable: FieldVariable, value: DataField, content: string, settingManager: SettingsManager, targetType: TargetType) {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@ -209,19 +220,19 @@ export class VariableUtil {
|
|||||||
}
|
}
|
||||||
switch (value.type) {
|
switch (value.type) {
|
||||||
case DataValueType.string:
|
case DataValueType.string:
|
||||||
content = this.replaceString(variable, value.value, content, settingManager);
|
content = this.replaceString(variable, value, value.value, content, settingManager, targetType);
|
||||||
break;
|
break;
|
||||||
case DataValueType.number:
|
case DataValueType.number:
|
||||||
content = content.replaceAll(variableStr, value.value.toString());
|
content = content.replaceAll(variableStr, this.handleText(value.value.toString(), targetType, value));
|
||||||
break;
|
break;
|
||||||
case DataValueType.date:
|
case DataValueType.date:
|
||||||
content = content.replaceAll(variableStr, value.value);
|
content = content.replaceAll(variableStr, this.handleText(value.value, targetType, value));
|
||||||
break;
|
break;
|
||||||
case DataValueType.array:
|
case DataValueType.array:
|
||||||
content = this.replaceArray(variable, value.value, content, settingManager);
|
content = this.replaceArray(variable, value.value, content, settingManager, targetType);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
content = content.replaceAll(variableStr, value.value);
|
content = content.replaceAll(variableStr, this.handleText(value.value, targetType, value));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -231,11 +242,13 @@ export class VariableUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理自定义参数
|
* 处理自定义参数
|
||||||
* @param template
|
|
||||||
* @param context
|
|
||||||
* @private
|
* @private
|
||||||
|
* @param subjectType
|
||||||
|
* @param variableMap
|
||||||
|
* @param settingMananger
|
||||||
|
* @param targetType
|
||||||
*/
|
*/
|
||||||
static handleCustomVariable(subjectType: SupportType, variableMap:Map<string, DataField>, settingMananger: SettingsManager): void {
|
static handleCustomVariable(subjectType: SupportType, variableMap:Map<string, DataField>, settingMananger: SettingsManager, targetType:TargetType): void {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const customProperties: CustomProperty[] = settingMananger.getSetting('customProperties');
|
const customProperties: CustomProperty[] = settingMananger.getSetting('customProperties');
|
||||||
if (!customProperties) {
|
if (!customProperties) {
|
||||||
@ -252,7 +265,7 @@ export class VariableUtil {
|
|||||||
variableMap.set(key,
|
variableMap.set(key,
|
||||||
new DataField(
|
new DataField(
|
||||||
key, DataValueType.string, value,
|
key, DataValueType.string, value,
|
||||||
VariableUtil.replace(variableMap, value, settingMananger)));
|
VariableUtil.replace(variableMap, value, settingMananger, targetType)));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,4 +276,16 @@ export class VariableUtil {
|
|||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static handleText(v: string, targetType: TargetType, dataField: DataField = null): string {
|
||||||
|
if (targetType === 'yml_text') {
|
||||||
|
return YamlUtil.handleText(v, dataField);
|
||||||
|
}
|
||||||
|
if (targetType === 'text') {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (targetType === 'path') {
|
||||||
|
return FileUtil.replaceSpecialCharactersForFileName(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
export default class YamlUtil {
|
import {DataField} from "./model/DataField";
|
||||||
|
import {DataValueType} from "../constant/Constsant";
|
||||||
|
|
||||||
|
export default class YamlUtil {
|
||||||
|
|
||||||
public static hasSpecialChar(str: string): boolean {
|
public static hasSpecialChar(str: string): boolean {
|
||||||
return SPECIAL_CHAR_REG.test(str);
|
return SPECIAL_CHAR_REG.test(str);
|
||||||
@ -14,21 +16,26 @@ export default class YamlUtil {
|
|||||||
return '"' + text + '"';
|
return '"' + text + '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static handleText(text: string) {
|
public static handleText(text: string, dataField: DataField = null): string {
|
||||||
return YamlUtil.hasSpecialChar(text)
|
if (YamlUtil.hasSpecialChar(text)) {
|
||||||
? YamlUtil.handleSpecialChar(text.replaceAll('"', '\\"'))
|
text = text.replaceAll('"', '\\"')
|
||||||
.replaceAll(/\s+/g,' ')
|
.replaceAll(/\s+/g, ' ')
|
||||||
.replaceAll('\n', '。')
|
.replaceAll('\n', '。')
|
||||||
.replaceAll('。。', '。')
|
.replaceAll('。。', '。')
|
||||||
.replace(/^" /, '"') // Remove leading "
|
.replace(/^" /, '"') // Remove leading "
|
||||||
.replace(/ "$/, '"') // Remove trailing "
|
.replace(/ "$/, '"') // Remove trailing "
|
||||||
: text;
|
if (dataField && dataField.type === DataValueType.date) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
YamlUtil.handleSpecialChar(text);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SPECIAL_CHAR_REG = /[{}\[\]&*#?|\-<>=!%@:"`,\n]/;
|
export const SPECIAL_CHAR_REG = /[{}\[\]&*#?|\-<>=!%@:"`,\n]/;
|
||||||
export const TITLE_ALIASES_SPECIAL_CHAR_REG_G = /[{}\[\]&*#?|\-<>=!%@:"`,, \n]/g;
|
export const TITLE_ALIASES_SPECIAL_CHAR_REG_G = /[{}\[\]&*#?|\-<>=!%@:"`,,\n]/g;
|
||||||
|
|
||||||
const SPECIAL_CHAR_REG_REPLACE: Map<string, string> = new Map([
|
const SPECIAL_CHAR_REG_REPLACE: Map<string, string> = new Map([
|
||||||
['{', '\\{'],
|
['{', '\\{'],
|
||||||
|
|||||||
@ -60,6 +60,8 @@
|
|||||||
"2.2.2": "0.12.0",
|
"2.2.2": "0.12.0",
|
||||||
"2.2.3": "0.12.0",
|
"2.2.3": "0.12.0",
|
||||||
"2.2.4": "0.12.0",
|
"2.2.4": "0.12.0",
|
||||||
"2.3.0": "0.12.0"
|
"2.3.0": "0.12.0",
|
||||||
|
"2.3.1": "0.12.0",
|
||||||
|
"2.3.2": "0.12.0"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user