feat: add shared cross-platform catbox media upload flow

Added `useFileUpload()` and `uploadToCatbox()` to centralize upload behavior and remove duplicated upload code from `post-form.tsx` and `reply-modal.tsx`. The flow now routes Android to `FileUploader`, Electron to hidden-input + catbox upload, and web to a translated fallback alert with consistent upload UI state.
This commit is contained in:
plebeius
2026-02-17 16:44:16 +08:00
parent 9c558d21c1
commit dbbc83d369
42 changed files with 545 additions and 118 deletions
@@ -0,0 +1,51 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { uploadToCatbox } from '../catbox-utils';
describe('uploadToCatbox', () => {
const originalFetch = global.fetch;
beforeEach(() => {
global.fetch = originalFetch;
});
it('POSTs file to catbox API and returns trimmed URL', async () => {
const mockFile = new File(['content'], 'test.png', { type: 'image/png' });
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve(' https://files.catbox.moe/abc123.png '),
}),
);
const url = await uploadToCatbox(mockFile);
expect(url).toBe('https://files.catbox.moe/abc123.png');
const call = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0];
expect(call[0]).toBe('https://catbox.moe/user/api.php');
expect(call[1]?.method).toBe('POST');
const body = call[1]?.body as FormData;
expect(body.get('reqtype')).toBe('fileupload');
expect(body.get('fileToUpload')).toBe(mockFile);
});
it('throws Error on non-ok response', async () => {
const mockFile = new File(['x'], 'x.txt', { type: 'text/plain' });
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: false,
status: 500,
statusText: 'Internal Server Error',
}),
);
await expect(uploadToCatbox(mockFile)).rejects.toThrow('Upload failed: 500 Internal Server Error');
});
it('throws when fetch rejects with a network error', async () => {
const mockFile = new File(['x'], 'x.txt', { type: 'text/plain' });
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Network down')));
await expect(uploadToCatbox(mockFile)).rejects.toThrow('Network down');
});
});
+25
View File
@@ -0,0 +1,25 @@
const CATBOX_API = 'https://catbox.moe/user/api.php';
/**
* Upload a file to catbox.moe.
* @param file - The file to upload
* @returns The URL of the uploaded file
* @throws Error when the response is not ok
*/
export async function uploadToCatbox(file: File): Promise<string> {
const formData = new FormData();
formData.append('reqtype', 'fileupload');
formData.append('fileToUpload', file);
const response = await fetch(CATBOX_API, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
}
const text = await response.text();
return text.trim();
}