Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Build

on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
os: [ubuntu-latest]
gotify-version: ["2.5.0", "2.6.1", "2.7.3"]

steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Plugin
env:
GOTIFY_VERSION: "${{ matrix.gotify-version }}"
run: |
make download-tools
make build

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.gotify-version }}
path: build/*.so
release:
name: Release Plugin
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Download Build Artifacts
uses: actions/download-artifact@v4
with:
path: build
pattern: build-*
merge-multiple: true

- run: ls -la build

- name: Tag the repository
id: tag
run: |
# See https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names
TAG=v$(date -Iseconds | sed 's/[T:\+]/-/g')
TIME=$(date '+%Y/%m/%d %H:%M')
echo "$TAG"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "time=$TIME" >> $GITHUB_OUTPUT
git config --global user.name "${GITHUB_ACTOR}"
git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git tag -a $TAG -m "Published version $TAG" ${GITHUB_SHA}
git push origin $TAG

- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: build/*.so
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.time }}
20 changes: 10 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
BUILDDIR=./build
GOTIFY_VERSION=v2.5.0
PLUGIN_NAME=telegram-plugin
PLUGIN_ENTRY=plugin.go
GO_VERSION=`cat $(BUILDDIR)/gotify-server-go-version`
DOCKER_BUILD_IMAGE=gotify/build
DOCKER_WORKDIR=/proj
DOCKER_RUN=docker run --rm -v "$$PWD/.:${DOCKER_WORKDIR}" -v "`go env GOPATH`/pkg/mod/.:/go/pkg/mod:ro" -w ${DOCKER_WORKDIR}
DOCKER_GO_BUILD=go build -mod=readonly -a -installsuffix cgo -ldflags "$$LD_FLAGS" -buildmode=plugin
GOMOD_CAP=go run github.com/gotify/plugin-api/cmd/gomod-cap

download-tools:
GO111MODULE=off go get -u github.com/gotify/plugin-api/cmd/gomod-cap
go get -u github.com/gotify/plugin-api/cmd/gomod-cap

create-build-dir:
mkdir -p ${BUILDDIR} || true

update-go-mod: create-build-dir
wget -LO ${BUILDDIR}/gotify-server.mod https://raw.githubusercontent.com/gotify/server/${GOTIFY_VERSION}/go.mod
$(GOMOD_CAP) -from ${BUILDDIR}/gotify-server.mod -to go.mod
GOTIFY_COMMIT=$(shell curl -s https://api.github.com/repos/gotify/server/git/ref/tags/v${GOTIFY_VERSION} | jq -r '.object.sha') && \
wget -O ${BUILDDIR}/gotify-server.mod https://raw.githubusercontent.com/gotify/server/$${GOTIFY_COMMIT}/go.mod
go run github.com/gotify/plugin-api/cmd/gomod-cap -from ${BUILDDIR}/gotify-server.mod -to go.mod
rm ${BUILDDIR}/gotify-server.mod || true
go mod tidy

get-gotify-server-go-version: create-build-dir
rm ${BUILDDIR}/gotify-server-go-version || true
wget -LO ${BUILDDIR}/gotify-server-go-version https://raw.githubusercontent.com/gotify/server/${GOTIFY_VERSION}/GO_VERSION
rm -f ${BUILDDIR}/gotify-server-go-version || true
GOTIFY_COMMIT=$(shell curl -s https://api.github.com/repos/gotify/server/git/ref/tags/v${GOTIFY_VERSION} | jq -r '.object.sha') && \
wget -O ${BUILDDIR}/gotify-server-go-version https://raw.githubusercontent.com/gotify/server/$${GOTIFY_COMMIT}/GO_VERSION

build-linux-amd64: get-gotify-server-go-version update-go-mod
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-amd64 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-amd64${FILE_SUFFIX}.so ${DOCKER_WORKDIR}
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-amd64 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-amd64-v${GOTIFY_VERSION}${FILE_SUFFIX}.so ${DOCKER_WORKDIR}

build-linux-arm-7: get-gotify-server-go-version update-go-mod
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm-7 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-arm-7${FILE_SUFFIX}.so ${DOCKER_WORKDIR}
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm-7 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-arm-7-v${GOTIFY_VERSION}${FILE_SUFFIX}.so ${DOCKER_WORKDIR}

build-linux-arm64: get-gotify-server-go-version update-go-mod
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm64 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-arm64${FILE_SUFFIX}.so ${DOCKER_WORKDIR}
${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm64 ${DOCKER_GO_BUILD} -o ${BUILDDIR}/${PLUGIN_NAME}-linux-arm64-v${GOTIFY_VERSION}${FILE_SUFFIX}.so ${DOCKER_WORKDIR}

build: build-linux-arm-7 build-linux-amd64 build-linux-arm64

Expand Down
65 changes: 51 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Gotify 2 Telegram
This Gotify plugin forwards all received messages to Telegram through the Telegram bot.
# Gotify 2 Telegram (and Discord)
This Gotify plugin forwards received messages to Telegram and/or Discord.

## Prerequisite
- A Telegram bot, bot token, and chat ID from bot conversation. You can get that information by following this [blog](https://medium.com/linux-shots/setup-telegram-bot-to-get-alert-notifications-90be7da4444).
Expand All @@ -8,14 +8,16 @@ This Gotify plugin forwards all received messages to Telegram through the Telegr
## Installation
* **By shared object**

1. Get the compatible shared object from [release](https://github.com/anhbh310/gotify2telegram/releases).
1. Get the compatible shared object from [release](https://github.com/lekoOwO/gotify2telegram/releases).

2. Put it into Gotify plugin folder.

3. Set secrets via environment variables (List of mandatory secrets is in [Appendix](#appendix)).

4. Restart gotify.

5. Config the plugin.

* **Build from source**

1. Change GOTIFY_VERSION in Makefile.
Expand All @@ -28,21 +30,56 @@ This Gotify plugin forwards all received messages to Telegram through the Telegr

3. Follow instructions from step 2 in the shared object installation.

## Configuration

The configuration contains four keys: `clients`, `gotify_host`, `token` and `discord`.

This plugin supports sending to Telegram, Discord, or both. Each `SubClient` may independently enable Telegram and/or Discord.

### Clients

The `clients` configuration key describes which client(channel?) we are going to listen on and which telegram channel (and topic optionally!) we are forwarding the message to.

```yaml
clients:
- app_id: "The Gotify App ID to be matched. use -1 for all-matching."
telegram:
chat_id: "ID of the telegram chat"
token: "The bot token"
thread_id: "Thread ID of the telegram topic. Leave it empty if we are not sending to a topic."
discord:
webhook_url: "https://discord.com/api/webhooks/..."
username: "Optional per-client username (falls back to global discord defaults if empty)"
avatar_url: "Optional per-client avatar URL (falls back to global defaults if empty)"
- app_id: "Maybe the second Gotify Client Token, yay!"
telegram:
chat_id: "ID of the telegram chat"
token: "The bot token"
thread_id: "Thread ID of the telegram topic. Leave it empty if we are not sending to a topic."
```

### Global Discord defaults

You can set global Discord defaults (used when per-client username/avatar are empty):

```yaml
discord:
username: "GotifyBot"
avatar_url: "https://example.com/avatar.png"
```

### Gotify Host

The `gotify_host` configuration key should be set to `ws://YOUR_GOTIFY_IP` (depending on your setup, `ws://localhost:80` will likely work by default)

### Token

The `token` configuration key should be set to a valid token that can be created in the "Clients" tab.

## Troubleshooting
1. When only the Gotify dashboard receives your message, but not Telegram:

If, when making the API call to get your bot's chat ID, no data is returned, you may need to change the bot's privacy settings.

- In the BotFather chat, list your created bots and select the respective bot for which you want to change the Group Privacy setting.
- Turn off the Group Privacy setting.

## Appendix
Mandatory secrets.

```(shell)
GOTIFY_HOST=ws://YOUR_GOTIFY_IP (depending on your setup, "ws://localhost:80" will likely work by default)
GOTIFY_CLIENT_TOKEN=YOUR_CLIENT_TOKEN (create a new Client in Gotify and use the Token from there, or you can use an existing client)
TELEGRAM_CHAT_ID=YOUR_TELEGRAM_CHAT_ID (conversation ID from the Telegram API call above)
TELEGRAM_BOT_TOKEN=YOUR_TELEGRAM_BOT_TOKEN (API token provided by BotFather)
```

98 changes: 98 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package main

import (
"fmt"
)

type Telegram struct {
ChatId string `yaml:"chat_id"`
BotToken string `yaml:"token"`
ThreadId string `yaml:"thread_id"`
}

type DiscordDefaults struct {
Username string `yaml:"username"`
AvatarURL string `yaml:"avatar_url"`
}

type Discord struct {
WebhookURL string `yaml:"webhook_url"`
DiscordDefaults
}

type SubClient struct {
AppId int `yaml:"app_id"`
Telegram Telegram `yaml:"telegram"`
Discord Discord `yaml:"discord"`
}

// Config is user plugin configuration
type Config struct {
Clients []SubClient `yaml:"clients"`
GotifyHost string `yaml:"gotify_host"`
GotifyClientToken string `yaml:"token"`
DiscordDefaults DiscordDefaults `yaml:"discord"`
}

// DefaultConfig implements plugin.Configurer
func (c *Plugin) DefaultConfig() interface{} {
return &Config{
Clients: []SubClient{
SubClient{
AppId: 0,
Telegram: Telegram{
ChatId: "-100123456789",
BotToken: "YourBotTokenHere",
ThreadId: "OptionalThreadIdHere",
},
Discord: Discord{
WebhookURL: "",
DiscordDefaults: DiscordDefaults{
Username: "DefaultUsername",
AvatarURL: "DefaultAvatarURL",
},
},
},
},
DiscordDefaults: DiscordDefaults{
Username: "DefaultUsername",
AvatarURL: "DefaultAvatarURL",
},
GotifyHost: "ws://localhost:80",
GotifyClientToken: "ExampleToken",
}
}

// ValidateAndSetConfig implements plugin.Configurer
func (c *Plugin) ValidateAndSetConfig(config interface{}) error {
newConfig := config.(*Config)

if newConfig.GotifyClientToken == "ExampleToken" {
return fmt.Errorf("gotify client token is required")
}
for i, client := range newConfig.Clients {
if client.AppId == 0 {
return fmt.Errorf("gotify app id is required for client %d", i)
}
// Require at least one destination: Telegram or Discord
if client.Telegram.BotToken == "" && client.Discord.WebhookURL == "" {
return fmt.Errorf("either telegram or discord must be configured for client %d", i)
}

if client.Telegram.BotToken != "" {
if client.Telegram.ChatId == "" {
return fmt.Errorf("telegram chat id is required for client %d", i)
}
}

if client.Discord.WebhookURL != "" {
// very basic validation
if len(client.Discord.WebhookURL) < 8 {
return fmt.Errorf("discord webhook url seems invalid for client %d", i)
}
}
}

c.config = newConfig
return nil
}
Loading