ya
This commit is contained in:
commit
5fc45b446c
6 changed files with 233 additions and 0 deletions
8
Taskfile.yml
Normal file
8
Taskfile.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
ko:
|
||||||
|
cmds:
|
||||||
|
- ko build .
|
||||||
|
env:
|
||||||
|
KO_DOCKER_REPO: gitea.nulo.in/nulo/zulip-checkin-cyborg
|
5
go.mod
Normal file
5
go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module gitea.nulo.in/Nulo/zulip-checkin-cyborg
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
|
2
go.sum
Normal file
2
go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
|
||||||
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
84
main.go
Normal file
84
main.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.nulo.in/Nulo/zulip-checkin-cyborg/zulip"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bot zulip.Bot
|
||||||
|
var stream string
|
||||||
|
var message string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bot.Url = os.Getenv("ZULIP_URL")
|
||||||
|
if len(bot.Url) == 0 {
|
||||||
|
log.Fatalln("Falta ZULIP_URL")
|
||||||
|
}
|
||||||
|
bot.Email = os.Getenv("ZULIP_BOT_EMAIL")
|
||||||
|
if len(bot.Email) == 0 {
|
||||||
|
log.Fatalln("Falta ZULIP_BOT_EMAIL")
|
||||||
|
}
|
||||||
|
bot.Key = os.Getenv("ZULIP_BOT_KEY")
|
||||||
|
if len(bot.Key) == 0 {
|
||||||
|
log.Fatalln("Falta ZULIP_BOT_KEY")
|
||||||
|
}
|
||||||
|
stream = os.Getenv("ZULIP_STREAM")
|
||||||
|
if len(stream) == 0 {
|
||||||
|
stream = "check-in bot test"
|
||||||
|
}
|
||||||
|
message = os.Getenv("ZULIP_MESSAGE")
|
||||||
|
if len(message) == 0 {
|
||||||
|
message = "prueba de mensaje"
|
||||||
|
}
|
||||||
|
|
||||||
|
cycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cycle() {
|
||||||
|
now := time.Now()
|
||||||
|
lastWeek := now.Add(-time.Hour * 24 * time.Duration(now.Weekday()-time.Monday))
|
||||||
|
err := createIfNotExists(lastWeek.Format("semana 2006-01-02"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Hour * 5)
|
||||||
|
cycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIfNotExists(topicName string) (err error) {
|
||||||
|
streamId, err := bot.GetStreamId(stream)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
topics, err := bot.GetStreamTopics(streamId)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
names := mapSlice(topics, func(t zulip.GetStreamTopicsResStream) string {
|
||||||
|
return t.Name
|
||||||
|
})
|
||||||
|
|
||||||
|
if slices.Contains(names, topicName) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bot.SendMessage(stream, topicName, message)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://gosamples.dev/generics-map-function/
|
||||||
|
func mapSlice[T any, M any](a []T, f func(T) M) []M {
|
||||||
|
n := make([]M, len(a))
|
||||||
|
for i, e := range a {
|
||||||
|
n[i] = f(e)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
3
readme.md
Normal file
3
readme.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Zulip Check-in Cyborg
|
||||||
|
|
||||||
|
Manda un mensaje por semana a un hilo para hacer check-ins. Para uso en [Sutty](https://sutty.coop.ar).
|
131
zulip/zulip.go
Normal file
131
zulip/zulip.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package zulip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bot struct {
|
||||||
|
Url string
|
||||||
|
Email string
|
||||||
|
Key string
|
||||||
|
HTTPClient http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) url() *url.URL {
|
||||||
|
u, err := url.Parse(b.Url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(u)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
func (b *Bot) authHeaders(h http.Header) {
|
||||||
|
h.Add("authorization", "basic "+base64.StdEncoding.EncodeToString([]byte(b.Email+":"+b.Key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) SendMessage(stream, topic, message string) error {
|
||||||
|
u := b.url()
|
||||||
|
u.Path = "/api/v1/messages"
|
||||||
|
|
||||||
|
values := url.Values{
|
||||||
|
"type": []string{"stream"},
|
||||||
|
"to": []string{stream},
|
||||||
|
"topic": []string{topic},
|
||||||
|
"content": []string{message},
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", u.String(), strings.NewReader(values.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
req.Header.Add("content-type", "application/x-www-form-urlencoded")
|
||||||
|
b.authHeaders(req.Header)
|
||||||
|
|
||||||
|
res, err := b.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error enviando mensaje", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
log.Printf("Zulip tiró un %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
byt, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error enviando mensaje", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Response: %s", string(byt))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type getStreamIdRes struct {
|
||||||
|
SteamId int `json:"stream_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) GetStreamId(name string) (int, error) {
|
||||||
|
u := b.url()
|
||||||
|
u.Path = "/api/v1/get_stream_id"
|
||||||
|
|
||||||
|
values := url.Values{"stream": []string{name}}
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
req, err := http.NewRequest("GET", u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
b.authHeaders(req.Header)
|
||||||
|
|
||||||
|
res, err := b.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return 0, errors.New(fmt.Sprintf("Zulip tiró un %d", res.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
r := getStreamIdRes{}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return r.SteamId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type getStreamTopicsRes struct {
|
||||||
|
Topics []GetStreamTopicsResStream `json:"topics"`
|
||||||
|
}
|
||||||
|
type GetStreamTopicsResStream struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) GetStreamTopics(streamId int) ([]GetStreamTopicsResStream, error) {
|
||||||
|
var r getStreamTopicsRes
|
||||||
|
|
||||||
|
u := b.url()
|
||||||
|
u.Path = fmt.Sprintf("/api/v1/users/me/%d/topics", streamId)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
b.authHeaders(req.Header)
|
||||||
|
|
||||||
|
res, err := b.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return r.Topics, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return r.Topics, errors.New(fmt.Sprintf("Zulip tiró un %d", res.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&r)
|
||||||
|
if err != nil {
|
||||||
|
return r.Topics, err
|
||||||
|
}
|
||||||
|
return r.Topics, nil
|
||||||
|
}
|
Loading…
Reference in a new issue