bulljobs don't respect common headers

added the common truth (user-agent.ts) to all bulljob services
This commit is contained in:
Tom Bernens
2026-05-15 20:03:40 -07:00
parent e65e737bee
commit 0561459782
16 changed files with 40 additions and 18 deletions
-7
View File
@@ -1,7 +0,0 @@
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const axios = (await import('axios')).default;
const { RMAB_USER_AGENT } = await import('@/lib/utils/user-agent');
axios.defaults.headers.common['User-Agent'] = RMAB_USER_AGENT;
}
}
+3 -2
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import { RMABLogger } from '../utils/logger'; import { RMABLogger } from '../utils/logger';
import { getConfigService } from '../services/config.service'; import { getConfigService } from '../services/config.service';
@@ -273,7 +274,7 @@ export class AudibleService {
timeout: 10000, timeout: 10000,
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'User-Agent': 'ReadMeABook/1.0', 'User-Agent': RMAB_USER_AGENT,
}, },
}); });
@@ -305,7 +306,7 @@ export class AudibleService {
timeout: 10000, timeout: 10000,
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'User-Agent': 'ReadMeABook/1.0', 'User-Agent': RMAB_USER_AGENT,
}, },
}); });
+2 -1
View File
@@ -7,13 +7,14 @@
*/ */
import axios from 'axios'; import axios from 'axios';
import { RMAB_USER_AGENT } from '@/lib/utils/user-agent';
import { RMABLogger } from '@/lib/utils/logger'; import { RMABLogger } from '@/lib/utils/logger';
const logger = RMABLogger.create('Audnexus.Authors'); const logger = RMABLogger.create('Audnexus.Authors');
const AUDNEXUS_BASE = 'https://api.audnex.us'; const AUDNEXUS_BASE = 'https://api.audnex.us';
const AUDNEXUS_TIMEOUT = 10000; const AUDNEXUS_TIMEOUT = 10000;
const AUDNEXUS_HEADERS = { 'User-Agent': 'ReadMeABook/1.0' }; const AUDNEXUS_HEADERS = { 'User-Agent': RMAB_USER_AGENT };
export interface AudnexusAuthorSearch { export interface AudnexusAuthorSearch {
asin: string; asin: string;
+2 -1
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import https from 'https'; import https from 'https';
import path from 'path'; import path from 'path';
import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts'; import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts';
@@ -50,7 +51,7 @@ export class DelugeService implements IDownloadClient {
? new https.Agent({ rejectUnauthorized: false }) : undefined; ? new https.Agent({ rejectUnauthorized: false }) : undefined;
if (httpsAgent) logger.info('[Deluge] SSL certificate verification disabled'); if (httpsAgent) logger.info('[Deluge] SSL certificate verification disabled');
this.client = axios.create({ baseURL: this.baseUrl, timeout: DOWNLOAD_CLIENT_TIMEOUT, httpsAgent }); this.client = axios.create({ baseURL: this.baseUrl, timeout: DOWNLOAD_CLIENT_TIMEOUT, httpsAgent, headers: { 'User-Agent': RMAB_USER_AGENT } });
} }
/** JSON-RPC call with automatic re-authentication on auth failure */ /** JSON-RPC call with automatic re-authentication on auth failure */
+6 -1
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '@/lib/utils/user-agent';
import https from 'https'; import https from 'https';
import zlib from 'zlib'; import zlib from 'zlib';
import { RMABLogger } from '@/lib/utils/logger'; import { RMABLogger } from '@/lib/utils/logger';
@@ -140,6 +141,7 @@ export class NZBGetService implements IDownloadClient {
baseURL: this.baseUrl, baseURL: this.baseUrl,
timeout: 30000, timeout: 30000,
httpsAgent: this.httpsAgent, httpsAgent: this.httpsAgent,
headers: { 'User-Agent': RMAB_USER_AGENT },
auth: { auth: {
username: this.username, username: this.username,
password: this.password, password: this.password,
@@ -226,7 +228,10 @@ export class NZBGetService implements IDownloadClient {
responseType: 'arraybuffer', responseType: 'arraybuffer',
timeout: 30000, timeout: 30000,
maxRedirects: 5, maxRedirects: 5,
headers: options?.sourceHeaders, headers: {
'User-Agent': RMAB_USER_AGENT,
...options?.sourceHeaders,
},
httpsAgent: url.startsWith('https') ? this.httpsAgent : undefined, httpsAgent: url.startsWith('https') ? this.httpsAgent : undefined,
}); });
+2
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import { parseStringPromise } from 'xml2js'; import { parseStringPromise } from 'xml2js';
import { RMABLogger } from '../utils/logger'; import { RMABLogger } from '../utils/logger';
@@ -83,6 +84,7 @@ export class PlexService {
constructor() { constructor() {
this.client = axios.create({ this.client = axios.create({
timeout: 10000, timeout: 10000,
headers: { 'User-Agent': RMAB_USER_AGENT },
}); });
} }
+2
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import { XMLParser } from 'fast-xml-parser'; import { XMLParser } from 'fast-xml-parser';
import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts'; import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts';
import { TorrentResult } from '../utils/ranking-algorithm'; import { TorrentResult } from '../utils/ranking-algorithm';
@@ -86,6 +87,7 @@ export class ProwlarrService {
this.client = axios.create({ this.client = axios.create({
baseURL: `${this.baseUrl}/api/v1`, baseURL: `${this.baseUrl}/api/v1`,
headers: { headers: {
'User-Agent': RMAB_USER_AGENT,
'X-Api-Key': this.apiKey, 'X-Api-Key': this.apiKey,
}, },
timeout: DOWNLOAD_CLIENT_TIMEOUT, timeout: DOWNLOAD_CLIENT_TIMEOUT,
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import https from 'https'; import https from 'https';
import path from 'path'; import path from 'path';
import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts'; import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts';
@@ -149,6 +150,7 @@ export class QBittorrentService implements IDownloadClient {
baseURL: `${this.baseUrl}/api/v2`, baseURL: `${this.baseUrl}/api/v2`,
timeout: DOWNLOAD_CLIENT_TIMEOUT, timeout: DOWNLOAD_CLIENT_TIMEOUT,
httpsAgent: this.httpsAgent, httpsAgent: this.httpsAgent,
headers: { 'User-Agent': RMAB_USER_AGENT },
// Support nginx/Apache reverse proxy with HTTP Basic Auth // Support nginx/Apache reverse proxy with HTTP Basic Auth
auth: { auth: {
username: this.username, username: this.username,
+6 -1
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '@/lib/utils/user-agent';
import https from 'https'; import https from 'https';
import FormData from 'form-data'; import FormData from 'form-data';
import { RMABLogger } from '@/lib/utils/logger'; import { RMABLogger } from '@/lib/utils/logger';
@@ -132,6 +133,7 @@ export class SABnzbdService implements IDownloadClient {
baseURL: this.baseUrl, baseURL: this.baseUrl,
timeout: 30000, timeout: 30000,
httpsAgent: this.httpsAgent, httpsAgent: this.httpsAgent,
headers: { 'User-Agent': RMAB_USER_AGENT },
}); });
} }
@@ -494,7 +496,10 @@ export class SABnzbdService implements IDownloadClient {
responseType: 'arraybuffer', responseType: 'arraybuffer',
timeout: 30000, timeout: 30000,
maxRedirects: 5, maxRedirects: 5,
headers: options?.sourceHeaders, headers: {
'User-Agent': RMAB_USER_AGENT,
...options?.sourceHeaders,
},
// Use the same SSL settings as the SABnzbd client if the NZB URL // Use the same SSL settings as the SABnzbd client if the NZB URL
// happens to be served over HTTPS with a self-signed cert // happens to be served over HTTPS with a self-signed cert
httpsAgent: url.startsWith('https') ? this.httpsAgent : undefined, httpsAgent: url.startsWith('https') ? this.httpsAgent : undefined,
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosInstance } from 'axios'; import axios, { AxiosInstance } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import https from 'https'; import https from 'https';
import path from 'path'; import path from 'path';
import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts'; import { DOWNLOAD_CLIENT_TIMEOUT } from '../constants/download-timeouts';
@@ -109,6 +110,7 @@ export class TransmissionService implements IDownloadClient {
baseURL: this.baseUrl, baseURL: this.baseUrl,
timeout: DOWNLOAD_CLIENT_TIMEOUT, timeout: DOWNLOAD_CLIENT_TIMEOUT,
httpsAgent: this.httpsAgent, httpsAgent: this.httpsAgent,
headers: { 'User-Agent': RMAB_USER_AGENT },
}); });
} }
@@ -12,6 +12,7 @@ import { getConfigService } from '../services/config.service';
import { RMABLogger } from '../utils/logger'; import { RMABLogger } from '../utils/logger';
import { extractDownloadUrl, ExtractedDownload } from '../services/ebook-scraper'; import { extractDownloadUrl, ExtractedDownload } from '../services/ebook-scraper';
import axios from 'axios'; import axios from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { createWriteStream } from 'fs'; import { createWriteStream } from 'fs';
import path from 'path'; import path from 'path';
@@ -302,7 +303,7 @@ async function downloadFileWithProgress(
responseType: 'stream', responseType: 'stream',
timeout: DOWNLOAD_TIMEOUT_MS, timeout: DOWNLOAD_TIMEOUT_MS,
headers: { headers: {
'User-Agent': 'ReadMeABook/1.0 (Audiobook Automation)', 'User-Agent': RMAB_USER_AGENT,
}, },
}); });
+2 -1
View File
@@ -4,6 +4,7 @@
*/ */
import axios, { AxiosError } from 'axios'; import axios, { AxiosError } from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import fs from 'fs/promises'; import fs from 'fs/promises';
import path from 'path'; import path from 'path';
@@ -19,7 +20,7 @@ export interface EbookDownloadResult {
error?: string; error?: string;
} }
const USER_AGENT = 'ReadMeABook/1.0 (Audiobook Automation)'; const USER_AGENT = RMAB_USER_AGENT;
const REQUEST_DELAY_MS = 1500; // 1.5 second delay between requests const REQUEST_DELAY_MS = 1500; // 1.5 second delay between requests
const DOWNLOAD_TIMEOUT_MS = 60000; // 60 seconds per download attempt const DOWNLOAD_TIMEOUT_MS = 60000; // 60 seconds per download attempt
const MAX_SLOW_LINK_ATTEMPTS = 5; const MAX_SLOW_LINK_ATTEMPTS = 5;
+2 -1
View File
@@ -7,6 +7,7 @@
*/ */
import axios from 'axios'; import axios from 'axios';
import { RMAB_USER_AGENT } from '@/lib/utils/user-agent';
import { XMLParser } from 'fast-xml-parser'; import { XMLParser } from 'fast-xml-parser';
import { prisma } from '@/lib/db'; import { prisma } from '@/lib/db';
import { Prisma } from '@/generated/prisma/client'; import { Prisma } from '@/generated/prisma/client';
@@ -83,7 +84,7 @@ export async function fetchAndValidateRss(rssUrl: string): Promise<{ shelfName:
for (let page = 1; page <= MAX_PAGES; page++) { for (let page = 1; page <= MAX_PAGES; page++) {
url.searchParams.set('page', page.toString()); url.searchParams.set('page', page.toString());
const response = await axios.get(url.toString(), { timeout: 15000 }); const response = await axios.get(url.toString(), { timeout: 15000, headers: { 'User-Agent': RMAB_USER_AGENT } });
const parsed = parseGoodreadsRss(response.data); const parsed = parseGoodreadsRss(response.data);
if (page === 1) { if (page === 1) {
@@ -7,6 +7,7 @@
*/ */
import axios from 'axios'; import axios from 'axios';
import { RMAB_USER_AGENT } from '@/lib/utils/user-agent';
import { RMABLogger } from '@/lib/utils/logger'; import { RMABLogger } from '@/lib/utils/logger';
const logger = RMABLogger.create('HardcoverAPI'); const logger = RMABLogger.create('HardcoverAPI');
@@ -120,6 +121,7 @@ export async function fetchHardcoverList(
headers: { headers: {
Authorization: `Bearer ${apiToken}`, Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'User-Agent': RMAB_USER_AGENT,
}, },
timeout: 30000, timeout: 30000,
}, },
+3 -2
View File
@@ -7,6 +7,7 @@ import fs from 'fs/promises';
import path from 'path'; import path from 'path';
import crypto from 'crypto'; import crypto from 'crypto';
import axios from 'axios'; import axios from 'axios';
import { RMAB_USER_AGENT } from '../utils/user-agent';
import { RMABLogger } from '../utils/logger'; import { RMABLogger } from '../utils/logger';
const logger = RMABLogger.create('ThumbnailCache'); const logger = RMABLogger.create('ThumbnailCache');
@@ -110,7 +111,7 @@ export class ThumbnailCacheService {
timeout: TIMEOUT_MS, timeout: TIMEOUT_MS,
maxContentLength: MAX_FILE_SIZE, maxContentLength: MAX_FILE_SIZE,
headers: { headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'User-Agent': RMAB_USER_AGENT,
}, },
}); });
@@ -185,7 +186,7 @@ export class ThumbnailCacheService {
timeout: TIMEOUT_MS, timeout: TIMEOUT_MS,
maxContentLength: MAX_FILE_SIZE, maxContentLength: MAX_FILE_SIZE,
headers: { headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'User-Agent': RMAB_USER_AGENT,
...(backendMode === 'audiobookshelf' && { Authorization: `Bearer ${authToken}` }), ...(backendMode === 'audiobookshelf' && { Authorization: `Bearer ${authToken}` }),
}, },
}); });
+2
View File
@@ -6,6 +6,7 @@
import fs from 'fs/promises'; import fs from 'fs/promises';
import path from 'path'; import path from 'path';
import axios from 'axios'; import axios from 'axios';
import { RMAB_USER_AGENT } from './user-agent';
import { tagMultipleFiles, checkFfmpegAvailable } from './metadata-tagger'; import { tagMultipleFiles, checkFfmpegAvailable } from './metadata-tagger';
import { RMABLogger } from './logger'; import { RMABLogger } from './logger';
import { copyFile } from './copy-file'; import { copyFile } from './copy-file';
@@ -740,6 +741,7 @@ export class FileOrganizer {
const response = await axios.get(url, { const response = await axios.get(url, {
responseType: 'arraybuffer', responseType: 'arraybuffer',
timeout: 30000, timeout: 30000,
headers: { 'User-Agent': RMAB_USER_AGENT },
}); });
await fs.writeFile(targetPath, response.data); await fs.writeFile(targetPath, response.data);