Skip to content

Commit

Permalink
feat: #130 支持发布到CSDN-完成授权验证
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed Sep 1, 2023
1 parent 5566040 commit 3f90230
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 15 deletions.
21 changes: 11 additions & 10 deletions src/adaptors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { useNotionApi } from "~/src/adaptors/api/notion/useNotionApi.ts"
import { useHexoApi } from "~/src/adaptors/api/hexo/useHexoApi.ts"
import { CommonBlogConfig } from "~/src/adaptors/api/base/commonBlogConfig.ts"
import { useGitlabhexoApi } from "~/src/adaptors/api/gitlab-hexo/useGitlabhexoApi.ts"
import { useCsdnWeb } from "~/src/adaptors/web/csdn/useCsdnWeb.ts"

/**
* 适配器统一入口
Expand Down Expand Up @@ -103,11 +104,11 @@ class Adaptors {
conf = cfg
break
}
// case SubPlatformType.Custom_CSDN: {
// const { cfg } = await useCsdnWeb(key)
// conf = cfg
// break
// }
case SubPlatformType.Custom_CSDN: {
const { cfg } = await useCsdnWeb(key)
conf = cfg
break
}
// case SubPlatformType.Custom_Jianshu: {
// const { cfg } = await useJianshuWeb(key)
// conf = cfg
Expand Down Expand Up @@ -193,11 +194,11 @@ class Adaptors {
blogAdaptor = webApi
break
}
// case SubPlatformType.Custom_CSDN: {
// const { webApi } = await useCsdnWeb(key, newCfg)
// blogAdaptor = webApi
// break
// }
case SubPlatformType.Custom_CSDN: {
const { webApi } = await useCsdnWeb(key, newCfg)
blogAdaptor = webApi
break
}
// case SubPlatformType.Custom_Jianshu: {
// const { webApi } = await useJianshuWeb(key, newCfg)
// blogAdaptor = webApi
Expand Down
2 changes: 1 addition & 1 deletion src/adaptors/web/base/baseWebApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ class BaseWebApi extends WebApi {
const header = headers.length > 0 ? headers[0] : {}
const webHeaders = [
{
Cookie: this.cfg.password,
...header,
Cookie: this.cfg.password,
},
]
return await this.proxyFetch(url, webHeaders, params, method, contentType)
Expand Down
46 changes: 46 additions & 0 deletions src/adaptors/web/csdn/csdnUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.
*/

import { describe, it } from "vitest"
import CsdnUtils from "~/src/adaptors/web/csdn/csdnUtils.ts"

describe("test csdnUtils", () => {
it("test generateXCaNonce", () => {
const result = CsdnUtils.generateXCaNonce()
console.log(result)
})

it("test generateXCaSignature", () => {
const url = "https://bizapi.csdn.net/blog-console-api/v1/user/info"
const method = "GET"
const accept = "*/*"

const xCaNonce = CsdnUtils.generateXCaNonce()
const xCaSignature = CsdnUtils.generateXCaSignature(url, method, accept, xCaNonce)

console.log("x-ca-nonce:", xCaNonce)
console.log("x-ca-signature:", xCaSignature)
})
})
87 changes: 87 additions & 0 deletions src/adaptors/web/csdn/csdnUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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.
*/

import Utf8 from "crypto-js/enc-utf8"
import CryptoJS from "crypto-js"
import Base64 from "crypto-js/enc-base64"

/**
* CSDN工具类,用于生成UUID和签名
*/
class CsdnUtils {
public static X_CA_KEY = "203803574"
public static APP_SECRET = "9znpamsyl2c7cdrr9sas0le9vbc3r6ba"

/**
* 生成UUID
*
* @returns 返回生成的UUID
*/
public static generateXCaNonce(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (e) {
const t = (16 * Math.random()) | 0,
n = "x" === e ? t : (3 & t) | 8
return n.toString(16)
})
}

/**
* 获取签名
*
* @param url - 请求URL
* @param method - HTTP方法
* @param accept - Accept头
* @param uuid - UUID
* @param content_type - Content-Type
* @returns 返回签名
*/
public static generateXCaSignature(
url: string,
method: string,
accept: string,
uuid: string,
content_type?: string | null
): string {
// https://github.com/brix/crypto-js/issues/189
// https://www.npmjs.com/package/crypto-js
const s = new URL(url)
const ekey = Utf8.parse(CsdnUtils.APP_SECRET)
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}`
} 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}`
}
const hmac = CryptoJS.HmacSHA256(toEnc, ekey)
const sign = Base64.stringify(hmac)
// console.log(uuid)
// console.log(sign)
return sign
}
}

export default CsdnUtils
75 changes: 71 additions & 4 deletions src/adaptors/web/csdn/csdnWebAdaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
*/

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 { AppInstance } from "~/src/appInstance.ts"
import { createAppLogger } from "~/src/utils/appLogger.ts"
import { CommonFetchClient } from "zhi-fetch-middleware"
import { isDev } from "~/src/utils/constants.ts"

/**
* CSDN网页授权适配器
Expand All @@ -34,22 +40,83 @@ import { BaseWebApi } from "~/src/adaptors/web/base/baseWebApi.ts"
* @since 0.9.0
*/
class CsdnWebAdaptor extends BaseWebApi {
private readonly commonFetchClient: any

/**
* 初始化知乎 API 适配器
*
* @param appInstance 应用实例
* @param cfg 配置项
*/
constructor(appInstance: AppInstance, 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")
}

public async getMetaData(): Promise<any> {
const res = await this.proxyFetch("https://bizapi.csdn.net/blog-console-api/v1/user/info")
const flag = !!res.data.csdnid
const res = await this.csdnFetch("https://bizapi.csdn.net/blog-console-api/v1/user/info")
const flag = !!res.data.username
this.logger.info(`get csdn metadata finished, flag => ${flag}`)
return {
flag: flag,
uid: res.data.csdnid,
uid: res.data.username,
title: res.data.username,
avatar: res.data.avatarurl,
avatar: res.data.avatar,
type: "csdn",
displayName: "CSDN",
supportTypes: ["markdown", "html"],
home: "https://mp.csdn.net/",
icon: "https://g.csdnimg.cn/static/logo/favicon32.ico",
}
}

// ================
// private methods
// ================
private async csdnFetch(
url: string,
headers: any = {},
params: any = undefined,
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" = "GET",
contentType?: string | null
) {
// 设置请求头
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",
...headers,
Cookie: this.cfg.password,
}

// 构建请求选项
const requestOptions: RequestInit = {
method: method,
headers: reqHeader,
body: params,
redirect: "follow",
}

// 发送请求并返回响应
const res = await this.commonFetchClient.fetchCall(url, requestOptions)
if (res?.code !== 200) {
throw new Error(res?.body?.message)
}
return res
}
}

export { CsdnWebAdaptor }

0 comments on commit 3f90230

Please sign in to comment.