correcting pixiv connection functionality
This commit is contained in:
@@ -89,7 +89,7 @@ async def list_platforms():
|
|||||||
"name": "Pixiv",
|
"name": "Pixiv",
|
||||||
"description": "Download artwork from Pixiv artists",
|
"description": "Download artwork from Pixiv artists",
|
||||||
"requires_auth": True,
|
"requires_auth": True,
|
||||||
"auth_type": "cookies",
|
"auth_type": "token",
|
||||||
"url_pattern": "https://www.pixiv.net/users/{user_id}",
|
"url_pattern": "https://www.pixiv.net/users/{user_id}",
|
||||||
"url_examples": [
|
"url_examples": [
|
||||||
"https://www.pixiv.net/users/12345678",
|
"https://www.pixiv.net/users/12345678",
|
||||||
@@ -103,6 +103,7 @@ async def list_platforms():
|
|||||||
"bookmarks": "Bookmarked works",
|
"bookmarks": "Bookmarked works",
|
||||||
},
|
},
|
||||||
"default_config": gdl_service.get_default_config_for_platform("pixiv"),
|
"default_config": gdl_service.get_default_config_for_platform("pixiv"),
|
||||||
|
"notes": "Requires OAuth refresh token. Run 'gallery-dl oauth:pixiv' to obtain one.",
|
||||||
},
|
},
|
||||||
"deviantart": {
|
"deviantart": {
|
||||||
"name": "DeviantArt",
|
"name": "DeviantArt",
|
||||||
|
|||||||
@@ -387,6 +387,8 @@ class GalleryDLService:
|
|||||||
"please login",
|
"please login",
|
||||||
"must be logged in",
|
"must be logged in",
|
||||||
"authentication required",
|
"authentication required",
|
||||||
|
"authenticationerror",
|
||||||
|
"refresh-token", # Pixiv OAuth
|
||||||
"session expired",
|
"session expired",
|
||||||
"invalid cookie",
|
"invalid cookie",
|
||||||
"cookies are expired",
|
"cookies are expired",
|
||||||
@@ -560,6 +562,12 @@ class GalleryDLService:
|
|||||||
config["extractor"]["discord"] = {}
|
config["extractor"]["discord"] = {}
|
||||||
config["extractor"]["discord"]["token"] = auth_token
|
config["extractor"]["discord"]["token"] = auth_token
|
||||||
|
|
||||||
|
# Add refresh token for Pixiv
|
||||||
|
if auth_token and platform == "pixiv":
|
||||||
|
if "pixiv" not in config["extractor"]:
|
||||||
|
config["extractor"]["pixiv"] = {}
|
||||||
|
config["extractor"]["pixiv"]["refresh-token"] = auth_token
|
||||||
|
|
||||||
# Write temporary config file
|
# Write temporary config file
|
||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
mode="w",
|
mode="w",
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ async def _download_source_async(source_id: int, download_id: int = None) -> dic
|
|||||||
cookies_path = None
|
cookies_path = None
|
||||||
auth_token = None
|
auth_token = None
|
||||||
|
|
||||||
if source_platform == "discord":
|
if source_platform in ("discord", "pixiv"):
|
||||||
auth_token = await cred_manager.get_token(source_platform)
|
auth_token = await cred_manager.get_token(source_platform)
|
||||||
if not auth_token:
|
if not auth_token:
|
||||||
logger.warning(f"No token for {source_platform}, download may fail")
|
logger.warning(f"No token for {source_platform}, download may fail")
|
||||||
|
|||||||
@@ -6,6 +6,18 @@
|
|||||||
let discordToken = null;
|
let discordToken = null;
|
||||||
let discordTokenCapturedAt = null;
|
let discordTokenCapturedAt = null;
|
||||||
|
|
||||||
|
// Store for Pixiv OAuth
|
||||||
|
let pixivRefreshToken = null;
|
||||||
|
let pixivTokenCapturedAt = null;
|
||||||
|
let pixivOAuthPending = null; // { codeVerifier, tabId, resolve, reject }
|
||||||
|
|
||||||
|
// Pixiv OAuth constants (these are public - used by all Pixiv mobile apps)
|
||||||
|
const PIXIV_CLIENT_ID = 'MOBrBDS8blbauoSck0ZfDbtuzpyT';
|
||||||
|
const PIXIV_CLIENT_SECRET = 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj';
|
||||||
|
const PIXIV_OAUTH_URL = 'https://app-api.pixiv.net/web/v1/login';
|
||||||
|
const PIXIV_TOKEN_URL = 'https://oauth.secure.pixiv.net/auth/token';
|
||||||
|
const PIXIV_REDIRECT_URI = 'https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback';
|
||||||
|
|
||||||
// Track initialization state
|
// Track initialization state
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
@@ -18,6 +30,7 @@ async function ensureInitialized() {
|
|||||||
try {
|
try {
|
||||||
await api.init();
|
await api.init();
|
||||||
await loadDiscordToken();
|
await loadDiscordToken();
|
||||||
|
await loadPixivToken();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
console.log('Extension initialized successfully');
|
console.log('Extension initialized successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -66,6 +79,190 @@ async function saveDiscordToken(token) {
|
|||||||
console.log('Discord token captured and saved');
|
console.log('Discord token captured and saved');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Pixiv token from storage
|
||||||
|
*/
|
||||||
|
async function loadPixivToken() {
|
||||||
|
const stored = await browser.storage.local.get(['pixivRefreshToken', 'pixivTokenCapturedAt']);
|
||||||
|
if (stored.pixivRefreshToken) {
|
||||||
|
pixivRefreshToken = stored.pixivRefreshToken;
|
||||||
|
pixivTokenCapturedAt = stored.pixivTokenCapturedAt;
|
||||||
|
console.log('Loaded Pixiv refresh token from storage (captured:', pixivTokenCapturedAt, ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save Pixiv refresh token to storage
|
||||||
|
*/
|
||||||
|
async function savePixivToken(token) {
|
||||||
|
pixivRefreshToken = token;
|
||||||
|
pixivTokenCapturedAt = new Date().toISOString();
|
||||||
|
await browser.storage.local.set({
|
||||||
|
pixivRefreshToken: token,
|
||||||
|
pixivTokenCapturedAt: pixivTokenCapturedAt
|
||||||
|
});
|
||||||
|
console.log('Pixiv refresh token captured and saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random code verifier for PKCE (43-128 chars, URL-safe)
|
||||||
|
*/
|
||||||
|
function generateCodeVerifier() {
|
||||||
|
const array = new Uint8Array(32);
|
||||||
|
crypto.getRandomValues(array);
|
||||||
|
return base64UrlEncode(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate code challenge from verifier (SHA-256 hash, base64url encoded)
|
||||||
|
*/
|
||||||
|
async function generateCodeChallenge(verifier) {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(verifier);
|
||||||
|
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||||
|
return base64UrlEncode(new Uint8Array(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 URL encode (no padding, URL-safe characters)
|
||||||
|
*/
|
||||||
|
function base64UrlEncode(buffer) {
|
||||||
|
const base64 = btoa(String.fromCharCode(...buffer));
|
||||||
|
return base64
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate Pixiv OAuth flow
|
||||||
|
* Opens a new tab for the user to log in, then captures the authorization code
|
||||||
|
*/
|
||||||
|
async function initiatePixivOAuth() {
|
||||||
|
// Generate PKCE values
|
||||||
|
const codeVerifier = generateCodeVerifier();
|
||||||
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
||||||
|
|
||||||
|
// Build OAuth URL
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
code_challenge: codeChallenge,
|
||||||
|
code_challenge_method: 'S256',
|
||||||
|
client: 'pixiv-android'
|
||||||
|
});
|
||||||
|
const authUrl = `${PIXIV_OAUTH_URL}?${params.toString()}`;
|
||||||
|
|
||||||
|
console.log('Initiating Pixiv OAuth flow...');
|
||||||
|
|
||||||
|
// Create a promise that will be resolved when we get the callback
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Store the pending OAuth state
|
||||||
|
pixivOAuthPending = {
|
||||||
|
codeVerifier,
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
timeoutId: setTimeout(() => {
|
||||||
|
pixivOAuthPending = null;
|
||||||
|
reject(new Error('Pixiv OAuth timed out. Please try again.'));
|
||||||
|
}, 5 * 60 * 1000) // 5 minute timeout
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open the auth URL in a new tab
|
||||||
|
browser.tabs.create({ url: authUrl }).then(tab => {
|
||||||
|
pixivOAuthPending.tabId = tab.id;
|
||||||
|
console.log('Opened Pixiv OAuth tab:', tab.id);
|
||||||
|
}).catch(err => {
|
||||||
|
clearTimeout(pixivOAuthPending.timeoutId);
|
||||||
|
pixivOAuthPending = null;
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange authorization code for tokens
|
||||||
|
*/
|
||||||
|
async function exchangePixivCode(code, codeVerifier) {
|
||||||
|
console.log('Exchanging Pixiv authorization code for tokens...');
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: PIXIV_CLIENT_ID,
|
||||||
|
client_secret: PIXIV_CLIENT_SECRET,
|
||||||
|
code: code,
|
||||||
|
code_verifier: codeVerifier,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
include_policy: 'true',
|
||||||
|
redirect_uri: PIXIV_REDIRECT_URI
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(PIXIV_TOKEN_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'User-Agent': 'PixivAndroidApp/5.0.234 (Android 11; Pixel 5)'
|
||||||
|
},
|
||||||
|
body: params.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error('Pixiv token exchange failed:', response.status, errorText);
|
||||||
|
throw new Error(`Token exchange failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Pixiv token exchange successful');
|
||||||
|
|
||||||
|
if (!data.refresh_token) {
|
||||||
|
throw new Error('No refresh token in response');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.refresh_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for Pixiv OAuth callback
|
||||||
|
* Intercepts the redirect to pixiv:// scheme and extracts the authorization code
|
||||||
|
*/
|
||||||
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
|
(details) => {
|
||||||
|
// Check if this is the OAuth callback
|
||||||
|
if (!pixivOAuthPending) return;
|
||||||
|
|
||||||
|
const url = new URL(details.url);
|
||||||
|
|
||||||
|
// Check for the callback URL pattern
|
||||||
|
if (url.pathname.includes('/callback') && url.searchParams.has('code')) {
|
||||||
|
const code = url.searchParams.get('code');
|
||||||
|
console.log('Captured Pixiv OAuth callback with code');
|
||||||
|
|
||||||
|
// Get the pending OAuth state
|
||||||
|
const { codeVerifier, resolve, reject, timeoutId, tabId } = pixivOAuthPending;
|
||||||
|
pixivOAuthPending = null;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
// Close the OAuth tab
|
||||||
|
if (tabId) {
|
||||||
|
browser.tabs.remove(tabId).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange the code for tokens
|
||||||
|
exchangePixivCode(code, codeVerifier)
|
||||||
|
.then(refreshToken => {
|
||||||
|
savePixivToken(refreshToken);
|
||||||
|
resolve(refreshToken);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel the request (don't actually navigate to the callback URL)
|
||||||
|
return { cancel: true };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ urls: ['*://app-api.pixiv.net/web/v1/users/auth/pixiv/callback*'] },
|
||||||
|
['blocking']
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for Discord API requests to capture Authorization header
|
* Listen for Discord API requests to capture Authorization header
|
||||||
*/
|
*/
|
||||||
@@ -178,6 +375,35 @@ async function exportCookies(platformKey) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pixiv - use OAuth flow to get refresh token
|
||||||
|
if (platformKey === 'pixiv') {
|
||||||
|
// If we already have a token, use it; otherwise initiate OAuth
|
||||||
|
if (!pixivRefreshToken) {
|
||||||
|
try {
|
||||||
|
await initiatePixivOAuth();
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Pixiv OAuth failed: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pixivRefreshToken) {
|
||||||
|
throw new Error('Failed to obtain Pixiv refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialData = pixivRefreshToken;
|
||||||
|
credentialType = 'token';
|
||||||
|
|
||||||
|
// Send to backend
|
||||||
|
const result = await api.uploadCredentials(platformKey, credentialType, credentialData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
platform: platformKey,
|
||||||
|
tokenCapturedAt: pixivTokenCapturedAt,
|
||||||
|
message: result.message || 'Refresh token exported successfully'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Other token-based platforms require manual entry
|
// Other token-based platforms require manual entry
|
||||||
throw new Error(`${platform.name} requires manual token entry in the web UI.`);
|
throw new Error(`${platform.name} requires manual token entry in the web UI.`);
|
||||||
}
|
}
|
||||||
@@ -216,8 +442,8 @@ async function exportAllCookies() {
|
|||||||
for (const platformKey of platforms) {
|
for (const platformKey of platforms) {
|
||||||
const platform = PLATFORMS[platformKey];
|
const platform = PLATFORMS[platformKey];
|
||||||
|
|
||||||
// Skip token-based platforms (except Discord if we have a token)
|
// Skip token-based platforms (except Discord and Pixiv if we have tokens)
|
||||||
if (platform.authType === 'token' && platformKey !== 'discord') {
|
if (platform.authType === 'token' && !['discord', 'pixiv'].includes(platformKey)) {
|
||||||
results[platformKey] = {
|
results[platformKey] = {
|
||||||
success: false,
|
success: false,
|
||||||
platform: platformKey,
|
platform: platformKey,
|
||||||
@@ -238,6 +464,17 @@ async function exportAllCookies() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip Pixiv if no token captured (don't auto-trigger OAuth in bulk export)
|
||||||
|
if (platformKey === 'pixiv' && !pixivRefreshToken) {
|
||||||
|
results[platformKey] = {
|
||||||
|
success: false,
|
||||||
|
platform: platformKey,
|
||||||
|
skipped: true,
|
||||||
|
message: 'No token - click Pixiv card to authenticate'
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
results[platformKey] = await exportCookies(platformKey);
|
results[platformKey] = await exportCookies(platformKey);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -275,6 +512,18 @@ async function getPlatformStatus() {
|
|||||||
? `Token captured ${new Date(discordTokenCapturedAt).toLocaleString()}`
|
? `Token captured ${new Date(discordTokenCapturedAt).toLocaleString()}`
|
||||||
: 'Open Discord in browser to capture token'
|
: 'Open Discord in browser to capture token'
|
||||||
};
|
};
|
||||||
|
} else if (key === 'pixiv') {
|
||||||
|
status[key] = {
|
||||||
|
name: platform.name,
|
||||||
|
authType: 'token',
|
||||||
|
hasToken: !!pixivRefreshToken,
|
||||||
|
tokenCapturedAt: pixivTokenCapturedAt,
|
||||||
|
hasCookies: false,
|
||||||
|
cookieCount: 0,
|
||||||
|
note: pixivRefreshToken
|
||||||
|
? `Token captured ${new Date(pixivTokenCapturedAt).toLocaleString()}`
|
||||||
|
: 'Click to authenticate with Pixiv'
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
status[key] = {
|
status[key] = {
|
||||||
name: platform.name,
|
name: platform.name,
|
||||||
|
|||||||
@@ -19,13 +19,16 @@
|
|||||||
"tabs",
|
"tabs",
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"webRequest",
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
"*://*.patreon.com/*",
|
"*://*.patreon.com/*",
|
||||||
"*://*.subscribestar.com/*",
|
"*://*.subscribestar.com/*",
|
||||||
"*://*.subscribestar.adult/*",
|
"*://*.subscribestar.adult/*",
|
||||||
"*://*.hentai-foundry.com/*",
|
"*://*.hentai-foundry.com/*",
|
||||||
"*://*.discord.com/*",
|
"*://*.discord.com/*",
|
||||||
"*://*.pixiv.net/*",
|
"*://*.pixiv.net/*",
|
||||||
"*://*.deviantart.com/*"
|
"*://*.deviantart.com/*",
|
||||||
|
"*://app-api.pixiv.net/*",
|
||||||
|
"*://oauth.secure.pixiv.net/*"
|
||||||
],
|
],
|
||||||
|
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ function createPlatformCard(key, platform, status) {
|
|||||||
card.className = 'platform-card';
|
card.className = 'platform-card';
|
||||||
card.dataset.platform = key;
|
card.dataset.platform = key;
|
||||||
|
|
||||||
// Discord is clickable if we have a token captured
|
// Discord and Pixiv are clickable (Pixiv initiates OAuth if no token)
|
||||||
// Other token-based platforms are disabled
|
// Other token-based platforms are disabled
|
||||||
const isDisabled = platform.authType === 'token' && key !== 'discord';
|
const isDisabled = platform.authType === 'token' && !['discord', 'pixiv'].includes(key);
|
||||||
const isDiscordWithoutToken = key === 'discord' && !status.hasToken;
|
const isDiscordWithoutToken = key === 'discord' && !status.hasToken;
|
||||||
|
|
||||||
if (isDisabled || isDiscordWithoutToken) {
|
if (isDisabled || isDiscordWithoutToken) {
|
||||||
@@ -236,6 +236,14 @@ function getStatusText(status, platform, key) {
|
|||||||
return 'Open Discord in browser to capture token';
|
return 'Open Discord in browser to capture token';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for Pixiv OAuth
|
||||||
|
if (key === 'pixiv') {
|
||||||
|
if (status.hasToken) {
|
||||||
|
return 'Token captured - ready to export';
|
||||||
|
}
|
||||||
|
return 'Click to authenticate with Pixiv';
|
||||||
|
}
|
||||||
|
|
||||||
if (platform.authType === 'token') {
|
if (platform.authType === 'token') {
|
||||||
return 'Manual token entry required';
|
return 'Manual token entry required';
|
||||||
}
|
}
|
||||||
@@ -261,6 +269,11 @@ function getStatusClass(status, platform, key) {
|
|||||||
return status.hasToken ? 'ready' : 'no-cookies';
|
return status.hasToken ? 'ready' : 'no-cookies';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for Pixiv
|
||||||
|
if (key === 'pixiv') {
|
||||||
|
return status.hasToken ? 'ready' : 'no-cookies';
|
||||||
|
}
|
||||||
|
|
||||||
if (platform.authType === 'token') {
|
if (platform.authType === 'token') {
|
||||||
return 'no-cookies';
|
return 'no-cookies';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,11 @@
|
|||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-alert type="info" variant="tonal" class="mb-4">
|
<v-alert type="info" variant="tonal" class="mb-4">
|
||||||
<template v-if="uploadPlatform?.auth_type === 'token'">
|
<template v-if="uploadPlatformKey === 'pixiv'">
|
||||||
|
Paste your Pixiv OAuth refresh token below.
|
||||||
|
Run <code>gallery-dl oauth:pixiv</code> to obtain one.
|
||||||
|
</template>
|
||||||
|
<template v-else-if="uploadPlatform?.auth_type === 'token'">
|
||||||
Paste your Discord user token below.
|
Paste your Discord user token below.
|
||||||
<strong>Never share your token with anyone!</strong>
|
<strong>Never share your token with anyone!</strong>
|
||||||
</template>
|
</template>
|
||||||
@@ -128,7 +132,17 @@
|
|||||||
How to get {{ uploadPlatform?.auth_type === 'token' ? 'your token' : 'cookies' }}
|
How to get {{ uploadPlatform?.auth_type === 'token' ? 'your token' : 'cookies' }}
|
||||||
</v-expansion-panel-title>
|
</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<template v-if="uploadPlatform?.auth_type === 'token'">
|
<template v-if="uploadPlatformKey === 'pixiv'">
|
||||||
|
<ol>
|
||||||
|
<li>Install gallery-dl: <code>pip install gallery-dl</code></li>
|
||||||
|
<li>Run: <code>gallery-dl oauth:pixiv</code></li>
|
||||||
|
<li>A browser window will open for Pixiv login</li>
|
||||||
|
<li>After authorizing, copy the refresh token displayed</li>
|
||||||
|
<li>Paste the token here</li>
|
||||||
|
</ol>
|
||||||
|
<p class="mt-2 text-caption">The refresh token is a long string that gallery-dl uses to authenticate with Pixiv's API.</p>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="uploadPlatform?.auth_type === 'token'">
|
||||||
<ol>
|
<ol>
|
||||||
<li>Open Discord in your browser</li>
|
<li>Open Discord in your browser</li>
|
||||||
<li>Press F12 to open Developer Tools</li>
|
<li>Press F12 to open Developer Tools</li>
|
||||||
@@ -285,6 +299,9 @@ function openUploadDialog(platform, info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPlaceholder() {
|
function getPlaceholder() {
|
||||||
|
if (uploadPlatformKey.value === 'pixiv') {
|
||||||
|
return 'AZX1abc2DEF3ghi...'
|
||||||
|
}
|
||||||
if (uploadPlatform.value?.auth_type === 'token') {
|
if (uploadPlatform.value?.auth_type === 'token') {
|
||||||
return 'mfa.AbCdEf123456...'
|
return 'mfa.AbCdEf123456...'
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-4
@@ -1,6 +1,6 @@
|
|||||||
# GallerySubscriber - Project Summary
|
# GallerySubscriber - Project Summary
|
||||||
|
|
||||||
**Updated:** 2026-02-02 (Added Pixiv and DeviantArt platform support)
|
**Updated:** 2026-02-04 (Fixed Pixiv authentication - requires OAuth refresh token)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -159,9 +159,17 @@ Download (1) ──→ (many) ContentItem
|
|||||||
| SubscribeStar Adult | Cookies | images, attachments | Infinite scroll (complete) |
|
| SubscribeStar Adult | Cookies | images, attachments | Infinite scroll (complete) |
|
||||||
| Hentai Foundry | Cookies | pictures, scraps, stories | All pages (complete) |
|
| Hentai Foundry | Cookies | pictures, scraps, stories | All pages (complete) |
|
||||||
| Discord | Token | messages, attachments, embeds | All threads included |
|
| Discord | Token | messages, attachments, embeds | All threads included |
|
||||||
| Pixiv | Cookies | illusts, manga, bookmarks | All works (complete) |
|
| Pixiv | Refresh Token | illusts, manga, bookmarks | All works (complete) |
|
||||||
| DeviantArt | Cookies (optional) | gallery, scraps, favorites | All deviations (complete) |
|
| DeviantArt | Cookies (optional) | gallery, scraps, favorites | All deviations (complete) |
|
||||||
|
|
||||||
|
**Pixiv Authentication:** Pixiv requires an OAuth refresh token (not cookies). The Firefox extension handles this automatically:
|
||||||
|
1. Click the Pixiv card in the extension popup
|
||||||
|
2. A new tab opens for Pixiv login
|
||||||
|
3. Log in with your Pixiv username/password
|
||||||
|
4. The extension automatically captures the OAuth token and exports it
|
||||||
|
|
||||||
|
Alternatively, you can manually obtain a token by running `gallery-dl oauth:pixiv` and adding it via the web UI.
|
||||||
|
|
||||||
**Note:** All platforms fetch complete history by default - no depth limits. Gallery-dl paginates through ALL available content.
|
**Note:** All platforms fetch complete history by default - no depth limits. Gallery-dl paginates through ALL available content.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -266,8 +274,11 @@ docker compose -f docker-compose.dev.yml up
|
|||||||
1. User installs Firefox extension
|
1. User installs Firefox extension
|
||||||
2. User configures API URL and key in extension options
|
2. User configures API URL and key in extension options
|
||||||
3. Extension detects supported platforms when user visits them
|
3. Extension detects supported platforms when user visits them
|
||||||
4. User clicks "Export Cookies" in popup
|
4. User clicks platform card in popup to export credentials:
|
||||||
5. Extension sends encrypted cookies to backend API
|
- **Cookie-based platforms** (Patreon, SubscribeStar, etc.): Exports browser cookies
|
||||||
|
- **Discord**: Captures auth token from API requests (browse Discord first)
|
||||||
|
- **Pixiv**: Opens OAuth login flow, captures refresh token automatically
|
||||||
|
5. Extension sends encrypted credentials to backend API
|
||||||
6. Backend stores credentials for gallery-dl authentication
|
6. Backend stores credentials for gallery-dl authentication
|
||||||
|
|
||||||
## Download Workflow
|
## Download Workflow
|
||||||
|
|||||||
Reference in New Issue
Block a user