Skip to content

Commit

Permalink
feat: enhance bot scheduling and configuration handling; add bearer a…
Browse files Browse the repository at this point in the history
…uthentication support
  • Loading branch information
ptsgrn committed Jan 8, 2025
1 parent cfc7b25 commit 20495d3
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 63 deletions.
Binary file modified bun.lockb
Binary file not shown.
12 changes: 9 additions & 3 deletions core/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,16 @@ export class Bot extends ServiceBase {
}

async schedule(options: {
pattern: string | Date;
pattern?: string | Date;
options?: CronOptions;
}) {
this.job = new Cron(options.pattern, {
} = {
pattern: this.info.frequency
}) {
const pattern = options.pattern
if (!pattern) {
throw new Error('No pattern or frequency specified');
}
this.job = new Cron(pattern, {
name: this.info.id,
timezone: this.config.bot.timezone,
...options.options
Expand Down
2 changes: 1 addition & 1 deletion core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const config = z.object({
toolforge: z.object({
login: z.string(),
tooluser: z.string(),
deploykey: z.string(),
webKey: z.string(),
}),
replica: z.object({
username: z.string(),
Expand Down
110 changes: 61 additions & 49 deletions core/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { createId } from '@paralleldrive/cuid2'
import { Command, InvalidArgumentError, Option } from '@commander-js/extra-typings'
import { version } from "../package.json"
import { ServiceBase } from './base'
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'

class ScriptRunner extends ServiceBase {
private cli = new Command()
private config = {
logLevel: "info",
configFile: "config.toml"
}
public scheduled: Record<string, Bot> = {}

async scriptModule(scriptName: string) {
if (!scriptName) {
Expand All @@ -37,6 +36,57 @@ class ScriptRunner extends ServiceBase {
return (new scriptModule.default) as unknown as Bot
}

async runScript(scriptName: string) {
scriptName = scriptName.replace(/^scripts\//, "").replace(/\.ts$/, "")
const scriptModule = await this.scriptModule(scriptName)
scriptModule.cli
.name('run ' + scriptName)
.description(scriptModule.scriptDescription)

// Ensure that we pass the global options correctly to the script
scriptModule.cli.addOption(new Option("-l, --log-level <level>", "Log level")
.choices(['debug', 'info', 'warn', 'error'])
.default(this.config.logger.level)
)
scriptModule.cli.addOption(new Option("-c, --config <file>", "Config file")
.default(this.config.logger.level)
)

// Pass the correct arguments to parse
scriptModule.cli.parse(process.argv.slice(2))

scriptModule.log.defaultMeta = {
script: scriptName,
rid: createId(),
}

try {
await scriptModule.beforeRun()
await scriptModule.run()
} catch (e) {
console.error(e)
}
await scriptModule.afterRun()
}

async startScheduled() {
const files = await readdir(join(import.meta.dir, '../scripts'), { recursive: true })
for (const file of files) {
if (!file.endsWith('.ts')) {
continue
}
const module = await import(`@scripts/${file}`)
const script = new module.default() as Bot
if (!script.info.frequency) {
this.log.debug(`Script ${script.info.id} has no frequency, skipping`)
continue
}
this.log.info(`Scheduled ${script.info.id} (${script.info.frequency})`)
this.scheduled[script.info.id] = script
await script.schedule()
}
}

async run() {
this.cli
.name('patsabot')
Expand All @@ -54,50 +104,7 @@ class ScriptRunner extends ServiceBase {
.argument('<script>', 'Script name')
.argument('[args...]', 'Script arguments')
.passThroughOptions()
.action(async (scriptName) => {
scriptName = scriptName.replace(/^scripts\//, "").replace(/\.ts$/, "")
const scriptModule = await this.scriptModule(scriptName)
scriptModule.cli
.name('run ' + scriptName)
.description(scriptModule.scriptDescription)

// Ensure that we pass the global options correctly to the script
scriptModule.cli.addOption(new Option("-l, --log-level <level>", "Log level")
.choices(['debug', 'info', 'warn', 'error'])
.default(this.config.logLevel)
)
scriptModule.cli.addOption(new Option("-c, --config <file>", "Config file")
.default(this.config.configFile)
)

// Handle global options within the script
scriptModule.cli.opts = () => ({
logLevel: this.config.logLevel,
configFile: this.config.configFile,
...scriptModule.cli.opts()
})

// Pass the correct arguments to parse
scriptModule.cli.parse(process.argv.slice(2))

scriptModule.log.defaultMeta = {
script: scriptName,
rid: createId(),
}

// Apply global log level
if (this.config.logLevel === 'debug') {
scriptModule.log.level = 'debug'
}

try {
await scriptModule.beforeRun()
await scriptModule.run()
} catch (e) {
console.error(e)
}
await scriptModule.afterRun()
});
.action(this.runScript.bind(this));

this.cli
.command('schedule <script>')
Expand Down Expand Up @@ -143,8 +150,13 @@ class ScriptRunner extends ServiceBase {
Replica.createReplicaTunnel(wiki, options.cluster, options.port)
})

this.cli
.command("start")
.description("Loading all pre-scheduled scripts and start the bot")
.action(this.startScheduled.bind(this))

// Ensure that we parse the correct arguments from process.argv
this.cli.parse()
this.cli.parse(Bun.argv)
}
}

Expand Down
38 changes: 28 additions & 10 deletions core/web.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import { Elysia, t } from "elysia"
import { jwt } from '@elysiajs/jwt'
import { bearer } from "@elysiajs/bearer"
import { swagger } from '@elysiajs/swagger'
import { $, ShellPromise, type Shell } from "bun"
import { $, ShellPromise } from "bun"
import { config } from '@core/config'
import { version } from "../package.json"

const now = () => new Date().toISOString()

export const app = new Elysia()
.use(swagger())
.use(
jwt({
name: "jwt",
secret: config.toolforge.deploykey
})
)
.get("/deploy", async function* ({ jwt, query }) {
const data = await jwt.verify(query.token)
.use(swagger({
swaggerOptions: {
persistAuthorization: true,
},
documentation: {
info: {
title: 'PatsaBot API',
version: version
},
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
}
}))
.get("/deploy", async function* ({ headers, set, error }) {
if (headers.authorization !== `Bearer ${config.toolforge.webKey}`) {
return error(401, "Unauthorized")
}

const commands = [
[$`git fetch`, "git fetch"],
[$`git reset --hard origin/main`, "git reset --hard origin/main"],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"module": "core/run.ts",
"dependencies": {
"@commander-js/extra-typings": "^13.0.0",
"@elysiajs/bearer": "^1.2.0",
"@elysiajs/jwt": "^1.2.0",
"@elysiajs/swagger": "^1.2.0",
"@paralleldrive/cuid2": "^2.2.2",
Expand Down
5 changes: 5 additions & 0 deletions scripts/database-reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export class DatabaseReportBot extends Bot {
headers: string[] = ['ที่']
preTableTemplates: string[] = []

constructor() {
super()
this.info.frequency = this.reportFrequency
}

get preTableHeader() {
return `\n\n${this.preTableTemplates.join("\n")}\n{| class="wikitable sortable static-row-numbers static-row-header-text"\n|- style="white-space: nowrap;"`
}
Expand Down

0 comments on commit 20495d3

Please sign in to comment.