2 Commits

Author SHA1 Message Date
anian 656ee0bcb4 新增控制台配置项;修改图片列表字段 2026-04-21 15:56:25 +08:00
anian 3e9d0b4441 新增微博图片下载与显示 2026-04-13 17:23:27 +08:00
11 changed files with 350 additions and 40 deletions
+8
View File
@@ -2,6 +2,14 @@
> 基于原作者v1.4.3代码修改 > 基于原作者v1.4.3代码修改
#### v1.4.3-4
1. 控制台新增并行处理数量、图片下载开关、图片下载位置、图片下载代理等配置
2. 修改图片列表字段为imageFiles(不再覆盖title
#### v1.4.3-3
1. title字段改为微博图片列表
2. 图片列表内文件将自动下载至/upload/image-host
#### v1.4.3-2 #### v1.4.3-2
1. 新增URL参数author用于查询作者 1. 新增URL参数author用于查询作者
2. 模板参数新增${authors}返回所有作者 2. 模板参数新增${authors}返回所有作者
+1 -1
View File
@@ -1 +1 @@
version=1.4.3-2 version=1.4.3-4
+30 -1
View File
@@ -87,7 +87,7 @@ importers:
specifier: ^2.21.0 specifier: ^2.21.0
version: 2.22.0(@tiptap/extension-collaboration@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(yjs@13.6.29))(@tiptap/extension-node-range@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.29(typescript@5.5.4)))(vue@3.5.29(typescript@5.5.4)) version: 2.22.0(@tiptap/extension-collaboration@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(yjs@13.6.29))(@tiptap/extension-node-range@3.20.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))(@tiptap/pm@3.20.0))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.29(typescript@5.5.4)))(vue@3.5.29(typescript@5.5.4))
'@kunkunyu/fridends-rss': '@kunkunyu/fridends-rss':
specifier: file:../packages/fridends-rss specifier: workspace:*
version: link:../packages/fridends-rss version: link:../packages/fridends-rss
'@tanstack/vue-query': '@tanstack/vue-query':
specifier: ^4.26.1 specifier: ^4.26.1
@@ -917,48 +917,56 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-arm64-musl@0.115.0': '@oxc-parser/binding-linux-arm64-musl@0.115.0':
resolution: {integrity: sha512-HjsZbJPH9mMd4swJRywVMsDZsJX0hyKb1iNHo5ijRl5yhtbO3lj7ImSrrL1oZ1VEg0te4iKmDGGz/6YPLd1G8w==} resolution: {integrity: sha512-HjsZbJPH9mMd4swJRywVMsDZsJX0hyKb1iNHo5ijRl5yhtbO3lj7ImSrrL1oZ1VEg0te4iKmDGGz/6YPLd1G8w==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@oxc-parser/binding-linux-ppc64-gnu@0.115.0': '@oxc-parser/binding-linux-ppc64-gnu@0.115.0':
resolution: {integrity: sha512-zhhePoBrd7kQx3oClX/W6NldsuCbuMqaN9rRsY+6/WoorAb4j490PG/FjqgAXscWp2uSW2WV9L+ksn0wHrvsrg==} resolution: {integrity: sha512-zhhePoBrd7kQx3oClX/W6NldsuCbuMqaN9rRsY+6/WoorAb4j490PG/FjqgAXscWp2uSW2WV9L+ksn0wHrvsrg==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-riscv64-gnu@0.115.0': '@oxc-parser/binding-linux-riscv64-gnu@0.115.0':
resolution: {integrity: sha512-t/IRojvUE9XrKu+/H1b8YINug+7Q6FLls5rsm2lxB5mnS8GN/eYAYrPgHkcg9/1SueRDSzGpDYu3lGWTObk1zw==} resolution: {integrity: sha512-t/IRojvUE9XrKu+/H1b8YINug+7Q6FLls5rsm2lxB5mnS8GN/eYAYrPgHkcg9/1SueRDSzGpDYu3lGWTObk1zw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-riscv64-musl@0.115.0': '@oxc-parser/binding-linux-riscv64-musl@0.115.0':
resolution: {integrity: sha512-79jBHSSh/YpQRAmvYoaCfpyToRbJ/HBrdB7hxK2ku2JMehjopTVo+xMJss/RV7/ZYqeezgjvKDQzapJbgcjVZA==} resolution: {integrity: sha512-79jBHSSh/YpQRAmvYoaCfpyToRbJ/HBrdB7hxK2ku2JMehjopTVo+xMJss/RV7/ZYqeezgjvKDQzapJbgcjVZA==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@oxc-parser/binding-linux-s390x-gnu@0.115.0': '@oxc-parser/binding-linux-s390x-gnu@0.115.0':
resolution: {integrity: sha512-nA1TpxkhNTIOMMyiSSsa7XIVJVoOU/SsVrHIz3gHvWweB5PHCQfO7w+Lb2EP0lBWokv7HtA/KbF7aLDoXzmuMw==} resolution: {integrity: sha512-nA1TpxkhNTIOMMyiSSsa7XIVJVoOU/SsVrHIz3gHvWweB5PHCQfO7w+Lb2EP0lBWokv7HtA/KbF7aLDoXzmuMw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-x64-gnu@0.115.0': '@oxc-parser/binding-linux-x64-gnu@0.115.0':
resolution: {integrity: sha512-9iVX789DoC3SaOOG+X6NcF/tVChgLp2vcHffzOC2/Z1JTPlz6bMG2ogvcW6/9s0BG2qvhNQImd+gbWYeQbOwVw==} resolution: {integrity: sha512-9iVX789DoC3SaOOG+X6NcF/tVChgLp2vcHffzOC2/Z1JTPlz6bMG2ogvcW6/9s0BG2qvhNQImd+gbWYeQbOwVw==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@oxc-parser/binding-linux-x64-musl@0.115.0': '@oxc-parser/binding-linux-x64-musl@0.115.0':
resolution: {integrity: sha512-RmQmk+mjCB0nMNfEYhaCxwofLo1Z95ebHw1AGvRiWGCd4zhCNOyskgCbMogIcQzSB3SuEKWgkssyaiQYVAA4hQ==} resolution: {integrity: sha512-RmQmk+mjCB0nMNfEYhaCxwofLo1Z95ebHw1AGvRiWGCd4zhCNOyskgCbMogIcQzSB3SuEKWgkssyaiQYVAA4hQ==}
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@oxc-parser/binding-openharmony-arm64@0.115.0': '@oxc-parser/binding-openharmony-arm64@0.115.0':
resolution: {integrity: sha512-viigraWWQhhDvX5aGq+wrQq58k00Xq3MHz/0R4AFMxGlZ8ogNonpEfNc73Q5Ly87Z6sU9BvxEdG0dnYTfVnmew==} resolution: {integrity: sha512-viigraWWQhhDvX5aGq+wrQq58k00Xq3MHz/0R4AFMxGlZ8ogNonpEfNc73Q5Ly87Z6sU9BvxEdG0dnYTfVnmew==}
@@ -1055,66 +1063,79 @@ packages:
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.59.0': '@rollup/rollup-linux-arm-musleabihf@4.59.0':
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.59.0': '@rollup/rollup-linux-arm64-gnu@4.59.0':
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.59.0': '@rollup/rollup-linux-arm64-musl@4.59.0':
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.59.0': '@rollup/rollup-linux-loong64-gnu@4.59.0':
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.59.0': '@rollup/rollup-linux-loong64-musl@4.59.0':
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.59.0': '@rollup/rollup-linux-ppc64-gnu@4.59.0':
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.59.0': '@rollup/rollup-linux-ppc64-musl@4.59.0':
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.59.0': '@rollup/rollup-linux-riscv64-gnu@4.59.0':
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.59.0': '@rollup/rollup-linux-riscv64-musl@4.59.0':
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.59.0': '@rollup/rollup-linux-s390x-gnu@4.59.0':
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.59.0': '@rollup/rollup-linux-x64-gnu@4.59.0':
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.59.0': '@rollup/rollup-linux-x64-musl@4.59.0':
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.59.0': '@rollup/rollup-openbsd-x64@4.59.0':
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
@@ -1180,41 +1201,49 @@ packages:
resolution: {integrity: sha512-tzLHo5upqlDWK3wSTit0m0iZ8N6pm6S42R/sfeOcPwERcTjhTrbQ6GOEbmwsay845EgzJbGWwaOzVeGLT55YCw==} resolution: {integrity: sha512-tzLHo5upqlDWK3wSTit0m0iZ8N6pm6S42R/sfeOcPwERcTjhTrbQ6GOEbmwsay845EgzJbGWwaOzVeGLT55YCw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rspack/binding-linux-arm64-gnu@1.7.7': '@rspack/binding-linux-arm64-gnu@1.7.7':
resolution: {integrity: sha512-uSq4qkvmAzSDUTKE2v4yUgHIBdTily1k3BcK5wBCGFm9OPODj5lQZpAdOHHIwu+Jxyjoa7Mb64tghhj9hZcXcA==} resolution: {integrity: sha512-uSq4qkvmAzSDUTKE2v4yUgHIBdTily1k3BcK5wBCGFm9OPODj5lQZpAdOHHIwu+Jxyjoa7Mb64tghhj9hZcXcA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rspack/binding-linux-arm64-musl@1.4.0-beta.0': '@rspack/binding-linux-arm64-musl@1.4.0-beta.0':
resolution: {integrity: sha512-g3YZCNB+oR8+CG83iOoACZrxiM9sKlB33QmJB2PFk5TTryhkNGEq3vwiyqe7AVPWYfuCCqoRdzPNzWBIN80cEg==} resolution: {integrity: sha512-g3YZCNB+oR8+CG83iOoACZrxiM9sKlB33QmJB2PFk5TTryhkNGEq3vwiyqe7AVPWYfuCCqoRdzPNzWBIN80cEg==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rspack/binding-linux-arm64-musl@1.7.7': '@rspack/binding-linux-arm64-musl@1.7.7':
resolution: {integrity: sha512-NhWCBfiu6plpmLRP6c6D5lBUaVrBr1nvjSEc7VyQF8TGh8URo2btH0wngEiX0nWvidsSlERt1l6Y5QPGuiCl1g==} resolution: {integrity: sha512-NhWCBfiu6plpmLRP6c6D5lBUaVrBr1nvjSEc7VyQF8TGh8URo2btH0wngEiX0nWvidsSlERt1l6Y5QPGuiCl1g==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rspack/binding-linux-x64-gnu@1.4.0-beta.0': '@rspack/binding-linux-x64-gnu@1.4.0-beta.0':
resolution: {integrity: sha512-TNIm9APDmcbbrwWgSxaIbq73r0cKrzyS0Ei7rB0TyX9EmFYSfsCdmdJMwG2yKP3p+egRIDMWU9AIrxL4HIMrBg==} resolution: {integrity: sha512-TNIm9APDmcbbrwWgSxaIbq73r0cKrzyS0Ei7rB0TyX9EmFYSfsCdmdJMwG2yKP3p+egRIDMWU9AIrxL4HIMrBg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rspack/binding-linux-x64-gnu@1.7.7': '@rspack/binding-linux-x64-gnu@1.7.7':
resolution: {integrity: sha512-aRvf8gCI7jDeEN9i4u9fY5coa3ZAyHzGVA4ZhTJCgZ5wWA5A9SQewMSq7khS1WAAFE1USlk1tUuPujnrGoYrGg==} resolution: {integrity: sha512-aRvf8gCI7jDeEN9i4u9fY5coa3ZAyHzGVA4ZhTJCgZ5wWA5A9SQewMSq7khS1WAAFE1USlk1tUuPujnrGoYrGg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rspack/binding-linux-x64-musl@1.4.0-beta.0': '@rspack/binding-linux-x64-musl@1.4.0-beta.0':
resolution: {integrity: sha512-PCfGShh6y0CqUX8XxuxkEKOBLELuxDZ/sacM047CBIet3CgvqmT0Ff2DKXFIu8Q+NWrKnzvopO7hPv4Zelku6A==} resolution: {integrity: sha512-PCfGShh6y0CqUX8XxuxkEKOBLELuxDZ/sacM047CBIet3CgvqmT0Ff2DKXFIu8Q+NWrKnzvopO7hPv4Zelku6A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rspack/binding-linux-x64-musl@1.7.7': '@rspack/binding-linux-x64-musl@1.7.7':
resolution: {integrity: sha512-ALPto4OT7snzXbYDyqkLfh1BvwDTTH1hPYXGUXBzQ0wEV7sXeyvxCC4yjH6B5MhR7W3tFuF4IfDy5Z4BxmOoGQ==} resolution: {integrity: sha512-ALPto4OT7snzXbYDyqkLfh1BvwDTTH1hPYXGUXBzQ0wEV7sXeyvxCC4yjH6B5MhR7W3tFuF4IfDy5Z4BxmOoGQ==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rspack/binding-wasm32-wasi@1.7.7': '@rspack/binding-wasm32-wasi@1.7.7':
resolution: {integrity: sha512-7DZvUp0v75n451qfZw1ppbPakL6NAc2gjb5e9AJcOb7KUMBHNyOxqpPo/jRYKxH7isPpLfpoId79WQGGNTTMAw==} resolution: {integrity: sha512-7DZvUp0v75n451qfZw1ppbPakL6NAc2gjb5e9AJcOb7KUMBHNyOxqpPo/jRYKxH7isPpLfpoId79WQGGNTTMAw==}
@@ -9,12 +9,24 @@ import java.util.List;
public class RssFeedSyncEvent extends ApplicationEvent { public class RssFeedSyncEvent extends ApplicationEvent {
private final Link link; private final Link link;
private final int sum; private final int sum;
private final int parallelCount;
private final boolean enableWeiboImageRecordAndDownload;
private final List<String> disableSyncList; private final List<String> disableSyncList;
private final String imageSavePath;
private final String imageBaseUrl;
private final String imageReferer;
public RssFeedSyncEvent(Object source, Link link, int sum, List<String> disableSyncList) { public RssFeedSyncEvent(Object source, Link link, int sum, int parallelCount,
boolean enableWeiboImageRecordAndDownload, List<String> disableSyncList,
String imageSavePath, String imageBaseUrl, String imageReferer) {
super(source); super(source);
this.link = link; this.link = link;
this.sum = sum; this.sum = sum;
this.parallelCount = parallelCount;
this.enableWeiboImageRecordAndDownload = enableWeiboImageRecordAndDownload;
this.disableSyncList = disableSyncList; this.disableSyncList = disableSyncList;
this.imageSavePath = imageSavePath;
this.imageBaseUrl = imageBaseUrl;
this.imageReferer = imageReferer;
} }
} }
@@ -33,6 +33,11 @@ public class CronFriendPost extends AbstractExtension {
private String cron; private String cron;
private String timezone; private String timezone;
private boolean suspend; private boolean suspend;
private boolean enableWeiboImageRecordAndDownload;
private int parallelCount;
private String imageSavePath;
private String imageBaseUrl;
private String imageReferer;
@Schema( @Schema(
minimum = "0" minimum = "0"
@@ -38,6 +38,9 @@ public class FriendPost extends AbstractExtension {
private Instant pubDate; private Instant pubDate;
private String linkName; private String linkName;
// Comma-separated image file names extracted from content.
private String imageFiles;
} }
} }
@@ -7,7 +7,6 @@ import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput; import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader; import com.rometools.rome.io.XmlReader;
import la.moony.friends.RssFeedSyncEvent; import la.moony.friends.RssFeedSyncEvent;
import la.moony.friends.extension.CronFriendPost;
import la.moony.friends.extension.FriendPost; import la.moony.friends.extension.FriendPost;
import la.moony.friends.extension.Link; import la.moony.friends.extension.Link;
import la.moony.friends.extension.RssFeedSyncLog; import la.moony.friends.extension.RssFeedSyncLog;
@@ -27,16 +26,32 @@ import run.halo.app.extension.ListResult;
import run.halo.app.extension.Metadata; import run.halo.app.extension.Metadata;
import run.halo.app.extension.PageRequestImpl; import run.halo.app.extension.PageRequestImpl;
import run.halo.app.extension.Unstructured; import run.halo.app.extension.Unstructured;
import run.halo.app.extension.controller.*; import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController;
import run.halo.app.extension.controller.DefaultQueue;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.RequestQueue;
import run.halo.app.extension.router.selector.FieldSelector; import run.halo.app.extension.router.selector.FieldSelector;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Semaphore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.springframework.data.domain.Sort.Order.asc; import static org.springframework.data.domain.Sort.Order.asc;
import static run.halo.app.extension.MetadataUtil.nullSafeAnnotations; import static run.halo.app.extension.MetadataUtil.nullSafeAnnotations;
@@ -45,17 +60,33 @@ import static run.halo.app.extension.index.query.QueryFactory.equal;
import static run.halo.app.extension.index.query.QueryFactory.isNull; import static run.halo.app.extension.index.query.QueryFactory.isNull;
@Component @Component
public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,SmartLifecycle { public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>, SmartLifecycle {
private static final Logger log = LoggerFactory.getLogger(RssSyncReconciler.class); private static final Logger log = LoggerFactory.getLogger(RssSyncReconciler.class);
// 新增:正则匹配所有img标签的src属性
private static final Pattern IMG_SRC_PATTERN = Pattern.compile(
"<img[^>]*?src\\s*=\\s*(['\"])(.*?)\\1[^>]*?>",
Pattern.CASE_INSENSITIVE
);
private static final Pattern MW2000_IMAGE_FILE_PATTERN = Pattern.compile(
".*/mw2000/([^/?#]+)(?:[?#].*)?$",
Pattern.CASE_INSENSITIVE
);
private static final String DEFAULT_IMAGE_BASE_URL = "https://wx3.sinaimg.cn/middle/";
private static final String DEFAULT_IMAGE_REFERER = "https://weibo.com/";
private static final String DEFAULT_IMAGE_HOST_PATH = "/root/.halo2/attachments/upload/image-host/";
private volatile boolean running = false; private volatile boolean running = false;
private final ExtensionClient client; private final ExtensionClient client;
private final RequestQueue<Request> queue; private final RequestQueue<Request> queue;
private final Controller controller; private final Controller controller;
private final ObjectMapper objectMapper = Unstructured.OBJECT_MAPPER; private final ObjectMapper objectMapper = Unstructured.OBJECT_MAPPER;
private volatile int configuredParallelCount = 1;
private volatile Semaphore syncSemaphore = new Semaphore(1, true);
public RssSyncReconciler(ExtensionClient client) { public RssSyncReconciler(ExtensionClient client) {
this.client = client; this.client = client;
@@ -65,9 +96,22 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
@Override @Override
public Result reconcile(Request request) { public Result reconcile(Request request) {
int parallelCount = normalizeParallelCount(request.parallelCount());
refreshSyncSemaphore(parallelCount);
Semaphore semaphore = this.syncSemaphore;
boolean acquired = false;
try {
semaphore.acquire();
acquired = true;
Link link = request.link(); Link link = request.link();
int sum = request.sum(); int sum = request.sum();
List<String> disableSyncList = request.disableSyncList(); List<String> disableSyncList = request.disableSyncList();
boolean enableWeiboImageRecordAndDownload =
request.enableWeiboImageRecordAndDownload();
String imageSavePath = request.imageSavePath();
String imageBaseUrl = request.imageBaseUrl();
String imageReferer = request.imageReferer();
var linkName = link.getMetadata().getName(); var linkName = link.getMetadata().getName();
String rssUrl = getRss(link); String rssUrl = getRss(link);
boolean isContains = false; boolean isContains = false;
@@ -81,13 +125,14 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
newRssFeedSyncLog.setMetadata(metadata); newRssFeedSyncLog.setMetadata(metadata);
newRssFeedSyncLog.setLinkName(linkName); newRssFeedSyncLog.setLinkName(linkName);
if (StringUtils.isNotEmpty(rssUrl) && !isContains) { if (StringUtils.isNotEmpty(rssUrl) && !isContains) {
tryToSynchronizeFriendPost(newRssFeedSyncLog, link, sum); tryToSynchronizeFriendPost(newRssFeedSyncLog, link, sum, imageSavePath,
}else if (StringUtils.isEmpty(rssUrl)) { imageBaseUrl, imageReferer, enableWeiboImageRecordAndDownload);
} else if (StringUtils.isEmpty(rssUrl)) {
newRssFeedSyncLog.setSyncTime(Instant.now()); newRssFeedSyncLog.setSyncTime(Instant.now());
newRssFeedSyncLog.setState(RssFeedSyncLog.RssFeedSyncLogState.nolink); newRssFeedSyncLog.setState(RssFeedSyncLog.RssFeedSyncLogState.nolink);
newRssFeedSyncLog.setFailureReason("No RSS link"); newRssFeedSyncLog.setFailureReason("No RSS link");
newRssFeedSyncLog.setFailureMessage("无 RSS 链接."); newRssFeedSyncLog.setFailureMessage("无 RSS 链接.");
}else if (isContains) { } else if (isContains) {
newRssFeedSyncLog.setSyncTime(Instant.now()); newRssFeedSyncLog.setSyncTime(Instant.now());
newRssFeedSyncLog.setState(RssFeedSyncLog.RssFeedSyncLogState.failed); newRssFeedSyncLog.setState(RssFeedSyncLog.RssFeedSyncLogState.failed);
newRssFeedSyncLog.setFailureReason("admin disables sync"); newRssFeedSyncLog.setFailureReason("admin disables sync");
@@ -101,10 +146,31 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
rssFeedSyncLog.setFailureReason(newRssFeedSyncLog.getFailureReason()); rssFeedSyncLog.setFailureReason(newRssFeedSyncLog.getFailureReason());
rssFeedSyncLog.setFailureMessage(newRssFeedSyncLog.getFailureMessage()); rssFeedSyncLog.setFailureMessage(newRssFeedSyncLog.getFailureMessage());
client.update(rssFeedSyncLog); client.update(rssFeedSyncLog);
}else { } else {
client.create(newRssFeedSyncLog); client.create(newRssFeedSyncLog);
} }
return Result.doNotRetry(); return Result.doNotRetry();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Interrupted while waiting for sync permit.", e);
return Result.doNotRetry();
} finally {
if (acquired) {
semaphore.release();
}
}
}
private synchronized void refreshSyncSemaphore(int parallelCount) {
if (parallelCount == configuredParallelCount) {
return;
}
configuredParallelCount = parallelCount;
syncSemaphore = new Semaphore(parallelCount, true);
}
private int normalizeParallelCount(int parallelCount) {
return parallelCount <= 0 ? 1 : parallelCount;
} }
public String getRss(Link link) { public String getRss(Link link) {
@@ -121,10 +187,10 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
linkRss = rssUri; linkRss = rssUri;
annotations.remove("rss_uri"); annotations.remove("rss_uri");
annotations.put("rss_url", rssUri); annotations.put("rss_url", rssUri);
}else { } else {
annotations.put("is_request","true"); annotations.put("is_request", "true");
if (StringUtils.isNotEmpty(linkRss)) { if (StringUtils.isNotEmpty(linkRss)) {
annotations.put("rss_url",linkRss); annotations.put("rss_url", linkRss);
} }
} }
updateLink(link); updateLink(link);
@@ -133,22 +199,18 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
return rssUrl; return rssUrl;
} }
public void updateLink(Link link) { public void updateLink(Link link) {
Map extensionMap = objectMapper.convertValue(link, Map.class); Map extensionMap = objectMapper.convertValue(link, Map.class);
var extension = new Unstructured(extensionMap); var extension = new Unstructured(extensionMap);
client.update(extension); client.update(extension);
} }
private void tryToSynchronizeFriendPost(RssFeedSyncLog syncLog, Link link, int sum) { private void tryToSynchronizeFriendPost(RssFeedSyncLog syncLog, Link link, int sum,
String imageSavePath, String imageBaseUrl, String imageReferer,
boolean enableWeiboImageRecordAndDownload) {
var linkName = link.getMetadata().getName(); var linkName = link.getMetadata().getName();
var annotations = nullSafeAnnotations(link); var annotations = nullSafeAnnotations(link);
var rssUrl = annotations.get("rss_url"); var rssUrl = annotations.get("rss_url");
CronFriendPost cron = new CronFriendPost();
var spec = new CronFriendPost.CronSpec();
spec.setSuccessfulRetainLimit(5);
cron.setSpec(spec);
syncLog.setSyncTime(Instant.now()); syncLog.setSyncTime(Instant.now());
List<FriendPost> friendPostList = fetchFriendPost(rssUrl, sum, syncLog); List<FriendPost> friendPostList = fetchFriendPost(rssUrl, sum, syncLog);
@@ -158,12 +220,12 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
var friendPostListOptions = new ListOptions(); var friendPostListOptions = new ListOptions();
friendPostListOptions.setFieldSelector(FieldSelector.of( friendPostListOptions.setFieldSelector(FieldSelector.of(
and(isNull("metadata.deletionTimestamp"), and(isNull("metadata.deletionTimestamp"),
equal("spec.postLink",rssPost.getSpec().getPostLink()) equal("spec.postLink", rssPost.getSpec().getPostLink())
) )
)); ));
long total = client.listBy(FriendPost.class, friendPostListOptions, long total = client.listBy(FriendPost.class, friendPostListOptions,
PageRequestImpl.ofSize(1)).getTotal(); PageRequestImpl.ofSize(1)).getTotal();
if (total==0) { if (total == 0) {
FriendPost friendPost = new FriendPost(); FriendPost friendPost = new FriendPost();
// 设置元数据才能保存 // 设置元数据才能保存
friendPost.setMetadata(new Metadata()); friendPost.setMetadata(new Metadata());
@@ -175,23 +237,43 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
friendPostSpec.setAuthorUrl(link.getSpec().getUrl()); friendPostSpec.setAuthorUrl(link.getSpec().getUrl());
friendPostSpec.setLinkName(linkName); friendPostSpec.setLinkName(linkName);
String description = friendPost.getSpec().getDescription(); String description = friendPost.getSpec().getDescription();
//解析html内容转换成文本
if (StringUtils.isNotEmpty(description)){ // 先提取图片文件名覆盖标题,再将 HTML 描述转换为纯文本
description = CommonUtils.parseAndTruncateHtml2Text(description, 100000); if (StringUtils.isNotEmpty(description)) {
List<String> imageFileNames = new ArrayList<>();
Matcher matcher = IMG_SRC_PATTERN.matcher(description);
while (matcher.find()) {
String imgSrc = matcher.group(2);
Matcher mw2000Matcher = MW2000_IMAGE_FILE_PATTERN.matcher(imgSrc);
if (mw2000Matcher.matches()) {
imageFileNames.add(mw2000Matcher.group(1));
}
}
if (enableWeiboImageRecordAndDownload && !imageFileNames.isEmpty()) {
String imageFiles = String.join(",", imageFileNames);
friendPostSpec.setImageFiles(imageFiles);
// 通知图片处理服务
notifyImageService(imageFiles, imageSavePath, imageBaseUrl,
imageReferer);
}
String pureText = CommonUtils.parseAndTruncateHtml2Text(description, 100000);
String regexp = "[ *|\\s*]*"; String regexp = "[ *|\\s*]*";
description = description.replaceFirst(regexp, "").trim(); pureText = pureText.replaceFirst(regexp, "").trim();
String weiboVideoRegex = "\\s[^\\s]*的微博视频 视频无法显示,请前往微博视频观看。"; String weiboVideoRegex = "\\s[^\\s]*的微博视频 视频无法显示,请前往微博视频观看。";
description = description.replaceAll(weiboVideoRegex, ""); pureText = pureText.replaceAll(weiboVideoRegex, "");
friendPostSpec.setDescription(description); friendPostSpec.setDescription(pureText);
} }
client.create(friendPost); client.create(friendPost);
} }
} }
delFriendPost(linkName,sum); delFriendPost(linkName, sum);
} }
} }
public List<FriendPost> fetchFriendPost(String rssAddress,int postsLimit, RssFeedSyncLog sync) { public List<FriendPost> fetchFriendPost(String rssAddress, int postsLimit, RssFeedSyncLog sync) {
List<FriendPost> friendPostList = new ArrayList<>(); List<FriendPost> friendPostList = new ArrayList<>();
try { try {
SyndFeed feed = new SyndFeedInput().build(new XmlReader(new URL(rssAddress))); SyndFeed feed = new SyndFeedInput().build(new XmlReader(new URL(rssAddress)));
@@ -205,7 +287,7 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
SyndContent content = entry.getDescription(); SyndContent content = entry.getDescription();
if (content != null) { if (content != null) {
description = content.getValue(); description = content.getValue();
}else { } else {
List<SyndContent> contents = entry.getContents(); List<SyndContent> contents = entry.getContents();
if (!contents.isEmpty()) { if (!contents.isEmpty()) {
description = contents.get(0).getValue(); description = contents.get(0).getValue();
@@ -235,10 +317,10 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
return friendPostList; return friendPostList;
} }
public void delFriendPost(String linkName,Integer sum) { public void delFriendPost(String linkName, Integer sum) {
var friendPostListOptions = new ListOptions(); var friendPostListOptions = new ListOptions();
friendPostListOptions.setFieldSelector(FieldSelector.of( friendPostListOptions.setFieldSelector(FieldSelector.of(
equal("spec.linkName",linkName) equal("spec.linkName", linkName)
)); ));
long total = client.listBy(FriendPost.class, friendPostListOptions, PageRequestImpl.ofSize(1)).getTotal(); long total = client.listBy(FriendPost.class, friendPostListOptions, PageRequestImpl.ofSize(1)).getTotal();
if (total > sum) { if (total > sum) {
@@ -253,6 +335,84 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
} }
} }
/**
* 通知图片处理服务有新的图片需要处理
* @param imageFiles 图片文件名,英文逗号分隔,如 "file1,file2,..."
*/
private void notifyImageService(String imageFiles, String imageSavePath, String imageBaseUrl,
String imageReferer) {
if (StringUtils.isBlank(imageFiles)) {
return;
}
new Thread(() -> downloadImagesByFiles(imageFiles, imageSavePath, imageBaseUrl,
imageReferer)).start();
}
private void downloadImagesByFiles(String imageFiles, String imageSavePath,
String imageBaseUrl, String imageReferer) {
if (StringUtils.isBlank(imageFiles)) {
return;
}
try {
Path imageHostDir = resolveImageHostDir(imageSavePath);
String resolvedBaseUrl = resolveImageBaseUrl(imageBaseUrl);
String resolvedReferer = resolveImageReferer(imageBaseUrl, imageReferer);
Files.createDirectories(imageHostDir);
String[] fileNames = imageFiles.split(",");
for (String fileName : fileNames) {
String trimmedName = fileName.trim();
if (StringUtils.isEmpty(trimmedName)) {
continue;
}
String encodedFileName = URLEncoder.encode(trimmedName, StandardCharsets.UTF_8)
.replace("+", "%20");
String downloadUrl = resolvedBaseUrl + encodedFileName;
Path targetPath = imageHostDir.resolve(trimmedName);
HttpURLConnection connection = (HttpURLConnection) new URL(downloadUrl)
.openConnection();
if (StringUtils.isNotBlank(resolvedReferer)) {
connection.setRequestProperty("Referer", resolvedReferer);
}
try (InputStream inputStream = connection.getInputStream()) {
Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
} finally {
connection.disconnect();
}
}
} catch (Exception e) {
log.warn("Failed to download images for files: {}", imageFiles, e);
}
}
private Path resolveImageHostDir(String imageSavePath) {
if (StringUtils.isBlank(imageSavePath)) {
return Path.of(DEFAULT_IMAGE_HOST_PATH);
}
return Path.of(imageSavePath.trim());
}
private String resolveImageBaseUrl(String imageBaseUrl) {
String resolvedBaseUrl = StringUtils.isBlank(imageBaseUrl)
? DEFAULT_IMAGE_BASE_URL
: imageBaseUrl.trim();
if (!resolvedBaseUrl.endsWith("/")) {
return resolvedBaseUrl + "/";
}
return resolvedBaseUrl;
}
private String resolveImageReferer(String imageBaseUrl, String imageReferer) {
if (StringUtils.isBlank(imageBaseUrl)) {
return DEFAULT_IMAGE_REFERER;
}
if (StringUtils.isBlank(imageReferer)) {
return null;
}
return imageReferer.trim();
}
static int pageNullSafe(Integer page) { static int pageNullSafe(Integer page) {
return ObjectUtils.defaultIfNull(page, 1); return ObjectUtils.defaultIfNull(page, 1);
} }
@@ -261,7 +421,6 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
return ObjectUtils.defaultIfNull(size, 10); return ObjectUtils.defaultIfNull(size, 10);
} }
@Override @Override
public Controller setupWith(ControllerBuilder builder) { public Controller setupWith(ControllerBuilder builder) {
return new DefaultController<>( return new DefaultController<>(
@@ -293,10 +452,16 @@ public class RssSyncReconciler implements Reconciler<RssSyncReconciler.Request>,
@EventListener(RssFeedSyncEvent.class) @EventListener(RssFeedSyncEvent.class)
public void onReplyEvent(RssFeedSyncEvent event) { public void onReplyEvent(RssFeedSyncEvent event) {
var request = new Request(event.getLink(),event.getSum(),event.getDisableSyncList()); var request = new Request(event.getLink(), event.getSum(), event.getDisableSyncList(),
event.isEnableWeiboImageRecordAndDownload(),
event.getImageSavePath(), event.getImageBaseUrl(), event.getImageReferer(),
event.getParallelCount());
queue.addImmediately(request); queue.addImmediately(request);
} }
public record Request(Link link,int sum,List<String> disableSyncList) { public record Request(Link link, int sum, List<String> disableSyncList,
boolean enableWeiboImageRecordAndDownload,
String imageSavePath, String imageBaseUrl,
String imageReferer, int parallelCount) {
} }
} }
@@ -74,14 +74,25 @@ public class RssFeedSyncLogServiceImpl implements RssFeedSyncLogService {
CronFriendPost cron = new CronFriendPost(); CronFriendPost cron = new CronFriendPost();
CronFriendPost.CronSpec cronSpec = new CronFriendPost.CronSpec(); CronFriendPost.CronSpec cronSpec = new CronFriendPost.CronSpec();
cronSpec.setSuccessfulRetainLimit(5); cronSpec.setSuccessfulRetainLimit(5);
cronSpec.setParallelCount(1);
cronSpec.setEnableWeiboImageRecordAndDownload(false);
cronSpec.setDisableSyncList(new ArrayList<>()); cronSpec.setDisableSyncList(new ArrayList<>());
cronSpec.setImageSavePath("");
cronSpec.setImageBaseUrl("");
cronSpec.setImageReferer("");
cron.setSpec(cronSpec); cron.setSpec(cronSpec);
return client.fetch(CronFriendPost.class, "cron-friends-default") return client.fetch(CronFriendPost.class, "cron-friends-default")
.defaultIfEmpty(cron) .defaultIfEmpty(cron)
.flatMap(cronFriendPost -> { .flatMap(cronFriendPost -> {
var spec = cronFriendPost.getSpec(); var spec = cronFriendPost.getSpec();
int successfulRetainLimit = spec.getSuccessfulRetainLimit(); int successfulRetainLimit = spec.getSuccessfulRetainLimit();
int parallelCount = spec.getParallelCount();
boolean enableWeiboImageRecordAndDownload =
spec.isEnableWeiboImageRecordAndDownload();
List<String> disableSyncList = spec.getDisableSyncList(); List<String> disableSyncList = spec.getDisableSyncList();
String imageSavePath = spec.getImageSavePath();
String imageBaseUrl = spec.getImageBaseUrl();
String imageReferer = spec.getImageReferer();
int sum = successfulRetainLimit == 0 ? 5 : successfulRetainLimit; int sum = successfulRetainLimit == 0 ? 5 : successfulRetainLimit;
var listOptions = new ListOptions(); var listOptions = new ListOptions();
FieldSelector fieldSelector = FieldSelector.of(isNull("metadata.deletionTimestamp")); FieldSelector fieldSelector = FieldSelector.of(isNull("metadata.deletionTimestamp"));
@@ -90,7 +101,11 @@ public class RssFeedSyncLogServiceImpl implements RssFeedSyncLogService {
} }
listOptions.setFieldSelector(fieldSelector); listOptions.setFieldSelector(fieldSelector);
return client.listAll(Link.class, listOptions, Sort.by("metadata.creationTimestamp")) return client.listAll(Link.class, listOptions, Sort.by("metadata.creationTimestamp"))
.doOnNext(link -> eventPublisher.publishEvent(new RssFeedSyncEvent(this, link,sum,disableSyncList))) .doOnNext(link -> eventPublisher.publishEvent(
new RssFeedSyncEvent(this, link, sum, parallelCount,
enableWeiboImageRecordAndDownload, disableSyncList, imageSavePath,
imageBaseUrl, imageReferer)
))
.then(); .then();
}); });
} }
+30
View File
@@ -32,6 +32,36 @@ export interface CronSpec {
* @memberof CronSpec * @memberof CronSpec
*/ */
'disableSyncList'?: Array<string>; 'disableSyncList'?: Array<string>;
/**
*
* @type {number}
* @memberof CronSpec
*/
'parallelCount'?: number;
/**
*
* @type {boolean}
* @memberof CronSpec
*/
'enableWeiboImageRecordAndDownload'?: boolean;
/**
*
* @type {string}
* @memberof CronSpec
*/
'imageSavePath'?: string;
/**
*
* @type {string}
* @memberof CronSpec
*/
'imageBaseUrl'?: string;
/**
*
* @type {string}
* @memberof CronSpec
*/
'imageReferer'?: string;
/** /**
* *
* @type {number} * @type {number}
@@ -38,6 +38,12 @@ export interface FriendPostSpec {
* @memberof FriendPostSpec * @memberof FriendPostSpec
*/ */
'description'?: string; 'description'?: string;
/**
* Comma-separated image file names.
* @type {string}
* @memberof FriendPostSpec
*/
'imageFiles'?: string;
/** /**
* *
* @type {string} * @type {string}
+37
View File
@@ -18,7 +18,12 @@ const initialFormState: CronFriendPost = {
cron: "@daily", cron: "@daily",
timezone:"Asia/Shanghai", timezone:"Asia/Shanghai",
suspend: false, suspend: false,
enableWeiboImageRecordAndDownload: false,
parallelCount: 1,
successfulRetainLimit: 0, successfulRetainLimit: 0,
imageSavePath: "",
imageBaseUrl: "",
imageReferer: "",
disableSyncList: undefined, disableSyncList: undefined,
}, },
kind: "CronFriendPost", kind: "CronFriendPost",
@@ -130,6 +135,20 @@ const { mutate:save, isLoading:saveIsLoading } = useMutation({
}, },
]" ]"
/> />
<FormKit
type="number"
name="parallelCount"
label="并行请求数量"
number="integer"
validation="required|number|min:1"
help="默认 1(串行),大于 1 为并行"
/>
<FormKit
type="checkbox"
name="enableWeiboImageRecordAndDownload"
label="记录并下载微博图片"
help="默认关闭,开启后才会记录 imageFiles 字段并下载微博图片"
/>
<FormKit <FormKit
type="number" type="number"
name="successfulRetainLimit" name="successfulRetainLimit"
@@ -138,6 +157,24 @@ const { mutate:save, isLoading:saveIsLoading } = useMutation({
validation="required|number|min:0" validation="required|number|min:0"
help="设置之后会保留的数据条数,设置为 0 即为5条" help="设置之后会保留的数据条数,设置为 0 即为5条"
/> />
<FormKit
type="text"
name="imageSavePath"
label="图片保存路径"
help="为空时使用默认路径 /root/.halo2/attachments/upload/image-host/"
/>
<FormKit
type="text"
name="imageBaseUrl"
label="图片下载 Base URL"
help="为空时默认 https://wx3.sinaimg.cn/middle/"
/>
<FormKit
type="text"
name="imageReferer"
label="图片下载 Referer"
help="当 Base URL 为空时默认 https://weibo.com/;当 Base URL 不为空时可留空"
/>
<LinkFormKit <LinkFormKit
name="disableSyncList" name="disableSyncList"
label="禁止同步名单" label="禁止同步名单"