Skip to content

Commit

Permalink
fix(telegram): update docs and generator for private channels (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
piksel committed May 27, 2022
1 parent 1210a6c commit a9246af
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 31 deletions.
19 changes: 16 additions & 3 deletions docs/services/telegram.md
Expand Up @@ -15,15 +15,18 @@ Talk to [the botfather](https://core.telegram.org/bots#6-botfather).

The `chats` param consists of one or more `Chat ID`s or `channel name`s.

### Channels
The channel names can be retrieved in the telegram client in the `Channel info` section.
### Public Channels
The channel names can be retrieved in the telegram client in the `Channel info` section for public channels.
Replace the `t.me/` prefix from the link with a `@`.

!!! note
Channels names need to be prefixed by `@` to identify them as such.

!!! note
If your channel only has an invite link (starting with `t.me/+`), you have to use it's Chat ID (see below)

### Chats
Group chats and private chats are identifieds by `Chat ID`s. Unfortunatly, they are generally not visible in the
Private channels, Group chats and private chats are identified by `Chat ID`s. Unfortunatly, they are generally not visible in the
telegram clients.
The easiest way to retrieve them is by using the `shoutrrr generate telegram` command which will guide you through
creating a URL with your target chats.
Expand All @@ -34,6 +37,16 @@ creating a URL with your target chats.
docker run --rm -it containrrr/shoutrrr generate telegram
```

### Asking @shoutrrrbot
Another way of retrieving the Chat IDs, is by forwarding a message from the target chat to the [@shoutrrrbot](https://t.me/shoutrrrbot).
It will reply with the Chat ID for the chat where the forwarded message was originally posted.
Note that it will not work correctly for Group chats, as those messages are just seen as being posted by a user, not in a specific chat.
Instead you can use the second method, which is to invite the @shoutrrrbot into your group chat and address a message to it (start the message with @shoutrrrbot). You can then safely kick the bot from the group.

The bot should be constantly online, unless it's usage exceeds the free tier on GCP. It's source is available at [github.com/containrrr/shoutrrrbot](https://github.com/containrrr/shoutrrrbot).



## Optional parameters

You can optionally specify the __`notification`__, __`parseMode`__ and __`preview`__ parameters in the URL:
Expand Down
50 changes: 31 additions & 19 deletions pkg/services/telegram/telegram_generator.go
Expand Up @@ -15,17 +15,15 @@ import (

// Generator is the telegram-specific URL generator
type Generator struct {
ud *generator.UserDialog
client *Client
chats []string
chatNames []string
chatTypes []string
done bool
owner *User
statusMessage int64
botName string
Reader io.Reader
Writer io.Writer
ud *generator.UserDialog
client *Client
chats []string
chatNames []string
chatTypes []string
done bool
botName string
Reader io.Reader
Writer io.Writer
}

// Generate a telegram Shoutrrr configuration from a user dialog
Expand All @@ -47,7 +45,6 @@ func (g *Generator) Generate(_ types.Service, props map[string]string, _ []strin
token := ud.QueryString("Enter your bot token:", generator.ValidateFormat(IsTokenValid), "token")

ud.Writeln("Fetching bot info...")
// ud.Writeln("Session token: %v", g.sessionToken)

g.client = &Client{token: token}
botInfo, err := g.client.GetBotInfo()
Expand Down Expand Up @@ -77,6 +74,9 @@ func (g *Generator) Generate(_ types.Service, props map[string]string, _ []strin
panic(err)
}

// If no updates were retrieved, prompt user to continue
promptDone := len(updates) == 0

for _, update := range updates {
lastUpdate = update.UpdateID + 1

Expand All @@ -88,25 +88,37 @@ func (g *Generator) Generate(_ types.Service, props map[string]string, _ []strin
if message != nil {
chat := message.Chat

source := message.Chat.Username
source := message.Chat.Name()
if message.From != nil {
source = message.From.Username
source = "@" + message.From.Username
}
ud.Writeln("Got Message '%v' from @%v in %v chat %v",

ud.Writeln("Got Message '%v' from %v in %v chat %v",
f.ColorizeString(message.Text),
f.ColorizeProp(source),
f.ColorizeEnum(chat.Type),
f.ColorizeNumber(chat.ID))
ud.Writeln(g.addChat(chat))
// Another chat was added, prompt user to continue
promptDone = true
} else if update.ChatMemberUpdate != nil {
cmu := update.ChatMemberUpdate
oldStatus := cmu.OldChatMember.Status
newStatus := cmu.NewChatMember.Status
ud.Writeln("Got a bot chat member update for %v, status was changed from %v to %v",
f.ColorizeProp(cmu.Chat.Name()),
f.ColorizeEnum(oldStatus),
f.ColorizeEnum(newStatus))
} else {
ud.Writeln("Got unknown Update. Ignored!")
}
}
if promptDone {
ud.Writeln("")

ud.Writeln("")

g.done = !ud.QueryBool(fmt.Sprintf("Got %v chat ID(s) so far. Want to add some more?",
f.ColorizeNumber(len(g.chats))), "")
g.done = !ud.QueryBool(fmt.Sprintf("Got %v chat ID(s) so far. Want to add some more?",
f.ColorizeNumber(len(g.chats))), "")
}
}

ud.Writeln("")
Expand Down
13 changes: 11 additions & 2 deletions pkg/services/telegram/telegram_generator_test.go
Expand Up @@ -2,13 +2,14 @@ package telegram_test

import (
"fmt"
"io"
"strings"

"github.com/jarcoal/httpmock"
"github.com/mattn/go-colorable"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"io"
"strings"

"github.com/containrrr/shoutrrr/pkg/services/telegram"
)
Expand Down Expand Up @@ -78,6 +79,13 @@ var _ = Describe("TelegramGenerator", func() {
}{
true,
[]telegram.Update{
{
ChatMemberUpdate: &telegram.ChatMemberUpdate{
Chat: &telegram.Chat{Type: `channel`, Title: `mockChannel`},
OldChatMember: &telegram.ChatMember{Status: `kicked`},
NewChatMember: &telegram.ChatMember{Status: `administrator`},
},
},
{
Message: &telegram.Message{
Text: "hi!",
Expand All @@ -102,6 +110,7 @@ var _ = Describe("TelegramGenerator", func() {
mockTyped(mockToken)
mockTyped(`no`)

Eventually(userIn).Should(gbytes.Say(`Got a bot chat member update for mockChannel, status was changed from kicked to administrator`))
Eventually(userIn).Should(gbytes.Say(`Got 1 chat ID\(s\) so far\. Want to add some more\?`))
Eventually(userIn).Should(gbytes.Say(`Selected chats:`))
Eventually(userIn).Should(gbytes.Say(`667 \(private\) @mockUser`))
Expand Down
41 changes: 34 additions & 7 deletions pkg/services/telegram/telegram_json.go
Expand Up @@ -138,16 +138,19 @@ type Update struct {
ChosenInlineResult *chosenInlineResult `json:"chosen_inline_result"`
//// Optional. New incoming callback query
CallbackQuery *callbackQuery `json:"callback_query"`

// API fields that are not used by the client has been commented out

//// Optional. New incoming shipping query. Only for invoices with flexible price
//ShippingQuery ShippingQuery `json:"shipping_query"`
//// Optional. New incoming pre-checkout query. Contains full information about checkout
//PreCheckoutQuery PreCheckoutQuery `json:"pre_checkout_query"`
/*
// Optional. New poll state. Bots receive only updates about stopped polls and polls, which are sent by the bot
Poll Poll `json:"poll"`
// Optional. A User changed their answer in a non-anonymous poll. Bots receive new votes only in polls that were sent by the bot itself.
Poll_answer PollAnswer `json:"poll_answer"`
*/
//// Optional. New poll state. Bots receive only updates about stopped polls and polls, which are sent by the bot
//Poll Poll `json:"poll"`
//// Optional. A User changed their answer in a non-anonymous poll. Bots receive new votes only in polls that were sent by the bot itself.
//Poll_answer PollAnswer `json:"poll_answer"`

ChatMemberUpdate *ChatMemberUpdate `json:"my_chat_member"`
}

// Chat represents a telegram conversation
Expand All @@ -160,7 +163,7 @@ type Chat struct {

// Name returns the name of the channel based on its type
func (c *Chat) Name() string {
if c.Type == "private" || c.Type == "channel" {
if c.Type == "private" || c.Type == "channel" && c.Username != "" {
return "@" + c.Username
}
return c.Title
Expand Down Expand Up @@ -191,3 +194,27 @@ type callbackQuery struct {
Message *Message `json:"Message"`
Data string `json:"data"`
}

// ChatMemberUpdate represents a member update in a telegram chat
type ChatMemberUpdate struct {
// Chat the user belongs to
Chat *Chat `json:"chat"`
// Performer of the action, which resulted in the change
From *User `json:"from"`
// Date the change was done in Unix time
Date int `json:"date"`
// Previous information about the chat member
OldChatMember *ChatMember `json:"old_chat_member"`
// New information about the chat member
NewChatMember *ChatMember `json:"new_chat_member"`
// Optional. Chat invite link, which was used by the user to join the chat; for joining by invite link events only.
// invite_link ChatInviteLink
}

// ChatMember represents the membership state for a user in a telegram chat
type ChatMember struct {
// The member's status in the chat
Status string `json:"status"`
// Information about the user
User *User `json:"user"`
}

0 comments on commit a9246af

Please sign in to comment.