lelegram: init
This is an IRC/Telegram bridge.
It does multi-account puppet-like access to IRC making everyone's life
easier.
Compared to teleirc it also:
- is smarter about converting messages
- uses teleimg for public image access
- is not written in JS
Experimental for now.
Change-Id: I66ba3f83abdfdea6463ab3be5380d8d3f2769291
diff --git a/personal/q3k/lelegram/telegram.go b/personal/q3k/lelegram/telegram.go
new file mode 100644
index 0000000..a80e76e
--- /dev/null
+++ b/personal/q3k/lelegram/telegram.go
@@ -0,0 +1,169 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
+ "github.com/golang/glog"
+)
+
+// telegramConnection runs a long-lived connection to the Telegram API to receive
+// updates and pipe resulting messages into telLog.
+func (s *server) telegramConnection(ctx context.Context) error {
+ u := tgbotapi.NewUpdate(0)
+ // TODO(q3k): figure out what the _fuck_ does this even mean
+ u.Timeout = 60
+
+ updates, err := s.tel.GetUpdatesChan(u)
+ if err != nil {
+ return fmt.Errorf("GetUpdatesChan(%+v): %v", u, err)
+ }
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case u, ok := <-updates:
+ if !ok {
+ return fmt.Errorf("Updates channel closed")
+ }
+
+ // Dispatch update.
+ switch {
+ case u.Message != nil:
+ if u.Message.Chat.ID != s.groupId {
+ glog.Infof("[ignored group %d] <%s> %v", u.Message.Chat.ID, u.Message.From, u.Message.Text)
+ continue
+ }
+ if msg := plainFromTelegram(s.tel.Self.ID, &u); msg != nil {
+ s.telLog <- msg
+ }
+ }
+ }
+ }
+}
+
+// telegramLoop maintains a telegramConnection.
+func (s *server) telegramLoop(ctx context.Context) {
+ for {
+ err := s.telegramConnection(ctx)
+ if err == ctx.Err() {
+ glog.Infof("Telegram connection closing: %v", err)
+ return
+ }
+
+ glog.Errorf("Telegram connection error: %v", err)
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(1 * time.Second):
+ continue
+ }
+ }
+}
+
+// plainFromTelegram turns a Telegram message into a plain text message.
+func plainFromTelegram(selfID int, u *tgbotapi.Update) *telegramPlain {
+ parts := []string{}
+
+ from := u.Message.From
+ replyto := u.Message.ReplyToMessage
+ text := u.Message.Text
+
+ // This message is in reply to someone.
+ if replyto != nil && text != "" && replyto.From != nil {
+ // The rendered name of the author of the quote.
+ ruid := "@" + replyto.From.String()
+
+ // First line of the quoted text.
+ quotedLine := ""
+
+ // Check if the quoted message is from our bridge.
+ if replyto.From.ID == selfID {
+ // Someone replied to an IRC bridge message, extract nick and line from there
+ // eg: "<q3k> foo bar baz" -> ruid = q3k; quotedLine = foo bar baz
+ t := replyto.Text
+ if strings.HasPrefix(t, "<") {
+ p := strings.SplitN(t[1:], ">", 2)
+ nick := p[0]
+ quoted := strings.TrimSpace(p[1])
+
+ // ensure nick looks sane
+ if len(nick) < 16 && len(strings.Fields(nick)) == 1 {
+ quotedLine = strings.TrimSpace(strings.Split(quoted, "\n")[0])
+ ruid = nick
+ }
+ }
+ } else {
+ // Someone replied to a native telegram message.
+ quoted := strings.TrimSpace(replyto.Text)
+ quotedLine = strings.TrimSpace(strings.Split(quoted, "\n")[0])
+ }
+
+ // If we have a line, quote it. Otherwise just refer to the nick without a quote.
+ if quotedLine != "" {
+ parts = append(parts, fmt.Sprintf("%s: >%s\n", ruid, quotedLine))
+ } else {
+ parts = append(parts, fmt.Sprintf("%s: ", ruid))
+ }
+ }
+
+ // This message contains a sticker.
+ if u.Message.Sticker != nil {
+ emoji := ""
+ if u.Message.Sticker.SetName != "" {
+ emoji += "/" + u.Message.Sticker.SetName
+ }
+ if u.Message.Sticker.Emoji != "" {
+ emoji += "/" + u.Message.Sticker.Emoji
+ }
+ parts = append(parts, fmt.Sprintf("<sticker%s>", emoji))
+ }
+
+ // This message contains an animation.
+ if u.Message.Animation != nil {
+ a := u.Message.Animation
+ parts = append(parts, fmt.Sprintf("<uploaded animation: %s >\n", fileURL(a.FileID, "mp4")))
+ }
+
+ // This message contains a document.
+ if u.Message.Document != nil {
+ d := u.Message.Document
+ fnp := strings.Split(d.FileName, ".")
+ ext := "bin"
+ if len(fnp) > 1 {
+ ext = fnp[len(fnp)-1]
+ }
+ parts = append(parts, fmt.Sprintf("<uploaded file: %s >\n", fileURL(d.FileID, ext)))
+ }
+
+ // This message contains a photo.
+ if u.Message.Photo != nil {
+ // Multiple entries are for different file sizes, choose the highest quality one.
+ hq := (*u.Message.Photo)[0]
+ for _, p := range *u.Message.Photo {
+ if p.FileSize > hq.FileSize {
+ hq = p
+ }
+ }
+ parts = append(parts, fmt.Sprintf("<uploaded photo: %s >\n", fileURL(hq.FileID, "jpg")))
+ }
+
+ // This message has some plain text.
+ if text != "" {
+ parts = append(parts, text)
+ }
+
+ // Was there anything that we extracted?
+ if len(parts) > 0 {
+ return &telegramPlain{from.String(), strings.Join(parts, " ")}
+ }
+ return nil
+}
+
+func fileURL(fid, ext string) string {
+ return flagTeleimgRoot + fid + "." + ext
+}