Skip to content

Commit

Permalink
feat: add bilibili support - category
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed Dec 17, 2024
1 parent d0b68ed commit 01ce51c
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 51 deletions.
20 changes: 18 additions & 2 deletions src/adaptors/web/bilibili/bilibiliConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,40 @@
*/

import { CommonWebConfig } from "~/src/adaptors/web/base/commonWebConfig.ts"
import { PageTypeEnum, PasswordType, PicbedServiceTypeEnum } from "zhi-blog-api"
import { CategoryTypeEnum, PageTypeEnum, PasswordType, PicbedServiceTypeEnum } from "zhi-blog-api"

/**
* B站配置
*
* @author terwer
* @since 1.31.0
*/
class BilibiliConfig extends CommonWebConfig {
public logoutUrl: string

constructor(password: string, middlewareUrl?: string) {
super("https://www.bilibili.com/opus", "https://api.bilibili.com", "", password, middlewareUrl)

// 方便过期之后退出
this.logoutUrl = "https://passport.bilibili.com/login"
// 预览地址
this.previewUrl = "/[postid]"
// 使用 md 发布
this.pageType = PageTypeEnum.Markdown
this.passwordType = PasswordType.PasswordType_Cookie
// cookie 模式不启用用户名
this.usernameEnabled = false
this.passwordType = PasswordType.PasswordType_Cookie
// 标签
this.tagEnabled = false
// 分类
this.cateEnabled = true
this.categoryType = CategoryTypeEnum.CategoryType_Single
this.allowCateChange = true
// 关闭知识空间
this.knowledgeSpaceEnabled = false
// 图床配置
this.picgoPicbedSupported = false
this.bundledPicbedSupported = true
this.picbedService = PicbedServiceTypeEnum.Bundled
}
}
Expand Down
62 changes: 60 additions & 2 deletions src/adaptors/web/bilibili/bilibiliMdUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ interface Op {

interface Paragraph {
para_type: number
text: {
text?: {
nodes: ContentNode[]
}
pic?: {
style?: number
pics?: any[]
}
}

interface ContentNode {
Expand Down Expand Up @@ -111,6 +115,9 @@ class BilibiliMdUtil {
case "NodeStrong":
this.processStrongNode(node, ops, paragraphs) // 处理加粗
break
case "NodeImage":
this.processImageNode(node, ops, paragraphs) // 处理图片
break
default:
Children.forEach((child) => this.processNode(child, ops, paragraphs))
}
Expand Down Expand Up @@ -214,10 +221,21 @@ class BilibiliMdUtil {
text: {
nodes: [],
},
pic: {
pics: [],
style: 1,
},
}
// 将 `paragraphChildNodes` 中的所有节点推送到 `nodes` 中
paragraphChildNodes.forEach((node) => {
paragraph.text.nodes.push(...node.text.nodes)
if (node.pic) {
paragraph.pic.style = node.pic.style
paragraph.pic.pics.push(...node.pic.pics)
} else if (node.text) {
paragraph.text.nodes.push(...node.text.nodes)
} else {
this.logger.warn("unknown node type", node)
}
})

paragraphs.push(paragraph)
Expand Down Expand Up @@ -285,6 +303,46 @@ class BilibiliMdUtil {
}
paragraphs.push(paragraph)
}

/**
* 处理图片节点
*/
private static processImageNode(node: Node, ops: Op[], paragraphs: Paragraph[]): void {
const imageUrl = node.Children?.find((child) => child.Type === "NodeLinkDest")?.Data || ""
const altText = node.Children?.find((child) => child.Type === "NodeLinkText")?.Data || "image"

// 添加图片到 ops
ops.push({
insert: "\n",
attributes: {
"native-image": {
alt: altText,
url: imageUrl,
width: 500,
height: 500,
size: 55183,
status: "loaded",
},
},
})

// 将图片转换为 content
const paragraph: Paragraph = {
para_type: 2,
pic: {
style: 1,
pics: [
{
url: imageUrl,
width: 500,
height: 500,
size: 53.8896484375,
},
],
},
}
paragraphs.push(paragraph)
}
}

export { BilibiliMdUtil }
160 changes: 122 additions & 38 deletions src/adaptors/web/bilibili/bilibiliWebAdaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
*/

import { BaseWebApi } from "~/src/adaptors/web/base/baseWebApi.ts"
import { Post } from "zhi-blog-api"
import { CategoryInfo, MediaObject, Post, UserBlog } from "zhi-blog-api"
import { BrowserUtil } from "zhi-device"
import CookieUtils from "~/src/utils/cookieUtils.ts"
import { BilibiliUtils } from "~/src/adaptors/web/bilibili/bilibiliUtils.ts"
import { IPublishCfg } from "~/src/types/IPublishCfg.ts"
import { JsonUtil, StrUtil } from "zhi-common"
import { MockBrowser } from "~/src/utils/MockBrowser.ts"
import { fileToBuffer } from "~/src/utils/polyfillUtils.ts"

class BilibiliWebAdaptor extends BaseWebApi {
private bilibiliMetaDataCfg = {} as any
Expand All @@ -52,6 +53,29 @@ class BilibiliWebAdaptor extends BaseWebApi {
}
}

public async getUsersBlogs(): Promise<Array<UserBlog>> {
let result: UserBlog[] = []

const res = await this.bilibiliFetch("/x/article/creative/list/all")
this.logger.debug("get bilibili all article res =>", res)
const columnList = res?.data?.lists

// 普通专栏
if (columnList && columnList.length > 0) {
columnList.forEach((item: any) => {
const userblog: UserBlog = new UserBlog()
userblog.blogid = item.id
userblog.blogName = item.name
// https://www.bilibili.com/read/readlist/rl898693
userblog.url = `https://www.bilibili.com/read/readlist/rl${item.id}`
result.push(userblog)
})
}

this.logger.debug("getUsersBlogs=>", result)
return result
}

// 发布预处理,可以在这里预设一些参数。会在实际新增和更新之前调用
public async preEditPost(post: Post, id?: string, publishCfg?: IPublishCfg): Promise<Post> {
const newPost = await super.preEditPost(post, id, publishCfg)
Expand Down Expand Up @@ -252,6 +276,103 @@ class BilibiliWebAdaptor extends BaseWebApi {
return true
}

public async deletePost(postid: string): Promise<boolean> {
const postMeta = this.getPostMeta(postid)
const params = JSON.stringify({
dyn_id_str: postMeta.dyn_id_str,
dyn_type: postMeta.dyn_type,
rid_str: postMeta.dyn_rid.toString(),
})
const res = await this.bilibiliFetch(
"/x/dynamic/feed/operate/remove",
{},
params,
"POST",
"application/json",
false,
true
)
this.logger.debug("bilibili delete post res=>", res)
if (res?.code !== 0) {
throw new Error(`哔哩哔哩文章删除失败,可能原因:${res?.code} ${res?.message}`)
}
return true
}

public async getPreviewUrl(postid: string): Promise<string> {
const dynId = this.getDynId(postid)
const previewUrl = this.cfg.previewUrl.replace(/\[postid]/g, dynId)
return previewUrl
}

public async getCategories(): Promise<CategoryInfo[]> {
const cats = [] as CategoryInfo[]
const res = await this.bilibiliFetch("/x/article/creative/list/all")
this.logger.debug("get bilibili lists =>", res)
if (res?.code === 0) {
const lists = res.data.lists
if (lists && lists.length > 0) {
lists.forEach((item: any) => {
const cat = new CategoryInfo()
cat.categoryId = item.id.toString()
cat.categoryName = item.name
cat.description = item.summary
cat.categoryDescription = item.summary
cats.push(cat)
})
}
}

return cats
}

public async uploadFile(mediaObject: MediaObject): Promise<any> {
const file = new Blob([mediaObject.bits], { type: mediaObject.type })
const filename = mediaObject.name
this.logger.debug(`bilibili start uploadFile ${filename}=>`, file)
if (file instanceof Blob) {
// uploadUrl
const uploadUrl = "/x/article/creative/article/upcover"

// 获取图片二进制数据
const bits = await fileToBuffer(file)
const blob = new Blob([bits], { type: file.type })

// formData
const formData: any = new FormData()
formData.append("binary", blob, filename)
formData.append("filename", filename)

// 发送请求
const res = await this.bilibiliFormFetch(uploadUrl, formData)
this.logger.debug("bilibili uploadFile res=>", res)
if (res?.code != 0) {
this.logger.error(`哔哩哔哩图片上传失败,可能原因:${res?.code} ${res?.message}`)
throw new Error(`哔哩哔哩图片上传失败,可能原因:${res?.code} ${res?.message}`)
}
const url = res.data.url
return {
id: url,
object_key: url,
url: url,
}
}

return {}
}

// ================
// private methods
// ================
private getDynId(postid: string) {
const postMeta = this.getPostMeta(postid)
return postMeta?.dyn_id_str
}

private getPostMeta(postid: string) {
return JsonUtil.safeParse<any>(postid, {} as any)
}

private async addDraft(post: Post) {
// 适配 B 站专门格式
this.logger.debug("bilibili before parse, md=>", post.markdown)
Expand Down Expand Up @@ -290,46 +411,9 @@ class BilibiliWebAdaptor extends BaseWebApi {
this.logger.debug("bilibili add draft res=>", res)
}

public async deletePost(postid: string): Promise<boolean> {
const postMeta = this.getPostMeta(postid)
const params = JSON.stringify({
dyn_id_str: postMeta.dyn_id_str,
dyn_type: postMeta.dyn_type,
rid_str: postMeta.dyn_rid.toString(),
})
const res = await this.bilibiliFetch(
"/x/dynamic/feed/operate/remove",
{},
params,
"POST",
"application/json",
false,
true
)
this.logger.debug("bilibili delete post res=>", res)
if (res?.code !== 0) {
throw new Error(`哔哩哔哩文章删除失败,可能原因:${res?.code} ${res?.message}`)
}
return true
}

public async getPreviewUrl(postid: string): Promise<string> {
const dynId = this.getDynId(postid)
const previewUrl = this.cfg.previewUrl.replace(/\[postid]/g, dynId)
return previewUrl
}

// ================
// private methods
// ================
private getDynId(postid: string) {
const postMeta = this.getPostMeta(postid)
return postMeta?.dyn_id_str
}

private getPostMeta(postid: string) {
return JsonUtil.safeParse<any>(postid, {} as any)
}

private async bilibiliFetch(
url: string,
Expand Down
10 changes: 1 addition & 9 deletions src/adaptors/web/bilibili/useBilibiliWeb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { LEGENCY_SHARED_PROXT_MIDDLEWARE } from "~/src/utils/constants.ts"
import { getDynPostidKey } from "~/src/platforms/dynamicConfig.ts"
import { BilibiliWebAdaptor } from "~/src/adaptors/web/bilibili/bilibiliWebAdaptor.ts"
import { Utils } from "~/src/utils/utils.ts"
import {PicbedServiceTypeEnum} from "zhi-blog-api";

/**
* 用于获取BilibiliWeb的API的自定义Hook
Expand Down Expand Up @@ -79,14 +78,7 @@ const useBilibiliWeb = async (key?: string, newCfg?: BilibiliConfig) => {
}
}

// 标签
cfg.tagEnabled = true
// Bilibili使用单选分类作为分区
cfg.cateEnabled = true
cfg.allowCateChange = true
// picbed service
cfg.picgoPicbedSupported = false
cfg.bundledPicbedSupported = true
// 新平台,暂时不需要强制指定配置

const webApi = new BilibiliWebAdaptor(appInstance, cfg)
return {
Expand Down

0 comments on commit 01ce51c

Please sign in to comment.