diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 459a692..4fbc4e2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,7 +21,6 @@ // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" - + "remoteUser": "root", "initializeCommand": "git config --global alias.co checkout && git config --global alias.br branch && git config --global alias.ci commit && git config --global alias.st status" } diff --git a/api/hook/index.go b/api/hook/index.go index c76c722..26481d9 100644 --- a/api/hook/index.go +++ b/api/hook/index.go @@ -1,6 +1,7 @@ package hook import ( + "encoding/json" "fmt" "log" "net/http" @@ -12,7 +13,6 @@ import ( "zeril-bot/utils/kqxs" "zeril-bot/utils/lunar" "zeril-bot/utils/qr" - "zeril-bot/utils/quote" "zeril-bot/utils/random" "zeril-bot/utils/shortener" "zeril-bot/utils/structs" @@ -20,17 +20,33 @@ import ( ) func Handler(w http.ResponseWriter, r *http.Request) { - data := r.Context().Value("data").(structs.HookData) + var data structs.HookData + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + panic(err) + } - channel.GetWg().Add(2) + bot := bot.NewBot(data) + res := make(map[string]string) - if data.CallbackQuery.Data != "" { - resolveCallback(data) - } else { - resolveCommand(data) + err = bot.ResolveHook() + if err != nil { + res["status"] = "ERROR" + res["code"] = "internal_error" + res["message"] = err.Error() + Response(w, res, http.StatusInternalServerError) + return } - channel.GetWg().Wait() + res["status"] = "OK" + Response(w, res, http.StatusOK) +} + +func Response(w http.ResponseWriter, res map[string]string, httpStatus int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(httpStatus) + mRes, _ := json.Marshal(res) + w.Write(mRes) } func resolveCommand(data structs.HookData) { @@ -53,7 +69,7 @@ func resolveCommand(data structs.HookData) { case "/groupid", "/groupid@zerill_bot": help.SendGroupId(chatId, string(data.Message.Chat.Type)) case "/quote", "/quote@zerill_bot": - quote.SendAQuote(chatId) + // quote.SendAQuote(chatId) case "/lunar", "/lunar@zerill_bot": lunar.SendLunarDateNow(chatId) case "/weather", "/weather@zerill_bot": @@ -96,7 +112,7 @@ func resolveCallback(callback structs.HookData) { func setBotIsTyping(chatId int) { go func() { - bot.SetTypingAction(chatId) - channel.GetWg().Done() + // bot.SetTypingAction(chatId) + // channel.GetWg().Done() }() } diff --git a/api/index.go b/api/index.go index 048b7eb..73ea403 100644 --- a/api/index.go +++ b/api/index.go @@ -15,10 +15,13 @@ func Handler(wri http.ResponseWriter, req *http.Request) { r := chi.NewRouter() r.Use(chiMiddle.Logger) - r.Use(middleware.PreRequest) + // r.Use(middleware.PreRequest) r.Use(middleware.Recoverer) - r.Get("/", func(w http.ResponseWriter, r *http.Request) {}) + r.NotFound(middleware.Handle404NotFound()) + r.MethodNotAllowed(middleware.Handle405MethodNotAllowed()) + + // r.Get("/", func(w http.ResponseWriter, r *http.Request) {}) r.Post("/api/hook", hook.Handler) r.Get("/url", url.Handler) r.Get("/trip", trip.Handler) diff --git a/utils/bot/bot.go b/utils/bot/bot.go index eb0c48f..fbfeccf 100644 --- a/utils/bot/bot.go +++ b/utils/bot/bot.go @@ -11,7 +11,12 @@ import ( "os" "path/filepath" "strconv" + "strings" + + // "zeril-bot/utils/quote" + "zeril-bot/utils/quote" "zeril-bot/utils/structs" + "zeril-bot/utils/telegram" ) var API_URL string = "https://api.telegram.org/bot" + os.Getenv("TELE_BOT_TOKEN") @@ -19,6 +24,87 @@ var API_URL string = "https://api.telegram.org/bot" + os.Getenv("TELE_BOT_TOKEN" var chatType string var chatFrom structs.From +type Bot struct { + HookData structs.HookData + typingCh chan struct{} + commandCh chan error +} + +func NewBot(hookData structs.HookData) *Bot { + tCh := make(chan struct{}) + commandCh := make(chan error) + + return &Bot{hookData, tCh, commandCh} +} + +func (b Bot) ResolveHook() error { + go b.setTypingAction() + + var err error + + switch { + case b.isCommand(): + go b.resolveCommand() + case b.isCallbackCommand(): + go b.resolveCallbackCommand() + } + + // to do refactor + <-b.typingCh + err = <-b.commandCh + + return err +} + +func (b Bot) resolveCommand() error { + var err error + + defer func() { + b.commandCh <- err + }() + + data := b.HookData + name := data.Message.From.FirstName + chatId := data.Message.Chat.ID + text := data.Message.Text + arr := strings.Fields(text) + + log.Printf("Yêu cầu từ bạn %s: %s", name, text) + + tData := telegram.Data{ + ChatId: chatId, + ChatType: "", + Username: "", + } + + command := arr[0] + + switch command { + case "/quote", "/quote@zerill_bot": + err = quote.SendAQuote(tData) + default: + // channel.SendMessage(chatId, "Tôi không hiểu câu lệnh của bạn !!!") + } + + return nil +} + +func (b Bot) resolveCallbackCommand() error { + return nil +} + +func (b Bot) isCommand() bool { + return b.HookData.CallbackQuery.Data == "" +} + +func (b Bot) isCallbackCommand() bool { + return b.HookData.CallbackQuery.Data != "" +} + +func (b Bot) getApiURL() string { + return "https://api.telegram.org/bot" + os.Getenv("TELE_BOT_TOKEN") +} + func SendMessage(chatId int, message string) { if chatType == "group" { message = message + "\n@" + chatFrom.Username @@ -168,10 +254,13 @@ func SendMessageWithReplyMarkup(chatId int, message string, replyMark []structs. log.Println("SendMessageWithReplyMarkup OK") } -func SetTypingAction(chatId int) { - uri := API_URL + "/sendChatAction" - req, err := http.NewRequest("GET", uri, nil) +func (b Bot) setTypingAction() { + defer close(b.typingCh) + + url := b.getApiURL() + chatId := b.HookData.Message.Chat.ID + req, err := http.NewRequest("GET", url+"/sendChatAction", nil) if err != nil { log.Println(err) return diff --git a/utils/middleware/index.go b/utils/middleware/index.go index f03bdf8..8bad06e 100644 --- a/utils/middleware/index.go +++ b/utils/middleware/index.go @@ -64,18 +64,14 @@ func PreRequest(next http.Handler) http.Handler { func Recoverer(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { defer func() { - path := r.URL.Path - if path == "/trip" { - return - } - - resp := make(map[string]string) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - // fmt.Println("After R") if rvr := recover(); rvr != nil { + resp := make(map[string]string) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + resp["status"] = "ERROR" resp["message"] = rvr.(string) + jsonResp, _ := json.Marshal(resp) logEntry := middleware.GetLogEntry(r) if logEntry != nil { @@ -84,11 +80,8 @@ func Recoverer(next http.Handler) http.Handler { middleware.PrintPrettyStack(rvr) } - } else { - resp["status"] = "OK" + w.Write(jsonResp) } - jsonResp, _ := json.Marshal(resp) - w.Write(jsonResp) }() next.ServeHTTP(w, r) @@ -96,3 +89,31 @@ func Recoverer(next http.Handler) http.Handler { return http.HandlerFunc(fn) } + +func Handle404NotFound() func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(404) + + res := make(map[string]string) + res["status"] = "ERROR" + res["error"] = "http.error" + res["message"] = "route does not exist" + + mRes, _ := json.Marshal(res) + w.Write(mRes) + } +} + +func Handle405MethodNotAllowed() func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(405) + + res := make(map[string]string) + res["status"] = "ERROR" + res["error"] = "http.error" + res["message"] = "method is not valid" + + mRes, _ := json.Marshal(res) + w.Write(mRes) + } +} diff --git a/utils/quote/quote.go b/utils/quote/quote.go index 1d48f4d..fb366d2 100644 --- a/utils/quote/quote.go +++ b/utils/quote/quote.go @@ -4,35 +4,41 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" - "zeril-bot/utils/channel" "zeril-bot/utils/structs" + "zeril-bot/utils/telegram" ) -func SendAQuote(chatId int) { - quote := getAQuote() +func SendAQuote(data telegram.Data) error { + quote, err := getAQuote() + if err != nil { + return err + } + quoteFormat := fmt.Sprintf(""%s" - %s", quote.Quote, quote.Author) - channel.SendMessage(chatId, quoteFormat) + data.Message = quoteFormat + + return telegram.SendMessage(data) } -func getAQuote() structs.QuoteData { +func getAQuote() (*structs.QuoteData, error) { res, err := http.Get("https://zenquotes.io/api/random") - if err != nil { - log.Panic(err) + return nil, err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } var data []structs.QuoteData err = json.Unmarshal(body, &data) - if err != nil { - log.Panic(err) + return nil, err } - return data[0] + return &data[0], nil } diff --git a/utils/telegram/telegram.go b/utils/telegram/telegram.go new file mode 100644 index 0000000..3cf9bdc --- /dev/null +++ b/utils/telegram/telegram.go @@ -0,0 +1,179 @@ +package telegram + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "strconv" + "zeril-bot/utils/structs" +) + +var API_URL string = "https://api.telegram.org/bot" + os.Getenv("TELE_BOT_TOKEN") + +func getApiURL() string { + return "https://api.telegram.org/bot" + os.Getenv("TELE_BOT_TOKEN") +} + +type Data struct { + ChatId int + ChatType string + Username string + Message string +} + +func SendMessage(data Data) error { + message := data.Message + if data.ChatType == "group" { + message = message + "\n@" + data.Username + } + + uri := API_URL + "/sendMessage" + req, err := http.NewRequest("GET", uri, nil) + + if err != nil { + return err + } + + q := req.URL.Query() + q.Add("chat_id", strconv.Itoa(data.ChatId)) + q.Add("text", message) + q.Add("parse_mode", "html") + + req.URL.RawQuery = q.Encode() + + client := &http.Client{} + + res, err := client.Do(req) + + if err != nil { + return err + } + + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return err + } + + var status structs.Status + + err = json.Unmarshal(body, &status) + if err != nil { + return err + } + + if status.Ok { + log.Println("SendMessage OK") + return nil + } + + return errors.New(string(body)) +} + +func SendPhoto(chatId int, path string) { + uri := API_URL + "/sendPhoto" + + file, _ := os.Open(path) + defer file.Close() + + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + writer.WriteField("chat_id", strconv.Itoa(chatId)) + + // if chatType == "group" { + // writer.WriteField("caption", "@"+chatFrom.Username) + // } + + part, _ := writer.CreateFormFile("photo", filepath.Base(path)) + io.Copy(part, file) + + writer.Close() + + req, _ := http.NewRequest("GET", uri, payload) + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + log.Panic(err) + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Panic(err) + } + + var status structs.Status + + err = json.Unmarshal(body, &status) + if err != nil { + log.Panic(err) + } + + if status.Ok == false { + log.Panic(string(body)) + } + + log.Println("SendPhoto OK") +} + +func SendMessageWithReplyMarkup(chatId int, message string, replyMark []structs.ButtonCallback) { + uri := API_URL + "/sendMessage" + + var markup structs.BodyReplyMarkup + markup.ReplyMarkup.InlineKeyboard = append(markup.ReplyMarkup.InlineKeyboard, replyMark) + marshalled, err := json.Marshal(markup) + + req, err := http.NewRequest("GET", uri, bytes.NewReader(marshalled)) + req.Header.Add("Content-Type", "application/json") + if err != nil { + log.Println(err) + return + } + // if chatType == "group" { + // message = message + "\n@" + chatFrom.Username + // } + + q := req.URL.Query() + q.Add("chat_id", strconv.Itoa(chatId)) + q.Add("text", message) + q.Add("parse_mode", "html") + + req.URL.RawQuery = q.Encode() + + client := &http.Client{} + + res, err := client.Do(req) + if err != nil { + log.Panic(err) + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Panic(err) + } + + var status structs.Status + + err = json.Unmarshal(body, &status) + if err != nil { + log.Panic(err) + } + + if status.Ok == false { + log.Panic(string(body)) + } + + log.Println("SendMessageWithReplyMarkup OK") +}