From 0fc0deba1aa1b380d454cd9a3413ac6dae750e7a Mon Sep 17 00:00:00 2001 From: Victor Chapaev Date: Sat, 29 Jun 2024 19:44:51 +0300 Subject: [PATCH] Initial commit --- .github/workflows/build.yml | 27 +++++++ .gitignore | 1 + Dockerfile | 8 ++ LICENSE | 21 ++++++ README.md | 44 +++++++++++ README_RU.md | 44 +++++++++++ action.yml | 28 +++++++ go.mod | 8 ++ go.sum | 75 +++++++++++++++++++ main.go | 142 ++++++++++++++++++++++++++++++++++++ 10 files changed, 398 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_RU.md create mode 100644 action.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..bac55b1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build Action +on: [push] + +jobs: + + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.21 + + - name: Build + run: go build -v . + + - name: Send notification + uses: chapvic/telegram-notify@master + if: always() + with: + chat: ${{ secrets.TELEGRAM_CHAT_ID }} + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} + status: ${{ job.status }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..578d9b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +telegram-notify diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e8aafc8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.21 AS builder +WORKDIR /app +COPY . /app +RUN CGO_ENABLED=0 go build -v -o telegram-notify . + +FROM alpine:latest +COPY --from=builder /app/telegram-notify /telegram-notify +ENTRYPOINT ["/telegram-notify"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2eef298 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Chapvic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..12a8498 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Send Notifications to Telegram + +[![Build](https://github.com/chapvic/telegram-notify/actions/workflows/build.yml/badge.svg)](https://github.com/chapvic/telegram-notify/actions/workflows/build.yml) + +Send workflow status notifications to Telegram with support different Git-servers (i.e. Gitea, Gogs) + +## Usage + +First of all, you need to create a Telegram bot by talking to [@BotFather](https://t.me/botfather) bot. See official guide here: https://core.telegram.org/bots#6-botfather + +If you want to get notifications to personal chat with bot, find your user id, for example by talking to [@jsondumpbot](https://t.me/jsondumpbot). + +Also you can use channel for notifications, in this case just get your channel name in format `@channelname`. + +Then add your bot token and user id (or channel name) to repository Secrets. + +Add following minimal step to the end of your workflow: + +```yaml + - name: Send notification to Telegram + uses: chapvic/telegram-notify@master + if: always() + with: + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} # Token secret + chat: ${{ secrets.TELEGRAM_CHAT_ID }} # User ID or channel name secret + status: ${{ job.status }} # Do not modify this line !!! +``` + +You can specify optional arguments in your workflow: + +```yaml + - name: Send notification to Telegram + uses: chapvic/telegram-notify@master + if: always() + with: + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} # Token secret + chat: ${{ secrets.TELEGRAM_CHAT_ID }} # User ID or channel name secret + status: ${{ job.status }} # Do not modify this line !!! + title: Some workflow title + message: Your notification text message + footer: Footer message +``` + +All additional arguments must be in the form of markdown text diff --git a/README_RU.md b/README_RU.md new file mode 100644 index 0000000..210c82c --- /dev/null +++ b/README_RU.md @@ -0,0 +1,44 @@ +# Отправка уведомлений в Телеграм + +[![Build](https://github.com/chapvic/telegram-notify/actions/workflows/build.yml/badge.svg)](https://github.com/chapvic/telegram-notify/actions/workflows/build.yml) + +Отправляет уведомления о состоянии выполнения рабочих потоков в ваш Телеграм-бот с поддержкой различных Git-серверов, таких как Gitea, Gogs. + +## Использование + +Сначала необходимо создать Телеграм-бот с помощью [@BotFather](https://t.me/botfather). Смотрите официальное руководство: https://core.telegram.org/bots#6-botfather + +Если вам необходимо получать уведомления в личный чат с помощью бота, то необходимо определить свой ID пользователя, например через бота [@jsondumpbot](https://t.me/jsondumpbot). + +Также вы можете использовать каналы для уведомлений, указав название канала в формате `@channelname`. + +Добавить токен вашего бота, ID пользователя (или названия Телеграм-канала) в раздел "Секреты" вашего репозитория. + +Добавить следующий шаг последним в ваш рабочий поток: + +```yaml + - name: Send notification to Telegram + uses: chapvic/telegram-notify@master + if: always() + with: + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} # Токен доступа к вашему боту + chat: ${{ secrets.TELEGRAM_CHAT_ID }} # ID пользователя или название канала + status: ${{ job.status }} # Не изменяйте это значение !!! +``` + +Вы можете указать необязательные аргументы в вашем рабочем потоке: + +```yaml + - name: Send notification to Telegram + uses: chapvic/telegram-notify@master + if: always() + with: + token: ${{ secrets.TELEGRAM_BOT_TOKEN }} # Токен доступа к вашему боту + chat: ${{ secrets.TELEGRAM_CHAT_ID }} # ID пользователя или название канала + status: ${{ job.status }} # Не изменяйте это значение !!! + title: Заголовок вашего сообщения + message: Основное сообщение + footer: Подпись вашего сообщения +``` + +Все передваемые дополнительные аргуменеты должны содержать текст в формате Markdown diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..2c19d06 --- /dev/null +++ b/action.yml @@ -0,0 +1,28 @@ +name: 'Send Notification to Telegram' +description: 'Send notification events to Telegram messenger' +author: 'chapvic' +inputs: + chat: + description: 'Chat to send: chat_id, channel_id or @channel_name' + required: true + token: + description: 'Telegram Bot Token' + required: true + status: + description: 'Job status' + required: true + title: + description: 'Message title' + required: false + message: + description: 'Notification message' + required: false + footer: + description: 'Message footer' + required: false +runs: + using: 'docker' + image: 'Dockerfile' +branding: + icon: 'send' + color: 'blue' diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1838dbf --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/chapvic/telegram-notify + +go 1.21 + +require ( + github.com/JohannesKaufmann/html-to-markdown v1.6.0 + github.com/go-telegram/bot v1.5.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c77381e --- /dev/null +++ b/go.sum @@ -0,0 +1,75 @@ +github.com/JohannesKaufmann/html-to-markdown v1.6.0 h1:04VXMiE50YYfCfLboJCLcgqF5x+rHJnb1ssNmqpLH/k= +github.com/JohannesKaufmann/html-to-markdown v1.6.0/go.mod h1:NUI78lGg/a7vpEJTz/0uOcYMaibytE4BUOQS8k78yPQ= +github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-telegram/bot v1.5.0 h1:q31yJ8iajFG54b17TgSs/Brl2YkWziRjf4Au5pe3xV0= +github.com/go-telegram/bot v1.5.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4d0b8c0 --- /dev/null +++ b/main.go @@ -0,0 +1,142 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + "strconv" + "context" + + "github.com/go-telegram/bot" + "github.com/go-telegram/bot/models" + "github.com/JohannesKaufmann/html-to-markdown/escape" +) + +var ( + icons = map[string] string { + "success": "✅", + "failure": "🔴", + "cancelled": "❌", + "info": "🔔", + } + + // Init zero value for ChatID + chat_id = 0 + + // Arguments list + token = os.Getenv("INPUT_TOKEN") // required + chat = os.Getenv("INPUT_CHAT") // required + status = os.Getenv("INPUT_STATUS") // recommended + title = os.Getenv("INPUT_TITLE") // optional + message = os.Getenv("INPUT_MESSAGE") // optional + footer = os.Getenv("INPUT_FOOTER") // optional + + // Github enviroment variables + actor = os.Getenv("GITHUB_ACTOR") // who's made a commit + server = os.Getenv("GITHUB_SERVER_URL") // git-server + workflow = os.Getenv("GITHUB_WORKFLOW") // workflow name + repo = os.Getenv("GITHUB_REPOSITORY") // repository name + commit = os.Getenv("GITHUB_SHA") // commit message + + // Format template for title + fmt_title = "%s %s *%s*" // icon, message, status +) + + +func main() { + + log.Printf("🚀 Starting Telegram Notify...") + + s := false + if token == "" { + log.Printf(" ⚡ Token is required!") + s = true + } + if chat == "" { + log.Printf(" ⚡ Chat ID is required!") + s = true + } + if s == true { + fatal("Notification was interrupted!") + } + + // Prepare ChatID numeric value (int64) + chat_id, err := strconv.ParseInt(chat, 10, 64) + if err != nil { + fatal(err) + } + + // Make status icon text + log.Printf(" - Check status message and create icon") + if status == "" { + warning("Status is not given! Set to 'info'...") + status = "info" + } else if status != "success" && status != "failure" && status != "cancelled" { + warning(fmt.Sprintf("Invalid status %v! Set to 'info'...", status)) + status = "info" + } + icon := icons[strings.ToLower(status)] + + workflow = escape.MarkdownCharacters(workflow) + title = escape.MarkdownCharacters(title) + message = escape.MarkdownCharacters(message) + footer = escape.MarkdownCharacters(footer) + + // Make title text + m_title := "" + log.Printf(" - Create notification title") + if title == "" { + m_title = fmt.Sprintf(fmt_title, icon, workflow, status) + } else { + m_title = fmt.Sprintf(fmt_title, icon, title, status) + } + msg := fmt.Sprintf("%s", m_title) + + // Append message text + log.Printf(" - Prepare notification message") + if message != "" { + msg = fmt.Sprintf("%v\n%v", msg, message) + } else { + warning("No message given! Using default notification message...") + m_commit := fmt.Sprintf("💬 Commit: [%v](%v/%v/commit/%v)", "open link", server, repo, commit) + msg = fmt.Sprintf("%v\n%v", msg, m_commit) + } + + // Append footer text + if footer != "" { + msg = fmt.Sprintf("%v\n%v", msg, footer) + } + + // Sending notification message + log.Printf("📢 Sending notification message...") + opts := []bot.Option{} + b, err := bot.New(token, opts...) + if err != nil { + fatal(err) + } + + _, err = b.SendMessage(context.TODO(), &bot.SendMessageParams{ + ChatID: chat_id, + Text: msg, + ParseMode: models.ParseModeMarkdown, + }) + if err != nil { + fatal(err) + } + + log.Printf("✅ Success!") + +} + + +// Output fatal message and terminate +func fatal(msg any) { + log.Fatal(fmt.Sprintf(`❗ Fatal: %s`, msg)) +} + + +// Output warning message +func warning(msg any) { + log.Printf(fmt.Sprintf(`⚠️ Warning: %s`, msg)) +}