Skip to content

Commit 616881a

Browse files
authored
feat: add toutiao support #10 (#11)
1 parent 9b315d5 commit 616881a

4 files changed

Lines changed: 166 additions & 15 deletions

File tree

src/background.js

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const PLATFORMS = [
44
{ id: 'juejin', name: 'Juejin', icon: 'https://lf-web-assets.juejin.cn/obj/juejin-web/xitu_juejin_web/static/favicons/favicon-32x32.png', url: 'https://juejin.cn', publishUrl: 'https://juejin.cn/editor/drafts/new' },
55
{ 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' },
66
{ id: 'zhihu', name: 'Zhihu', icon: 'https://static.zhihu.com/heifetz/favicon.ico', url: 'https://www.zhihu.com', publishUrl: 'https://zhuanlan.zhihu.com/write' },
7+
{ 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' },
78
]
89

910
// 当前同步任务的 Tab Group ID
@@ -90,6 +91,15 @@ const LOGIN_CHECK_CONFIG = {
9091
avatar: response?.avatar_url,
9192
}),
9293
},
94+
toutiao: {
95+
api: 'https://mp.toutiao.com/mp/agw/media/get_media_info',
96+
method: 'GET',
97+
checkLogin: (response) => response?.err_no === 0 && response?.data?.media?.display_name,
98+
getUserInfo: (response) => ({
99+
username: response?.data?.media?.display_name,
100+
avatar: response?.data?.media?.https_avatar_url,
101+
}),
102+
},
93103
}
94104

95105
// 消息监听
@@ -172,7 +182,7 @@ async function checkPlatformLogin(platform) {
172182

173183
let data = null
174184
const contentType = response.headers.get('content-type') || ''
175-
if (contentType.includes('application/json')) {
185+
if (contentType.includes('application/json') || contentType.includes('text/plain')) {
176186
try { data = await response.json() } catch (e) { data = null }
177187
}
178188

@@ -243,30 +253,51 @@ async function checkLoginByCookie(platformId, config) {
243253
}
244254
}
245255

246-
// 从微信公众号页面抓取用户信息
256+
// 从页面抓取用户信息
247257
if (config.fetchUserInfoFromPage && config.userInfoUrl) {
248258
try {
249259
const response = await fetch(config.userInfoUrl, {
250260
method: 'GET',
251261
credentials: 'include'
252262
})
253263
const html = await response.text()
254-
// 从 HTML 中提取公众号名称
255-
const nameMatch = html.match(/nick_name\s*[:=]\s*["']([^"']+)["']/i) ||
256-
html.match(/<span[^>]*class="nickname"[^>]*>([^<]+)<\/span>/i)
257-
if (nameMatch) {
258-
username = nameMatch[1]
264+
265+
// 头条号用户信息提取
266+
if (platformId === 'toutiao') {
267+
// 尝试从页面中提取用户名
268+
const nameMatch = html.match(/\"name\"\s*:\s*\"([^"]+)\"/i) ||
269+
html.match(/screen_name[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/i) ||
270+
html.match(/<span[^>]*class="[^"]*name[^"]*"[^>]*>([^<]+)<\/span>/i)
271+
if (nameMatch) {
272+
username = nameMatch[1]
273+
}
274+
// 尝试从页面中提取头像
275+
const avatarMatch = html.match(/\"avatar_url\"\s*:\s*\"([^"]+)\"/i) ||
276+
html.match(/avatar[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/i)
277+
if (avatarMatch) {
278+
avatar = avatarMatch[1].replace(/\\/g, '')
279+
}
280+
console.log(`[COSE] ${platformId} 用户信息:`, username, avatar ? '有头像' : '无头像')
259281
}
260-
// 从 HTML 中提取头像
261-
const avatarMatch = html.match(/head_img\s*[:=]\s*["']([^"']+)["']/i) ||
262-
html.match(/<img[^>]*class="avatar"[^>]*src="([^"]+)"/i)
263-
if (avatarMatch) {
264-
avatar = avatarMatch[1].replace(/\\x26amp;/g, '&').replace(/\\/g, '')
265-
if (!avatar.startsWith('http')) {
266-
avatar = 'https://mp.weixin.qq.com' + avatar
282+
// 微信公众号用户信息提取
283+
else {
284+
// 从 HTML 中提取公众号名称
285+
const nameMatch = html.match(/nick_name\s*[:=]\s*["']([^"']+)["']/i) ||
286+
html.match(/<span[^>]*class="nickname"[^>]*>([^<]+)<\/span>/i)
287+
if (nameMatch) {
288+
username = nameMatch[1]
267289
}
290+
// 从 HTML 中提取头像
291+
const avatarMatch = html.match(/head_img\s*[:=]\s*["']([^"']+)["']/i) ||
292+
html.match(/<img[^>]*class="avatar"[^>]*src="([^"]+)"/i)
293+
if (avatarMatch) {
294+
avatar = avatarMatch[1].replace(/\\x26amp;/g, '&').replace(/\\/g, '')
295+
if (!avatar.startsWith('http')) {
296+
avatar = 'https://mp.weixin.qq.com' + avatar
297+
}
298+
}
299+
console.log(`[COSE] ${platformId} 用户信息:`, username, avatar ? '有头像' : '无头像')
268300
}
269-
console.log(`[COSE] ${platformId} 用户信息:`, username, avatar ? '有头像' : '无头像')
270301
} catch (e) {
271302
console.log(`[COSE] ${platformId} 获取用户信息失败:`, e.message)
272303
}
@@ -687,6 +718,41 @@ function fillContentOnPage(content, platformId) {
687718
else if (host.includes('zhihu.com')) {
688719
console.log('[COSE] 知乎由导入文档功能处理')
689720
}
721+
// 今日头条
722+
else if (host.includes('toutiao.com')) {
723+
// 填充标题 - 头条使用 textarea 作为标题输入
724+
const titleInput = await waitFor('textarea[placeholder*="标题"], input[placeholder*="标题"], .editor-title textarea, .title-input textarea')
725+
if (titleInput) {
726+
titleInput.focus()
727+
// 使用 nativeInputValueSetter 触发 React 状态更新
728+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set
729+
|| Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set
730+
if (nativeInputValueSetter) {
731+
nativeInputValueSetter.call(titleInput, title)
732+
} else {
733+
titleInput.value = title
734+
}
735+
titleInput.dispatchEvent(new Event('input', { bubbles: true }))
736+
titleInput.dispatchEvent(new Event('change', { bubbles: true }))
737+
console.log('[COSE] 头条标题填充成功')
738+
} else {
739+
console.log('[COSE] 头条未找到标题输入框')
740+
}
741+
742+
// 等待编辑器加载
743+
await new Promise(resolve => setTimeout(resolve, 1000))
744+
745+
// 头条使用富文本编辑器
746+
const editor = document.querySelector('.ProseMirror, [contenteditable="true"], .editor-content')
747+
if (editor) {
748+
editor.focus()
749+
editor.innerHTML = body || contentToFill.replace(/\n/g, '<br>')
750+
editor.dispatchEvent(new Event('input', { bubbles: true }))
751+
console.log('[COSE] 头条内容填充成功')
752+
} else {
753+
console.log('[COSE] 头条未找到编辑器')
754+
}
755+
}
690756
// 通用处理
691757
else {
692758
const titleSelectors = ['input[placeholder*="标题"]', 'input[name="title"]', 'textarea[placeholder*="标题"]']

src/inject.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
{ id: 'juejin', name: 'Juejin', icon: 'https://lf-web-assets.juejin.cn/obj/juejin-web/xitu_juejin_web/static/favicons/favicon-32x32.png', title: '掘金', type: 'juejin' },
6868
{ id: 'wechat', name: 'WeChat', icon: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico', title: '微信公众号', type: 'wechat' },
6969
{ id: 'zhihu', name: 'Zhihu', icon: 'https://static.zhihu.com/heifetz/favicon.ico', title: '知乎', type: 'zhihu' },
70+
{ id: 'toutiao', name: 'Toutiao', icon: 'https://sf3-cdn-tos.toutiaostatic.com/obj/eden-cn/uhbfnupkbps/toutiao_favicon.ico', title: '今日头条', type: 'toutiao' },
7071
]
7172

7273
// 暴露 $cose 全局对象

src/platforms/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ const PLATFORMS = [
3838
title: '知乎',
3939
type: 'zhihu',
4040
},
41+
{
42+
id: 'toutiao',
43+
name: 'Toutiao',
44+
icon: 'https://sf3-cdn-tos.toutiaostatic.com/obj/eden-cn/uhbfnupkbps/toutiao_favicon.ico',
45+
url: 'https://mp.toutiao.com',
46+
publishUrl: 'https://mp.toutiao.com/profile_v4/graphic/publish',
47+
title: '今日头条',
48+
type: 'toutiao',
49+
},
4150
]
4251

4352
// 登录检测配置
@@ -76,6 +85,15 @@ const LOGIN_CHECK_CONFIG = {
7685
avatar: response?.avatar_url,
7786
}),
7887
},
88+
toutiao: {
89+
api: 'https://mp.toutiao.com/mp/agw/media/get_media_info',
90+
method: 'GET',
91+
checkLogin: (response) => response?.err_no === 0 && response?.data?.media?.display_name,
92+
getUserInfo: (response) => ({
93+
username: response?.data?.media?.display_name,
94+
avatar: response?.data?.media?.https_avatar_url,
95+
}),
96+
},
7997
}
8098

8199
// 根据 hostname 获取平台填充函数
@@ -84,6 +102,7 @@ function getPlatformFiller(hostname) {
84102
if (hostname.includes('juejin.cn')) return 'juejin'
85103
if (hostname.includes('mp.weixin.qq.com')) return 'wechat'
86104
if (hostname.includes('zhihu.com')) return 'zhihu'
105+
if (hostname.includes('toutiao.com')) return 'toutiao'
87106
return 'generic'
88107
}
89108

src/platforms/toutiao.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// 今日头条平台配置
2+
const ToutiaoPlatform = {
3+
id: 'toutiao',
4+
name: 'Toutiao',
5+
icon: 'https://sf3-cdn-tos.toutiaostatic.com/obj/eden-cn/uhbfnupkbps/toutiao_favicon.ico',
6+
url: 'https://mp.toutiao.com',
7+
publishUrl: 'https://mp.toutiao.com/profile_v4/graphic/publish',
8+
title: '今日头条',
9+
type: 'toutiao',
10+
}
11+
12+
// 今日头条登录检测配置
13+
const ToutiaoLoginConfig = {
14+
api: 'https://mp.toutiao.com/mp/agw/media/get_media_info',
15+
method: 'GET',
16+
checkLogin: (response) => response?.err_no === 0 && response?.data?.media?.display_name,
17+
getUserInfo: (response) => ({
18+
username: response?.data?.media?.display_name,
19+
avatar: response?.data?.media?.https_avatar_url,
20+
}),
21+
}
22+
23+
// 今日头条内容填充函数
24+
async function fillToutiaoContent(content, waitFor, setInputValue) {
25+
const { title, body, markdown } = content
26+
const contentToFill = body || markdown || ''
27+
28+
// 填充标题 - 头条使用 textarea 作为标题输入
29+
const titleInput = await waitFor('textarea[placeholder*="标题"], input[placeholder*="标题"], .editor-title textarea, .title-input textarea')
30+
if (titleInput) {
31+
titleInput.focus()
32+
// 使用 nativeInputValueSetter 触发 React 状态更新
33+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')?.set
34+
|| Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set
35+
if (nativeInputValueSetter) {
36+
nativeInputValueSetter.call(titleInput, title)
37+
} else {
38+
titleInput.value = title
39+
}
40+
titleInput.dispatchEvent(new Event('input', { bubbles: true }))
41+
titleInput.dispatchEvent(new Event('change', { bubbles: true }))
42+
console.log('[COSE] 头条标题填充成功')
43+
} else {
44+
console.log('[COSE] 头条未找到标题输入框')
45+
}
46+
47+
// 等待编辑器加载
48+
await new Promise(resolve => setTimeout(resolve, 1000))
49+
50+
// 头条使用富文本编辑器
51+
const editor = document.querySelector('.ProseMirror, [contenteditable="true"], .editor-content')
52+
if (editor) {
53+
editor.focus()
54+
editor.innerHTML = contentToFill.replace(/\n/g, '<br>')
55+
editor.dispatchEvent(new Event('input', { bubbles: true }))
56+
console.log('[COSE] 头条内容填充成功')
57+
} else {
58+
console.log('[COSE] 头条未找到编辑器')
59+
}
60+
}
61+
62+
// 导出
63+
if (typeof module !== 'undefined' && module.exports) {
64+
module.exports = { ToutiaoPlatform, ToutiaoLoginConfig, fillToutiaoContent }
65+
}

0 commit comments

Comments
 (0)