Skip to content

Commit c815b81

Browse files
committed
feat: fetch summary
1 parent 96d7a1d commit c815b81

File tree

6 files changed

+110
-13
lines changed

6 files changed

+110
-13
lines changed

Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ ENV GOARCH=amd64
1616
# Set working directory
1717
WORKDIR /app
1818

19+
# Copy go mod files first for better caching
20+
COPY go.mod go.sum ./
21+
22+
# Download dependencies (cached if go.mod/go.sum unchanged)
23+
RUN go mod download
24+
25+
# Copy source code
1926
COPY . .
2027

2128
# Build the application

README.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@
77
You can configure HTTP-backed bot commands in `bot.json`. Example commands included:
88

99
- `/bot joke` — uses icanhazdadjoke (returns the `joke` field)
10-
- `/bot quote` — uses quotable.io (returns the `content` field)
1110
- `/bot catfact` — uses catfact.ninja (returns the `fact` field)
11+
- `/bot summary` — fetches daily link summaries from linkstash API
1212

13-
Add or change commands in `bot.json` and set `BOT_CONFIG_PATH` in `config.json` if you place it elsewhere. The bot will prefix responses using `BOT_REPLY_LABEL` in `config.json` (defaults to `> `).
13+
Add or change commands in `bot.json` and set `BOT_CONFIG_PATH` in `config.json` if you place it elsewhere. The bot will prefix responses using `BOT_REPLY_LABEL` in `config.json` (defaults to `[BOT]\n`).
14+
15+
### Room-specific bot configuration
16+
17+
Bot commands are enabled per room via the `allowedCommands` array in `config.json`:
18+
19+
- `"allowedCommands": []` — Enable bot with all commands allowed
20+
- `"allowedCommands": ["summary", "joke"]` — Enable bot with only specific commands
21+
- Omit `allowedCommands` — Bot disabled in that room
22+
23+
This allows fine-grained control over which commands are available in each room.
1424

1525
pairs nicely with [lava](https://polarhive.net/lava)
1626

@@ -38,7 +48,15 @@ Edit `config.json`:
3848
- `MATRIX_USER`: Your Matrix user ID
3949
- `MATRIX_PASSWORD`: Password
4050
- `MATRIX_RECOVERY_KEY`: For E2EE verification
41-
- `MATRIX_ROOM_ID`: Array of rooms to watch
51+
- `MATRIX_ROOM_ID`: Array of rooms to watch, each with:
52+
- `id`: Room ID
53+
- `comment`: Human-readable name
54+
- `hook`: Optional webhook URL for link processing
55+
- `key`: Webhook auth key
56+
- `sendUser`/`sendTopic`: Whether to include user/topic in webhooks
57+
- `allowedCommands`: Array of allowed bot commands (empty = all, omit = disabled)
58+
- `BOT_REPLY_LABEL`: Bot response prefix (default: `[BOT]\n`)
59+
- `LINKSTASH_URL`: Base URL for linkstash service (used in summary bot)
4260
- `MATRIX_DEVICE_NAME`: Device name
4361
- `DEBUG`: Enable debug logging
4462

ash.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ func (s *MetaSyncStore) SaveRoomAccountData(ctx context.Context, userID id.UserI
7272
}
7373

7474
type RoomIDEntry struct {
75-
ID string `json:"id"`
76-
Comment string `json:"comment"`
77-
Hook string `json:"hook,omitempty"`
78-
Key string `json:"key,omitempty"`
79-
SendUser bool `json:"sendUser,omitempty"`
80-
SendTopic bool `json:"sendTopic,omitempty"`
81-
BotEnabled bool `json:"botEnabled,omitempty"`
75+
ID string `json:"id"`
76+
Comment string `json:"comment"`
77+
Hook string `json:"hook,omitempty"`
78+
Key string `json:"key,omitempty"`
79+
SendUser bool `json:"sendUser,omitempty"`
80+
SendTopic bool `json:"sendTopic,omitempty"`
81+
AllowedCommands []string `json:"allowedCommands,omitempty"`
8282
}
8383

8484
type Config struct {
@@ -92,6 +92,7 @@ type Config struct {
9292
LinksPath string `json:"LINKS_JSON_PATH"`
9393
BotConfigPath string `json:"BOT_CONFIG_PATH"`
9494
BotReplyLabel string `json:"BOT_REPLY_LABEL,omitempty"`
95+
LinkstashURL string `json:"LINKSTASH_URL,omitempty"`
9596
SyncTimeoutMS int `json:"SYNC_TIMEOUT_MS"`
9697
Debug bool `json:"DEBUG"`
9798
DeviceName string `json:"MATRIX_DEVICE_NAME"`
@@ -669,7 +670,11 @@ func run(ctx context.Context, metaDB *sql.DB, messagesDB *sql.DB, cfg *Config) e
669670
return
670671
}
671672
log.Info().Str("sender", string(ev.Sender)).Str("room", currentRoom.Comment).Msg(truncate(msgData.Msg.Body, 100))
672-
if currentRoom.BotEnabled && strings.HasPrefix(msgData.Msg.Body, "/bot") {
673+
if cfg.BotReplyLabel != "" && strings.Contains(msgData.Msg.Body, cfg.BotReplyLabel) {
674+
log.Debug().Str("label", cfg.BotReplyLabel).Msg("skipped bot processing due to bot reply label")
675+
return
676+
}
677+
if currentRoom.AllowedCommands != nil && strings.HasPrefix(msgData.Msg.Body, "/bot") {
673678
select {
674679
case <-readyChan:
675680
case <-evCtx.Done():
@@ -683,10 +688,12 @@ func run(ctx context.Context, metaDB *sql.DB, messagesDB *sql.DB, cfg *Config) e
683688
var body string
684689
if cmd == "" || cmd == "hi" {
685690
body = "hello"
691+
} else if len(currentRoom.AllowedCommands) > 0 && !inSlice(currentRoom.AllowedCommands, cmd) {
692+
body = "command not allowed in this room"
686693
} else {
687694
if botCfg != nil {
688695
if cmdCfg, ok := botCfg.Commands[cmd]; ok {
689-
resp, err := FetchBotCommand(evCtx, &cmdCfg)
696+
resp, err := FetchBotCommand(evCtx, &cmdCfg, cfg.LinkstashURL)
690697
if err != nil {
691698
log.Error().Err(err).Str("cmd", cmd).Msg("failed to fetch bot command")
692699
body = fmt.Sprintf("sorry, couldn't fetch %s right now", cmd)
@@ -813,6 +820,15 @@ func run(ctx context.Context, metaDB *sql.DB, messagesDB *sql.DB, cfg *Config) e
813820
return ctx.Err()
814821
}
815822

823+
func inSlice(slice []string, item string) bool {
824+
for _, s := range slice {
825+
if s == item {
826+
return true
827+
}
828+
}
829+
return false
830+
}
831+
816832
func truncate(s string, maxLen int) string {
817833
if len(s) <= maxLen {
818834
return s

bot.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func LoadBotConfig(path string) (*BotConfig, error) {
4242
}
4343

4444
// FetchBotCommand executes the configured command and returns a string to post.
45-
func FetchBotCommand(ctx context.Context, c *BotCommand) (string, error) {
45+
func FetchBotCommand(ctx context.Context, c *BotCommand, linkstashURL string) (string, error) {
4646
method := c.Method
4747
if method == "" {
4848
method = "GET"
@@ -82,6 +82,10 @@ func FetchBotCommand(ctx context.Context, c *BotCommand) (string, error) {
8282
if s, ok := v.(string); ok {
8383
return strings.TrimSpace(s), nil
8484
}
85+
// Check if it's an array of posts (for summary)
86+
if arr, ok := v.([]interface{}); ok {
87+
return formatPosts(arr, linkstashURL), nil
88+
}
8589
// try to marshal the value to string
8690
if v != nil {
8791
b, _ := json.Marshal(v)
@@ -110,3 +114,24 @@ func extractJSONPath(root interface{}, path string) interface{} {
110114
}
111115
return cur
112116
}
117+
118+
// formatPosts formats an array of post objects into a readable string.
119+
func formatPosts(posts []interface{}, linkstashURL string) string {
120+
var sb strings.Builder
121+
limit := 5
122+
if len(posts) < limit {
123+
limit = len(posts)
124+
}
125+
for i := 0; i < limit; i++ {
126+
p := posts[i]
127+
if m, ok := p.(map[string]interface{}); ok {
128+
title, _ := m["title"].(string)
129+
url, _ := m["url"].(string)
130+
if title != "" && url != "" {
131+
sb.WriteString(fmt.Sprintf("- %s (%s)\n", title, url))
132+
}
133+
}
134+
}
135+
sb.WriteString(fmt.Sprintf("\nSee full list: %s", linkstashURL))
136+
return sb.String()
137+
}

bot.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
"method": "GET",
1414
"url": "https://catfact.ninja/fact",
1515
"json_path": "fact"
16+
},
17+
"summary": {
18+
"method": "GET",
19+
"url": "https://linkstash.hsp-ec.xyz/api/summary",
20+
"json_path": "summary"
1621
}
1722
}
1823
}

config.json.example

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"MATRIX_HOMESERVER": "https://matrix.homeserver.tld",
3+
"MATRIX_USER": "@username:homeserver.tld",
4+
"MATRIX_PASSWORD": "xxxxx",
5+
"MATRIX_RECOVERY_KEY": "xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx",
6+
"MATRIX_ROOM_ID": [
7+
{
8+
"id": "!id:beeper.local",
9+
"comment": "room_1",
10+
"hook": "https://linkstash.your-domain.tld/api/add",
11+
"key": "mentor-here",
12+
"sendUser": true,
13+
"sendTopic": true,
14+
"allowedCommands": []
15+
}
16+
],
17+
"BOT_REPLY_LABEL": "[BOT] ",
18+
"LINKSTASH_URL": "https://linkstash.your-domain.tld",
19+
"MATRIX_DEVICE_NAME": "ash",
20+
"META_DB_PATH": "./data/meta.db",
21+
"DB_PATH": "./data/messages.db",
22+
"LINKS_JSON_PATH": "./data/links.json",
23+
"BOT_CONFIG_PATH": "./bot.json",
24+
"DEBUG": false,
25+
"OPT_OUT_TAG": "#private"
26+
}

0 commit comments

Comments
 (0)