339 lines
10 KiB
JavaScript
339 lines
10 KiB
JavaScript
// f0ckm Uploader - Background Service Worker
|
|
|
|
const DEFAULT_SETTINGS = {
|
|
uploadMode: "desktop", // "desktop" (f0ckm://) or "direct" (API)
|
|
apiUrl: "https://f0ckm.com/api/v2/upload",
|
|
apiKey: "",
|
|
rating: "",
|
|
tags: "",
|
|
visibility: "0",
|
|
isOc: false,
|
|
urlType: "post"
|
|
};
|
|
|
|
async function getSettings() {
|
|
return new Promise((resolve) => {
|
|
chrome.storage.sync.get(DEFAULT_SETTINGS, (items) => {
|
|
resolve(items || DEFAULT_SETTINGS);
|
|
});
|
|
});
|
|
}
|
|
|
|
const badgeCache = {};
|
|
|
|
function updateBadge(tabId, url) {
|
|
if (!tabId) return;
|
|
const isWeb = url && (url.startsWith("http://") || url.startsWith("https://"));
|
|
const isYT = isWeb && (url.includes("youtube.com") || url.includes("youtu.be"));
|
|
const targetText = isWeb ? "1" : "";
|
|
const targetColor = isYT ? "#ff0000" : "#0066ff";
|
|
|
|
const cacheKey = `${targetText}:${targetColor}`;
|
|
if (badgeCache[tabId] === cacheKey) {
|
|
return;
|
|
}
|
|
|
|
badgeCache[tabId] = cacheKey;
|
|
|
|
try {
|
|
if (targetText) {
|
|
chrome.action.setBadgeText({ text: targetText, tabId: tabId });
|
|
chrome.action.setBadgeBackgroundColor({ color: targetColor, tabId: tabId });
|
|
} else {
|
|
chrome.action.setBadgeText({ text: "", tabId: tabId });
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
chrome.tabs.onActivated.addListener(async (activeInfo) => {
|
|
try {
|
|
const tab = await chrome.tabs.get(activeInfo.tabId);
|
|
if (tab) updateBadge(activeInfo.tabId, tab.url);
|
|
} catch (e) {}
|
|
});
|
|
|
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
if (tab && (changeInfo.status === "complete" || changeInfo.url)) {
|
|
updateBadge(tabId, tab.url);
|
|
}
|
|
});
|
|
|
|
function setupContextMenus() {
|
|
chrome.contextMenus.removeAll(() => {
|
|
chrome.contextMenus.create({
|
|
id: "f0ckm_upload_image",
|
|
title: "Upload image to f0ckm",
|
|
contexts: ["image"]
|
|
});
|
|
chrome.contextMenus.create({
|
|
id: "f0ckm_upload_video",
|
|
title: "Upload media to f0ckm",
|
|
contexts: ["video", "audio"]
|
|
});
|
|
chrome.contextMenus.create({
|
|
id: "f0ckm_upload_link",
|
|
title: "Upload link with f0ckm",
|
|
contexts: ["link"]
|
|
});
|
|
chrome.contextMenus.create({
|
|
id: "f0ckm_upload_selection",
|
|
title: "Upload selected URL with f0ckm",
|
|
contexts: ["selection"]
|
|
});
|
|
chrome.contextMenus.create({
|
|
id: "f0ckm_upload_page",
|
|
title: "Upload page URL to f0ckm",
|
|
contexts: ["page"]
|
|
});
|
|
});
|
|
}
|
|
|
|
chrome.runtime.onInstalled.addListener(() => {
|
|
setupContextMenus();
|
|
});
|
|
|
|
chrome.runtime.onStartup.addListener(() => {
|
|
setupContextMenus();
|
|
});
|
|
|
|
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
|
let targetUrl = "";
|
|
if (info.menuItemId === "f0ckm_upload_image" || info.menuItemId === "f0ckm_upload_video") {
|
|
targetUrl = info.srcUrl || info.linkUrl;
|
|
} else if (info.menuItemId === "f0ckm_upload_link") {
|
|
targetUrl = info.linkUrl;
|
|
} else if (info.menuItemId === "f0ckm_upload_selection") {
|
|
targetUrl = info.selectionText ? info.selectionText.trim() : "";
|
|
} else if (info.menuItemId === "f0ckm_upload_page") {
|
|
targetUrl = info.pageUrl || (tab ? tab.url : "");
|
|
}
|
|
|
|
if (targetUrl) {
|
|
await processUpload(targetUrl);
|
|
}
|
|
});
|
|
|
|
chrome.commands.onCommand.addListener(async (command) => {
|
|
if (command === "upload-page") {
|
|
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
if (tabs && tabs.length > 0 && tabs[0].url) {
|
|
await processUpload(tabs[0].url);
|
|
}
|
|
}
|
|
});
|
|
|
|
function normalizeApiUrl(url) {
|
|
if (!url) return "";
|
|
url = url.trim().replace(/^['"]|['"]$/g, "");
|
|
if (!url) return "";
|
|
if (!/^https?:\/\//i.test(url)) {
|
|
url = "http://" + url;
|
|
}
|
|
try {
|
|
const parsed = new URL(url);
|
|
let path = parsed.pathname.replace(/\/+$/, "");
|
|
if (!path || path === "") {
|
|
return `${parsed.origin}/api/v2/upload`;
|
|
} else if (path.endsWith("/api/v2")) {
|
|
return `${parsed.origin}/upload`;
|
|
} else if (!path.endsWith("/upload")) {
|
|
return `${parsed.origin}/api/v2/upload`;
|
|
}
|
|
return url;
|
|
} catch (e) {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
async function copyToClipboard(text) {
|
|
if (!text) return;
|
|
try {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
await navigator.clipboard.writeText(text);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
async function testConnection(apiUrl, apiKey) {
|
|
if (!apiUrl || !apiKey) {
|
|
return { success: false, msg: "API URL and API Key are required." };
|
|
}
|
|
const normUrl = normalizeApiUrl(apiUrl);
|
|
try {
|
|
const res = await fetch(normUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Api-Key": apiKey,
|
|
"Authorization": `Bearer ${apiKey}`
|
|
},
|
|
credentials: "omit",
|
|
body: JSON.stringify({ api_key: apiKey, key: apiKey })
|
|
});
|
|
if (res.status === 200) {
|
|
return { success: true, msg: "Connection successful! (HTTP 200)" };
|
|
} else if (res.status === 401) {
|
|
return { success: false, msg: "Unauthorized: Invalid API Key." };
|
|
} else if (res.status === 400 || res.status === 422) {
|
|
return { success: true, msg: "Connection successful! (API key verified)" };
|
|
} else {
|
|
let text = `HTTP ${res.status}`;
|
|
try {
|
|
const json = await res.json();
|
|
if (json.msg || json.error || json.message) text = json.msg || json.error || json.message;
|
|
} catch (e) {}
|
|
return { success: false, msg: text };
|
|
}
|
|
} catch (err) {
|
|
return { success: false, msg: err.message || "Connection failed. Check server URL and network." };
|
|
}
|
|
}
|
|
|
|
async function processUpload(targetUrl) {
|
|
if (!targetUrl) return;
|
|
|
|
const settings = await getSettings();
|
|
|
|
if (settings.uploadMode === "desktop") {
|
|
// 1. Try local loopback HTTP API first (Direct desktop handoff)
|
|
const localApiUrl = "http://127.0.0.1:18739/upload?url=" + encodeURIComponent(targetUrl);
|
|
|
|
try {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
const res = await fetch(localApiUrl, { signal: controller.signal });
|
|
clearTimeout(timeoutId);
|
|
|
|
if (res.ok) {
|
|
return;
|
|
}
|
|
} catch (err) {}
|
|
|
|
// 2. Fallback: Custom protocol scheme in active tab
|
|
const customUrl = "f0ckm://upload?url=" + encodeURIComponent(targetUrl);
|
|
try {
|
|
chrome.tabs.create({ url: customUrl, active: true }, (newTab) => {
|
|
if (newTab && newTab.id) {
|
|
setTimeout(() => {
|
|
chrome.tabs.remove(newTab.id).catch(() => {});
|
|
}, 4500);
|
|
}
|
|
});
|
|
} catch (e) {}
|
|
} else {
|
|
// Direct API Upload mode -> POST JSON payload with url to f0ckm API!
|
|
showNotification("Uploading...", `Uploading ${targetUrl.substring(0, 45)}...`, "uploading");
|
|
|
|
try {
|
|
if (!settings.apiUrl || !settings.apiKey) {
|
|
showNotification("Upload Error", "API URL and API Key must be configured in extension options.", "error");
|
|
return null;
|
|
}
|
|
|
|
const normalizedUrl = normalizeApiUrl(settings.apiUrl);
|
|
|
|
const payload = {
|
|
url: targetUrl,
|
|
tags: settings.tags || "url,upload",
|
|
visibility: settings.visibility || "0",
|
|
api_key: settings.apiKey,
|
|
key: settings.apiKey
|
|
};
|
|
|
|
if (settings.rating && settings.rating !== "default" && settings.rating !== "") {
|
|
const ratingMap = { s: "sfw", q: "nsfw", e: "nsfl", sfw: "sfw", nsfw: "nsfw", nsfl: "nsfl" };
|
|
payload.rating = ratingMap[settings.rating.toLowerCase()] || settings.rating;
|
|
}
|
|
if (settings.isOc) payload.is_oc = "1";
|
|
|
|
const uploadRes = await fetch(normalizedUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Api-Key": settings.apiKey,
|
|
"Authorization": `Bearer ${settings.apiKey}`
|
|
},
|
|
credentials: "omit",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
if (!uploadRes.ok) {
|
|
let errorMsg = `HTTP Error ${uploadRes.status}`;
|
|
try {
|
|
const errJson = await uploadRes.json();
|
|
if (errJson.msg || errJson.error || errJson.message) {
|
|
errorMsg = errJson.msg || errJson.error || errJson.message;
|
|
}
|
|
} catch (e) {}
|
|
showNotification("Upload Failed", errorMsg, "error");
|
|
return null;
|
|
}
|
|
|
|
const json = await uploadRes.json();
|
|
|
|
const postUrl = json.url || json.post_url || json.link || (json.data && (json.data.url || json.data.post_url)) || "";
|
|
const directUrl = json.file_url || json.direct_url || json.target_url || json.file || (json.data && (json.data.file_url || json.data.direct_url)) || "";
|
|
const resultUrl = (settings.urlType === "direct" && directUrl) ? directUrl : (postUrl || directUrl);
|
|
|
|
if (resultUrl) {
|
|
await copyToClipboard(resultUrl);
|
|
showNotification("Upload Successful!", `Link copied to clipboard: ${resultUrl}`, "success");
|
|
return resultUrl;
|
|
} else if (json.success) {
|
|
showNotification("Upload Successful!", "Upload processed successfully.", "success");
|
|
return "success";
|
|
} else {
|
|
const msg = json.msg || json.error || "Upload failed";
|
|
showNotification("Upload Failed", msg, "error");
|
|
return null;
|
|
}
|
|
} catch (err) {
|
|
showNotification("Upload Error", err.message || "An error occurred during network request", "error");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function showNotification(title, message, iconType = "info") {
|
|
if (chrome.notifications) {
|
|
chrome.notifications.create({
|
|
type: "basic",
|
|
iconUrl: "icons/icon128.png",
|
|
title: title,
|
|
message: message,
|
|
priority: 2
|
|
});
|
|
}
|
|
}
|
|
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
if (request.action === "get_active_tab") {
|
|
chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
|
|
if (tabs && tabs.length > 0 && tabs[0].url) {
|
|
sendResponse({ tab: tabs[0] });
|
|
} else {
|
|
chrome.tabs.query({ active: true, currentWindow: true }, (tabs2) => {
|
|
if (tabs2 && tabs2.length > 0 && tabs2[0].url) {
|
|
sendResponse({ tab: tabs2[0] });
|
|
} else {
|
|
chrome.tabs.query({ active: true }, (tabs3) => {
|
|
sendResponse({ tab: (tabs3 && tabs3.length > 0) ? tabs3[0] : null });
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return true;
|
|
} else if (request.action === "upload_url") {
|
|
processUpload(request.url).then((resUrl) => {
|
|
sendResponse({ status: "ok", resultUrl: resUrl || "" });
|
|
});
|
|
return true;
|
|
} else if (request.action === "test_connection") {
|
|
testConnection(request.apiUrl, request.apiKey).then((res) => {
|
|
sendResponse(res);
|
|
});
|
|
return true;
|
|
}
|
|
});
|