Telegram бот для хуков GitLab

Сразу откажусь от ответственности и реальных или мнимых убытков нанесённых данным постом - вообще какие претензии к человеку который до этого на GO только HelloWord написал ;)

На этого бата возложена только одна задача - принять WebHook от Gitlab и отрапортовать об этом в канал Telegram. Соответственно Бот должен быть приглашен в этот канал.

Бот принимает только одну команду “/start_hook”, она должна быть подана в канале в который планируется вести публикацию событий - при этом бот запомнит ID канала. (мультиканальность не предусмотрена)

Web сервис для принятия хуков запущен на localhost порт 8000 (по умолчанию) что в свою очередь предполагает что данный бот будет запущен непосредствено на сервере Gitlab Community. Так как не предусмотренно никаких средств авторизации (пока) то не стоит использовать его в других вариантах.

Как вариант можно организовать “авторизацию” заменив “/hook” на параметр подлиннее - подобие API токена

1
r.POST("/a673f1b90e7d82dae08eedddce14f9910df467a868569e059e66ac4431acfc72", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

и в проекте, в хуках, задавать адрес не http://localhost:8000/hook а http://localhost:8000/a6..0e7d82…….431acfc72.

В результате на каждый Push в репозиторий, в Telegram канал будет падать уведомление

Необходимо задать API ключ и имя администратора (только от этого пользователя будут приниматься команды) - константы TelegramAPI и AdminName

Проект на Github telegram_gitlab_bot для как говорится “обратной связи” :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package main

import (
  "encoding/json"
  "fmt"
  "log"
  "net/http"
  "strconv"
  "strings"

  "github.com/Syfaro/telegram-bot-api"
  "github.com/julienschmidt/httprouter"
)

type (
  // Структура Gitlab JSON
  GitLabHook struct {
      ObjectKind   string     `json:"object_kind"`
      Ref          string     `json:"ref"`
      UserName     string     `json:"user_name"`
      Repository   Repository `json:"repository"`
      Commit       []Commit   `json:"commits"`
      TotalCommits int        `json:"total_commits_count"`
  }
  Repository struct {
      Homepage string `json:"homepage"`
      Name     string `json:"name"`
  }
  Commit struct {
      Id      string `json:"id"`
      Message string `json:"message"`
      Url     string `json:"url"`
      Author  Author `json:"author"`
  }
  Author struct {
      Name  string `json:"name"`
      Email string `json:"email"`
  }
  // -------------
  HookMessage struct {
      Text string
  }
)

// API ключ для бота - выданный @BotFather
const TelegramAPI string = "YOU BOT API"
const AdminName string = "Admin username without @"

var (
  webhook_response chan HookMessage
  ChatID           int
  Text             string
  UserName         string
  FromUserID       int
  Message          tgbotapi.Chattable
  GitLabChatID     int
)

func main() {
  // Канал для хуков
  webhook_response = make(chan HookMessage, 5)
  // Подключаем бота
  tgbot, err := tgbotapi.NewBotAPI(TelegramAPI)
  if err != nil {
      log.Panic(err)
  }

  ucfg := tgbotapi.NewUpdate(0)
  ucfg.Timeout = 60
  updates, err := tgbot.GetUpdatesChan(ucfg)
  // Запускаем поток с WEB сервисом
  go WebHook()
  // Магия
  for {
      select {
      // Ловим обновления с канала бота
      case update := <-updates:
          // Текст сообщения в чате.
          // Если это в привате бота то любой текст
          // Если из публичного чата то всё начинающееся с "/"
          Text = update.Message.Text
          // ID диалога, если приват то ID равен ID пользователя
          // Если сообщение потупило из публичного чата то ID этого чата
          ChatID = update.Message.Chat.ID
          // Имя пользователя от которого поступило сообщение
          UserName = update.Message.From.UserName
          // ID этого пользователя
          FromUserID = update.Message.From.ID
          // Отлавливаем команду "/start_hook" в диалогах
          if Text == "/start_hook" && UserName == AdminName {
              GitLabChatID = ChatID
              Message := tgbotapi.NewMessage(ChatID, fmt.Sprintf("Пользователь %s запустил публикацию событий Git репозиториев в этот канал", UserName))
              tgbot.Send(Message)
          }
          // Обрабатываем сообщения от WebHook
      case hook := <-webhook_response:
          if GitLabChatID != 0 { // Обрабатываем только PUSH хуки
              Message := tgbotapi.NewMessage(GitLabChatID, hook.Text)
              tgbot.Send(Message)
          }
      }
  }
}

// WEB сервис
func WebHook() {
  r := httprouter.New()
  // Обрабатываем запрос к корню, как-бы и не нужен но пусть будет
  r.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
      fmt.Fprint(w, "Sorry!\n")
  })
  // Обрабатываем POST запрос от GitLab
  r.POST("/hook", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
      u := GitLabHook{}

      json.NewDecoder(r.Body).Decode(&u)

      webhook_response <- MakeMessage(u)

      w.Header().Set("Content-Type", "application/json")
      w.WriteHeader(201)
  })

  http.ListenAndServe("localhost:3000", r)
}

func MakeMessage(hook GitLabHook) HookMessage {
  message := "New commits in \"" + hook.Repository.Name + "\"\n" +
      "Branch: " + strings.Replace(hook.Ref, "refs/heads/", "", -1) + "\n" +
      "User: " + hook.UserName + "\n\n"
  for _, commit := range hook.Commit {
      message += fmt.Sprintf("🔹 %s \n", strings.TrimRight(commit.Message, "\n"))
  }
  message += "\nTotal commits: " + strconv.Itoa(hook.TotalCommits) + "\n"
  return HookMessage{message}
}

Комментарии