Compare commits

...

1 Commits

Author SHA1 Message Date
admin dd9e628ac2 fix(extension): 修复三平台注入填充 2026-08-17 11:23:41 +08:00
5 changed files with 179 additions and 16 deletions
+16 -5
View File
@@ -18,10 +18,6 @@ function normalizeString(value) {
return normalized || undefined
}
function normalizeText(value) {
return String(value || '').replace(/\s+/g, '')
}
function parseAliyunPublishedUrl(rawUrl) {
if (!rawUrl) return null
@@ -100,6 +96,7 @@ function inspectAliyunTaskState(rawUrl, publicUrl, draftSaved = false) {
function fillAliyunContent(title, markdown, html, plainText) {
const markdownContent = markdown || ''
const normalizeText = value => String(value || '').replace(/\s+/g, '')
const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds))
function findFirst(selectors) {
@@ -177,6 +174,11 @@ function fillAliyunContent(title, markdown, html, plainText) {
return normalizeText(value).length >= minimumLength
}
function hasExpectedTitle(input) {
const expectedTitle = normalizeText(title)
return expectedTitle.length === 0 || normalizeText(input?.value) === expectedTitle
}
function hasExpectedRichText(editor) {
const textLength = normalizeText(editor?.innerText || editor?.textContent).length
const imageCount = editor?.querySelectorAll?.('img').length || 0
@@ -206,7 +208,13 @@ function fillAliyunContent(title, markdown, html, plainText) {
'.article-title input',
])
if (!titleInput) return { success: false, error: '未找到阿里云开发者社区标题输入框' }
if (title) setInputValue(titleInput, title)
if (title) {
setInputValue(titleInput, title)
await sleep(200)
if (!hasExpectedTitle(titleInput)) {
return { success: false, error: '阿里云开发者社区未确认接收标题' }
}
}
if (!markdownContent) return { success: true, method: 'title-only' }
await switchToMarkdownEditor()
@@ -220,6 +228,9 @@ function fillAliyunContent(title, markdown, html, plainText) {
}
const textarea = await waitForElement([
'.editor textarea.textarea',
'textarea#article-editor',
'textarea.textarea',
'.markdown-editor textarea',
'.editor-container textarea',
'textarea[placeholder*="正文"]',
+3 -5
View File
@@ -16,10 +16,6 @@ function normalizeString(value) {
return normalized || undefined
}
function normalizeText(value) {
return String(value || '').replace(/\s+/g, '')
}
function getQiehaoDraftId(url) {
return normalizeString(
url.searchParams.get('draftId')
@@ -98,6 +94,7 @@ function inspectQiehaoTaskState(rawUrl, draftSaved = false) {
function fillQiehaoContent(title, html, plainText) {
const contentHtml = html || ''
const contentText = plainText || contentHtml.replace(/<[^>]+>/g, ' ')
const normalizeText = value => String(value || '').replace(/\s+/g, '')
const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds))
function findFirst(selectors) {
@@ -151,7 +148,8 @@ function fillQiehaoContent(title, html, plainText) {
}
function hasExpectedTitle(editor) {
return normalizeText(editor?.textContent).length >= Math.min(1, normalizeText(title).length)
const expectedTitle = normalizeText(title)
return expectedTitle.length === 0 || normalizeText(editor?.textContent) === expectedTitle
}
function hasExpectedContent(editor) {
@@ -9,10 +9,6 @@ const TencentCloudPlatform = {
autoFillSupported: true,
}
function normalizeText(value) {
return String(value || '').replace(/\s+/g, '')
}
function normalizeString(value) {
if (typeof value === 'number') return String(value)
if (typeof value !== 'string') return undefined
@@ -99,6 +95,7 @@ function inspectTencentCloudTaskState(rawUrl, publicUrl, draftSaved = false) {
function fillTencentCloudContent(title, markdown, html, plainText) {
const contentToFill = markdown || plainText || ''
const normalizeText = value => String(value || '').replace(/\s+/g, '')
const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds))
function findFirst(selectors) {
@@ -160,6 +157,11 @@ function fillTencentCloudContent(title, markdown, html, plainText) {
}
}
function hasExpectedTitle(input) {
const expectedTitle = normalizeText(title)
return expectedTitle.length === 0 || normalizeText(input?.value) === expectedTitle
}
function hasExpectedRichText(editor) {
const expectedTextLength = normalizeText(plainText || contentToFill).length
const expectedImageCount = (String(html || '').match(/<img\b/gi) || []).length
@@ -224,7 +226,13 @@ function fillTencentCloudContent(title, markdown, html, plainText) {
'.article-title input',
])
if (!titleInput) return { success: false, error: '未找到腾讯云开发者社区标题输入框' }
if (title) setInputValue(titleInput, title)
if (title) {
setInputValue(titleInput, title)
await sleep(200)
if (!hasExpectedTitle(titleInput)) {
return { success: false, error: '腾讯云开发者社区未确认接收标题' }
}
}
if (!contentToFill) return { success: true, method: 'title-only' }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "AiToEarn - 内容营销助手",
"version": "1.19.2",
"version": "1.19.3",
"description": "AiToEarn 自维护内容扩展:网页采集、账号检测、多平台文章分发与受控人工互动",
"permissions": [
"activeTab",
+146
View File
@@ -1,5 +1,6 @@
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { runInNewContext } from 'node:vm'
import {
BaijiahaoLoginConfig,
ToutiaoLoginConfig,
@@ -33,6 +34,7 @@ import { inspectSohuTaskState } from '../distribution/cose/core/platforms/sohu.j
import { parseAliyunAccount } from '../distribution/cose/detection/src/platforms/aliyun.js'
import {
AliyunPlatform,
fillAliyunContent,
inspectAliyunTaskState,
parseAliyunPublishedUrl,
} from '../distribution/cose/core/platforms/aliyun.js'
@@ -49,12 +51,14 @@ import {
} from '../distribution/cose/core/platforms/oschina.js'
import { parseQiehaoAccount } from '../distribution/cose/detection/src/platforms/qiehao.js'
import {
fillQiehaoContent,
inspectQiehaoTaskState,
parseQiehaoPublishedUrl,
QiehaoPlatform,
} from '../distribution/cose/core/platforms/qiehao.js'
import { parseTencentCloudAccount } from '../distribution/cose/detection/src/platforms/tencentcloud.js'
import {
fillTencentCloudContent,
inspectTencentCloudTaskState,
parseTencentCloudPublishedUrl,
TencentCloudPlatform,
@@ -72,6 +76,131 @@ import {
} from '../distribution/cose/core/platforms/index.js'
import { canReturnExistingPlatformTaskState } from '../distribution/cose/core/task-state.js'
async function runSerializedTitleFill(fillFunction, args, options = {}) {
const truncateTitle = options.truncateTitle === true
class FakeEvent {
constructor(type) {
this.type = type
}
}
class FakeInputElement {
constructor() {
this.disabled = false
this.offsetParent = {}
this.storedValue = ''
}
get value() {
return this.storedValue
}
set value(nextValue) {
const normalized = String(nextValue || '')
this.storedValue = truncateTitle ? normalized.slice(0, 1) : normalized
}
focus() {}
dispatchEvent() {
return true
}
}
class FakeTextAreaElement extends FakeInputElement {}
const input = new FakeInputElement()
const contentEditable = {
disabled: false,
offsetParent: {},
storedHtml: '',
textContent: '',
focus() {},
dispatchEvent() {
return true
},
get innerHTML() {
return this.storedHtml
},
set innerHTML(nextHtml) {
this.storedHtml = String(nextHtml || '')
const normalized = this.storedHtml.replace(/<[^>]+>/g, '').replace(/&lt;/g, '<')
this.textContent = truncateTitle ? normalized.slice(0, 1) : normalized
},
}
const titleElement = options.contentEditable ? contentEditable : input
const selection = {
removeAllRanges() {},
addRange() {},
}
const document = {
documentElement: {},
querySelector() {
return titleElement
},
querySelectorAll() {
return [titleElement]
},
createRange() {
return { selectNodeContents() {} }
},
execCommand() {
return false
},
}
const immediateTimeout = callback => {
callback()
return 1
}
const window = {
HTMLInputElement: FakeInputElement,
HTMLTextAreaElement: FakeTextAreaElement,
getSelection() {
return selection
},
setTimeout: immediateTimeout,
}
class FakeMutationObserver {
observe() {}
disconnect() {}
}
const isolatedFill = runInNewContext(`(${fillFunction.toString()})`, {
ClipboardEvent: FakeEvent,
DataTransfer: class {},
document,
Event: FakeEvent,
HTMLInputElement: FakeInputElement,
HTMLTextAreaElement: FakeTextAreaElement,
InputEvent: FakeEvent,
MutationObserver: FakeMutationObserver,
setTimeout: immediateTimeout,
window,
})
const result = await isolatedFill(...args)
return {
result,
title: options.contentEditable ? contentEditable.textContent : input.value,
}
}
async function assertSerializedTitleFill({ fillFunction, args, contentEditable = false, error }) {
const expectedTitle = args[0]
const success = await runSerializedTitleFill(fillFunction, args, { contentEditable })
assert.equal(success.result.success, true)
assert.equal(success.result.method, 'title-only')
assert.equal(success.title, expectedTitle)
const truncated = await runSerializedTitleFill(fillFunction, args, {
contentEditable,
truncateTitle: true,
})
assert.equal(truncated.result.success, false)
assert.equal(truncated.result.error, error)
}
assert.deepEqual(
inspectJuejinTaskState('https://juejin.cn/editor/drafts/new'),
{
@@ -788,6 +917,23 @@ const qiehaoDetectorSource = await readFile(
)
assert.doesNotMatch(qiehaoDetectorSource, /chrome\.cookies|Cookie\s*:/)
await assertSerializedTitleFill({
fillFunction: fillQiehaoContent,
args: ['企鹅号完整标题验证', '', ''],
contentEditable: true,
error: '企鹅号未确认接收标题',
})
await assertSerializedTitleFill({
fillFunction: fillTencentCloudContent,
args: ['腾讯云完整标题验证', '', '', ''],
error: '腾讯云开发者社区未确认接收标题',
})
await assertSerializedTitleFill({
fillFunction: fillAliyunContent,
args: ['阿里云完整标题验证', '', '', ''],
error: '阿里云开发者社区未确认接收标题',
})
const backgroundSource = await readFile(
new URL('../distribution/cose/background.js', import.meta.url),
'utf8',