import { BaseCommand, args, flags } from '@adonisjs/core/ace' import { CommandOptions } from '@adonisjs/core/types/ace' import Group from '#models/group' import NntpService from '#services/NntpService' import { parseGroupResponse } from '#lib/nntp/group' /** * Adds newsgroups to index (or reactivates them). The scheduler picks them * up on its next tick, starting INDEX_INITIAL_BACKFILL articles back. */ export default class GroupAdd extends BaseCommand { public static commandName = 'group:add' public static description = 'Adds newsgroups to index.' public static options: CommandOptions = { startApp: true, } @args.spread({ description: 'Group names, e.g. alt.binaries.hdtv' }) declare names: string[] @flags.boolean({ description: 'Deactivate the groups instead' }) declare disable: boolean @flags.boolean({ description: 'Also download bodies to identify obfuscated posts (one body per article; bandwidth-heavy)', }) declare bodies: boolean @flags.number({ description: 'Start this many articles back instead of INDEX_INITIAL_BACKFILL (new groups only)' }) declare backfill?: number @flags.boolean({ description: 'With --backfill: also for groups indexed before, skipping what was missed since' }) declare reset: boolean @flags.boolean({ description: "Start at the server's oldest article: the group's full history (also for groups indexed before)" }) declare fromStart: boolean public async run() { for (const name of this.names) { if (!/^[a-z0-9][a-z0-9.+_-]*$/i.test(name)) { this.logger.error(`Not a valid group name: ${name}`) continue } const group = await Group.firstOrCreate({ name }, { name, active: !this.disable, fetchBodies: this.bodies }) group.active = !this.disable if (this.bodies) group.fetchBodies = true if (this.fromStart) { // The scheduler continues after last_queued_id; 0 would mean "new group" to it. const { first } = await this.groupInfo(name) group.lastQueuedId = first > 1n ? first - 1n : 1n group.lastIndexedId = group.lastQueuedId } else if (this.backfill !== undefined && (this.reset || BigInt(group.lastQueuedId ?? 0) === 0n)) { const { last } = await this.groupInfo(name) group.lastQueuedId = (last > BigInt(this.backfill) ? last - BigInt(this.backfill) : 0n) group.lastIndexedId = group.lastQueuedId } await group.save() this.logger.info( `${name}: ${group.active ? 'active' : 'inactive'}, bodies ${group.fetchBodies ? 'on' : 'off'}` + (this.backfill !== undefined || this.fromStart ? `, starting after article ${group.lastQueuedId}` : '') ) } if (this.backfill !== undefined || this.fromStart) await NntpService.shutdown() } private async groupInfo(name: string) { const conn = await NntpService.acquire() let failed = false try { return parseGroupResponse((await conn.group(name)).resp) } catch (error) { failed = true throw error } finally { NntpService.release(conn, failed) } } }