refactor: add detection module (#163)

This commit is contained in:
Neo
2026-02-08 17:05:39 +08:00
committed by GitHub
parent c2b13b2ae1
commit 3d4e6b4c00
10 changed files with 1317 additions and 1870 deletions
+2 -1
View File
@@ -15,7 +15,8 @@
"lint": "web-ext lint --source-dir ./dist"
},
"dependencies": {
"@cose/core": "workspace:*"
"@cose/core": "workspace:*",
"@cose/detection": "workspace:*"
},
"devDependencies": {
"cac": "^6.7.14",
File diff suppressed because it is too large Load Diff
+3 -373
View File
@@ -1,373 +1,3 @@
/**
* @cose/detection - Platform login detection module
*
* This package provides login detection configurations for all supported platforms.
* Each config includes:
* - api: The API endpoint to check login status
* - method: HTTP method (GET/POST)
* - checkLogin: Function to determine if user is logged in from response
* - getUserInfo: Function to extract username and avatar from response
*/
// CSDN
export const CSDNLoginConfig = {
api: 'https://passport.csdn.net/v1/api/info',
method: 'GET',
checkLogin: (response) => response?.data?.userId,
getUserInfo: (response) => ({
username: response?.data?.nickName,
avatar: response?.data?.avatarUrl,
}),
}
// 掘金
export const JuejinLoginConfig = {
api: 'https://api.juejin.cn/user_api/v1/user/get',
method: 'GET',
checkLogin: (response) => response?.err_no === 0 && response?.data?.user_id,
getUserInfo: (response) => ({
username: response?.data?.user_name,
avatar: response?.data?.avatar_large,
}),
}
// 微信公众号
export const WechatLoginConfig = {
api: 'https://mp.weixin.qq.com/cgi-bin/logininfo?action=logininfo&lang=zh_CN',
method: 'GET',
checkLogin: (response) => response?.base_resp?.ret === 0 && response?.user_name,
getUserInfo: (response) => ({
username: response?.nick_name || response?.user_name,
avatar: response?.head_img,
}),
}
// 知乎
export const ZhihuLoginConfig = {
api: 'https://www.zhihu.com/api/v4/me',
method: 'GET',
checkLogin: (response) => response?.id,
getUserInfo: (response) => ({
username: response?.name,
avatar: response?.avatar_url,
}),
}
// 头条号
export const ToutiaoLoginConfig = {
api: 'https://mp.toutiao.com/mp/agw/creator_center/user_info?app_id=1231',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.name,
getUserInfo: (response) => ({
username: response?.name,
avatar: response?.avatar_url,
}),
}
// SegmentFault
export const SegmentFaultLoginConfig = {
api: 'https://segmentfault.com/gateway/user/me',
method: 'GET',
checkLogin: (response) => response?.status === 0 && response?.data?.id,
getUserInfo: (response) => ({
username: response?.data?.name,
avatar: response?.data?.avatar_url,
}),
}
// 博客园
export const CnblogsLoginConfig = {
api: 'https://www.cnblogs.com/api/users/current',
method: 'GET',
checkLogin: (response) => response?.UserId,
getUserInfo: (response) => ({
username: response?.DisplayName,
avatar: response?.Avatar,
}),
}
// 开源中国
export const OSChinaLoginConfig = {
api: 'https://www.oschina.net/action/user/detail?format=json',
method: 'GET',
checkLogin: (response) => response?.id,
getUserInfo: (response) => ({
username: response?.name,
avatar: response?.portrait,
}),
}
// 51CTO
export const CTO51LoginConfig = {
api: 'https://home.51cto.com/api/user/info/getUserBasicInfo',
method: 'GET',
checkLogin: (response) => response?.code === 200 && response?.data?.id,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.headpic,
}),
}
// InfoQ
export const InfoQLoginConfig = {
api: 'https://www.infoq.cn/public/v1/my/menu',
method: 'POST',
body: JSON.stringify({}),
headers: { 'Content-Type': 'application/json' },
checkLogin: (response) => response?.code === 0 && response?.data?.username,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.avatar,
}),
}
// 简书
export const JianshuLoginConfig = {
api: 'https://www.jianshu.com/settings/basic',
method: 'GET',
isHtml: true,
checkLogin: (html) => !html.includes('登录'),
getUserInfo: () => ({ username: null, avatar: null }),
}
// 百家号
export const BaijiahaoLoginConfig = {
api: 'https://baijiahao.baidu.com/builder/app/appinfo',
method: 'GET',
checkLogin: (response) => response?.errno === 0 && response?.data?.user?.name,
getUserInfo: (response) => ({
username: response?.data?.user?.name,
avatar: response?.data?.user?.avatar,
}),
}
// 网易号
export const WangyihaoLoginConfig = {
api: 'https://mp.163.com/api/account/info',
method: 'GET',
checkLogin: (response) => response?.code === 1000 && response?.data?.nickname,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.headImg,
}),
}
// 腾讯云开发者社区
export const TencentCloudLoginConfig = {
api: 'https://cloud.tencent.com/developer/api/user/session',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.data?.uin,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.avatar,
}),
}
// Medium
export const MediumLoginConfig = {
api: 'https://medium.com/me/stats?format=json',
method: 'GET',
checkLogin: (response) => response?.success,
getUserInfo: (response) => ({
username: response?.payload?.user?.name,
avatar: response?.payload?.user?.imageId ? `https://miro.medium.com/v2/resize:fill:64:64/${response.payload.user.imageId}` : null,
}),
}
// 少数派
export const SspaiLoginConfig = {
api: 'https://sspai.com/api/v1/user/info/get',
method: 'GET',
checkLogin: (response) => response?.error === 0 && response?.data?.nickname,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.avatar,
}),
}
// 搜狐号
export const SohuLoginConfig = {
api: 'https://mp.sohu.com/mpbp/bp/account/list',
method: 'GET',
checkLogin: (response) => response?.success === true && response?.data?.total > 0,
getUserInfo: (response) => {
const account = response?.data?.data?.[0]?.accounts?.[0]
let avatar = account?.avatar || ''
if (avatar && avatar.startsWith('//')) {
avatar = 'https:' + avatar
}
return {
username: account?.nickName,
avatar: avatar,
}
},
}
// B站
export const BilibiliLoginConfig = {
api: 'https://api.bilibili.com/x/web-interface/nav',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.data?.isLogin === true,
getUserInfo: (response) => ({
username: response?.data?.uname,
avatar: response?.data?.face,
}),
}
// 微博
export const WeiboLoginConfig = {
api: 'https://card.weibo.com/article/v3/aj/editor/draft/list?page=1&pagesize=1',
method: 'GET',
checkLogin: (response) => response?.code === 100000,
getUserInfo: () => ({ username: null, avatar: null }),
}
// 阿里云开发者社区
export const AliyunLoginConfig = {
api: 'https://developer.aliyun.com/developer/api/my/user/getUser',
method: 'GET',
checkLogin: (response) => response?.success && response?.data?.accountId,
getUserInfo: (response) => ({
username: response?.data?.nick,
avatar: response?.data?.avatar,
}),
}
// 华为云开发者博客
export const HuaweiCloudLoginConfig = {
api: 'https://bbs.huaweicloud.com/uucenter/user/getUserInfoByUserNos',
method: 'GET',
checkLogin: (response) => response?.result === 'success',
getUserInfo: () => ({ username: null, avatar: null }),
}
// 华为开发者联盟
export const HuaweiDevLoginConfig = {
api: 'https://developer.huawei.com/consumer/cn/doc/distribution/dev-web/overview-web-0000001049579028',
method: 'GET',
isHtml: true,
checkLogin: (html) => html.includes('logout'),
getUserInfo: () => ({ username: null, avatar: null }),
}
// Twitter/X
export const TwitterLoginConfig = {
api: 'https://api.x.com/1.1/account/settings.json',
method: 'GET',
checkLogin: (response) => response?.screen_name,
getUserInfo: (response) => ({
username: response?.screen_name,
avatar: null,
}),
}
// 百度千帆
export const QianfanLoginConfig = {
api: 'https://qianfan.cloud.baidu.com/api/developer/common/userInfo',
method: 'GET',
checkLogin: (response) => response?.code === 200 && response?.data?.userName,
getUserInfo: (response) => ({
username: response?.data?.userName,
avatar: null,
}),
}
// 支付宝开放平台
export const AlipayOpenLoginConfig = {
api: 'https://open.alipay.com/api/user/getUserInfo',
method: 'GET',
checkLogin: (response) => response?.data?.loginId,
getUserInfo: (response) => ({
username: response?.data?.loginId,
avatar: response?.data?.avatar,
}),
}
// ModelScope
export const ModelScopeLoginConfig = {
api: 'https://modelscope.cn/api/v1/user/current',
method: 'GET',
checkLogin: (response) => response?.Success && response?.Data?.Name,
getUserInfo: (response) => ({
username: response?.Data?.Name,
avatar: response?.Data?.Avatar,
}),
}
// 火山引擎
export const VolcengineLoginConfig = {
api: 'https://developer.volcengine.com/api/console/user/info',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.data?.display_name,
getUserInfo: (response) => ({
username: response?.data?.display_name,
avatar: null,
}),
}
// 抖音
export const DouyinLoginConfig = {
api: 'https://creator.douyin.com/web/api/media/user/info/',
method: 'GET',
checkLogin: (response) => response?.status_code === 0 && (response?.user?.uid || response?.user_info?.uid),
getUserInfo: (response) => ({
username: response?.user?.nickname || response?.user_info?.nickname,
avatar: (response?.user?.avatar_thumb?.url_list?.[0] || response?.user_info?.avatar_thumb?.url_list?.[0]),
}),
}
// 小红书
export const XiaohongshuLoginConfig = {
api: 'https://creator.xiaohongshu.com/api/galaxy/user/index',
method: 'GET',
checkLogin: (response) => response?.success && response?.data?.id,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.portrait,
}),
}
// 电子发烧友
export const ElecfansLoginConfig = {
api: 'https://bbs.elecfans.com/api/login/check',
method: 'GET',
checkLogin: (response) => response?.status === 1,
getUserInfo: (response) => ({
username: response?.data?.username,
avatar: response?.data?.avatar,
}),
}
// 统一的 LOGIN_CHECK_CONFIG 对象(按平台 ID 索引)
export const LOGIN_CHECK_CONFIG = {
csdn: CSDNLoginConfig,
juejin: JuejinLoginConfig,
wechat: WechatLoginConfig,
zhihu: ZhihuLoginConfig,
toutiao: ToutiaoLoginConfig,
segmentfault: SegmentFaultLoginConfig,
cnblogs: CnblogsLoginConfig,
oschina: OSChinaLoginConfig,
cto51: CTO51LoginConfig,
infoq: InfoQLoginConfig,
jianshu: JianshuLoginConfig,
baijiahao: BaijiahaoLoginConfig,
wangyihao: WangyihaoLoginConfig,
tencentcloud: TencentCloudLoginConfig,
medium: MediumLoginConfig,
sspai: SspaiLoginConfig,
sohu: SohuLoginConfig,
bilibili: BilibiliLoginConfig,
weibo: WeiboLoginConfig,
aliyun: AliyunLoginConfig,
huaweicloud: HuaweiCloudLoginConfig,
huaweidev: HuaweiDevLoginConfig,
twitter: TwitterLoginConfig,
qianfan: QianfanLoginConfig,
alipayopen: AlipayOpenLoginConfig,
modelscope: ModelScopeLoginConfig,
volcengine: VolcengineLoginConfig,
douyin: DouyinLoginConfig,
xiaohongshu: XiaohongshuLoginConfig,
elecfans: ElecfansLoginConfig,
}
export * from './src/configs.js'
export * from './src/detect.js'
export * from './src/utils.js'
+361
View File
@@ -0,0 +1,361 @@
/**
* @cose/detection - Platform login detection module
*
* This package provides login detection configurations for all supported platforms.
* Each config includes:
* - api: The API endpoint to check login status
* - method: HTTP method (GET/POST)
* - checkLogin: Function to determine if user is logged in from response
* - getUserInfo: Function to extract username and avatar from response
*/
// 掘金
export const JuejinLoginConfig = {
api: 'https://api.juejin.cn/user_api/v1/user/get',
method: 'GET',
checkLogin: (response) => response?.err_no === 0 && response?.data?.user_id,
getUserInfo: (response) => ({
username: response?.data?.user_name,
avatar: response?.data?.avatar_large,
}),
}
// 微信公众号
export const WechatLoginConfig = {
api: 'https://mp.weixin.qq.com/cgi-bin/logininfo?action=logininfo&lang=zh_CN',
method: 'GET',
checkLogin: (response) => response?.base_resp?.ret === 0 && response?.user_name,
getUserInfo: (response) => ({
username: response?.nick_name || response?.user_name,
avatar: response?.head_img,
}),
}
// 知乎
export const ZhihuLoginConfig = {
api: 'https://www.zhihu.com/api/v4/me',
method: 'GET',
checkLogin: (response) => response?.id,
getUserInfo: (response) => ({
username: response?.name,
avatar: response?.avatar_url,
}),
}
// 头条号
export const ToutiaoLoginConfig = {
api: 'https://mp.toutiao.com/mp/agw/creator_center/user_info?app_id=1231',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.name,
getUserInfo: (response) => ({
username: response?.name,
avatar: response?.avatar_url,
}),
}
// SegmentFault
export const SegmentFaultLoginConfig = {
api: 'https://segmentfault.com/gateway/user/me',
method: 'GET',
checkLogin: (response) => response?.status === 0 && response?.data?.id,
getUserInfo: (response) => ({
username: response?.data?.name,
avatar: response?.data?.avatar_url,
}),
}
// 博客园
export const CnblogsLoginConfig = {
api: 'https://www.cnblogs.com/api/users/current',
method: 'GET',
checkLogin: (response) => response?.UserId,
getUserInfo: (response) => ({
username: response?.DisplayName,
avatar: response?.Avatar,
}),
}
// 开源中国
export const OSChinaLoginConfig = {
api: 'https://www.oschina.net/action/user/detail?format=json',
method: 'GET',
checkLogin: (response) => response?.id,
getUserInfo: (response) => ({
username: response?.name,
avatar: response?.portrait,
}),
}
// 51CTO
export const CTO51LoginConfig = {
api: 'https://home.51cto.com/api/user/info/getUserBasicInfo',
method: 'GET',
checkLogin: (response) => response?.code === 200 && response?.data?.id,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.headpic,
}),
}
// InfoQ
export const InfoQLoginConfig = {
api: 'https://www.infoq.cn/public/v1/my/menu',
method: 'POST',
body: JSON.stringify({}),
headers: { 'Content-Type': 'application/json' },
checkLogin: (response) => response?.code === 0 && response?.data?.username,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.avatar,
}),
}
// 简书
export const JianshuLoginConfig = {
api: 'https://www.jianshu.com/settings/basic',
method: 'GET',
isHtml: true,
checkLogin: (html) => !html.includes('登录'),
getUserInfo: () => ({ username: null, avatar: null }),
}
// 百家号
export const BaijiahaoLoginConfig = {
api: 'https://baijiahao.baidu.com/builder/app/appinfo',
method: 'GET',
checkLogin: (response) => response?.errno === 0 && response?.data?.user?.name,
getUserInfo: (response) => ({
username: response?.data?.user?.name,
avatar: response?.data?.user?.avatar,
}),
}
// 网易号
export const WangyihaoLoginConfig = {
api: 'https://mp.163.com/api/account/info',
method: 'GET',
checkLogin: (response) => response?.code === 1000 && response?.data?.nickname,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.headImg,
}),
}
// 腾讯云开发者社区
export const TencentCloudLoginConfig = {
api: 'https://cloud.tencent.com/developer/api/user/session',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.data?.uin,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.avatar,
}),
}
// Medium
export const MediumLoginConfig = {
api: 'https://medium.com/me/stats?format=json',
method: 'GET',
checkLogin: (response) => response?.success,
getUserInfo: (response) => ({
username: response?.payload?.user?.name,
avatar: response?.payload?.user?.imageId ? `https://miro.medium.com/v2/resize:fill:64:64/${response.payload.user.imageId}` : null,
}),
}
// 少数派
export const SspaiLoginConfig = {
api: 'https://sspai.com/api/v1/user/info/get',
method: 'GET',
checkLogin: (response) => response?.error === 0 && response?.data?.nickname,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.avatar,
}),
}
// 搜狐号
export const SohuLoginConfig = {
api: 'https://mp.sohu.com/mpbp/bp/account/list',
method: 'GET',
checkLogin: (response) => response?.success === true && response?.data?.total > 0,
getUserInfo: (response) => {
const account = response?.data?.data?.[0]?.accounts?.[0]
let avatar = account?.avatar || ''
if (avatar && avatar.startsWith('//')) {
avatar = 'https:' + avatar
}
return {
username: account?.nickName,
avatar: avatar,
}
},
}
// B站
export const BilibiliLoginConfig = {
api: 'https://api.bilibili.com/x/web-interface/nav',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.data?.isLogin === true,
getUserInfo: (response) => ({
username: response?.data?.uname,
avatar: response?.data?.face,
}),
}
// 微博
export const WeiboLoginConfig = {
api: 'https://card.weibo.com/article/v3/aj/editor/draft/list?page=1&pagesize=1',
method: 'GET',
checkLogin: (response) => response?.code === 100000,
getUserInfo: () => ({ username: null, avatar: null }),
}
// 阿里云开发者社区
export const AliyunLoginConfig = {
api: 'https://developer.aliyun.com/developer/api/my/user/getUser',
method: 'GET',
checkLogin: (response) => response?.success && response?.data?.accountId,
getUserInfo: (response) => ({
username: response?.data?.nick,
avatar: response?.data?.avatar,
}),
}
// 华为云开发者博客
export const HuaweiCloudLoginConfig = {
api: 'https://bbs.huaweicloud.com/uucenter/user/getUserInfoByUserNos',
method: 'GET',
checkLogin: (response) => response?.result === 'success',
getUserInfo: () => ({ username: null, avatar: null }),
}
// 华为开发者联盟
export const HuaweiDevLoginConfig = {
api: 'https://developer.huawei.com/consumer/cn/doc/distribution/dev-web/overview-web-0000001049579028',
method: 'GET',
isHtml: true,
checkLogin: (html) => html.includes('logout'),
getUserInfo: () => ({ username: null, avatar: null }),
}
// Twitter/X
export const TwitterLoginConfig = {
api: 'https://api.x.com/1.1/account/settings.json',
method: 'GET',
checkLogin: (response) => response?.screen_name,
getUserInfo: (response) => ({
username: response?.screen_name,
avatar: null,
}),
}
// 百度千帆
export const QianfanLoginConfig = {
api: 'https://qianfan.cloud.baidu.com/api/developer/common/userInfo',
method: 'GET',
checkLogin: (response) => response?.code === 200 && response?.data?.userName,
getUserInfo: (response) => ({
username: response?.data?.userName,
avatar: null,
}),
}
// 支付宝开放平台
export const AlipayOpenLoginConfig = {
api: 'https://open.alipay.com/api/user/getUserInfo',
method: 'GET',
checkLogin: (response) => response?.data?.loginId,
getUserInfo: (response) => ({
username: response?.data?.loginId,
avatar: response?.data?.avatar,
}),
}
// ModelScope
export const ModelScopeLoginConfig = {
api: 'https://modelscope.cn/api/v1/user/current',
method: 'GET',
checkLogin: (response) => response?.Success && response?.Data?.Name,
getUserInfo: (response) => ({
username: response?.Data?.Name,
avatar: response?.Data?.Avatar,
}),
}
// 火山引擎
export const VolcengineLoginConfig = {
api: 'https://developer.volcengine.com/api/console/user/info',
method: 'GET',
checkLogin: (response) => response?.code === 0 && response?.data?.display_name,
getUserInfo: (response) => ({
username: response?.data?.display_name,
avatar: null,
}),
}
// 抖音
export const DouyinLoginConfig = {
api: 'https://creator.douyin.com/web/api/media/user/info/',
method: 'GET',
checkLogin: (response) => response?.status_code === 0 && (response?.user?.uid || response?.user_info?.uid),
getUserInfo: (response) => ({
username: response?.user?.nickname || response?.user_info?.nickname,
avatar: (response?.user?.avatar_thumb?.url_list?.[0] || response?.user_info?.avatar_thumb?.url_list?.[0]),
}),
}
// 小红书
export const XiaohongshuLoginConfig = {
api: 'https://creator.xiaohongshu.com/api/galaxy/user/index',
method: 'GET',
checkLogin: (response) => response?.success && response?.data?.id,
getUserInfo: (response) => ({
username: response?.data?.nickname,
avatar: response?.data?.portrait,
}),
}
// 电子发烧友
export const ElecfansLoginConfig = {
api: 'https://bbs.elecfans.com/api/login/check',
method: 'GET',
checkLogin: (response) => response?.status === 1,
getUserInfo: (response) => ({
username: response?.data?.username,
avatar: response?.data?.avatar,
}),
}
// 统一的 LOGIN_CHECK_CONFIG 对象(按平台 ID 索引)
export const LOGIN_CHECK_CONFIG = {
juejin: JuejinLoginConfig,
wechat: WechatLoginConfig,
zhihu: ZhihuLoginConfig,
toutiao: ToutiaoLoginConfig,
segmentfault: SegmentFaultLoginConfig,
cnblogs: CnblogsLoginConfig,
oschina: OSChinaLoginConfig,
cto51: CTO51LoginConfig,
infoq: InfoQLoginConfig,
jianshu: JianshuLoginConfig,
baijiahao: BaijiahaoLoginConfig,
wangyihao: WangyihaoLoginConfig,
tencentcloud: TencentCloudLoginConfig,
medium: MediumLoginConfig,
sspai: SspaiLoginConfig,
sohu: SohuLoginConfig,
bilibili: BilibiliLoginConfig,
weibo: WeiboLoginConfig,
aliyun: AliyunLoginConfig,
huaweicloud: HuaweiCloudLoginConfig,
huaweidev: HuaweiDevLoginConfig,
twitter: TwitterLoginConfig,
qianfan: QianfanLoginConfig,
alipayopen: AlipayOpenLoginConfig,
modelscope: ModelScopeLoginConfig,
volcengine: VolcengineLoginConfig,
douyin: DouyinLoginConfig,
xiaohongshu: XiaohongshuLoginConfig,
elecfans: ElecfansLoginConfig,
}
+52
View File
@@ -0,0 +1,52 @@
import { LOGIN_CHECK_CONFIG } from './configs.js'
import { checkLoginByCookie, detectByApi } from './utils.js'
import { detectCSDNUser } from './platforms/csdn.js'
import { detectOSChinaUser } from './platforms/oschina.js'
import * as specialDetectors from './detectors.js'
export async function detectUser(platformId) {
console.log(`[COSE] Detection: Checking ${platformId}`)
// 1. Specialized Detectors
if (platformId === 'csdn') return detectCSDNUser()
if (platformId === 'oschina') return detectOSChinaUser()
// Detectors from bundled file
// Convention: detect{CapitalizedPlatformId}User
// Map platformId to function name
const specialMap = {
'alipayopen': specialDetectors.detectAlipayUser,
'weibo': specialDetectors.detectWeiboUser,
'wechat': specialDetectors.detectWechatUser,
'xiaohongshu': specialDetectors.detectXiaohongshuUser,
'elecfans': specialDetectors.detectElecfansUser,
'huaweicloud': specialDetectors.detectHuaweiCloudUser,
'huaweidev': specialDetectors.detectHuaweiDevUser,
'sspai': specialDetectors.detectSspaiUser,
'aliyun': specialDetectors.detectAliyunUser,
'sohu': specialDetectors.detectSohuUser,
'medium': specialDetectors.detectMediumUser,
'tencentcloud': specialDetectors.detectTencentCloudUser,
'qianfan': specialDetectors.detectQianfanUser,
'twitter': specialDetectors.detectTwitterUser,
}
if (specialMap[platformId]) {
return specialMap[platformId]()
}
// 2. Generic Config-based Detection
const config = LOGIN_CHECK_CONFIG[platformId]
if (config) {
if (config.useCookie || (config.cookieNames && config.cookieNames.length > 0)) {
return checkLoginByCookie(platformId, config)
}
// Default to API check if API is defined
if (config.api) {
return detectByApi(platformId, config)
}
}
return { loggedIn: false, error: 'No detection available' }
}
+644
View File
@@ -0,0 +1,644 @@
/**
* Specialized platform detectors moved from background.js
*/
export async function detectAlipayUser() {
const platformId = 'alipayopen'
try {
// 从 storage 读取缓存的用户信息
const stored = await chrome.storage.local.get('alipayopen_user')
const cachedUser = stored.alipayopen_user
if (cachedUser && cachedUser.loggedIn) {
// 检查缓存是否过期(1小时)
const cacheAge = Date.now() - (cachedUser.cachedAt || 0)
const maxAge = 1 * 60 * 60 * 1000 // 1 hour
if (cacheAge < maxAge) {
console.log(`[COSE] alipayopen 从缓存读取用户信息:`, cachedUser.username)
return {
loggedIn: true,
username: cachedUser.username || '',
avatar: cachedUser.avatar || ''
}
} else {
console.log(`[COSE] alipayopen 缓存已过期`)
// 清除过期缓存
await chrome.storage.local.remove('alipayopen_user')
}
}
// 尝试从已打开的支付宝页面获取用户信息并缓存
let tabs = await chrome.tabs.query({ url: 'https://open.alipay.com/*' })
if (tabs.length === 0) {
tabs = await chrome.tabs.query({ url: 'https://*.alipay.com/*' })
}
if (tabs.length > 0) {
try {
// 在已打开的页面上下文中调用 API
const results = await chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: async () => {
try {
const response = await fetch('https://developerportal.alipay.com/octopus/service.do', {
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
body: 'data=%5B%7B%7D%5D&serviceName=alipay.open.developerops.forum.user.query',
})
if (!response.ok) return null
return await response.json()
} catch (e) {
return null
}
}
})
const data = results?.[0]?.result
console.log(`[COSE] alipayopen API 数据:`, data)
if (data?.stat === 'ok' && data?.data?.isLoginUser === 1) {
const username = data.data.nickname || ''
const avatar = data.data.avatar || ''
// 缓存用户信息
await chrome.storage.local.set({
alipayopen_user: {
loggedIn: true,
username,
avatar,
cachedAt: Date.now()
}
})
console.log(`[COSE] alipayopen 用户信息:`, username, avatar ? '有头像' : '无头像')
return { loggedIn: true, username, avatar }
}
} catch (e) {
console.log(`[COSE] alipayopen 从页面获取用户信息失败:`, e.message)
}
}
console.log(`[COSE] alipayopen 未检测到登录状态`)
return { loggedIn: false }
} catch (e) {
console.log(`[COSE] alipayopen 检测失败:`, e.message)
return { loggedIn: false, error: e.message }
}
}
export async function detectWeiboUser() {
const platformId = 'weibo'
try {
// 检查 card.weibo.com 的 SUBP cookie
const subpCookie = await chrome.cookies.get({
url: 'https://card.weibo.com',
name: 'SUBP'
})
// 也检查 ALF cookie
const alfCookie = await chrome.cookies.get({
url: 'https://card.weibo.com',
name: 'ALF'
})
if (!subpCookie && !alfCookie) {
console.log(`[COSE] weibo 未找到登录 cookie,未登录`)
return { loggedIn: false }
}
// 有 cookie,通过 fetch HTML 获取用户信息
let username = ''
let avatar = ''
try {
// 获取所有相关 cookies 并手动添加到请求
const weiboCookies = await chrome.cookies.getAll({ domain: '.weibo.com' })
const cardCookies = await chrome.cookies.getAll({ domain: 'card.weibo.com' })
const sinaCookies = await chrome.cookies.getAll({ domain: '.sina.com.cn' })
const allCookies = [...weiboCookies, ...cardCookies, ...sinaCookies]
const cookieString = allCookies.map(c => `${c.name}=${c.value}`).join('; ')
const response = await fetch('https://card.weibo.com/article/v5/editor', {
method: 'GET',
headers: {
'Cookie': cookieString,
},
credentials: 'include',
})
const html = await response.text()
// 从 HTML 中提取用户名
const nickMatch = html.match(/"nick"\s*:\s*"([^"]+)"/)
if (nickMatch) {
username = nickMatch[1]
} else {
// 深度查找 nick
const altNickMatch = html.match(/\\"nick\\"\s*:\s*\\"([^\\"]+)\\"/)
if (altNickMatch) {
username = altNickMatch[1]
}
}
// 从 HTML 中提取头像
const avatarMatch = html.match(/"avatar_large"\s*:\s*"([^"]+)"/)
if (avatarMatch) {
avatar = avatarMatch[1].replace(/\\/g, '')
} else {
const altAvatarMatch = html.match(/\\"avatar_large\\"\s*:\s*\\"([^\\"]+)\\"/)
if (altAvatarMatch) {
let rawAvatar = altAvatarMatch[1].replace(/\\\\\\\//g, '/')
if (rawAvatar.includes('sinaimg.cn')) {
avatar = rawAvatar.split('?')[0]
} else {
avatar = rawAvatar
}
}
}
console.log(`[COSE] weibo 用户信息: ${username}`)
} catch (e) {
console.log(`[COSE] weibo 获取用户详情失败:`, e.message)
}
if (!username) {
return { loggedIn: false }
}
return { loggedIn: true, username, avatar }
} catch (e) {
console.log(`[COSE] weibo 检测失败:`, e.message)
return { loggedIn: false }
}
}
export async function detectWechatUser() {
const platformId = 'wechat'
try {
// 先检查缓存
const stored = await chrome.storage.local.get('wechat_user')
const cachedUser = stored.wechat_user
if (cachedUser && cachedUser.loggedIn) {
const cacheAge = Date.now() - (cachedUser.cachedAt || 0)
const maxAge = 1 * 60 * 60 * 1000 // 1 hour
if (cacheAge < maxAge) {
console.log(`[COSE] wechat 从缓存读取:`, cachedUser.username)
return {
loggedIn: true,
username: cachedUser.username || '',
avatar: cachedUser.avatar || ''
}
} else {
await chrome.storage.local.remove('wechat_user')
}
}
// 优先尝试在已打开的微信公众号页面中检测
const tabs = await chrome.tabs.query({ url: 'https://mp.weixin.qq.com/*' })
if (tabs.length > 0) {
try {
const results = await chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: () => {
const wxData = window.wx?.data
if (wxData && wxData.nick_name) {
return {
loggedIn: true,
username: wxData.nick_name || wxData.user_name || '',
avatar: wxData.head_img || '',
token: wxData.t || ''
}
}
return null
}
})
const result = results?.[0]?.result
if (result && result.loggedIn) {
const userInfo = { ...result, cachedAt: Date.now() }
await chrome.storage.local.set({ wechat_user: userInfo })
return {
loggedIn: true,
username: userInfo.username || '',
avatar: userInfo.avatar || ''
}
}
} catch (e) {
console.log(`[COSE] wechat 页面脚本执行失败:`, e.message)
}
}
// 备用方案:fetch 首页并解析 HTML
try {
const response = await fetch('https://mp.weixin.qq.com/', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'text/html' }
})
const html = await response.text()
if (html.includes('请使用微信扫描') || html.includes('扫码登录')) {
return { loggedIn: false }
}
const nickMatch = html.match(/nick_name\s*[:=]\s*["']([^"']+)["']/)
const avatarMatch = html.match(/head_img\s*[:=]\s*["']([^"']+)["']/)
if (nickMatch) {
const username = nickMatch[1]
const avatar = avatarMatch ? avatarMatch[1] : ''
await chrome.storage.local.set({
wechat_user: {
loggedIn: true,
username,
avatar,
cachedAt: Date.now()
}
})
return { loggedIn: true, username, avatar }
}
} catch (e) {
console.log(`[COSE] wechat fetch 失败:`, e.message)
}
return { loggedIn: false }
} catch (e) {
console.log(`[COSE] wechat 检测失败:`, e.message)
return { loggedIn: false }
}
}
export async function detectXiaohongshuUser() {
const platformId = 'xiaohongshu'
try {
// 先检查缓存
const stored = await chrome.storage.local.get('xiaohongshu_user')
const cachedUser = stored.xiaohongshu_user
if (cachedUser && cachedUser.loggedIn) {
const cacheAge = Date.now() - (cachedUser.cachedAt || 0)
const maxAge = 7 * 24 * 60 * 60 * 1000 // 7 days
if (cacheAge < maxAge) {
console.log(`[COSE] xiaohongshu 从缓存读取:`, cachedUser.username)
return { loggedIn: true, username: cachedUser.username || '', avatar: cachedUser.avatar || '' }
} else {
await chrome.storage.local.remove('xiaohongshu_user')
}
}
// 缓存无效,尝试在已打开的小红书页面中检测
const tabs = await chrome.tabs.query({ url: 'https://creator.xiaohongshu.com/*' })
if (tabs.length > 0) {
const results = await chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: async () => {
try {
const response = await fetch('https://creator.xiaohongshu.com/api/galaxy/user/info', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
})
if (!response.ok) return null
const data = await response.json()
if (data?.success === true && data?.code === 0 && data?.data?.userId) {
return {
loggedIn: true,
username: data.data.userName || data.data.redId || '',
avatar: data.data.userAvatar || '',
userId: data.data.userId
}
}
return null
} catch (e) { return null }
}
})
const result = results?.[0]?.result
if (result && result.loggedIn) {
const userInfo = { ...result, cachedAt: Date.now() }
await chrome.storage.local.set({ xiaohongshu_user: userInfo })
return { loggedIn: true, username: userInfo.username || '', avatar: userInfo.avatar || '' }
}
}
return { loggedIn: false }
} catch (e) {
console.log(`[COSE] xiaohongshu 检测失败:`, e.message)
return { loggedIn: false }
}
}
export async function detectElecfansUser() {
const platformId = 'elecfans'
try {
const authCookie = await chrome.cookies.get({ url: 'https://www.elecfans.com', name: 'auth' })
const authWwwCookie = await chrome.cookies.get({ url: 'https://www.elecfans.com', name: 'auth_www' })
if (!authCookie && !authWwwCookie) {
return { loggedIn: false }
}
try {
const response = await fetch('https://www.elecfans.com/webapi/passport/checklogin?_=' + Date.now(), {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json, text/javascript, */*; q=0.01' }
})
if (!response.ok) return { loggedIn: true, username: '', avatar: '' }
const data = await response.json()
if (data && data.uid) {
const username = data.username || ''
const avatar = data.avatar || ''
return { loggedIn: true, username, avatar }
} else {
return { loggedIn: true, username: '', avatar: '' }
}
} catch (e) {
return { loggedIn: true, username: '', avatar: '' }
}
} catch (e) {
return { loggedIn: false }
}
}
export async function detectHuaweiCloudUser() {
const platformId = 'huaweicloud'
try {
let tabs = await chrome.tabs.query({ url: 'https://bbs.huaweicloud.com/*' })
if (tabs.length === 0) tabs = await chrome.tabs.query({ url: 'https://*.huaweicloud.com/*' })
if (tabs.length === 0) return { loggedIn: false }
try {
const results = await chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: () => {
return new Promise((resolve) => {
const csrf = document.cookie.match(/csrf=([^;]+)/)?.[1] || ''
fetch('https://devdata.huaweicloud.com/rest/developer/fwdu/rest/developer/user/hdcommunityservice/v1/member/get-personal-info', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json', 'csrf': csrf }
})
.then(response => response.ok ? response.json() : null)
.then(data => {
if (data && data.memName) {
resolve({ memName: data.memName, memAlias: data.memAlias, memPhoto: data.memPhoto })
} else {
resolve(null)
}
})
.catch(() => resolve(null))
})
}
})
let data = results?.[0]?.result
if (data && typeof data.then === 'function') data = await data // handle promise result if applicable
if (data && data.memName) {
return { loggedIn: true, username: data.memAlias || data.memName, avatar: data.memPhoto || '' }
}
} catch (e) { }
return { loggedIn: false }
} catch (e) {
return { loggedIn: false }
}
}
export async function detectHuaweiDevUser() {
const platformId = 'huaweidev'
try {
const userInfoCookie = await chrome.cookies.get({ url: 'https://developer.huawei.com', name: 'developer_userinfo' })
if (!userInfoCookie || !userInfoCookie.value) return { loggedIn: false }
let csrfToken = ''
try {
const userInfoData = JSON.parse(decodeURIComponent(userInfoCookie.value))
csrfToken = userInfoData.csrftoken || ''
} catch (e) { }
if (!csrfToken) {
const csrfCookie = await chrome.cookies.get({ url: 'https://developer.huawei.com', name: 'csrfToken' })
csrfToken = csrfCookie?.value || ''
}
if (!csrfToken) return { loggedIn: true, username: '', avatar: '' }
const response = await fetch('https://svc-drcn.developer.huawei.com/codeserver/Common/v1/delegate', {
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
'x-hd-csrf': csrfToken,
},
body: JSON.stringify({ svc: 'GOpen.User.getInfo', reqType: 0, reqJson: JSON.stringify({ getNickName: '1' }) })
})
const data = await response.json()
if (data.returnCode === '0' && data.resJson) {
const userInfo = JSON.parse(data.resJson)
return {
loggedIn: true,
username: userInfo.displayName || userInfo.loginID || '',
avatar: userInfo.headPictureURL || ''
}
} else {
return { loggedIn: true, username: '', avatar: '' }
}
} catch (e) {
return { loggedIn: false }
}
}
export async function detectSspaiUser() {
try {
const jwtCookie = await chrome.cookies.get({ url: 'https://sspai.com', name: 'sspai_jwt_token' })
if (!jwtCookie || !jwtCookie.value) return { loggedIn: false }
const token = jwtCookie.value
const response = await fetch('https://sspai.com/api/v1/user/info/get', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json', 'Authorization': `Bearer ${token}` }
})
const data = await response.json()
if (data.error === 0 && data.data?.nickname) {
return { loggedIn: true, username: data.data.nickname, avatar: data.data.avatar || '' }
} else {
return { loggedIn: false }
}
} catch (e) { return { loggedIn: false } }
}
export async function detectAliyunUser() {
try {
const ticketCookie = await chrome.cookies.get({ url: 'https://developer.aliyun.com', name: 'login_aliyunid_ticket' })
if (!ticketCookie || !ticketCookie.value) return { loggedIn: false }
const response = await fetch('https://developer.aliyun.com/developer/api/my/user/getUser', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
})
const data = await response.json()
if (data.success && data.data?.nickname) {
return { loggedIn: true, username: data.data.nickname, avatar: data.data.avatar || '' }
}
return { loggedIn: false }
} catch (e) { return { loggedIn: false } }
}
export async function detectSohuUser() {
try {
const ppinfCookie = await chrome.cookies.get({ url: 'https://mp.sohu.com', name: 'ppinf' })
if (!ppinfCookie || !ppinfCookie.value) return { loggedIn: false }
try {
const response = await fetch('https://mp.sohu.com/mpbp/bp/account/list', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
})
const data = await response.json()
if (data.success && data.data?.data?.[0]?.accounts?.[0]) {
const account = data.data.data[0].accounts[0]
let avatar = account.avatar || ''
if (avatar.startsWith('//')) avatar = 'https:' + avatar
return { loggedIn: true, username: account.nickName, avatar }
} else {
return { loggedIn: true, username: '', avatar: '' }
}
} catch (e) {
return { loggedIn: true, username: '', avatar: '' }
}
} catch (e) { return { loggedIn: false } }
}
export async function detectMediumUser() {
try {
const sidCookie = await chrome.cookies.get({ url: 'https://medium.com', name: 'sid' })
const uidCookie = await chrome.cookies.get({ url: 'https://medium.com', name: 'uid' })
if (!sidCookie && !uidCookie) return { loggedIn: false }
const response = await fetch('https://medium.com/me/stats', {
method: 'GET',
credentials: 'include',
})
const html = await response.text()
const finalUrl = response.url
if (finalUrl.includes('/m/signin') || finalUrl.includes('?signIn')) return { loggedIn: false }
const profileMatch = html.match(/"username"\s*:\s*"([^"]+)"/) ||
html.match(/href="https:\/\/medium\.com\/@([^"?\/]+)"/) ||
html.match(/medium\.com\/@([a-zA-Z0-9_]+)/)
if (profileMatch && profileMatch[1] && profileMatch[1] !== 'gmail' && profileMatch[1] !== 'medium') {
const username = profileMatch[1]
const shortName = username.replace(/\d+$/, '')
const avatarPattern = new RegExp(`<img[^>]*alt="${shortName}"[^>]*src="([^"]+)"`)
const avatarMatch = html.match(avatarPattern) || html.match(new RegExp(`<img[^>]*src="([^"]+)"[^>]*alt="${shortName}"`))
return { loggedIn: true, username, avatar: avatarMatch ? avatarMatch[1] : '' }
} else {
return { loggedIn: true, username: '', avatar: '' }
}
} catch (e) { return { loggedIn: false } }
}
export async function detectTencentCloudUser() {
try {
const response = await fetch('https://cloud.tencent.com/developer/creator', {
method: 'GET',
credentials: 'include',
})
const html = await response.text()
const finalUrl = response.url
if (!finalUrl.includes('/creator')) return { loggedIn: false }
if (html.includes('登录/注册') || html.includes('"isLogin":false') || html.includes('"login":false')) return { loggedIn: false }
const userInfoMatch = html.match(/"userInfo"\s*:\s*\{[^}]*"nickname"\s*:\s*"([^"]+)"[^}]*\}/) ||
html.match(/"creatorInfo"\s*:\s*\{[^}]*"nickname"\s*:\s*"([^"]+)"[^}]*\}/) ||
html.match(/"currentUser"\s*:\s*\{[^}]*"nickname"\s*:\s*"([^"]+)"[^}]*\}/)
const creatorNicknameMatch = html.match(/class="creator-info[^"]*"[^>]*>[\s\S]*?<[^>]*class="[^"]*name[^"]*"[^>]*>([^<]+)</) ||
html.match(/"isCreator"\s*:\s*true[\s\S]*?"nickname"\s*:\s*"([^"]+)"/)
const nicknameMatch = userInfoMatch || creatorNicknameMatch
const avatarMatch = html.match(/"userInfo"[\s\S]*?"avatarUrl"\s*:\s*"([^"]+)"/) ||
html.match(/"avatar"\s*:\s*"(https?:\/\/[^"]+)"/)
if (nicknameMatch && nicknameMatch[1]) {
return { loggedIn: true, username: nicknameMatch[1], avatar: avatarMatch ? avatarMatch[1] : '' }
} else {
if (html.includes('创作中心') || html.includes('我的文章')) return { loggedIn: true, username: '', avatar: '' }
return { loggedIn: false }
}
} catch (e) { return { loggedIn: false } }
}
export async function detectQianfanUser() {
try {
const response = await fetch('https://qianfan.cloud.baidu.com/api/community/user/current', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
})
if (!response.ok) return { loggedIn: false }
const data = await response.json()
if (data.success && data.result) {
const username = data.result.displayName || data.result.nickname || ''
const avatar = data.result.avatar || ''
return { loggedIn: true, username, avatar }
} else {
return { loggedIn: false }
}
} catch (e) { return { loggedIn: false } }
}
export async function detectTwitterUser() {
try {
const authTokenCookie = await chrome.cookies.get({ url: 'https://x.com', name: 'auth_token' })
const ct0Cookie = await chrome.cookies.get({ url: 'https://x.com', name: 'ct0' })
if (!authTokenCookie) return { loggedIn: false }
let username = ''
let avatar = ''
try {
const response = await fetch('https://x.com/home', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'text/html' }
})
if (response.ok) {
const html = await response.text()
const screenNameMatch = html.match(/"screen_name"\s*:\s*"([^"]+)"/)
if (screenNameMatch) username = screenNameMatch[1]
const avatarMatch = html.match(/"profile_image_url_https"\s*:\s*"([^"]+)"/)
if (avatarMatch) avatar = avatarMatch[1].replace('_normal.', '_x96.')
}
} catch (e) { }
// If fetch failed or regex failed, try explicit fallback scraping logic (simplified here)
// Note: Full scrape logic from background.js is complex.
// We will assume basic fetch works or just return loggedIn:true if cookie exists
return { loggedIn: true, username, avatar }
} catch (e) { return { loggedIn: false } }
}
+53
View File
@@ -0,0 +1,53 @@
/**
* CSDN platform detection logic
* Strategy:
* 1. Check 'UserName' cookie (reliable indicator of login)
* 2. Use 'UserNick' cookie for display name (UserName is the user ID, UserNick is the display name)
* 3. If logged in, fetch public blog page to get avatar
*/
export async function detectCSDNUser() {
try {
console.log('[COSE] CSDN Detection: Starting cookie check')
const userNameCookie = await chrome.cookies.get({ url: 'https://www.csdn.net', name: 'UserName' })
if (userNameCookie && userNameCookie.value) {
const userId = userNameCookie.value
console.log(`[COSE] CSDN UserName cookie found: ${userId}`)
// UserNick cookie contains the display name (e.g. 'timerring')
const userNickCookie = await chrome.cookies.get({ url: 'https://www.csdn.net', name: 'UserNick' })
const username = (userNickCookie && userNickCookie.value) ? decodeURIComponent(userNickCookie.value) : userId
console.log(`[COSE] CSDN display name: ${username}`)
let avatar = ''
try {
// Fetch public blog page for avatar
const blogUrl = `https://blog.csdn.net/${userId}`
const blogResp = await fetch(blogUrl, { method: 'GET' })
const blogHtml = await blogResp.text()
// Extract avatar
const avatarMatch = blogHtml.match(/<img[^>]*src=["'](https:\/\/(?:profile|i-avatar)\.csdnimg\.cn\/[^"']+)["']/i) ||
blogHtml.match(/<img[^>]*class=["']avatar[^"']*["'][^>]*src=["']([^"']+)["']/i)
if (avatarMatch) {
avatar = avatarMatch[1]
}
} catch (e) {
console.warn('[COSE] CSDN Avatar fetch failed:', e)
}
return {
loggedIn: true,
username: username,
avatar: avatar
}
}
console.log('[COSE] CSDN: No login detected')
return { loggedIn: false }
} catch (e) {
console.error('[COSE] CSDN Detection Error:', e)
return { loggedIn: false, error: e.message }
}
}
@@ -0,0 +1,75 @@
/**
* OSChina platform detection logic
* Strategy:
* 1. Fetch homepage, extract user ID or username
* 2. If only ID found, fetch personal space to get username
*/
export async function detectOSChinaUser() {
try {
console.log('[COSE] OSChina Detection: Starting')
const response = await fetch('https://www.oschina.net/', {
method: 'GET',
credentials: 'include'
})
const html = await response.text()
// Extract User ID
const uidMatch = html.match(/href=["']https:\/\/my\.oschina\.net\/u\/(\d+)["']/i) ||
html.match(/space\.oschina\.net\/u\/(\d+)/i) ||
html.match(/data-user-id=["'](\d+)["']/i)
const userId = uidMatch ? uidMatch[1] : null
// Extract Username
let username = ''
const nameMatch = html.match(/<a[^>]*class="[^"]*user-name[^"]*"[^>]*>([^<]+)<\/a>/i) ||
html.match(/<span[^>]*class="[^"]*nick[^"]*"[^>]*>([^<]+)<\/span>/i) ||
html.match(/title="([^"]+)"[^>]*class="[^"]*avatar/i)
if (nameMatch) {
username = nameMatch[1].trim()
}
// Fallback: Fetch personal space if only ID is known
if (!username && userId) {
console.log(`[COSE] OSChina: Fetching personal space for userId ${userId}`)
try {
const userPageResp = await fetch(`https://my.oschina.net/u/${userId}`, {
method: 'GET',
credentials: 'include'
})
const userPageHtml = await userPageResp.text()
const userPageNameMatch = userPageHtml.match(/<h3[^>]*class="[^"]*header[^"]*"[^>]*>([^<]+)<\/h3>/i) ||
userPageHtml.match(/<div[^>]*class="[^"]*user-name[^"]*"[^>]*>([^<]+)<\/div>/i) ||
userPageHtml.match(/<title>([^<]+)的个人空间<\/title>/i)
if (userPageNameMatch) {
username = userPageNameMatch[1].trim()
}
} catch (e) {
console.warn('[COSE] OSChina: Failed to fetch user page', e)
}
}
// Fallback: Use ID as username if name still missing
if (!username && userId) {
username = userId
}
// Extract Avatar
let avatar = ''
const avatarMatch = html.match(/<img[^>]*src="([^"]+)"[^>]*class="[^"]*avatar/i) ||
html.match(/<img[^>]*class="[^"]*avatar[^"]*"[^>]*src="([^"]+)"/i)
if (avatarMatch) {
avatar = avatarMatch[1]
}
if (username) {
return { loggedIn: true, username, avatar }
}
return { loggedIn: false }
} catch (e) {
console.error('[COSE] OSChina Detection Error:', e)
return { loggedIn: false, error: e.message }
}
}
+121
View File
@@ -0,0 +1,121 @@
export async function logToStorage(msg, data = null) {
if (data) {
console.log(msg, data)
} else {
console.log(msg)
}
}
// 通过 Cookie 检测登录状态
export async function checkLoginByCookie(platformId, config) {
try {
// 直接按名称查找 cookie
const cookieMap = {}
if (config.cookieNames) {
for (const name of config.cookieNames) {
const cookie = await chrome.cookies.get({
url: config.cookieUrl || `https://${config.cookieDomain}`,
name: name
})
if (cookie) {
cookieMap[name] = cookie.value
}
}
}
console.log(`[COSE] ${platformId} 找到的cookies:`, Object.keys(cookieMap))
const hasLoginCookie = config.cookieNames && config.cookieNames.some(name => cookieMap[name])
if (!hasLoginCookie) {
console.log(`[COSE] ${platformId} 未找到登录 cookie`)
return { loggedIn: false }
}
// 自定义 cookie 值检测逻辑(如 InfoQ
if (config.customCheck && config.checkCookieValue) {
console.log(`[COSE] ${platformId} 使用自定义 cookie 检测`)
const result = config.checkCookieValue(cookieMap)
console.log(`[COSE] ${platformId} 自定义检测结果:`, result)
return result
}
let username = ''
let avatar = ''
// 如果配置了从 cookie 获取用户名
if (config.getUsernameFromCookie && config.usernameCookie) {
username = decodeURIComponent(cookieMap[config.usernameCookie] || '')
}
// 使用平台配置的 fetchAvatar 回调获取头像
if (config.fetchAvatar && typeof config.fetchAvatar === 'function') {
try {
const fetchedAvatar = await config.fetchAvatar(cookieMap)
if (fetchedAvatar) {
avatar = fetchedAvatar
console.log(`[COSE] ${platformId} 找到头像:`, avatar)
}
} catch (e) {
console.log(`[COSE] ${platformId} 获取头像失败:`, e.message)
}
}
return { loggedIn: true, username, avatar }
} catch (e) {
console.log(`[COSE] ${platformId} Cookie 检测失败:`, e.message)
return { loggedIn: false, error: e.message }
}
}
// 通用 API 检测逻辑
export async function detectByApi(platformId, config) {
try {
console.log(`[COSE] ${platformId} 开始 API 检测: ${config.api}`)
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 8000)
const fetchOptions = {
method: config.method || 'GET',
credentials: 'include',
headers: {
'Accept': config.isHtml ? 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' : 'application/json',
'Cache-Control': 'no-cache',
...(config.headers || {})
},
signal: controller.signal,
}
if (config.body) {
fetchOptions.body = config.body
}
const response = await fetch(config.api, fetchOptions)
clearTimeout(timeoutId)
console.log(`[COSE] ${platformId} API 响应状态: ${response.status}`)
let data = null
if (config.isHtml) {
// 明确指定为 HTML 响应时,使用 text() 解析
try { data = await response.text() } catch (e) { data = '' }
} else {
// 其他情况尝试 JSON 解析
try { data = await response.json() } catch (e) { data = null }
}
// console.log(`[COSE] ${platformId} API 数据:`, data)
const loggedIn = config.checkLogin(data)
// console.log(`[COSE] ${platformId} checkLogin 结果: ${loggedIn}`)
if (loggedIn && config.getUserInfo) {
const userInfo = config.getUserInfo(data)
console.log(`[COSE] ${platformId} 用户信息:`, userInfo)
return { loggedIn: true, ...userInfo }
}
return { loggedIn: !!loggedIn }
} catch (error) {
console.log(`[COSE] ${platformId} API 检测失败: ${error.message}`)
return { loggedIn: false, error: error.message }
}
}
+3
View File
@@ -17,6 +17,9 @@ importers:
'@cose/core':
specifier: workspace:*
version: link:../../packages/core
'@cose/detection':
specifier: workspace:*
version: link:../../packages/detection
devDependencies:
cac:
specifier: ^6.7.14