diff --git a/.circleci/config.yml b/.circleci/config.yml index d22e92b..1dadb53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,9 +9,10 @@ version: 2.1 workflows: main: jobs: - - go_1-15 - - go_1-16 - - go_1-17 + - go_1-18 + - go_1-19 + - go_1-20 + - go_1-25 - build_docs commands: @@ -19,7 +20,7 @@ commands: description: Run linter checks on TeleIRC. steps: - checkout - - run: + - run: name: Download and install golintci-lint. command: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b $(go env GOPATH)/bin v1.43.0 - run: @@ -34,21 +35,21 @@ commands: command: go test -coverprofile=c.out ./... jobs: - go_1-15: + go_1-18: docker: - - image: cimg/go:1.15 + - image: cimg/go:1.18 steps: - golintci-lint - teleirc-test - go_1-16: + go_1-19: docker: - - image: cimg/go:1.16 + - image: cimg/go:1.19 steps: - golintci-lint - teleirc-test - go_1-17: + go_1-20: docker: - - image: cimg/go:1.17 + - image: cimg/go:1.20 steps: - golintci-lint - run: @@ -64,6 +65,30 @@ jobs: command: | sed -i 's/github.com\/ritlug\/teleirc\///g' c.out /tmp/cc-test-reporter after-build + go_1-25: + docker: + - image: cimg/go:1.25 + steps: + - checkout + - run: + name: Download and install golintci-lint. + command: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b $(go env GOPATH)/bin v2.6.1 + - run: + name: Run Go linter checks. + command: golangci-lint run + - teleirc-test + - run: + name: Display test coverage summary. + command: | + go tool cover -func=c.out + echo "Total coverage:" + go tool cover -func=c.out | grep total | awk '{print $3}' + - run: + name: Generate HTML coverage report. + command: go tool cover -html=c.out -o coverage.html + - store_artifacts: + path: coverage.html + destination: coverage-report build_docs: docker: - image: cimg/python:3.10 diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 822d48f..bec890c 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -1,49 +1,47 @@ -name: Build and Push Docker Image - +name: Build and Publish Docker Image + +#on: +# push: +# branches: +# - main +# tags: +# - 'v*' on: + pull_request: + branches: + - main push: - branches: [main] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -concurrency: - group: teleirc-docker-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + tags: + - 'v*' jobs: - build-and-push-image: - name: Build and Push Image - + build-and-push: + name: Build and Push Docker Image to GHCR runs-on: ubuntu-latest permissions: contents: read packages: write + id-token: write steps: - - name: Checkout repository + - name: Checkout code uses: actions/checkout@v4 - - name: Log in to the Container registry - uses: docker/login-action@v2 + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Set up docker buildx + uses: docker/setup-buildx-action@v3 - - name: Build and push Docker image - uses: docker/build-push-action@v4 + - name: Build and push docker image + uses: docker/build-push-action@v6 with: - push: true context: . file: ./deployments/container/Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + push: true + tags: | + ghcr.io/ritlug/${{ github.repository##*/ }}:${{ github.sha }} diff --git a/cmd/teleirc.go b/cmd/teleirc.go index fa8921c..1217ba6 100644 --- a/cmd/teleirc.go +++ b/cmd/teleirc.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "syscall" + "strconv" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/ritlug/teleirc/internal" @@ -15,7 +16,9 @@ import ( var ( flagPath = flag.String("conf", ".env", "config file") - flagDebug = flag.Bool("debug", false, "enable debugging output") + flagDebug = flag.Bool("debug", func() bool { env, _ := strconv.ParseBool(os.Getenv("DEBUG")); return env }(), "enable debugging output") + flagMuteIrc = flag.Bool("muteirc", func() bool { env, _ := strconv.ParseBool(os.Getenv("DISABLE_RELAY_TO_IRC")); return env }(), "disable Telegram messages to IRC") + flagMuteTg = flag.Bool("mutetelegram", func() bool { env, _ := strconv.ParseBool(os.Getenv("DISABLE_RELAY_TO_TELEGRAM")); return env }(), "disable IRC messages to Telegram") flagVersion = flag.Bool("version", false, "displays current version of TeleIRC") version string ) @@ -33,6 +36,13 @@ func main() { // Notify that logger is enabled logger.LogDebug("Debug mode enabled!") + if *flagMuteIrc { + logger.LogInfo("Relaying messages to IRC is turned OFF!") + } + if *flagMuteTg { + logger.LogInfo("Relaying messages to Telegram is turned OFF!") + } + settings, err := internal.LoadConfig(*flagPath) if err != nil { logger.LogError(err) @@ -43,10 +53,10 @@ func main() { signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) var tgapi *tgbotapi.BotAPI - tgClient := tg.NewClient(&settings.Telegram, &settings.IRC, &settings.Imgur, tgapi, logger) + tgClient := tg.NewClient(&settings.Telegram, &settings.IRC, &settings.Imgur, tgapi, logger, *flagMuteIrc) tgChan := make(chan error) - ircClient := irc.NewClient(&settings.IRC, &settings.Telegram, logger) + ircClient := irc.NewClient(&settings.IRC, &settings.Telegram, logger, *flagMuteTg) ircChan := make(chan error) go ircClient.StartBot(ircChan, tgClient.SendMessage) diff --git a/deployments/container/Dockerfile b/deployments/container/Dockerfile index 633cd8a..d813999 100644 --- a/deployments/container/Dockerfile +++ b/deployments/container/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine AS builder +FROM golang:1.25-alpine AS builder WORKDIR /app @@ -16,8 +16,6 @@ RUN adduser -D teleirc-user USER teleirc-user COPY --from=builder /app/teleirc /opt/teleirc/teleirc -COPY --from=builder /app/.env /opt/teleirc/conf WORKDIR /opt/teleirc ENTRYPOINT [ "./teleirc" ] -CMD [ "-conf", "/opt/teleirc/conf", "-debug", "true" ] diff --git a/deployments/container/docker-compose.yml.example b/deployments/container/docker-compose.yml.example index 59ce7db..63797c1 100644 --- a/deployments/container/docker-compose.yml.example +++ b/deployments/container/docker-compose.yml.example @@ -6,7 +6,6 @@ version: '3' services: teleirc: build: - context: ../../ - dockerfile: ./deployments/container/Dockerfile - env_file: ../../.env + context: https://github.com/RITlug/teleirc.git + dockerfile: deployments/container/Dockerfile user: teleirc diff --git a/docs/user/config-file-glossary.rst b/docs/user/config-file-glossary.rst index 21beffc..c7bec9b 100644 --- a/docs/user/config-file-glossary.rst +++ b/docs/user/config-file-glossary.rst @@ -3,8 +3,27 @@ Config file glossary #################### This page is a glossary of different settings in the ``env.example`` configuration file. -All values shown are the default settings. -This glossary is intended for advanced users. + +.. note:: + All values shown are the default settings. + This glossary is intended for advanced users. + + +************ +General settings +************ + +Configuration settings +======================== + +``DEBUG=false`` + (Optional) Verbose logging, enabled when set to `true` + +``DISABLE_RELAY_TO_IRC=false`` + (Optional) Fully disables bridging messages from Telegram → IRC when set to `true` + +``DISABLE_RELAY_TO_TELEGRAM=false`` + (Optional) Fully disables bridging messages from IRC → Telegram when set to `true` ************ @@ -171,6 +190,13 @@ Telegram settings ``SHOW_DISCONNECT_MESSAGE=true`` Sends a message to Telegram when the bot disconnects from the IRC side. +``PREFER_FIRSTNAME=false`` + Prefer users adjustable «First name» from Telegram, over their @usernames, when sending messages to IRC channel + (Fallback will still be the @username if first name is not available) + +``QUOTE_NICKNAME=false`` + Place IRC nickname in a blockquote section of the message to Telegram, instead of inline message prefix. + ************** Imgur settings ************** diff --git a/docs/user/quick-start.md b/docs/user/quick-start.md index 53e2fe9..9c19560 100644 --- a/docs/user/quick-start.md +++ b/docs/user/quick-start.md @@ -132,15 +132,35 @@ There are two ways to deploy TeleIRC persistently: Containers are the easiest way to deploy TeleIRC. Dockerfiles and other deployment resources are available in ``deployments/``. -#### Build TeleIRC +Ensure you have [docker](https://www.docker.com/) installed. + +#### Build TeleIRC docker image -1. Ensure you have [docker](https://www.docker.com/) installed 1. Enter container deployment directory (`cd deployments/container`) 1. Build image (`./build_image.sh`) 1. Run container (`docker run teleirc:latest`) -**NOTE**: -**This deployment method assumes you have a complete .env file** +> [!NOTE] +> This deployment can optionally copy a standalone .env file + + +#### Run TeleIRC using Docker compose + +1. Enter container deployment directory (`cd deployments/container`) +1. Run service using `docker compose`: + +```bash +IRC_SERVER=chat.freenode.net \ +IRC_CHANNEL='#channelname' \ +IRC_BOT_NAME='teleirc' \ +TELEIRC_TOKEN='000000000:AAAAAAaAAa2AaAAaoAAAA-a_aaAAaAaaaAA' \ +TELEGRAM_CHAT_ID='-0000000000000' \ +docker compose up -d teleirc +``` + +> [!TIP] +> Instead you can also add `environment:` entries via `docker-compose.yml`, or pass a standalone `.env` file using the CLI: +> `docker compose --env-file ../../.env up --build -d teleirc` ### Run binary diff --git a/env.example b/env.example index c9282e7..48ca44f 100644 --- a/env.example +++ b/env.example @@ -2,6 +2,19 @@ # See the Config File Glossary for instructions. # https://docs.teleirc.com/en/latest/user/config-file-glossary/ +############################################################################### +# # +# General settings # +# # +############################################################################### + +#####----- Configuration settings -----##### +DEBUG=false +DISABLE_RELAY_TO_IRC=false +DISABLE_RELAY_TO_TELEGRAM=false + + + ############################################################################### # # # IRC configuration settings # @@ -75,6 +88,9 @@ SHOW_NICK_MESSAGE=false SHOW_LEAVE_MESSAGE=false LEAVE_MESSAGE_ALLOW_LIST="" SHOW_DISCONNECT_MESSAGE=true +PREFER_FIRSTNAME=false +QUOTE_NICKNAME=false + ################################################################################ diff --git a/internal/config.go b/internal/config.go index 4d22cc9..a37628a 100644 --- a/internal/config.go +++ b/internal/config.go @@ -3,6 +3,7 @@ package internal import ( "fmt" "os" + "path/filepath" "strings" "github.com/caarlos0/env/v6" @@ -65,6 +66,8 @@ type TelegramSettings struct { ShowNickMessage bool `env:"SHOW_NICK_MESSAGE" envDefault:"false"` ShowDisconnectMessage bool `env:"SHOW_DISCONNECT_MESSAGE" envDefault:"false"` MaxMessagePerMinute int `env:"MAX_MESSAGE_PER_MINUTE" envDefault:"20"` + PreferName bool `env:"PREFER_FIRSTNAME" envDefault:"false"` + QuoteNick bool `env:"QUOTE_NICKNAME" envDefault:"false"` } // ImgurSettings includes settings related to Imgur uploading for Telegram photos @@ -135,13 +138,33 @@ func LoadConfig(path string) (*Settings, error) { if err := validate.RegisterValidation("notempty", validateEmptyString); err != nil { return nil, err } - // Attempt to load environment variables from path if path was provided - if path != ".env" && path != "" { - if err := godotenv.Load(path); err != nil { - return nil, err + // If a path was provided, try to load it. + if path != "" { + if info, err := os.Stat(path); err == nil { + // If the path is a directory, look for /.env. + if info.IsDir() { + envFile := filepath.Join(path, defaultPath) + if _, err := os.Stat(envFile); err == nil { + if err := godotenv.Load(envFile); err != nil { + return nil, err + } + } + } else { + // path exists and is a file — attempt to load it + if err := godotenv.Load(path); err != nil { + return nil, err + } + } + } else { + // If the provided path does not exist, continue and rely on passed process ENV variables + if os.IsNotExist(err) { + warning.Printf("config path %q not provided or does not exist; continuing and using process environment variables", path) + } else { + return nil, err + } } } else if _, err := os.Stat(defaultPath); !os.IsNotExist(err) { - // Attempt to load from defaultPath if defaultPath exists + // Attempt to load from defaultPath if it exists if err := godotenv.Load(defaultPath); err != nil { return nil, err } diff --git a/internal/handlers/irc/handlers.go b/internal/handlers/irc/handlers.go index be77130..5289d25 100644 --- a/internal/handlers/irc/handlers.go +++ b/internal/handlers/irc/handlers.go @@ -2,6 +2,7 @@ package irc import ( "fmt" + "html" "regexp" "strings" @@ -129,7 +130,13 @@ func messageHandler(c ClientInterface) func(*girc.Client, girc.Event) { // Strips out ACTION word from text formatted = "* " + e.Source.Name + msg[7:len(msg)-1] } else { - formatted = c.IRCSettings().Prefix + e.Source.Name + c.IRCSettings().Suffix + " " + e.Params[1] + if c.TgSettings().QuoteNick { + ircNicknameFormatted := c.IRCSettings().Prefix + e.Source.Name + c.IRCSettings().Suffix + ircMessageNoHtml := regexp.MustCompile(`<.*?>`).ReplaceAllString(e.Params[1], "") + formatted = "
" + html.EscapeString(ircNicknameFormatted) + "
\n" + html.EscapeString(strings.NewReplacer(">", "", "<", "").Replace(ircMessageNoHtml)) + } else { + formatted = c.IRCSettings().Prefix + e.Source.Name + c.IRCSettings().Suffix + " " + e.Params[1] + } } if hasNoForwardPrefix(c, e.Params[1]) { diff --git a/internal/handlers/irc/irc.go b/internal/handlers/irc/irc.go index ccbccce..c2d6947 100644 --- a/internal/handlers/irc/irc.go +++ b/internal/handlers/irc/irc.go @@ -18,12 +18,13 @@ type Client struct { TelegramSettings *internal.TelegramSettings logger internal.DebugLogger sendToTg func(string) + disableTgRelay bool } /* NewClient returns a new IRCClient based on the provided settings */ -func NewClient(settings *internal.IRCSettings, telegramSettings *internal.TelegramSettings, logger internal.DebugLogger) Client { +func NewClient(settings *internal.IRCSettings, telegramSettings *internal.TelegramSettings, logger internal.DebugLogger, disableTgRelay bool) Client { logger.LogInfo("Creating new IRC bot client...") client := girc.New(girc.Config{ Server: settings.Server, @@ -52,7 +53,7 @@ func NewClient(settings *internal.IRCSettings, telegramSettings *internal.Telegr } } - return Client{client, settings, telegramSettings, logger, nil} + return Client{client, settings, telegramSettings, logger, nil, disableTgRelay} } /* @@ -120,6 +121,10 @@ func (c Client) Logger() internal.DebugLogger { SendToTg sends a message to Telegram */ func (c Client) SendToTg(msg string) { + if c.disableTgRelay { + c.logger.LogDebug("Relaying to Telegram is disabled, skipping IRC message") + return + } c.sendToTg(msg) } diff --git a/internal/handlers/telegram/handler.go b/internal/handlers/telegram/handler.go index 021f49d..fa0b3ac 100644 --- a/internal/handlers/telegram/handler.go +++ b/internal/handlers/telegram/handler.go @@ -59,7 +59,7 @@ messageHandler handles the Message Telegram Object, which formats the Telegram update into a simple string for IRC. */ func messageHandler(tg *Client, u tgbotapi.Update) { - username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From) + username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From, tg.Settings.PreferName) formatted := "" if tg.IRCSettings.NoForwardPrefix != "" && strings.HasPrefix(u.Message.Text, tg.IRCSettings.NoForwardPrefix) { @@ -93,8 +93,8 @@ replyHandler handles when users reply to a Telegram message */ func replyHandler(tg *Client, u tgbotapi.Update) { replyText := strings.Trim(u.Message.ReplyToMessage.Text, " ") - username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From) - replyUser := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.ReplyToMessage.From) + username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From, tg.Settings.PreferName) + replyUser := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.ReplyToMessage.From, tg.Settings.PreferName) // Only show a portion of the reply text if replyTextAsRunes := []rune(replyText); len(replyTextAsRunes) > tg.Settings.ReplyLength { @@ -121,7 +121,7 @@ func joinHandler(tg *Client, users *[]tgbotapi.User) { if tg.IRCSettings.ShowJoinMessage { for _, user := range *users { user := user - username := GetFullUsername(tg.IRCSettings.ShowZWSP, &user) + username := GetFullUsername(tg.IRCSettings.ShowZWSP, &user, tg.Settings.PreferName) formatted := username + " has joined the Telegram Group!" tg.sendToIrc(formatted) } @@ -133,7 +133,7 @@ partHandler handles when users leave the Telegram group */ func partHandler(tg *Client, user *tgbotapi.User) { if tg.IRCSettings.ShowLeaveMessage { - username := GetFullUsername(tg.IRCSettings.ShowZWSP, user) + username := GetFullUsername(tg.IRCSettings.ShowZWSP, user, tg.Settings.PreferName) formatted := username + " has left the Telegram Group!" tg.sendToIrc(formatted) @@ -145,7 +145,7 @@ stickerHandler handles the Message.Sticker Telegram Object, which formats the Telegram message into its base Emoji unicode character. */ func stickerHandler(tg *Client, u tgbotapi.Update) { - username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From) + username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From, tg.Settings.PreferName) formatted := fmt.Sprintf("%s%s%s %s", tg.Settings.Prefix, username, @@ -160,7 +160,7 @@ exists, and sends notification to IRC */ func photoHandler(tg *Client, u tgbotapi.Update) { link := uploadImage(tg, u) - username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From) + username := GetUsername(tg.IRCSettings.ShowZWSP, u.Message.From, tg.Settings.PreferName) caption := u.Message.Caption if caption == "" { caption = "No caption provided." @@ -180,7 +180,7 @@ documentHandler receives a document object from Telegram, and sends a notification to IRC. */ func documentHandler(tg *Client, u *tgbotapi.Message) { - username := GetUsername(tg.IRCSettings.ShowZWSP, u.From) + username := GetUsername(tg.IRCSettings.ShowZWSP, u.From, tg.Settings.PreferName) formatted := username + " shared a file" if u.Document.MimeType != "" { formatted += " (" + u.Document.MimeType + ")" @@ -204,7 +204,7 @@ func locationHandler(tg *Client, u *tgbotapi.Message) { return } - username := GetUsername(tg.IRCSettings.ShowZWSP, u.From) + username := GetUsername(tg.IRCSettings.ShowZWSP, u.From, tg.Settings.PreferName) formatted := username + " shared their location: (" // f means do not use an exponent. diff --git a/internal/handlers/telegram/helpers.go b/internal/handlers/telegram/helpers.go index 19b65ab..6973593 100644 --- a/internal/handlers/telegram/helpers.go +++ b/internal/handlers/telegram/helpers.go @@ -5,9 +5,12 @@ import ( ) /* -GetUsername takes showZWSP condition and user then returns username with or without ​. +GetUsername takes showZWSP and preferFirstname conditions and user then returns First name or username with or without ​. */ -func GetUsername(showZWSP bool, u *tgbotapi.User) string { +func GetUsername(showZWSP bool, u *tgbotapi.User, preferFirstname bool) string { + if preferFirstname && u.FirstName != "" { + return u.FirstName + } if u.UserName == "" { return u.FirstName } @@ -18,9 +21,12 @@ func GetUsername(showZWSP bool, u *tgbotapi.User) string { } /* -GetFullUsername takes showZWSP condition and user then returns full username with or without ​. +GetFullUsername takes showZWSP and preferFirstname conditions and user then returns full name or username with or without ​. */ -func GetFullUsername(showZWSP bool, u *tgbotapi.User) string { +func GetFullUsername(showZWSP bool, u *tgbotapi.User, preferFirstname bool) string { + if preferFirstname && u.FirstName != "" { + return u.FirstName + } if u.UserName == "" { return u.FirstName } diff --git a/internal/handlers/telegram/telegram.go b/internal/handlers/telegram/telegram.go index 1479f86..c8618a8 100644 --- a/internal/handlers/telegram/telegram.go +++ b/internal/handlers/telegram/telegram.go @@ -11,20 +11,21 @@ Client contains information for the Telegram bridge, including the TelegramSettings needed to run the bot */ type Client struct { - api *tgbotapi.BotAPI - Settings *internal.TelegramSettings - IRCSettings *internal.IRCSettings - ImgurSettings *internal.ImgurSettings - logger internal.DebugLogger - sendToIrc func(string) + api *tgbotapi.BotAPI + Settings *internal.TelegramSettings + IRCSettings *internal.IRCSettings + ImgurSettings *internal.ImgurSettings + logger internal.DebugLogger + sendToIrc func(string) + disableIrcRelay bool } /* NewClient creates a new Telegram bot client */ -func NewClient(settings *internal.TelegramSettings, ircsettings *internal.IRCSettings, imgur *internal.ImgurSettings, tgapi *tgbotapi.BotAPI, logger internal.DebugLogger) *Client { +func NewClient(settings *internal.TelegramSettings, ircsettings *internal.IRCSettings, imgur *internal.ImgurSettings, tgapi *tgbotapi.BotAPI, logger internal.DebugLogger, disableIrcRelay bool) *Client { logger.LogInfo("Creating new Telegram bot client...") - return &Client{api: tgapi, Settings: settings, IRCSettings: ircsettings, ImgurSettings: imgur, logger: logger} + return &Client{api: tgapi, Settings: settings, IRCSettings: ircsettings, ImgurSettings: imgur, logger: logger, disableIrcRelay: disableIrcRelay} } /* @@ -33,6 +34,9 @@ SendMessage sends a message to the Telegram channel specified in the settings func (tg *Client) SendMessage(msg string) { newMsg := tgbotapi.NewMessage(tg.Settings.ChatID, "") newMsg.Text = msg + if tg.Settings.QuoteNick { + newMsg.ParseMode = "HTML" + } if _, err := tg.api.Send(newMsg); err != nil { var attempts int = 0 @@ -66,16 +70,23 @@ func (tg *Client) StartBot(errChan chan<- error, sendMessage func(string)) { tg.logger.LogInfo("Authorized on account", tg.api.Self.UserName) tg.sendToIrc = sendMessage - u := tgbotapi.NewUpdate(0) - u.Timeout = 60 + if !tg.disableIrcRelay { + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 - updates, err := tg.api.GetUpdatesChan(u) - if err != nil { - errChan <- err - tg.logger.LogError(err) - } + updates, err := tg.api.GetUpdatesChan(u) + if err != nil { + errChan <- err + tg.logger.LogError(err) + return + } - updateHandler(tg, updates) + updateHandler(tg, updates) - errChan <- nil + errChan <- nil + } else { + tg.logger.LogInfo("Telegram -> IRC relay disabled, but Telegram bot remains active for IRC -> Telegram messages") + // Block forever to keep the goroutine alive + select {} + } }