From 6086047a8afa7c66c3060a9cba27d1e4dc7b7075 Mon Sep 17 00:00:00 2001 From: terwer Date: Sat, 2 Sep 2023 17:17:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20#130=20=E6=94=AF=E6=8C=81=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E5=88=B0CSDN-=E6=94=AF=E6=8C=81=E4=B8=93=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...22\344\273\266\345\274\200\345\217\221.md" | 4 +- src/adaptors/web/base/webUtils.ts | 59 ++++++++ src/adaptors/web/csdn/csdnConfig.ts | 8 +- src/adaptors/web/csdn/csdnUtils.spec.ts | 3 +- src/adaptors/web/csdn/csdnUtils.ts | 4 +- src/adaptors/web/csdn/csdnWebAdaptor.ts | 136 +++++++++++++----- .../publish/form/PublishCategories.vue | 2 +- .../set/publish/PlatformAddForm.vue | 2 +- .../singleplatform/SingleSettingIndex.vue | 3 +- .../singleplatform/web/CsdnSetting.vue | 54 +++++++ src/locales/en_US.ts | 5 + src/locales/zh_CN.ts | 9 +- 12 files changed, 241 insertions(+), 48 deletions(-) create mode 100644 src/adaptors/web/base/webUtils.ts create mode 100644 src/components/set/publish/singleplatform/web/CsdnSetting.vue diff --git "a/docs/\346\217\222\344\273\266\345\274\200\345\217\221.md" "b/docs/\346\217\222\344\273\266\345\274\200\345\217\221.md" index f49506f4..0e04747d 100644 --- "a/docs/\346\217\222\344\273\266\345\274\200\345\217\221.md" +++ "b/docs/\346\217\222\344\273\266\345\274\200\345\217\221.md" @@ -35,4 +35,6 @@ 适配器 src/adaptors/index.ts getCfg 配置 src/adaptors/index.ts getAdaptor YAML适配器(不一定有) - \ No newline at end of file +6. 开启配置页面 + 在 src/components/set/publish/singleplatform/SingleSettingIndex.vue 注册配置 + \ No newline at end of file diff --git a/src/adaptors/web/base/webUtils.ts b/src/adaptors/web/base/webUtils.ts new file mode 100644 index 00000000..1abceeb0 --- /dev/null +++ b/src/adaptors/web/base/webUtils.ts @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, Terwer . All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Terwer designates this + * particular file as subject to the "Classpath" exception as provided + * by Terwer in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com + * or visit www.terwer.space if you need additional information or have any + * questions. + */ + +/** + * Web工具类,包含用于操作Cookie的方法 + * + * @since 1.12.0 + */ +class WebUtils { + /** + * 从Cookie字符串中读取指定键的值 + * + * @param key - 要查找的Cookie键 + * @param cookieString - 包含Cookie的字符串 + * @returns 包含键对应值的Cookie值,如果未找到则返回空字符串 + */ + public static readCookie(key: string, cookieString: string): string { + // 将Cookie字符串分割成Cookie键值对数组 + const cookies = cookieString.split(";") + + // 遍历Cookie数组,查找指定键的Cookie + for (const cookie of cookies) { + const [cookieKey, cookieValue] = cookie.split("=") + + // 去除Cookie键的空白字符并比较是否与指定键相符 + if (cookieKey.trim() === key) { + // 返回解码后的Cookie值,如果解码失败则返回空字符串 + return decodeURIComponent(cookieValue) ?? "" + } + } + + // 如果未找到指定键的Cookie则返回空字符串 + return "" + } +} + +export default WebUtils diff --git a/src/adaptors/web/csdn/csdnConfig.ts b/src/adaptors/web/csdn/csdnConfig.ts index 30747e83..64e2bba8 100644 --- a/src/adaptors/web/csdn/csdnConfig.ts +++ b/src/adaptors/web/csdn/csdnConfig.ts @@ -31,14 +31,14 @@ import { CategoryTypeEnum, PageTypeEnum, PasswordType } from "zhi-blog-api" */ export class CsdnConfig extends CommonWebConfig { constructor(username: string, password: string, middlewareUrl?: string) { - super("https://blog.csdn.net", "https://blog.csdn.net/api", username, password, middlewareUrl) + super("https://blog.csdn.net", "https://bizapi.csdn.net", username, password, middlewareUrl) this.previewUrl = "[userid]/article/details/[postid]" - this.pageType = PageTypeEnum.Html - this.usernameEnabled = true + this.pageType = PageTypeEnum.Markdown + this.usernameEnabled = false this.passwordType = PasswordType.PasswordType_Cookie this.cateEnabled = false this.knowledgeSpaceEnabled = true - this.knowledgeSpaceTitle = "分类" + this.knowledgeSpaceTitle = "专栏" this.knowledgeSpaceType = CategoryTypeEnum.CategoryType_Single this.allowKnowledgeSpaceChange = false this.placeholder.knowledgeSpaceReadonlyModeTip = diff --git a/src/adaptors/web/csdn/csdnUtils.spec.ts b/src/adaptors/web/csdn/csdnUtils.spec.ts index 88a2aaed..ebb74d3e 100644 --- a/src/adaptors/web/csdn/csdnUtils.spec.ts +++ b/src/adaptors/web/csdn/csdnUtils.spec.ts @@ -37,9 +37,10 @@ describe("test csdnUtils", () => { const url = "https://bizapi.csdn.net/blog/phoenix/console/v1/column/list?type=all" const method = "GET" const accept = "*/*" + const contentType = "application/json" const xCaNonce = CsdnUtils.generateXCaNonce() - const xCaSignature = CsdnUtils.generateXCaSignature(url, method, accept, xCaNonce) + const xCaSignature = CsdnUtils.generateXCaSignature(url, method, accept, xCaNonce, contentType) console.log("x-ca-nonce:", xCaNonce) console.log("x-ca-signature:", xCaSignature) diff --git a/src/adaptors/web/csdn/csdnUtils.ts b/src/adaptors/web/csdn/csdnUtils.ts index 524cb4dc..c8bda3a0 100644 --- a/src/adaptors/web/csdn/csdnUtils.ts +++ b/src/adaptors/web/csdn/csdnUtils.ts @@ -62,7 +62,7 @@ class CsdnUtils { method: string, accept: string, uuid: string, - content_type?: string | null + content_type: string ): string { // https://github.com/brix/crypto-js/issues/189 // https://www.npmjs.com/package/crypto-js @@ -71,7 +71,7 @@ class CsdnUtils { let toEnc: string if (method === "GET") { const path = s.pathname + s.search - toEnc = `GET\n${accept}\n\n\n\nx-ca-key:${CsdnUtils.X_CA_KEY}\nx-ca-nonce:${uuid}\n${path}` + toEnc = `GET\n${accept}\n\n${content_type}\n\nx-ca-key:${CsdnUtils.X_CA_KEY}\nx-ca-nonce:${uuid}\n${path}` } else { const path = s.pathname toEnc = `POST\n${accept}\n\n${content_type}\n\nx-ca-key:${CsdnUtils.X_CA_KEY}\nx-ca-nonce:${uuid}\n${path}` diff --git a/src/adaptors/web/csdn/csdnWebAdaptor.ts b/src/adaptors/web/csdn/csdnWebAdaptor.ts index c350a1d2..6f6973e8 100644 --- a/src/adaptors/web/csdn/csdnWebAdaptor.ts +++ b/src/adaptors/web/csdn/csdnWebAdaptor.ts @@ -25,12 +25,9 @@ import { BaseWebApi } from "~/src/adaptors/web/base/baseWebApi.ts" import CsdnUtils from "~/src/adaptors/web/csdn/csdnUtils.ts" -import { CsdnConfig } from "~/src/adaptors/web/csdn/csdnConfig.ts" -import { PublisherAppInstance } from "~/src/publisherAppInstance.ts" -import { createAppLogger } from "~/src/utils/appLogger.ts" -import { CommonFetchClient } from "zhi-fetch-middleware" -import { isDev } from "~/src/utils/constants.ts" -import { UserBlog } from "zhi-blog-api" +import { CategoryInfo, UserBlog } from "zhi-blog-api" +import { StrUtil } from "zhi-common" +import WebUtils from "~/src/adaptors/web/base/webUtils.ts" /** * CSDN网页授权适配器 @@ -41,22 +38,18 @@ import { UserBlog } from "zhi-blog-api" * @since 0.9.0 */ class CsdnWebAdaptor extends BaseWebApi { - private readonly commonFetchClient: any - - /** - * 初始化知乎 API 适配器 - * - * @param appInstance 应用实例 - * @param cfg 配置项 - */ - constructor(appInstance: PublisherAppInstance, cfg: CsdnConfig) { - super(appInstance, cfg) - this.cfg = cfg - - const middlewareUrl = this.cfg.middlewareUrl ?? "https://api.terwer.space/api/middleware" - this.commonFetchClient = new CommonFetchClient(appInstance, "", middlewareUrl, isDev) - this.logger = createAppLogger("zhihu-web-adaptor") - } + // /** + // * 初始化CSDN API 适配器 + // * + // * @param appInstance 应用实例 + // * @param cfg 配置项 + // */ + // constructor(appInstance: PublisherAppInstance, cfg: CsdnConfig) { + // super(appInstance, cfg) + // this.cfg = cfg + // + // this.logger = createAppLogger("csdn-web-adaptor") + // } public async getMetaData(): Promise { const res = await this.csdnFetch("https://bizapi.csdn.net/blog-console-api/v1/user/info") @@ -78,10 +71,74 @@ class CsdnWebAdaptor extends BaseWebApi { public async getUsersBlogs(): Promise> { let result: UserBlog[] = [] + const res = await this.csdnFetch("https://bizapi.csdn.net/blog/phoenix/console/v1/column/list?type=all") + this.logger.debug("get csdn columns =>", res) + if (res?.code === 200) { + const columnList = res?.data?.list + const column = columnList?.column ?? [] + const payCcolumn = columnList?.pay_column ?? [] + + // 普通专栏 + column.forEach((item: any) => { + const userblog: UserBlog = new UserBlog() + userblog.blogid = item.id + userblog.blogName = item.edit_title + userblog.url = item.column_url + // userblog.imgUrl = item.img_url + result.push(userblog) + }) + + // 付费专栏 + payCcolumn.forEach((item: any) => { + const userblog: UserBlog = new UserBlog() + userblog.blogid = item.id + userblog.blogName = `[付费]${item.edit_title}` + userblog.url = item.column_url + // userblog.imgUrl = item.img_url + result.push(userblog) + }) + } + this.logger.debug("getUsersBlogs=>", result) return result } + public async getCategories(): Promise { + const cats = [] as CategoryInfo[] + + const res = await this.csdnFetch("https://bizapi.csdn.net/blog/phoenix/console/v1/column/list?type=all") + this.logger.debug("get csdn columns =>", res) + if (res?.code === 200) { + const columnList = res?.data?.list + const column = columnList?.column ?? [] + const payCcolumn = columnList?.pay_column ?? [] + + // 普通专栏 + column.forEach((item: any) => { + const cat = new CategoryInfo() + + cat.categoryId = item.id + cat.categoryName = item.edit_title + cat.description = item.column_url + cat.categoryDescription = item.desc + cats.push(cat) + }) + + // 付费专栏 + payCcolumn.forEach((item: any) => { + const cat = new CategoryInfo() + + cat.categoryId = item.id + cat.categoryName = `[付费]${item.edit_title}` + cat.description = item.column_url + cat.categoryDescription = item.desc + cats.push(cat) + }) + } + + return cats + } + // ================ // private methods // ================ @@ -90,41 +147,50 @@ class CsdnWebAdaptor extends BaseWebApi { headers: any = {}, params: any = undefined, method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" = "GET", - contentType?: string | null + contentType: string = "application/json" ) { // 设置请求头 const accept = "*/*" - const APPLICATION_JSON = "application/json" const xcakey = CsdnUtils.X_CA_KEY const xCaNonce = CsdnUtils.generateXCaNonce() const xCaSignature = CsdnUtils.generateXCaSignature(url, method, accept, xCaNonce, contentType) - const reqHeader = { - accept, - ...(contentType ? { "content-type": contentType } : {}), - "x-ca-key": xcakey, - "x-ca-nonce": xCaNonce, - "x-ca-signature": xCaSignature, - "x-ca-signature-headers": "x-ca-key,x-ca-nonce", + const reqHeaderMap = new Map() + reqHeaderMap.set("accept", accept) + reqHeaderMap.set("content-type", contentType) + reqHeaderMap.set("x-ca-key", xcakey) + reqHeaderMap.set("x-ca-nonce", xCaNonce) + reqHeaderMap.set("x-ca-signature", xCaSignature) + reqHeaderMap.set("x-ca-signature-headers", "x-ca-key,x-ca-nonce") + + const mergedHeaders = { + ...Object.fromEntries(reqHeaderMap), ...headers, - Cookie: this.cfg.password, } // 构建请求选项 const requestOptions: RequestInit = { method: method, - headers: reqHeader, + headers: mergedHeaders, body: params, - redirect: "follow", } // 发送请求并返回响应 - const res = await this.commonFetchClient.fetchCall(url, requestOptions) + this.logger.debug("csdn url =>", url) + this.logger.debug("csdn requestOptions =>", requestOptions) + const res = await this.webProxyFetch(url, [mergedHeaders], params, method, contentType) if (res?.code !== 200) { throw new Error(res?.body?.message) } return res } + + public async getPreviewUrl(postid: string): Promise { + const token = this.cfg.password + const userid = WebUtils.readCookie("UserName", token) + const previewUrl = this.cfg.previewUrl.replace(/\[userid\]/g, userid).replace(/\[postid\]/g, postid) + return StrUtil.pathJoin(this.cfg.home ?? "", previewUrl) + } } export { CsdnWebAdaptor } diff --git a/src/components/publish/form/PublishCategories.vue b/src/components/publish/form/PublishCategories.vue index 530da273..370bd55d 100644 --- a/src/components/publish/form/PublishCategories.vue +++ b/src/components/publish/form/PublishCategories.vue @@ -145,7 +145,7 @@ const fetchCate = async () => { />
-
+
推荐的分类: {{ rtag }} diff --git a/src/components/set/publish/PlatformAddForm.vue b/src/components/set/publish/PlatformAddForm.vue index 09762fc3..e57b09ab 100644 --- a/src/components/set/publish/PlatformAddForm.vue +++ b/src/components/set/publish/PlatformAddForm.vue @@ -271,7 +271,7 @@ initPage() - + diff --git a/src/components/set/publish/singleplatform/SingleSettingIndex.vue b/src/components/set/publish/singleplatform/SingleSettingIndex.vue index 0ece7b44..3ea79d5e 100644 --- a/src/components/set/publish/singleplatform/SingleSettingIndex.vue +++ b/src/components/set/publish/singleplatform/SingleSettingIndex.vue @@ -56,7 +56,8 @@ const subtype = getSubPlatformTypeByKey(apiType) - + + diff --git a/src/components/set/publish/singleplatform/web/CsdnSetting.vue b/src/components/set/publish/singleplatform/web/CsdnSetting.vue new file mode 100644 index 00000000..42550910 --- /dev/null +++ b/src/components/set/publish/singleplatform/web/CsdnSetting.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 5314d16c..fd7d4fad 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -429,6 +429,11 @@ export default { "setting.zhihu.apiUrl.tip": "Usually fixed, do not modify", "setting.zhihu.previewUrl.tip": "Zhihu platform article preview rules, usually: //[postid]", + "setting.csdn.home.tip": "CSDN blog homepage address, usually fixed", + "setting.csdn.password.tip": "CSDN cookies, do not modify", + "setting.csdn.apiUrl.tip": "Usually fixed, do not modify", + "setting.csdn.previewUrl.tip": "CSDN Platform Article Preview Rules", + "setting.picgo.refer.to": "For details, please refer to:", "setting.picgo.refer.to.online.doc": "Picgo configuration online documentation", "setting.picgo.picbed": "Picbed setting", diff --git a/src/locales/zh_CN.ts b/src/locales/zh_CN.ts index 8743d387..f564a5be 100644 --- a/src/locales/zh_CN.ts +++ b/src/locales/zh_CN.ts @@ -417,11 +417,16 @@ export default { "可前往 https://developer.atlassian.com/cloud/confluence/rest/v1/#api-wiki-rest-api-content-get 查看文档", "setting.conf.previewUrl.tip": "Confluence平台文章预览规则,通常是:/pages/[postid]", - "setting.zhihu.home.tip": "专栏首页地址,通常固定", + "setting.zhihu.home.tip": "知乎专栏首页地址,通常固定", "setting.zhihu.username.tip": "知乎用户名,必须设置正确,否则无法获取专栏", "setting.zhihu.password.tip": "知乎Cookie,请勿修改", "setting.zhihu.apiUrl.tip": "通常固定,请勿修改", - "setting.zhihu.previewUrl.tip": "知乎平台文章预览规则,通常是://[postid]", + "setting.zhihu.previewUrl.tip": "知乎平台文章预览规则,通常是:/[postid]", + + "setting.csdn.home.tip": "CSDN博客首页地址,通常固定", + "setting.csdn.password.tip": "CSDNCookie,请勿修改", + "setting.csdn.apiUrl.tip": "通常固定,请勿修改", + "setting.csdn.previewUrl.tip": "CSDN平台文章预览规则", "setting.picgo.refer.to": "详情请参考:", "setting.picgo.refer.to.online.doc": "PicGO配置在线文档",