Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd9e628ac2 |
@@ -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*="正文"]',
|
||||
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "AiToEarn - 内容营销助手",
|
||||
"version": "1.19.2",
|
||||
"version": "1.19.3",
|
||||
"description": "AiToEarn 自维护内容扩展:网页采集、账号检测、多平台文章分发与受控人工互动",
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
|
||||
@@ -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(/</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',
|
||||
|
||||
Reference in New Issue
Block a user