chore(media-upload): remove Postimages provider

Postimages cannot support WebView automation (site blocks automated uploads).
Imgur works reliably. Keep only Catbox and Imgur as supported providers.
This commit is contained in:
plebeius
2026-02-28 19:13:43 +08:00
parent 274a6fb4b1
commit 7fd3e3fd45
28 changed files with 389 additions and 727 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
*
* Diagnostics (parity with Android): selector match/timeout info included in errors
* for debugging. Poll interval 500ms, timeout per recipe matches Android where
* providers overlap (imgur/postimages: 45s).
* providers overlap (imgur: 45s).
*/
import { BrowserWindow } from 'electron';
import { MEDIA_UPLOAD_RECIPES } from './media-upload-recipes.js';
@@ -29,7 +29,7 @@ export function isDirectMediaUrl(url) {
/**
* Run automated upload for a provider.
* @param {Object} options
* @param {string} options.provider - Provider id (catbox, imgur, postimages)
* @param {string} options.provider - Provider id (catbox, imgur)
* @param {string} options.filePath - Absolute path to the file to upload
* @returns {Promise<{ url: string; provider: string }>}
* @throws {Error} On missing recipe, blocked indicators, timeout, or invalid URL
+1 -5
View File
@@ -29,7 +29,6 @@ describe('media-upload-automation', () => {
it('returns false for non-direct URLs (guards against non-media pages)', () => {
expect(isDirectMediaUrl('https://imgur.com/abc123')).toBe(false);
expect(isDirectMediaUrl('https://imgur.com/upload')).toBe(false);
expect(isDirectMediaUrl('https://postimages.org')).toBe(false);
expect(isDirectMediaUrl('')).toBe(false);
});
@@ -41,12 +40,9 @@ describe('media-upload-automation', () => {
});
describe('media-upload-automation + recipes integration', () => {
it('imgur and postimages success extractors target direct-media domains', () => {
it('imgur success extractor targets direct-media domain', () => {
const imgurSelectors = MEDIA_UPLOAD_RECIPES.imgur.successExtractor.selectorCandidates;
expect(imgurSelectors.some((s) => s.includes('i.imgur.com'))).toBe(true);
const postimagesSelectors = MEDIA_UPLOAD_RECIPES.postimages.successExtractor.selectorCandidates;
expect(postimagesSelectors.some((s) => s.includes('postimg') || s.includes('i.postimg'))).toBe(true);
});
it('all providers have fallback selector chains for file input and submit', () => {
+5 -18
View File
@@ -4,13 +4,12 @@
* Selectors are candidate-based: try each until one matches. Fail fast on blocked indicators.
*
* Android vs Electron differences (documented to prevent drift):
* - Android (MediaUploadRecipes.java): imgur/postimages only; no catbox (uses different flow).
* Uses WebChromeClient file chooser interception; trigger JS clicks file input.
* - Electron: catbox/imgur/postimages. Uses CDP DOM.setFileInputFiles + submit button click.
* - Android (MediaUploadRecipes.java): imgur only; no catbox (uses different flow).
* Uses DataTransfer JS injection.
* - Electron: catbox/imgur. Uses CDP DOM.setFileInputFiles + submit button click.
* Catbox: Electron-only; keep behavior unchanged.
* - Blocked/success selectors: reconciled with Android for imgur/postimages; Electron may add
* extra candidates (e.g. [data-captcha], .login-form) where DOM differs.
* - Timeouts: imgur/postimages 45s (parity); catbox 30s (Electron-only).
* - Blocked/success selectors: reconciled with Android for imgur.
* - Timeouts: imgur 45s; catbox 30s (Electron-only).
*
* @typedef {Object} ProviderRecipe
* @property {string} uploadUrl - Full URL of the provider's upload page
@@ -49,18 +48,6 @@ export const MEDIA_UPLOAD_RECIPES = Object.freeze({
blockedIndicators: Object.freeze(['#challenge', '.captcha', '[data-captcha]', '.g-recaptcha', '#recaptcha', '.login-form', '.signin', '.login']),
timeoutMs: 45_000,
}),
/* Reconciled with Android MediaUploadRecipes (postimages): file input, success extractors, blocked indicators. */
postimages: Object.freeze({
uploadUrl: 'https://postimages.org',
fileInputSelectorCandidates: Object.freeze(['input[type="file"]', 'input[type=file]', '#uploadFile', '.fileinput']),
submitSelectorCandidates: Object.freeze(['button[type="submit"]', '[type="submit"]', '.btn-upload']),
successExtractor: Object.freeze({
selectorCandidates: Object.freeze(['input[readonly][value*="postimg"]', 'a[href*="i.postimg.cc"]', '[class*="direct-link"]', 'textarea']),
attribute: 'value',
}),
blockedIndicators: Object.freeze(['#challenge', '.captcha', '.g-recaptcha', '#recaptcha']),
timeoutMs: 45_000,
}),
});
/**
+1 -3
View File
@@ -9,7 +9,6 @@ describe('media-upload-recipes', () => {
it('exports MEDIA_UPLOAD_RECIPES with expected providers', () => {
expect(Object.keys(MEDIA_UPLOAD_RECIPES)).toContain('catbox');
expect(Object.keys(MEDIA_UPLOAD_RECIPES)).toContain('imgur');
expect(Object.keys(MEDIA_UPLOAD_RECIPES)).toContain('postimages');
});
it('validateRecipes passes for current recipes', () => {
@@ -28,9 +27,8 @@ describe('media-upload-recipes', () => {
}
});
it('imgur and postimages have 45s timeout (parity with Android)', () => {
it('imgur has 45s timeout (parity with Android)', () => {
expect(MEDIA_UPLOAD_RECIPES.imgur.timeoutMs).toBe(45_000);
expect(MEDIA_UPLOAD_RECIPES.postimages.timeoutMs).toBe(45_000);
});
it('selector fallback order: generic file input before provider-specific', () => {