sotobot 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/lib/bot.d.ts +26 -7
- package/lib/bot.js +27 -46
- package/lib/cloudflare.d.ts +0 -5
- package/lib/commands.d.ts +7 -9
- package/lib/drivers/github.d.ts +21 -0
- package/lib/drivers/github.js +55 -0
- package/lib/drivers/types.d.ts +16 -0
- package/lib/drivers/types.js +1 -0
- package/lib/main.d.ts +7 -8
- package/lib/main.js +5 -11
- package/lib/options.d.ts +3 -0
- package/lib/options.js +1 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# sotobot 🤖
|
|
2
|
+
|
|
3
|
+
A serverless-focused GitHub bot framework.
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> This project is in early development and will have many breaking changes.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install sotobot
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createBot, GitHubDriver } from 'sotobot';
|
|
18
|
+
import { createServer } from 'sotobot/cloudflare.js';
|
|
19
|
+
|
|
20
|
+
const driver = new GitHubDriver({
|
|
21
|
+
appId: env.APP_ID,
|
|
22
|
+
privateKey: env.PRIVATE_KEY,
|
|
23
|
+
webhookSecret: env.WEBHOOK_SECRET,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const bot = createBot({ name: 'soto' }, driver);
|
|
27
|
+
|
|
28
|
+
bot.addEventListener('botCommand', async (event) => {
|
|
29
|
+
const { command, args, owner, repo, pullRequestNumber, source } =
|
|
30
|
+
event.detail;
|
|
31
|
+
|
|
32
|
+
if (command !== 'ping') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { octokit } = source;
|
|
37
|
+
await octokit.request(
|
|
38
|
+
'POST /repos/{owner}/{repo}/issues/{issue_number}/comments',
|
|
39
|
+
{
|
|
40
|
+
owner,
|
|
41
|
+
repo,
|
|
42
|
+
issue_number: pullRequestNumber,
|
|
43
|
+
body: 'pong 🏓',
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export default createServer(bot);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
[MIT](./LICENSE)
|
package/lib/bot.d.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export interface
|
|
4
|
-
|
|
1
|
+
import { Driver } from './drivers/types.js';
|
|
2
|
+
import { BotOptions } from './options.js';
|
|
3
|
+
export interface BotCommandEventDetail {
|
|
4
|
+
bot: Bot;
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
repo: string;
|
|
8
|
+
owner: string;
|
|
9
|
+
pullRequestNumber: number;
|
|
10
|
+
source: unknown;
|
|
5
11
|
}
|
|
6
|
-
export
|
|
12
|
+
export interface BotEventMap {
|
|
13
|
+
botCommand: CustomEvent<BotCommandEventDetail>;
|
|
14
|
+
}
|
|
15
|
+
interface BotEventTarget extends EventTarget {
|
|
16
|
+
addEventListener<TEvent extends keyof BotEventMap>(event: TEvent, listener: (ev: BotEventMap[TEvent]) => void): void;
|
|
17
|
+
addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
18
|
+
}
|
|
19
|
+
declare const eventTarget: {
|
|
20
|
+
new (): BotEventTarget;
|
|
21
|
+
prototype: BotEventTarget;
|
|
22
|
+
};
|
|
23
|
+
export declare class Bot extends eventTarget {
|
|
7
24
|
#private;
|
|
8
|
-
|
|
9
|
-
|
|
25
|
+
get name(): string;
|
|
26
|
+
get driver(): Driver;
|
|
27
|
+
constructor(driver: Driver, options: BotOptions);
|
|
10
28
|
handleRequest(request: Request): Promise<Response>;
|
|
11
29
|
}
|
|
30
|
+
export {};
|
package/lib/bot.js
CHANGED
|
@@ -1,57 +1,38 @@
|
|
|
1
|
-
import { createWebMiddleware } from '@octokit/webhooks';
|
|
2
1
|
import { parseCommand } from './comments.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
#app;
|
|
7
|
-
#middleware;
|
|
8
|
-
#commands = new Map();
|
|
2
|
+
const eventTarget = EventTarget;
|
|
3
|
+
export class Bot extends eventTarget {
|
|
4
|
+
#driver;
|
|
9
5
|
#options;
|
|
10
|
-
|
|
11
|
-
this.#options
|
|
12
|
-
this.#app = app;
|
|
13
|
-
this.#app.webhooks.onError(this.#onError);
|
|
14
|
-
this.#app.webhooks.on('issue_comment.created', this.#onIssueComment);
|
|
15
|
-
this.#middleware = createWebMiddleware(this.#app.webhooks, {
|
|
16
|
-
path: WEBHOOK_PATH,
|
|
17
|
-
});
|
|
6
|
+
get name() {
|
|
7
|
+
return this.#options.name;
|
|
18
8
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
9
|
+
get driver() {
|
|
10
|
+
return this.#driver;
|
|
11
|
+
}
|
|
12
|
+
constructor(driver, options) {
|
|
13
|
+
super();
|
|
14
|
+
this.#driver = driver;
|
|
15
|
+
this.#options = options;
|
|
16
|
+
this.#driver.addEventListener('pr.commentCreated', this.#onPRCommentCreated);
|
|
28
17
|
}
|
|
29
18
|
handleRequest(request) {
|
|
30
|
-
return this.#
|
|
19
|
+
return this.#driver.handleRequest(request);
|
|
31
20
|
}
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const command = parseCommand(this.#options.name, payload.comment.body);
|
|
21
|
+
#onPRCommentCreated = async (ev) => {
|
|
22
|
+
const command = parseCommand(this.#options.name, ev.detail.body);
|
|
37
23
|
if (!command) {
|
|
38
24
|
return;
|
|
39
25
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
await handler({ octokit, payload, args: command.args });
|
|
53
|
-
};
|
|
54
|
-
#onError = (error) => {
|
|
55
|
-
console.error('Webhook handler error', error);
|
|
26
|
+
this.dispatchEvent(new CustomEvent('botCommand', {
|
|
27
|
+
detail: {
|
|
28
|
+
bot: this,
|
|
29
|
+
command: command.name,
|
|
30
|
+
args: command.args,
|
|
31
|
+
repo: ev.detail.repository,
|
|
32
|
+
owner: ev.detail.owner,
|
|
33
|
+
pullRequestNumber: ev.detail.pullRequestNumber,
|
|
34
|
+
source: ev.detail.raw,
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
56
37
|
};
|
|
57
38
|
}
|
package/lib/cloudflare.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { Bot } from './bot.js';
|
|
2
|
-
export interface ServerOptions {
|
|
3
|
-
privateKey: string;
|
|
4
|
-
appId: string;
|
|
5
|
-
webhookSecret: string;
|
|
6
|
-
}
|
|
7
2
|
type BotFactory = () => Promise<Bot>;
|
|
8
3
|
export declare function createServer(bot: Bot | BotFactory): {
|
|
9
4
|
fetch(request: Request): Promise<Response>;
|
package/lib/commands.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EmitterWebhookEvent } from '@octokit/webhooks/types';
|
|
3
|
-
type IssueCommentEvent = EmitterWebhookEvent<'issue_comment.created'> & {
|
|
4
|
-
octokit: Octokit;
|
|
5
|
-
};
|
|
1
|
+
import type { Bot } from './bot.js';
|
|
6
2
|
export interface CommandContext {
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
command: string;
|
|
4
|
+
raw: unknown;
|
|
9
5
|
args: string[];
|
|
6
|
+
repo: string;
|
|
7
|
+
owner: string;
|
|
8
|
+
pullRequestNumber: number | undefined;
|
|
10
9
|
}
|
|
11
|
-
export type CommandHandler = (context: CommandContext) => Promise<void>;
|
|
12
|
-
export {};
|
|
10
|
+
export type CommandHandler = (bot: Bot, context: CommandContext) => Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Octokit } from '@octokit/core';
|
|
2
|
+
import type { HandlerFunction } from '@octokit/webhooks/types';
|
|
3
|
+
import { Driver, PRCommentCreatedEventDetail } from './types.js';
|
|
4
|
+
export interface GitHubDriverOptions {
|
|
5
|
+
privateKey: string;
|
|
6
|
+
appId: string;
|
|
7
|
+
webhookSecret: string;
|
|
8
|
+
}
|
|
9
|
+
type IssueCommentCreatedEvent = Parameters<HandlerFunction<'issue_comment.created', {
|
|
10
|
+
octokit: Octokit;
|
|
11
|
+
}>>[0];
|
|
12
|
+
export interface GitHubDriverEventMap {
|
|
13
|
+
error: CustomEvent<unknown>;
|
|
14
|
+
commentCreated: CustomEvent<PRCommentCreatedEventDetail<IssueCommentCreatedEvent>>;
|
|
15
|
+
}
|
|
16
|
+
export declare class GitHubDriver extends EventTarget implements Driver {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(options: GitHubDriverOptions);
|
|
19
|
+
handleRequest(request: Request): Promise<Response>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { App } from '@octokit/app';
|
|
2
|
+
import { createWebMiddleware } from '@octokit/webhooks';
|
|
3
|
+
import { WEBHOOK_PATH } from '../constants.js';
|
|
4
|
+
import { hasWriteAccess } from '../octokit.js';
|
|
5
|
+
export class GitHubDriver extends EventTarget {
|
|
6
|
+
#app;
|
|
7
|
+
#middleware;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super();
|
|
10
|
+
const { privateKey, appId, webhookSecret } = options;
|
|
11
|
+
this.#app = new App({
|
|
12
|
+
appId,
|
|
13
|
+
privateKey,
|
|
14
|
+
webhooks: {
|
|
15
|
+
secret: webhookSecret,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
this.#app.webhooks.onError(this.#onError);
|
|
19
|
+
this.#app.webhooks.on('issue_comment.created', this.#onIssueComment);
|
|
20
|
+
this.#middleware = createWebMiddleware(this.#app.webhooks, {
|
|
21
|
+
path: WEBHOOK_PATH,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
handleRequest(request) {
|
|
25
|
+
return this.#middleware(request);
|
|
26
|
+
}
|
|
27
|
+
#onIssueComment = async (event) => {
|
|
28
|
+
const { payload, octokit } = event;
|
|
29
|
+
if (!payload.issue.pull_request) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const username = payload.comment.user?.login;
|
|
33
|
+
if (!username) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const allowed = await hasWriteAccess(octokit, payload.repository.owner.login, payload.repository.name, username);
|
|
37
|
+
if (!allowed) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.dispatchEvent(new CustomEvent('pr.commentCreated', {
|
|
41
|
+
detail: {
|
|
42
|
+
raw: event,
|
|
43
|
+
body: payload.comment.body || '',
|
|
44
|
+
repository: payload.repository.name,
|
|
45
|
+
owner: payload.repository.owner.login,
|
|
46
|
+
pullRequestNumber: payload.issue.number,
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
#onError = (error) => {
|
|
51
|
+
this.dispatchEvent(new CustomEvent('error', {
|
|
52
|
+
detail: error,
|
|
53
|
+
}));
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface PRCommentCreatedEventDetail<T> {
|
|
2
|
+
body: string;
|
|
3
|
+
raw: T;
|
|
4
|
+
repository: string;
|
|
5
|
+
owner: string;
|
|
6
|
+
pullRequestNumber: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DriverEventMap {
|
|
9
|
+
error: CustomEvent<unknown>;
|
|
10
|
+
'pr.commentCreated': CustomEvent<PRCommentCreatedEventDetail<unknown>>;
|
|
11
|
+
}
|
|
12
|
+
export interface Driver {
|
|
13
|
+
handleRequest(request: Request): Promise<Response>;
|
|
14
|
+
addEventListener<TEvent extends keyof DriverEventMap>(event: TEvent, listener: (ev: DriverEventMap[TEvent]) => void): void;
|
|
15
|
+
addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/main.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Bot
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
bot: BotOptions;
|
|
7
|
-
}
|
|
8
|
-
export declare function createBot(options: CreateBotOptions): Bot;
|
|
1
|
+
import { Bot } from './bot.js';
|
|
2
|
+
import { BotOptions } from './options.js';
|
|
3
|
+
import { Driver } from './drivers/types.js';
|
|
4
|
+
export declare function createBot(options: BotOptions, driver: Driver): Bot;
|
|
5
|
+
export { Bot, type BotCommandEventDetail, type BotEventMap } from './bot.js';
|
|
9
6
|
export * from './commands.js';
|
|
7
|
+
export * from './drivers/types.js';
|
|
8
|
+
export * from './drivers/github.js';
|
package/lib/main.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { App } from '@octokit/app';
|
|
2
1
|
import { Bot } from './bot.js';
|
|
3
|
-
export function createBot(options) {
|
|
4
|
-
const
|
|
5
|
-
const app = new App({
|
|
6
|
-
appId,
|
|
7
|
-
privateKey,
|
|
8
|
-
webhooks: {
|
|
9
|
-
secret: webhookSecret,
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
const bot = new Bot(app, options.bot);
|
|
2
|
+
export function createBot(options, driver) {
|
|
3
|
+
const bot = new Bot(driver, options);
|
|
13
4
|
return bot;
|
|
14
5
|
}
|
|
6
|
+
export { Bot } from './bot.js';
|
|
15
7
|
export * from './commands.js';
|
|
8
|
+
export * from './drivers/types.js';
|
|
9
|
+
export * from './drivers/github.js';
|
package/lib/options.d.ts
ADDED
package/lib/options.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|