Adding Slack modules

This commit is contained in:
lwoodard
2026-04-13 11:27:18 -06:00
parent 1d9f0b519b
commit 639a08a808
5 changed files with 522 additions and 9 deletions

301
README.md
View File

@@ -21,6 +21,8 @@ No Elgato software required — communicates directly with the device over USB H
- Runs as a systemd user service (Linux) or launchd agent (macOS), starts automatically at login
- No Stream Deck app, no Node.js, no Electron
- **Modules** — define reusable, parameterised commands in `modules.yaml` with Go templates; secrets stay in env vars, config stays in dotfiles. First built-in example: Slack (status, presence, snooze). See [Modules](#modules).
**Planned:** text/label overlays on keys, multi-page layouts, AUR package — see [Roadmap](#roadmap)
---
@@ -37,13 +39,16 @@ streamdeck-go/
├── internal/
│ ├── config/
│ │ └── config.go # YAML parsing with XDG-aware defaults
── device/
└── streamdeck.go # USB HID communication, image encoding, button reads
── device/
└── streamdeck.go # USB HID communication, image encoding, button reads
│ └── modules/
│ └── modules.go # Module registry, template resolution, env/expiry helpers
├── systemd/
│ └── streamdeck-go.service # systemd user service (Linux)
├── launchd/
│ └── com.woodarddigital.streamdeck-go.plist # launchd agent (macOS)
├── config.example.yaml # Starter config (copied to ~/.config on install)
├── modules.example.yaml # Example modules file (Slack integration)
├── Makefile # build / install / uninstall
├── go.mod
└── go.sum
@@ -53,10 +58,15 @@ streamdeck-go/
```
~/.config/streamdeck-go/config.yaml
~/.config/streamdeck-go/modules.yaml (optional)
├── fsnotify watcher ──── file saved? ──▶ cancel ctx → reload config → restart run()
├── fsnotify watcher ──── file saved? ──▶ cancel ctx → reload both → restart run()
└──▶ run(ctx)
└──▶ run(ctx, registry)
├── module resolution ──▶ key has module/function?
│ └──▶ registry.Resolve() → rendered shell command
│ (templates expanded, env vars resolved)
├── static icon ──▶ device.SetKeyImage() (scale → flip → JPEG → HID)
@@ -391,6 +401,94 @@ keys:
---
### Launching applications
On Linux, GUI apps are typically launched by their binary name (`firefox`, `ghostty`, `nautilus`). On macOS, apps live in `/Applications/` as `.app` bundles and need to be opened differently.
#### macOS — `open -a`
The `open -a` command launches (or focuses) a macOS application by name:
```yaml
keys:
0:
icon: firefox.png
command: "open -a Firefox"
1:
icon: slack.png
command: "open -a Slack"
2:
icon: ghostty.png
command: "open -a Ghostty"
3:
icon: finder.png
command: "open -a Finder ~/Documents" # open Finder to a specific folder
4:
icon: vscode.png
command: "open -a 'Visual Studio Code'" # quote names with spaces
```
**How `open -a` works:**
- If the app is already running, it brings it to the front (no duplicate launched).
- If the app is not running, it launches it.
- The app name matches what's in `/Applications/` minus the `.app` extension.
- To find the exact name: `ls /Applications/` or `osascript -e 'tell application "System Events" to get name of every process whose background only is false'`
**Opening files and URLs:**
```yaml
keys:
5:
icon: project.png
command: "open -a 'Visual Studio Code' ~/Projects/myproject" # open a folder in VS Code
6:
icon: notes.png
command: "open ~/Documents/notes.txt" # opens in default app for .txt
7:
icon: github.png
command: "open https://github.com" # opens in default browser
```
#### Linux — direct binary
```yaml
keys:
0:
icon: firefox.png
command: firefox
1:
icon: slack.png
command: slack
2:
icon: ghostty.png
command: ghostty
3:
icon: files.png
command: nautilus ~/Documents
```
Most Linux desktop apps can also be launched with their `.desktop` file via `gtk-launch`:
```yaml
keys:
4:
icon: vscode.png
command: "gtk-launch code" # uses the .desktop file name (without .desktop)
```
#### Cross-platform keys
If you use the same config on both platforms, you can use a shell one-liner:
```yaml
keys:
0:
icon: browser.png
command: "if [ \"$(uname)\" = \"Darwin\" ]; then open -a Firefox; else firefox; fi"
```
---
### Terminal & SSH commands
Any shell command works — including launching terminals and SSH sessions.
@@ -404,7 +502,7 @@ keys:
command: ghostty # Linux
1:
icon: terminal.png
command: open -a Terminal # macOS — or: open -a iTerm
command: open -a Ghostty # macOS — or: open -a Terminal, open -a iTerm
```
**SSH:**
@@ -506,6 +604,199 @@ commands:
---
### Modules
Modules let you define reusable commands in a separate `modules.yaml` file instead of inlining shell commands in your config. This is especially useful for API-driven integrations like Slack where commands are long `curl` calls with tokens and JSON payloads.
#### How it works
1. Create `~/.config/streamdeck-go/modules.yaml` (next to your `config.yaml`).
2. Define modules with named functions. Each function has an `exec` template and optional default `params`.
3. Reference them in `config.yaml` with `module`, `function`, and optional `params` overrides.
The daemon watches `modules.yaml` for changes alongside `config.yaml` — edits to either file trigger a live reload.
#### Module file format
```yaml
# ~/.config/streamdeck-go/modules.yaml
modules:
slack:
set_status:
params:
emoji: ":speech_balloon:"
text: "In a meeting"
expiry: "1h"
exec: |
curl -s -X POST https://slack.com/api/users.profile.set \
-H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \
-H "Content-Type: application/json" \
-d '{"profile":{"status_emoji":"{{.emoji}}","status_text":"{{.text}}","status_expiration":{{expiry .expiry}}}}'
clear_status:
exec: |
curl -s -X POST https://slack.com/api/users.profile.set \
-H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \
-H "Content-Type: application/json" \
-d '{"profile":{"status_emoji":"","status_text":"","status_expiration":0}}'
set_presence:
params:
presence: "away"
exec: |
curl -s -X POST https://slack.com/api/users.setPresence \
-H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \
-H "Content-Type: application/json" \
-d '{"presence":"{{.presence}}"}'
snooze:
params:
minutes: "60"
exec: |
curl -s -X POST https://slack.com/api/dnd.setSnooze \
-H "Authorization: Bearer {{env "SLACK_TOKEN"}}" \
-H "Content-Type: application/json" \
-d '{"num_minutes":{{.minutes}}}'
end_snooze:
exec: |
curl -s -X POST https://slack.com/api/dnd.endSnooze \
-H "Authorization: Bearer {{env "SLACK_TOKEN"}}"
```
A full example is included at [`modules.example.yaml`](modules.example.yaml) in the repo root.
#### Using modules in config.yaml
Reference a module function instead of writing inline commands:
```yaml
keys:
# Set Slack status to "In a meeting" for 1 hour (uses module defaults)
10:
icon: meeting.png
module: slack
function: set_status
# Override default params — different emoji, text, and duration
11:
icon: lunch.png
module: slack
function: set_status
params:
emoji: ":knife_fork_plate:"
text: "Out to lunch"
expiry: "30m"
# Clear Slack status
12:
icon: clear-status.png
module: slack
function: clear_status
# Go away / come back
13:
icon: away.png
module: slack
function: set_presence
params:
presence: "away"
14:
icon: active.png
module: slack
function: set_presence
params:
presence: "auto"
# Snooze notifications for 60 minutes
15:
icon: snooze.png
module: slack
function: snooze
# End snooze
16:
icon: unsnooze.png
module: slack
function: end_snooze
```
Poll commands also support modules — use `module`, `function`, and `params` inside the `poll` block:
```yaml
keys:
17:
icon_true: dnd-on.png
icon_false: dnd-off.png
module: slack
function: snooze
poll:
module: slack
function: check_dnd
interval: 10s
match: "snooze_enabled.*true"
```
#### Template helpers
Two helpers are available in `exec` templates:
| Helper | Usage | Description |
|---|---|---|
| `env` | `{{env "VAR_NAME"}}` | Returns the value of an environment variable |
| `expiry` | `{{expiry .duration}}` | Converts a Go duration string (e.g. `"1h"`, `"30m"`) to a Unix epoch timestamp. `"0"` or `""` returns `"0"` (no expiry) |
#### Secrets and tokens
**Tokens and secrets must never go in `modules.yaml` or `config.yaml`.** Use the `{{env "VAR_NAME"}}` template helper to read them from environment variables at runtime.
**Setting up your Slack token:**
1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps) with these OAuth scopes:
- `users.profile:write` — set/clear status
- `users:write` — set presence (away/auto)
- `dnd:write` — snooze/unsnooze notifications
2. Install the app to your workspace and copy the **User OAuth Token** (`xoxp-...`).
3. Export the token in your shell profile (`~/.zshrc`, `~/.bash_profile`, etc.):
```bash
export SLACK_TOKEN="xoxp-your-token-here"
```
4. Make sure the service can see the variable:
**macOS** — add to `~/Library/LaunchAgents/com.woodarddigital.streamdeck-go.plist` inside the `<dict>` block:
```xml
<key>EnvironmentVariables</key>
<dict>
<key>SLACK_TOKEN</key>
<string>xoxp-your-token-here</string>
</dict>
```
**Linux** — import into the systemd user environment:
```bash
systemctl --user import-environment SLACK_TOKEN
```
Or add an `Environment=` line via `systemctl --user edit streamdeck-go`:
```ini
[Service]
Environment=SLACK_TOKEN=xoxp-your-token-here
```
> **Security notes:**
> - The `modules.yaml` and `config.yaml` files can safely live in your dotfiles repo — they contain no secrets.
> - On Linux, the systemd override file (`~/.config/systemd/user/streamdeck-go.service.d/override.conf`) is user-readable only (mode 600). Still, prefer `import-environment` over hardcoding tokens in unit files when possible.
> - On macOS, launchd plist files in `~/Library/LaunchAgents/` are user-readable only by default.
> - Never commit tokens to git. Add `.env` files to `.gitignore` if you use one.
---
### Supported icon formats
| Format | Notes |