mirror of
https://github.com/doocs/cose.git
synced 2026-08-30 17:59:27 +08:00
feat: add segmentfault support (#21)
This commit is contained in:
@@ -35,17 +35,16 @@ _**C**reate **O**nce **S**ync **E**verywhere_
|
||||
- 微信公众号同步时完整保留渲染样式并自动保存为草稿
|
||||
|
||||
|
||||
## 支持的平台
|
||||
## 已支持的平台
|
||||
|
||||
| 平台 | 说明 |
|
||||
|------|------|
|
||||
| CSDN | 自动填充 Markdown 内容 |
|
||||
| 掘金 | 自动填充 Markdown 内容 |
|
||||
| 微信公众号 | 保留完整样式,自动保存草稿 |
|
||||
| 知乎 | 自动填充 Markdown 内容 |
|
||||
| 今日头条 | 自动填充 Markdown 内容 |
|
||||
- 微信公众号
|
||||
- 今日头条
|
||||
- 知乎
|
||||
- CSDN
|
||||
- 掘金
|
||||
- 思否
|
||||
|
||||
更多想要添加的平台欢迎提 [Issue](https://github.com/doocs/cose/issues) 。
|
||||
更多想要添加的平台欢迎提 [Issue](https://github.com/doocs/cose/issues) !
|
||||
|
||||
## 开发者模式测试
|
||||
|
||||
|
||||
+68
-1
@@ -5,6 +5,7 @@ const PLATFORMS = [
|
||||
{ id: 'wechat', name: 'WeChat', icon: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico', url: 'https://mp.weixin.qq.com', publishUrl: 'https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=10' },
|
||||
{ id: 'zhihu', name: 'Zhihu', icon: 'https://static.zhihu.com/heifetz/favicon.ico', url: 'https://www.zhihu.com', publishUrl: 'https://zhuanlan.zhihu.com/write' },
|
||||
{ id: 'toutiao', name: 'Toutiao', icon: 'https://sf3-cdn-tos.toutiaostatic.com/obj/eden-cn/uhbfnupkbps/toutiao_favicon.ico', url: 'https://mp.toutiao.com', publishUrl: 'https://mp.toutiao.com/profile_v4/graphic/publish' },
|
||||
{ id: 'segmentfault', name: 'SegmentFault', icon: 'https://static.segmentfault.com/main_site_next/prod/favicon.ico', url: 'https://segmentfault.com', publishUrl: 'https://segmentfault.com/write' },
|
||||
]
|
||||
|
||||
// 当前同步任务的 Tab Group ID
|
||||
@@ -100,6 +101,13 @@ const LOGIN_CHECK_CONFIG = {
|
||||
avatar: response?.data?.media?.https_avatar_url,
|
||||
}),
|
||||
},
|
||||
segmentfault: {
|
||||
useCookie: true,
|
||||
cookieUrl: 'https://segmentfault.com',
|
||||
cookieNames: ['PHPSESSID'],
|
||||
fetchUserInfoFromPage: true,
|
||||
userInfoUrl: 'https://segmentfault.com/write',
|
||||
},
|
||||
}
|
||||
|
||||
// 消息监听
|
||||
@@ -279,8 +287,32 @@ async function checkLoginByCookie(platformId, config) {
|
||||
}
|
||||
console.log(`[COSE] ${platformId} 用户信息:`, username, avatar ? '有头像' : '无头像')
|
||||
}
|
||||
// 思否用户信息提取(从 __NEXT_DATA__ 中获取)
|
||||
// 注意:思否的 PHPSESSID 即使未登录也存在,必须通过用户信息判断登录状态
|
||||
else if (platformId === 'segmentfault') {
|
||||
// 从 __NEXT_DATA__ 中提取用户信息
|
||||
const nextDataMatch = html.match(/<script\s+id="__NEXT_DATA__"[^>]*>([^<]+)<\/script>/i)
|
||||
if (nextDataMatch) {
|
||||
try {
|
||||
const nextData = JSON.parse(nextDataMatch[1])
|
||||
const sessionUser = nextData?.props?.pageProps?.initialState?.global?.sessionUser?.user
|
||||
if (sessionUser && sessionUser.name) {
|
||||
username = sessionUser.name
|
||||
avatar = sessionUser.avatar_url || ''
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`[COSE] ${platformId} 解析 NEXT_DATA 失败:`, e.message)
|
||||
}
|
||||
}
|
||||
// 思否:只有成功提取到用户名才算已登录
|
||||
if (!username) {
|
||||
console.log(`[COSE] ${platformId} 未登录或无法获取用户信息`)
|
||||
return { loggedIn: false }
|
||||
}
|
||||
console.log(`[COSE] ${platformId} 用户信息:`, username, avatar ? '有头像' : '无头像')
|
||||
}
|
||||
// 微信公众号用户信息提取
|
||||
else {
|
||||
else if (platformId === 'wechat') {
|
||||
// 从 HTML 中提取公众号名称
|
||||
const nameMatch = html.match(/nick_name\s*[:=]\s*["']([^"']+)["']/i) ||
|
||||
html.match(/<span[^>]*class="nickname"[^>]*>([^<]+)<\/span>/i)
|
||||
@@ -749,6 +781,41 @@ function fillContentOnPage(content, platformId) {
|
||||
console.log('[COSE] 头条未找到编辑器')
|
||||
}
|
||||
}
|
||||
// 思否 SegmentFault
|
||||
else if (host.includes('segmentfault.com')) {
|
||||
// 填充标题
|
||||
const titleInput = await waitFor('input#title, input[placeholder*="标题"]')
|
||||
if (titleInput) {
|
||||
titleInput.focus()
|
||||
titleInput.value = title
|
||||
titleInput.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
titleInput.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
console.log('[COSE] 思否标题填充成功')
|
||||
} else {
|
||||
console.log('[COSE] 思否未找到标题输入框')
|
||||
}
|
||||
|
||||
// 等待编辑器加载
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 思否使用 CodeMirror 编辑器
|
||||
const cmElement = document.querySelector('.CodeMirror')
|
||||
if (cmElement && cmElement.CodeMirror) {
|
||||
cmElement.CodeMirror.setValue(contentToFill)
|
||||
console.log('[COSE] 思否 CodeMirror 填充成功')
|
||||
} else {
|
||||
// 降级到 textarea
|
||||
const textarea = document.querySelector('textarea')
|
||||
if (textarea) {
|
||||
textarea.focus()
|
||||
textarea.value = contentToFill
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
console.log('[COSE] 思否 textarea 填充成功')
|
||||
} else {
|
||||
console.log('[COSE] 思否 未找到编辑器')
|
||||
}
|
||||
}
|
||||
}
|
||||
// 通用处理
|
||||
else {
|
||||
const titleSelectors = ['input[placeholder*="标题"]', 'input[name="title"]', 'textarea[placeholder*="标题"]']
|
||||
|
||||
+16
-11
@@ -68,6 +68,7 @@
|
||||
{ id: 'wechat', name: 'WeChat', icon: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico', title: '微信公众号', type: 'wechat' },
|
||||
{ id: 'zhihu', name: 'Zhihu', icon: 'https://static.zhihu.com/heifetz/favicon.ico', title: '知乎', type: 'zhihu' },
|
||||
{ id: 'toutiao', name: 'Toutiao', icon: 'https://sf3-cdn-tos.toutiaostatic.com/obj/eden-cn/uhbfnupkbps/toutiao_favicon.ico', title: '今日头条', type: 'toutiao' },
|
||||
{ id: 'segmentfault', name: 'SegmentFault', icon: 'https://youke2.picui.cn/s1/2025/12/21/6947b2935a41e.ico', title: '思否', type: 'segmentfault' },
|
||||
]
|
||||
|
||||
// 暴露 $cose 全局对象
|
||||
@@ -93,17 +94,21 @@
|
||||
const result = await sendMessage('CHECK_PLATFORM_STATUS', { platforms: PLATFORMS })
|
||||
const status = result?.status || {}
|
||||
|
||||
const accounts = PLATFORMS.map(p => ({
|
||||
uid: p.id,
|
||||
type: p.type,
|
||||
title: p.title,
|
||||
displayName: status[p.id]?.username || p.title,
|
||||
icon: p.icon,
|
||||
avatar: status[p.id]?.avatar,
|
||||
home: '',
|
||||
checked: false,
|
||||
loggedIn: status[p.id]?.loggedIn || false,
|
||||
}))
|
||||
const accounts = PLATFORMS.map(p => {
|
||||
const platformStatus = status[p.id] || {}
|
||||
const isLoggedIn = platformStatus.loggedIn || false
|
||||
return {
|
||||
uid: p.id,
|
||||
type: p.type,
|
||||
title: p.title,
|
||||
displayName: isLoggedIn ? (platformStatus.username || p.title) : p.title,
|
||||
icon: p.icon,
|
||||
avatar: platformStatus.avatar,
|
||||
home: '',
|
||||
checked: false,
|
||||
loggedIn: isLoggedIn,
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(accounts)
|
||||
|
||||
@@ -47,6 +47,15 @@ const PLATFORMS = [
|
||||
title: '今日头条',
|
||||
type: 'toutiao',
|
||||
},
|
||||
{
|
||||
id: 'segmentfault',
|
||||
name: 'SegmentFault',
|
||||
icon: 'https://static.segmentfault.com/main_site_next/prod/favicon.ico',
|
||||
url: 'https://segmentfault.com',
|
||||
publishUrl: 'https://segmentfault.com/write',
|
||||
title: '思否',
|
||||
type: 'segmentfault',
|
||||
},
|
||||
]
|
||||
|
||||
// 登录检测配置
|
||||
@@ -94,6 +103,13 @@ const LOGIN_CHECK_CONFIG = {
|
||||
avatar: response?.data?.media?.https_avatar_url,
|
||||
}),
|
||||
},
|
||||
segmentfault: {
|
||||
useCookie: true,
|
||||
cookieUrl: 'https://segmentfault.com',
|
||||
cookieNames: ['PHPSESSID'],
|
||||
fetchUserInfoFromPage: true,
|
||||
userInfoUrl: 'https://segmentfault.com/write',
|
||||
},
|
||||
}
|
||||
|
||||
// 根据 hostname 获取平台填充函数
|
||||
@@ -103,6 +119,7 @@ function getPlatformFiller(hostname) {
|
||||
if (hostname.includes('mp.weixin.qq.com')) return 'wechat'
|
||||
if (hostname.includes('zhihu.com')) return 'zhihu'
|
||||
if (hostname.includes('toutiao.com')) return 'toutiao'
|
||||
if (hostname.includes('segmentfault.com')) return 'segmentfault'
|
||||
return 'generic'
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 思否平台配置
|
||||
const SegmentFaultPlatform = {
|
||||
id: 'segmentfault',
|
||||
name: 'SegmentFault',
|
||||
icon: 'https://static.segmentfault.com/main_site_next/prod/favicon.ico',
|
||||
url: 'https://segmentfault.com',
|
||||
publishUrl: 'https://segmentfault.com/write',
|
||||
title: '思否',
|
||||
type: 'segmentfault',
|
||||
}
|
||||
|
||||
// 思否登录检测配置
|
||||
const SegmentFaultLoginConfig = {
|
||||
useCookie: true,
|
||||
cookieUrl: 'https://segmentfault.com',
|
||||
cookieNames: ['PHPSESSID'],
|
||||
fetchUserInfoFromPage: true,
|
||||
userInfoUrl: 'https://segmentfault.com/write',
|
||||
}
|
||||
|
||||
// 思否内容填充函数
|
||||
async function fillSegmentFaultContent(content, waitFor, setInputValue) {
|
||||
const { title, body, markdown } = content
|
||||
const contentToFill = markdown || body || ''
|
||||
|
||||
// 填充标题
|
||||
const titleInput = await waitFor('input#title, input[placeholder*="标题"]')
|
||||
if (titleInput) {
|
||||
titleInput.focus()
|
||||
titleInput.value = title
|
||||
titleInput.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
titleInput.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
console.log('[COSE] 思否标题填充成功')
|
||||
}
|
||||
|
||||
// 等待编辑器加载
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// 思否使用 CodeMirror 编辑器
|
||||
const cmElement = document.querySelector('.CodeMirror')
|
||||
if (cmElement && cmElement.CodeMirror) {
|
||||
cmElement.CodeMirror.setValue(contentToFill)
|
||||
console.log('[COSE] 思否 CodeMirror 填充成功')
|
||||
} else {
|
||||
// 降级到 textarea
|
||||
const textarea = document.querySelector('textarea')
|
||||
if (textarea) {
|
||||
textarea.focus()
|
||||
textarea.value = contentToFill
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
console.log('[COSE] 思否 textarea 填充成功')
|
||||
} else {
|
||||
console.log('[COSE] 思否 未找到编辑器')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { SegmentFaultPlatform, SegmentFaultLoginConfig, fillSegmentFaultContent }
|
||||
}
|
||||
Reference in New Issue
Block a user