Skip to content

Commit

Permalink
fix: #1109 support telegra.ph login
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed Apr 1, 2024
1 parent 968da63 commit 4b5649b
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 131 deletions.
86 changes: 43 additions & 43 deletions README.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions README_zh_CN.md
Expand Up @@ -57,19 +57,19 @@
| 博客 | Jvue || 完全兼容 || 完全兼容 || 支持 | 内置代理 | 不需要 | [官网](https://github.com/terwer/jvue) |
| 博客 | WordPress || 完全兼容 || 完全兼容 || 支持 | 内置代理 | 不需要 | [官网](https://wordpress.org/) |
| 博客 | WordPress.com || 完全兼容 || 完全兼容 || 支持 | 内置代理 | 不需要 | [官网](https://wordpress.com) |
| 社交媒体 | 知乎 || 完全兼容 || PC 验证 || | | | [官网](https://www.zhihu.com/) |
| 社交媒体 | CSDN || 完全兼容 || PC 验证 || | | | [官网](https://www.csdn.net/) |
| 社交媒体 | 微信公众号 || 完全兼容 || PC 验证 || | | | [官网](https://mp.weixin.qq.com/) |
| 社交媒体 | 简书 || 完全兼容 || PC 验证 || | | | [官网](https://www.jianshu.com/) |
| 社交媒体 | 掘金 || 完全兼容 || PC 验证 || | | | [官网](https://juejin.cn/) |
| 社交媒体 | 知乎 || 完全兼容 || PC 验证 || 支持 | 内置代理 | 不需要 | [官网](https://www.zhihu.com/) |
| 社交媒体 | CSDN || 完全兼容 || PC 验证 || 支持 | 内置代理 | 不需要 | [官网](https://www.csdn.net/) |
| 社交媒体 | 微信公众号 || 完全兼容 || PC 验证 || 支持 | 内置代理 | 不需要 | [官网](https://mp.weixin.qq.com/) |
| 社交媒体 | 简书 || 完全兼容 || PC 验证 || 支持 | 内置代理 | 不需要 | [官网](https://www.jianshu.com/) |
| 社交媒体 | 掘金 || 完全兼容 || PC 验证 || 不支持 | 内置代理 | 不需要 | [官网](https://juejin.cn/) |
| 社区 | 52破解 | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://www.52pojie.cn/) |
| 社区 | Bilibili | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://www.bilibili.com/) |
| 社区 | 小红书 | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://www.xiaohongshu.com/) |
| 社区 | 豆瓣 | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://www.douban.com/) |
| 其他 | Xlog | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://xlog.cn/) |
| 其他 | Mdnice | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://mdnice.com/) |
| 其他 | Flowus | TODO | TODO | TODO | TODO | TODO | TODO | TODO | TODO | [官网](https://www.flowus.com/) |
| 其他 | telegra.ph || 部分兼容 || 部分兼容 || | | | [官网](https://telegra.ph) |
| 其他 | telegra.ph || 部分兼容 || 部分兼容 || 不支持 | 自建代理 | 自建代理 | [官网](https://telegra.ph) |

## 核心特色

Expand Down
52 changes: 29 additions & 23 deletions src/adaptors/api/base/baseBlogApi.ts
Expand Up @@ -28,7 +28,7 @@ import { PublisherAppInstance } from "~/src/publisherAppInstance.ts"
import { createAppLogger, ILogger } from "~/src/utils/appLogger.ts"
import { useProxy } from "~/src/composables/useProxy.ts"
import { BaseExtendApi } from "~/src/adaptors/base/baseExtendApi.ts"
import { JsonUtil, StrUtil } from "zhi-common"
import { JsonUtil } from "zhi-common"
import { useSiyuanDevice } from "~/src/composables/useSiyuanDevice.ts"
import { Base64 } from "js-base64"
import FormDataUtils from "~/src/utils/FormDataUtils.ts"
Expand Down Expand Up @@ -132,29 +132,36 @@ export class BaseBlogApi extends BlogApi {
| "base32-hex"
| "hex" = "text"
) {
const isCorsProxyAvailable = !StrUtil.isEmptyString(this.cfg.corsAnywhereUrl)
const header = headers.length > 0 ? headers[0] : {}

// 如果没有可用的 CORS 代理或者没有强制使用代理,使用默认的自动检测机制
if (this.isUseSiyuanProxy || !isCorsProxyAvailable) {
this.logger.info("Using legency api fetch")
// const proxyFetch = async (
// url: string,
// headers: any[] = [],
// params: any = {},
// method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" = "GET",
// contentType: string = "application/json",
// forceProxy: boolean = false
// ) => {
if (this.isUseSiyuanProxy || (!this.isUseSiyuanProxy && forceProxy)) {
this.logger.info("Using legency api fetch")
return this.proxyFetch(url, headers, params, method, contentType, forceProxy, payloadEncoding, responseEncoding)
// remove cors fetch header
delete header["x-cors-headers"]
const blogHeaders = [
{
...header,
},
]
return this.proxyFetch(
url,
blogHeaders,
params,
method,
contentType,
forceProxy,
payloadEncoding,
responseEncoding
)
} else {
// const corsFetch = async (
// url: string,
// headers: any[] = [],
// params: BodyInit = undefined,
// method: "GET" | "POST" | "PUT" | "DELETE" = "GET"
// )
this.logger.info("Using cors api fetch")
return this.corsFetch(url, headers, params, method)
const blogHeaders = [
{
...header,
},
]
return this.corsFetch(url, blogHeaders, params, method)
}
}

Expand All @@ -167,13 +174,12 @@ export class BaseBlogApi extends BlogApi {
* @param forceProxy - 是否强制使用代理,默认为 false
*/
public async apiProxyFormFetch(url: string, headers: any[], formData: FormData, forceProxy: boolean = false) {
const isCorsProxyAvailable = !StrUtil.isEmptyString(this.cfg.corsAnywhereUrl)
// 如果没有可用的 CORS 代理或者没有强制使用代理,使用默认的自动检测机制
if (this.isUseSiyuanProxy || !isCorsProxyAvailable) {
if (this.isUseSiyuanProxy || (!this.isUseSiyuanProxy && forceProxy)) {
this.logger.info("Using legency api formFetch")
const { isInSiyuanOrSiyuanNewWin } = useSiyuanDevice()

if (!isInSiyuanOrSiyuanNewWin()) {
if (!isInSiyuanOrSiyuanNewWin() || forceProxy) {
const fetchResult = await this.apiProxyFetch(
url,
headers,
Expand Down
81 changes: 67 additions & 14 deletions src/adaptors/api/telegraph/telegraphApiAdaptor.ts
Expand Up @@ -29,6 +29,7 @@ import { TelegraphConfig } from "~/src/adaptors/api/telegraph/telegraphConfig.ts
import { JsonUtil, StrUtil } from "zhi-common"
import CookieUtils from "~/src/utils/cookieUtils.ts"
import md from "telegraph.md"
import FormDataUtils from "~/src/utils/FormDataUtils.ts"

/**
* Telegraph API 适配器
Expand All @@ -41,14 +42,31 @@ class TelegraphApiAdaptor extends BaseBlogApi {
public async getUsersBlogs(): Promise<UserBlog[]> {
const result: UserBlog[] = []

const checkJson = await this.telegraphFetch("/check", "page_id=0", "POST")
if (checkJson.error) {
throw new Error("telegra.ph request error =>" + checkJson.error)
}
const corsHeaders = JsonUtil.safeParse<any>(checkJson["cors-received-headers"], {})
const cookies = corsHeaders["Set-Cookie-Array"]
let cookies: any
let checkJson = await this.telegraphFetch("/check", "page_id=0", "POST")
this.logger.debug("checkJson =>", checkJson)

if (checkJson["cors-received-headers"]) {
// use cors proxy
if (checkJson.error) {
throw new Error("telegra.ph request error =>" + checkJson.error)
}
const corsHeaders = JsonUtil.safeParse<any>(checkJson["cors-received-headers"], {})
cookies = corsHeaders["Set-Cookie-Array"]
} else {
// use siyuan proxy
if (checkJson.status == 200) {
cookies = checkJson.headers["Set-Cookie"]
const body = JsonUtil.safeParse(checkJson.body, {})
checkJson = {
...checkJson,
...body,
}
} else {
throw new Error(`telegra.ph request error ${checkJson.status}=>` + checkJson.body)
}
}

// 数据适配
const userblog: UserBlog = new UserBlog()
const cfg = this.cfg as TelegraphConfig
Expand All @@ -57,9 +75,10 @@ class TelegraphApiAdaptor extends BaseBlogApi {
userblog.url = cfg.apiUrl
// 元数据映射
// @since 1.20.0
const newCookies = CookieUtils.addCookieArray(this.cfg?.corsCookieArray ?? [], cookies)
userblog.metadataMap = {
password: checkJson.save_hash,
corsCookieArray: CookieUtils.addCookieArray(this.cfg?.corsCookieArray ?? [], cookies),
corsCookieArray: newCookies,
}
result.push(userblog)
this.logger.debug("get telegraph cfg =>", result)
Expand All @@ -68,6 +87,9 @@ class TelegraphApiAdaptor extends BaseBlogApi {
}

public async newPost(post: Post, _publish?: boolean): Promise<string> {
// 这里不用这个,因为 telegraph 必须强制代理
// const { FormData, Blob } = FormDataUtils.getFormData(this.appInstance)

const formData = new FormData()
const content = md(post.description)
const blobData = new Blob([JSON.stringify(content)], { type: "text/plain" })
Expand Down Expand Up @@ -95,6 +117,8 @@ class TelegraphApiAdaptor extends BaseBlogApi {
}

public async editPost(postid: string, post: Post, publish?: boolean): Promise<boolean> {
const { FormData, Blob } = FormDataUtils.getFormData(this.appInstance)

const postMeta = JsonUtil.safeParse<any>(postid, {})

const formData = new FormData()
Expand Down Expand Up @@ -153,16 +177,39 @@ class TelegraphApiAdaptor extends BaseBlogApi {
header: Record<any, any> = {}
) {
const contentType = "text/plain"
// const tphUuidObj = CookieUtils.getCookieObject(this.cfg.corsCookieArray, this.TPH_UUID_KEY)
// if (!StrUtil.isEmptyString(tphUuidObj[this.TPH_UUID_KEY])) {
// header["Cookie"] = `${this.TPH_UUID_KEY}=${tphUuidObj[this.TPH_UUID_KEY]}`
// }

// const headers = {
// "Content-Type": contentType,
// origin: "https://telegra.ph",
// referer: "https://telegra.ph/",
// ...header,
// }
let xCorsHeaders: Record<any, any> = {}

// header

// x-cors-headers
const tphUuidObj = CookieUtils.getCookieObject(this.cfg.corsCookieArray, this.TPH_UUID_KEY)
if (!StrUtil.isEmptyString(tphUuidObj[this.TPH_UUID_KEY])) {
header["Cookie"] = `${this.TPH_UUID_KEY}=${tphUuidObj[this.TPH_UUID_KEY]}`
xCorsHeaders["Cookie"] = `${this.TPH_UUID_KEY}=${tphUuidObj[this.TPH_UUID_KEY]}`
}
xCorsHeaders["origin"] = "https://telegra.ph"
xCorsHeaders["referer"] = "https://telegra.ph/"
xCorsHeaders["Content-Type"] = contentType

const headers = {
"Content-Type": contentType,
origin: "https://telegra.ph",
referer: "https://telegra.ph/",
...header,
// for cors proxy
// siyuan proxy should ignore this header
"x-cors-headers": JSON.stringify(xCorsHeaders),
}
for (const [xkey, xvalue] of Object.entries(xCorsHeaders)) {
headers[xkey] = xvalue
}

const body = params

// 输出日志
Expand All @@ -172,7 +219,7 @@ class TelegraphApiAdaptor extends BaseBlogApi {
this.logger.debug("向 Telegraph 请求数据,headers =>", headers)
this.logger.debug("向 Telegraph 请求数据,body =>", body)

const resJson = await this.apiProxyFetch(apiUrl, [headers], body, method, contentType, true)
const resJson = await this.apiProxyFetch(apiUrl, [headers], body, method, contentType, false)
this.logger.debug("向 Telegraph 请求数据,resJson =>", resJson)

return resJson ?? null
Expand All @@ -199,8 +246,14 @@ class TelegraphApiAdaptor extends BaseBlogApi {
xCorsHeaders["referer"] = "https://telegra.ph/"

const headers = {
// for cors proxy
// siyuan proxy should ignore this header
"x-cors-headers": JSON.stringify(xCorsHeaders),
}
for (const [xkey, xvalue] of Object.entries(xCorsHeaders)) {
headers[xkey] = xvalue
}

const options: RequestInit = {
method: "POST",
headers: headers,
Expand All @@ -210,7 +263,7 @@ class TelegraphApiAdaptor extends BaseBlogApi {
this.logger.debug("向 Telegraph 发送表单数据,apiUrl =>", apiUrl)
this.logger.debug("向 Telegraph 发送表单数据,options =>", options)

const resJson = await this.apiProxyFormFetch(apiUrl, [headers], formData, true)
const resJson = await this.apiProxyFormFetch(apiUrl, [headers], formData, false)
if (resJson.error) {
throw new Error(
"telegra.ph 发布错误,注意:切换设备(包括从PC到浏览器环境)需要重新验证,并且获取新token。详细错误 =>" +
Expand Down
9 changes: 2 additions & 7 deletions src/adaptors/api/telegraph/useTelegraphApi.ts
Expand Up @@ -31,7 +31,7 @@ import { JsonUtil, ObjectUtil, StrUtil } from "zhi-common"
import { Utils } from "~/src/utils/utils.ts"
import { getDynPostidKey } from "~/src/platforms/dynamicConfig.ts"
import { TelegraphApiAdaptor } from "~/src/adaptors/api/telegraph/telegraphApiAdaptor.ts"
import { CORS_PROXT_URL, LEGENCY_SHARED_PROXT_MIDDLEWARE } from "~/src/utils/constants.ts"
import { LEGENCY_SHARED_PROXT_MIDDLEWARE } from "~/src/utils/constants.ts"

const useTelegraphApi = async (key: string, newCfg?: TelegraphConfig) => {
const logger = createAppLogger("use-telegraph-api")
Expand All @@ -51,7 +51,7 @@ const useTelegraphApi = async (key: string, newCfg?: TelegraphConfig) => {
if (ObjectUtil.isEmptyObject(cfg)) {
const telegraphUrl = Utils.emptyOrDefault(process.env.VITE_TELEGRAPH_URL, "https://telegra.ph")
const middlewareUrl = Utils.emptyOrDefault(process.env.VITE_MIDDLEWARE_URL, LEGENCY_SHARED_PROXT_MIDDLEWARE)
const telegraphToken = Utils.emptyOrDefault(process.env.VITE_TELEGRAPH_TOKEN, CORS_PROXT_URL)
const telegraphToken = Utils.emptyOrDefault(process.env.VITE_TELEGRAPH_TOKEN, "")
cfg = new TelegraphConfig(telegraphUrl, telegraphToken, middlewareUrl)
logger.info("Configuration is empty, using default environment variables.")
} else {
Expand All @@ -63,11 +63,6 @@ const useTelegraphApi = async (key: string, newCfg?: TelegraphConfig) => {
// 默认值
cfg.posidKey = getDynPostidKey(key)
}
// 初始化corsAnywhereUrl
if (StrUtil.isEmptyString(cfg.corsAnywhereUrl)) {
// 默认值
cfg.corsAnywhereUrl = Utils.emptyOrDefault(process.env.VITE_CORS_ANYWHERE_URL, CORS_PROXT_URL)
}
}

cfg.usernameEnabled = true
Expand Down
31 changes: 18 additions & 13 deletions src/adaptors/web/base/baseWebApi.ts
Expand Up @@ -192,17 +192,17 @@ class BaseWebApi extends WebApi {
| "hex" = "text"
) {
const header = headers.length > 0 ? headers[0] : {}
const webHeaders = [
{
...header,
Cookie: this.cfg.password,
},
]

const isCorsProxyAvailable = !StrUtil.isEmptyString(this.cfg.corsAnywhereUrl)
// 如果没有可用的 CORS 代理或者没有强制使用代理,使用默认的自动检测机制
if (this.isUseSiyuanProxy || !isCorsProxyAvailable) {
if (this.isUseSiyuanProxy || (!this.isUseSiyuanProxy && forceProxy)) {
this.logger.info("Using legency web fetch")
// remove cors fetch header
delete header["x-cors-headers"]
const webHeaders = [
{
...header,
Cookie: this.cfg.password,
},
]
return await this.proxyFetch(
url,
webHeaders,
Expand All @@ -215,7 +215,13 @@ class BaseWebApi extends WebApi {
)
} else {
this.logger.info("Using cors web fetch")
return this.corsFetch(url, headers, params, method)
const webHeaders = [
{
...header,
Cookie: this.cfg.password,
},
]
return this.corsFetch(url, webHeaders, params, method)
}
}

Expand Down Expand Up @@ -253,13 +259,12 @@ class BaseWebApi extends WebApi {
| "base32-hex"
| "hex" = "text"
) {
const isCorsProxyAvailable = !StrUtil.isEmptyString(this.cfg.corsAnywhereUrl)
// 如果没有可用的 CORS 代理或者没有强制使用代理,使用默认的自动检测机制
if (this.isUseSiyuanProxy || !isCorsProxyAvailable) {
if (this.isUseSiyuanProxy || (!this.isUseSiyuanProxy && forceProxy)) {
this.logger.info("Using legency web formFetch")

const { isInSiyuanOrSiyuanNewWin } = useSiyuanDevice()
if (!isInSiyuanOrSiyuanNewWin()) {
if (!isInSiyuanOrSiyuanNewWin() || forceProxy) {
const fetchResult = await this.webProxyFetch(
url,
headers,
Expand Down
3 changes: 3 additions & 0 deletions src/adaptors/web/csdn/csdnWebAdaptor.ts
Expand Up @@ -29,6 +29,7 @@ import { BlogConfig, CategoryInfo, PageTypeEnum, Post, UserBlog } from "zhi-blog
import { JsonUtil } from "zhi-common"
import WebUtils from "~/src/adaptors/web/base/webUtils.ts"
import _ from "lodash-es"
import FormDataUtils from "~/src/utils/FormDataUtils.ts";

/**
* CSDN网页授权适配器
Expand Down Expand Up @@ -303,6 +304,8 @@ class CsdnWebAdaptor extends BaseWebApi {
}

public async uploadFile(file: File | Blob, filename?: string): Promise<any> {
const { FormData, Blob } = FormDataUtils.getFormData(this.appInstance)

this.logger.debug(`csdn start uploadFile ${filename}=>`, file)
if (file instanceof Blob) {
const uploadData = await this.requestUpload(filename)
Expand Down
3 changes: 2 additions & 1 deletion src/adaptors/web/jianshu/jianshuWebAdaptor.ts
Expand Up @@ -182,14 +182,15 @@ class JianshuWebAdaptor extends BaseWebApi {
}

public async uploadFile(file: File | Blob, filename?: string): Promise<any> {
const { FormData, Blob } = FormDataUtils.getFormData(this.appInstance)

this.logger.debug(`jianshu start uploadFile ${filename}=>`, file)
if (file instanceof Blob) {
// import
const win = this.appInstance.win
if (!win.require) {
throw new Error("非常抱歉,目前仅思源笔记PC客户端支持上传图片")
}
const { FormData, Blob } = FormDataUtils.getFormData(this.appInstance)

// uploadUrl
const uploadUrl = "https://upload.qiniup.com/"
Expand Down

0 comments on commit 4b5649b

Please sign in to comment.