Two processes, one NNTP connection each (the coordinator 40 -> 38): - spots:read (service spotter): free.pt's overviews after the last article seen, each spot's full header verified (lib/spotnet from voyager 1, as Spotweb), its description, website and NZB articles; the password its title or text names; delete/dispose messages applied. - spots:inspect (service spot-inspector): each spot's compressed NZB read, its files listed (spot_files), each matched to the header pass's release by the same release identity in one of its groups within 48 h (release_spots); unmatched spots matched again hourly, after 24 h a release of their own (source spot) with the spot's NZB. - release_passwords: per release and password, its source (nzb, spot-title, spot-text) and spot; the catalog keeps only passworded. XoverClient.head(); PROBE_CONNECTIONS default 10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
334 lines
7.7 KiB
TypeScript
334 lines
7.7 KiB
TypeScript
/**
|
||
* Spotnet's category tree, from Spotweb's SpotCategories (English names).
|
||
* A spot has a head category (0–3) and subcategories per type letter:
|
||
* `a` format/platform, `b` source/format/genre, `c` language/bitrate/genre,
|
||
* `d` genre, `z` kind (movie, series, book, …).
|
||
*/
|
||
export const SPOT_HEAD_CATEGORIES = ['Image', 'Sound', 'Games', 'Applications']
|
||
|
||
/** What each type letter means per head category. */
|
||
export const SPOT_SUBCAT_TYPES: Record<string, string>[] = [
|
||
{ a: 'Format', b: 'Source', c: 'Language', d: 'Genre', z: 'Type' },
|
||
{ a: 'Format', b: 'Source', c: 'Bitrate', d: 'Genre', z: 'Type' },
|
||
{ a: 'Platform', b: 'Format', c: 'Genre', z: 'Type' },
|
||
{ a: 'Platform', b: 'Genre', z: 'Type' },
|
||
]
|
||
|
||
const NAMES: Record<string, Record<number, string>>[] = [
|
||
{
|
||
a: {
|
||
0: 'DivX',
|
||
1: 'WMV',
|
||
2: 'MPG',
|
||
3: 'DVD5',
|
||
4: 'HD other',
|
||
5: 'ePub',
|
||
6: 'Blu-ray',
|
||
7: 'HD-DVD',
|
||
8: 'WMVHD',
|
||
9: 'x264',
|
||
10: 'DVD9',
|
||
11: 'PDF',
|
||
12: 'Bitmap',
|
||
13: 'Vector',
|
||
14: '3D',
|
||
15: 'UHD',
|
||
},
|
||
b: {
|
||
0: 'CAM',
|
||
1: '(S)VCD',
|
||
2: 'Promo',
|
||
3: 'Retail',
|
||
4: 'TV',
|
||
6: 'Satellite',
|
||
7: 'R5',
|
||
8: 'Telecine',
|
||
9: 'Telesync',
|
||
10: 'Scan',
|
||
11: 'WEB-DL',
|
||
12: 'WEBRip',
|
||
13: 'HDRip',
|
||
},
|
||
c: {
|
||
0: 'No subtitles',
|
||
1: 'Dutch subtitles (external)',
|
||
2: 'Dutch subtitles (builtin)',
|
||
3: 'English subtitles (external)',
|
||
4: 'English subtitles (builtin)',
|
||
6: 'Dutch subtitles (available)',
|
||
7: 'English subtitles (available)',
|
||
10: 'English audio/written',
|
||
11: 'Dutch audio/written',
|
||
12: 'German audio/written',
|
||
13: 'French audio/written',
|
||
14: 'Spanish audio/written',
|
||
15: 'Asian audio/written',
|
||
},
|
||
d: {
|
||
0: 'Action',
|
||
1: 'Adventure',
|
||
2: 'Animation',
|
||
3: 'Cabaret',
|
||
4: 'Comedy',
|
||
5: 'Crime',
|
||
6: 'Documentary',
|
||
7: 'Drama',
|
||
8: 'Family',
|
||
9: 'Fantasy',
|
||
10: 'Arthouse',
|
||
11: 'Television',
|
||
12: 'Horror',
|
||
13: 'Music',
|
||
14: 'Musical',
|
||
15: 'Mystery',
|
||
16: 'Romance',
|
||
17: 'Science Fiction',
|
||
18: 'Sport',
|
||
19: 'Short movie',
|
||
20: 'Thriller',
|
||
21: 'War',
|
||
22: 'Western',
|
||
23: 'Erotica (hetero)',
|
||
24: 'Erotica (gay male)',
|
||
25: 'Erotica (gay female)',
|
||
26: 'Erotica (bi)',
|
||
28: 'Asian',
|
||
29: 'Anime',
|
||
30: 'Cover',
|
||
31: 'Comicbook',
|
||
32: 'Cartoons',
|
||
33: 'Youth',
|
||
34: 'Business',
|
||
35: 'Computer',
|
||
36: 'Hobby',
|
||
37: 'Cooking',
|
||
38: 'Handwork',
|
||
39: 'Craftwork',
|
||
40: 'Health',
|
||
41: 'History',
|
||
42: 'Psychology',
|
||
43: 'Newspaper',
|
||
44: 'Magazine',
|
||
45: 'Science',
|
||
46: 'Female',
|
||
47: 'Religion',
|
||
48: 'Roman',
|
||
49: 'Biography',
|
||
50: 'Detective',
|
||
51: 'Animals',
|
||
52: 'Humor',
|
||
53: 'Travel',
|
||
54: 'True story',
|
||
55: 'Non-fiction',
|
||
56: 'Politics',
|
||
57: 'Poetry',
|
||
58: 'Fairy tale',
|
||
59: 'Technical',
|
||
60: 'Art',
|
||
72: 'Bi',
|
||
73: 'Lesbian',
|
||
74: 'Homo',
|
||
75: 'Hetero',
|
||
76: 'Amature',
|
||
77: 'Group',
|
||
78: 'POV',
|
||
79: 'Solo',
|
||
80: 'Young',
|
||
81: 'Soft',
|
||
82: 'Fetish',
|
||
83: 'Old',
|
||
84: 'Fat',
|
||
85: 'SM',
|
||
86: 'Rough',
|
||
87: 'Dark',
|
||
88: 'Hentai',
|
||
89: 'Outside',
|
||
},
|
||
z: { 0: 'Movie', 1: 'Series', 2: 'Book', 3: 'Erotica', 4: 'Picture' },
|
||
},
|
||
{
|
||
a: {
|
||
0: 'MP3',
|
||
1: 'WMA',
|
||
2: 'WAV',
|
||
3: 'OGG',
|
||
4: 'EAC',
|
||
5: 'DTS',
|
||
6: 'AAC',
|
||
7: 'APE',
|
||
8: 'FLAC',
|
||
},
|
||
b: { 0: 'CD', 1: 'Radio', 2: 'Compilation', 3: 'DVD', 4: 'Other', 5: 'Vinyl', 6: 'Stream' },
|
||
c: {
|
||
0: 'Variable',
|
||
1: '< 96kbit',
|
||
2: '96kbit',
|
||
3: '128kbit',
|
||
4: '160kbit',
|
||
5: '192kbit',
|
||
6: '256kbit',
|
||
7: '320kbit',
|
||
8: 'Lossless',
|
||
9: 'Other',
|
||
},
|
||
d: {
|
||
0: 'Blues',
|
||
1: 'Compilation',
|
||
2: 'Cabaret',
|
||
3: 'Dance',
|
||
4: 'Diverse',
|
||
5: 'Hardcore',
|
||
6: 'World',
|
||
7: 'Jazz',
|
||
8: 'Youth',
|
||
9: 'Classical',
|
||
10: 'Kleinkunst',
|
||
11: 'Dutch',
|
||
12: 'New Age',
|
||
13: 'Pop',
|
||
14: 'RnB',
|
||
15: 'Hiphop',
|
||
16: 'Reggae',
|
||
17: 'Religious',
|
||
18: 'Rock',
|
||
19: 'Soundtracks',
|
||
20: 'Other',
|
||
21: 'Hardstyle',
|
||
22: 'Asian',
|
||
23: 'Disco',
|
||
24: 'Classics',
|
||
25: 'Metal',
|
||
26: 'Country',
|
||
27: 'Dubstep',
|
||
28: 'Nederhop',
|
||
29: 'DnB',
|
||
30: 'Electro',
|
||
31: 'Folk',
|
||
32: 'Soul',
|
||
33: 'Trance',
|
||
34: 'Balkan',
|
||
35: 'Techno',
|
||
36: 'Ambient',
|
||
37: 'Latin',
|
||
38: 'Live',
|
||
},
|
||
z: { 0: 'Album', 1: 'Liveset', 2: 'Podcast', 3: 'Audiobook' },
|
||
},
|
||
{
|
||
a: {
|
||
0: 'Windows',
|
||
1: 'Macintosh',
|
||
2: 'Linux',
|
||
3: 'Playstation',
|
||
4: 'Playstation 2',
|
||
5: 'PSP',
|
||
6: 'Xbox',
|
||
7: 'Xbox 360',
|
||
8: 'Gameboy Advance',
|
||
9: 'Gamecube',
|
||
10: 'Nintendo DS',
|
||
11: 'Nintento Wii',
|
||
12: 'Playstation 3',
|
||
13: 'Windows Phone',
|
||
14: 'iOS',
|
||
15: 'Android',
|
||
16: 'Nintendo 3DS',
|
||
17: 'Playstation 4',
|
||
18: 'XBox 1',
|
||
},
|
||
b: { 0: 'ISO', 1: 'Rip', 2: 'Retail', 3: 'DLC', 4: '', 5: 'Patch', 6: 'Crack' },
|
||
c: {
|
||
0: 'Action',
|
||
1: 'Adventure',
|
||
2: 'Strategy',
|
||
3: 'Roleplaying',
|
||
4: 'Simulation',
|
||
5: 'Race',
|
||
6: 'Flying',
|
||
7: 'Shooter',
|
||
8: 'Platform',
|
||
9: 'Sport',
|
||
10: 'Child/youth',
|
||
11: 'Puzzle',
|
||
12: 'Other',
|
||
13: 'Boardgame',
|
||
14: 'Cards',
|
||
15: 'Education',
|
||
16: 'Music',
|
||
17: 'Family',
|
||
},
|
||
},
|
||
{
|
||
a: {
|
||
0: 'Windows',
|
||
1: 'Macintosh',
|
||
2: 'Linux',
|
||
3: 'OS/2',
|
||
4: 'Windows Phone',
|
||
5: 'Navigation systems',
|
||
6: 'iOS',
|
||
7: 'Android',
|
||
},
|
||
b: {
|
||
0: 'Audio',
|
||
1: 'Video',
|
||
2: 'Graphics',
|
||
3: 'CD/DVD Tools',
|
||
4: 'Media players',
|
||
5: 'Rippers & Encoders',
|
||
6: 'Plugins',
|
||
7: 'Database tools',
|
||
8: 'Email software',
|
||
9: 'Photo',
|
||
10: 'Screensavers',
|
||
11: 'Skin software',
|
||
12: 'Drivers',
|
||
13: 'Browsers',
|
||
14: 'Download managers',
|
||
15: 'Download',
|
||
16: 'Usenet software',
|
||
17: 'RSS Readers',
|
||
18: 'FTP software',
|
||
19: 'Firewalls',
|
||
20: 'Antivirus software',
|
||
21: 'Antispyware software',
|
||
22: 'Optimization software',
|
||
23: 'Security software',
|
||
24: 'System software',
|
||
25: 'Other',
|
||
26: 'Educational',
|
||
27: 'Office',
|
||
28: 'Internet',
|
||
29: 'Communication',
|
||
30: 'Development',
|
||
31: 'Spotnet',
|
||
},
|
||
},
|
||
]
|
||
|
||
export interface SpotCategoryLabels {
|
||
head: string | null
|
||
/** In type order (z, a, b, c, d), e.g. { type: 'Language', values: ['Dutch subtitles (builtin)'] }. */
|
||
subcats: { type: string; values: string[] }[]
|
||
}
|
||
|
||
/** Readable names for a spot's head category and subcategories (e.g. { a: [9], z: [0] }). */
|
||
export function spotCategoryLabels(
|
||
category: number | null,
|
||
subcats: Record<string, number[]> | null
|
||
): SpotCategoryLabels {
|
||
if (category === null || !(category in NAMES)) return { head: null, subcats: [] }
|
||
const labels: SpotCategoryLabels['subcats'] = []
|
||
for (const letter of ['z', 'a', 'b', 'c', 'd']) {
|
||
const values = [
|
||
...new Set(
|
||
(subcats?.[letter] ?? [])
|
||
.map((n) => NAMES[category][letter]?.[n])
|
||
.filter((v): v is string => Boolean(v))
|
||
),
|
||
]
|
||
if (values.length) labels.push({ type: SPOT_SUBCAT_TYPES[category][letter] ?? letter, values })
|
||
}
|
||
return { head: SPOT_HEAD_CATEGORIES[category], subcats: labels }
|
||
}
|