Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/fwcd/d2
Command-based virtual assistant for Discord and other platforms
https://github.com/fwcd/d2
discord linux virtual-assistant
Last synced: about 1 month ago
JSON representation
Command-based virtual assistant for Discord and other platforms
- Host: GitHub
- URL: https://github.com/fwcd/d2
- Owner: fwcd
- License: gpl-3.0
- Created: 2019-03-14T17:59:53.000Z (almost 6 years ago)
- Default Branch: main
- Last Pushed: 2024-12-05T22:46:42.000Z (about 1 month ago)
- Last Synced: 2024-12-05T23:27:15.079Z (about 1 month ago)
- Topics: discord, linux, virtual-assistant
- Language: Swift
- Homepage:
- Size: 19.6 MB
- Stars: 15
- Watchers: 3
- Forks: 5
- Open Issues: 73
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# D2
[![Build](https://github.com/fwcd/d2/actions/workflows/build.yml/badge.svg)](https://github.com/fwcd/d2/actions/workflows/build.yml)
[![Docker](https://github.com/fwcd/d2/actions/workflows/docker.yml/badge.svg)](https://github.com/fwcd/d2/actions/workflows/docker.yml)General-purpose assistant for Discord and IRC with more than 340 commands, including:
- **Multiplayer games**, including chess, uno, hangman, codenames, wordle and others
- **50+ Web APIs**, including dictionaries, news, weather, comics, recipes and more
- **Image processors**, including various filters, animators and a QR code generator
- **Mathematical utilities**, including equation solvers, plotters, matrix operations and a LaTeX renderer
- **Musical utilities**, including chord finders, fretboard and piano visualizers
- **Programming utilities**, including a Haskell API search and a Prolog interpreter
- **Moderational utilities**, including automatic thread management, message previews, role reactions and bulk deletion
- **Miscellaneous utilities**, including polls and coin flips
- **Humorous and fun stuff**, including various party games and joke finders## Getting Started
### Locally
To build and run D2 locally, make sure to have the following installed:
- Linux or macOS
- Swift 6+
- Node.js and npm (for LaTeX rendering)On Ubuntu, run
```sh
Scripts/install-dependencies-apt
```If you use another distribution, use your native package manager to install the equivalent packages.
On macOS, run
```sh
Scripts/install-dependencies-brew
```Create a folder named `local` under the repository and add configuration files as described in [the configuration section](#configuration).
To install the dependencies for node packages used by D2, run
```sh
Scripts/install-node-dependencies
```Finally, use `swift build` to build D2 and `swift run` to run it. With `swift test` you can run the test suite.
> To suppress warnings, you can append `-Xswiftc -suppress-warnings` after `swift build` or `swift run`.
> If your build fails with module redefinition errors regarding FFI includes, run `Scripts/remove-commandlinetools-ffi-includes` and try building again.
### With Docker (Compose)
Make sure to have recent versions of Docker and Docker Compose installed and create a volume named `d2local` using `docker volume create d2local`.
In this volume, add configuration files as described in [the configuration section](#configuration).
> [See here](https://stackoverflow.com/a/55683656) for instructions on how to copy files into a Docker volume
You can then use `docker-compose build` to build the image and `docker-compose up` to run it (add the `-d` flag to run it in daemonized mode).
### With Kubernetes (Helm)
Make sure to have `kubectl` + `helm` installed and connected to a Kubernetes cluster. The cluster should have a persistent volume available.
> In a local cluster (where persistent volumes generally aren't provisioned automatically), you may find the `d2-local-storage` chart useful, which registers a persistent volume. To use it, create a folder such as `./local/k8s` and run `helm upgrade --install --set storage.hostPath=$PWD/local/k8s d2-local-storage Helm/d2-local-storage`.
Create a `values.yaml` in some local location (e.g. in `local` or outside the repository) containing D2 configurations (see [the configuration section](#configuration) for details on the schema):
```yaml
d2:
adminWhitelist:
users:
- value: 'YOUR_DISCORD_USER_ID'
clientName: Discord
config:
commandPrefix: '%'
hostInfo:
instanceName: prod/k8s/yourDisplayName
platformTokens:
discord: 'YOUR_DISCORD_API_TOKEN'
netApiKeys: # optional
mapQuest: YOUR_MAP_QUEST_KEY
wolframAlpha: YOUR_WOLFRAM_ALPHA_KEY
gitlab: YOUR_GITLAB_KEY
openweathermap: YOUR_OPENWEATHERMAP_KEY
...
```You can now upgrade/install D2 to the cluster using `helm upgrade --install -f path/to/local/values.yaml d2 Helm/d2`.
To uninstall it, just run `helm uninstall d2`.
> Note that the persistent volume storage claim by `d2` might still persist after uninstalling, in which case you could do [something like this](https://stackoverflow.com/questions/50667437/what-to-do-with-released-persistent-volume) (or reinstall `d2-local-storage`, if you installed it earlier).
## Configuration
Navigate to your `local` folder or volume (as described in the sections above).
### Required
Create a file named `platformTokens.json` in `local` containing the API tokens (at least one of them should be specified):
```json
{
"discord": "YOUR_DISCORD_API_TOKEN",
"irc": [
{
"host": "YOUR_IRC_HOST",
"port": 6667,
"nickname": "YOUR_IRC_USERNAME",
"password": "YOUR_IRC_PASSWORD"
}
]
}
```> For more information e.g. on how to connect to the Twitch IRC API, see [this guide](https://dev.twitch.tv/docs/irc/guide/)
### Optional
Create a file named `config.json` in `local` (or the `d2local` volume):
```json
{
"prefix": "%"
}
```Create a file named `adminWhitelist.json` in `local` (or the `d2local` volume) containing a list of Discord usernames that have full permissions:
```json
{
"users": [
{
"value": "YOUR_DISCORD_USER_ID",
"clientName": "Discord"
}
]
}
```Create a file named `netApiKeys.json` in `local` (or the `d2local` volume) containing various API keys:
```json
{
"mapQuest": "YOUR_MAP_QUEST_KEY",
"wolframAlpha": "YOUR_WOLFRAM_ALPHA_KEY",
"gitlab": "YOUR_GITLAB_PERSONAL_ACCESS_TOKEN"
}
```Create a folder named `memeTemplates` in `local` containing PNG images. Any fully transparent sections will be filled by a user-defined image, once the corresponding command is invoked.
## Architecture
The program consists of a single executable:
- `D2`, the main executable
This executable depends on several library targets:
- `D2Handlers`, top-level message/event handling
- `D2Commands`, the command framework and the implementations
- `D2MessageIO`, the messaging framework (abstracting over the Discord library)
- `D2DiscordIO`, the Discord implementation
- `D2IRCIO`, the IRC/Twitch implementation
- `D2Permissions`, permission management
- `D2Script`, an experimental DSL that can be used to script commands
- `D2NetAPIs`, client implementations of various web APIs### D2
The executable application. Sets up messaging backends (like Discord) and the top-level event handler (`D2Delegate`). Besides other events, the `D2Delegate` handles incoming messages and forwards them to multiple `MessageHandler`s. One of these is `CommandHandler`, which in turn parses the command and invokes the actual command.
### D2Commands
At a basic level, the `Command` protocol consists of a single method named `invoke` that carries information about the user's request:
```swift
protocol Command: AnyObject {
...func invoke(with input: RichValue, output: any CommandOutput, context: CommandContext) async
...
}
```The arguments each represent a part of the invocation context. Given a request such as `%commandname arg1 arg2`, the implementor would receive:
| Parameter | Value |
| --------- | ----- |
| `input` | `.text("arg1 arg2")` |
| `output` | `DiscordOutput` |
| `context` | `CommandContext` containing the message, the client and the command registry |Since `output: any CommandOutput` represents a polymorphic object, the output of an invocation does not necessarily get sent to the Discord channel where the request originated from. For example, if the user creates a piped request such as `%first | second | third`, only the third command would operate on a `DiscordOutput`. Both the first and the second command call a `PipeOutput` instead that passes any values to the next command:
```swift
class PipeOutput: CommandOutput {
private let sink: any Command
private let context: CommandContext
private let args: String
private let next: (any CommandOutput)?init(withSink sink: any Command, context: CommandContext, args: String, next: (any CommandOutput)? = nil) {
self.sink = sink
self.args = args
self.context = context
self.next = next
}func append(_ value: RichValue) async {
let nextInput = args.isEmpty ? value : (.text(args) + value)
await sink.invoke(with: nextInput, output: next ?? PrintOutput(), context: context)
}
}
```Often the `Command` protocol is too low-level to be adopted directly, since the input can be of any form (including embeds or images). To address this, there are subprotocols that provide a simpler template interface for implementors:
```swift
protocol StringCommand: Command {
func invoke(with input: String, output: any CommandOutput, context: CommandContext) async
}
````StringCommand` is useful when the command accepts a single string as an argument or if a custom argument parser is used. Its default implementation of `Command.invoke` passes either `args`, if not empty, or otherwise `input.content` to `StringCommand.invoke`.
```swift
protocol ArgCommand: Command {
associatedtype Args: Argvar argPattern: Args { get }
func invoke(with input: [Args], output: any CommandOutput, context: CommandContext) async
}
````ArgCommand` should be adopted if the command expects a fixed structure of arguments.