diff --git a/bot.go b/bot.go index 558168f..b78342e 100644 --- a/bot.go +++ b/bot.go @@ -112,12 +112,13 @@ func main() { phabricatorClient, err = gonduit.Dial(viper.GetString("phabricator.url"), &core.ClientOptions{ APIToken: viper.GetString("phabricator.token"), Timeout: time.Second * 20, + // Client: client, }) if err != nil { log.Fatalf("Error connecting to phabricator, %s", err) } - go runTaskServer() + runTaskServer() telegramClient, err = tgbotapi.NewBotAPI(viper.GetString("telegram.token")) if err != nil { diff --git a/go.mod b/go.mod index df4c940..ba46566 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/alufers/phabricatorTeleNotifier go 1.19 require ( - github.com/davecgh/go-spew v1.1.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 + github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd github.com/spf13/viper v1.13.0 github.com/uber/gonduit v0.13.0 ) diff --git a/go.sum b/go.sum index 3fe8768..c9757aa 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd h1:PppHBegd3uPZ3Y/Iax/2mlCFJm1w4Qf/zP1MdW4ju2o= +github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= diff --git a/logging_transport.go b/logging_transport.go new file mode 100644 index 0000000..7b1fcb8 --- /dev/null +++ b/logging_transport.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httputil" +) + +type loggingTransport struct{} + +func (s *loggingTransport) RoundTrip(r *http.Request) (*http.Response, error) { + bytes, _ := httputil.DumpRequestOut(r, true) + + resp, err := http.DefaultTransport.RoundTrip(r) + // err is returned after dumping the response + + respBytes, _ := httputil.DumpResponse(resp, true) + bytes = append(bytes, respBytes...) + + fmt.Printf("%s\n", bytes) + + return resp, err +} diff --git a/task_server.go b/task_server.go index 8b3ebf0..d52c565 100644 --- a/task_server.go +++ b/task_server.go @@ -1,12 +1,23 @@ package main import ( + "bytes" _ "embed" + "encoding/base64" + "encoding/json" "fmt" "html/template" + "io" "log" "net/http" + "regexp" + "sort" + "strings" + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" + "github.com/spf13/viper" "github.com/uber/gonduit/requests" ) @@ -29,13 +40,92 @@ var prioritiesSorted = []string{ "Normal", "Low", "Wishlist", + "*", +} + +func mdToHTML(md string) string { + // create markdown parser with extensions + extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock + p := parser.NewWithExtensions(extensions) + doc := p.Parse([]byte(md)) + + // create HTML renderer with extensions + htmlFlags := html.CommonFlags | html.HrefTargetBlank + opts := html.RendererOptions{Flags: htmlFlags} + renderer := html.NewRenderer(opts) + + return string(markdown.Render(doc, renderer)) } func runTaskServer() { mux := http.NewServeMux() tpl := template.Must(template.New("tasks").Parse(tplData)) + mux.HandleFunc("/get-phabri-file", func(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + infoReq := &GetFileInfoRequest{ + ID: id, + } + var info GetFileInfoResp + err := phabricatorClient.Call("file.info", infoReq, &info) + if err != nil { + log.Printf("Error: %v", err) + fmt.Fprintf(w, "Error: %v", err) + return + } + + ///DDD + baseURL := viper.GetString("phabricator.url") + apiToken := viper.GetString("phabricator.token") + + // Define the API endpoint and parameters + apiEndpoint := "/api/file.download" + phid := info.Phid + + // Construct the full URL + fullURL := baseURL + apiEndpoint + + // Prepare the request body + body := bytes.NewBufferString(fmt.Sprintf("api.token=%s&phid=%s", apiToken, phid)) + // Make the HTTP POST request + response, err := http.Post(fullURL, "application/x-www-form-urlencoded", body) + if err != nil { + log.Printf("Error: %v", err) + fmt.Fprintf(w, "Error: %v", err) + return + } + defer response.Body.Close() + + // Read the response body + responseBody, err := io.ReadAll(response.Body) + if err != nil { + log.Printf("Error: %v", err) + fmt.Fprintf(w, "Error: %v", err) + return + } + + var download FileDownloadResp + + err = json.Unmarshal(responseBody, &download) + if err != nil { + log.Printf("Error: %v", err) + fmt.Fprintf(w, "Error: %v", err) + return + } + + ////ENDDDD + + data, err := base64.StdEncoding.DecodeString(download.Result) + if err != nil { + log.Printf("Error: %v", err) + fmt.Fprintf(w, "Error: %v", err) + return + } + w.Header().Set("Content-type", info.MimeType) + w.Write(data) + + }) mux.HandleFunc("/phabri-tasks-for-dashboard", func(w http.ResponseWriter, r *http.Request) { // query := r.URL.Query() // if query.Get("token") != viper.GetString("server.token") { @@ -55,11 +145,20 @@ func runTaskServer() { return } + tasksPreSorted := make([]PhabricatorTask, 0, len(tasks)) + for _, v := range tasks { + tasksPreSorted = append(tasksPreSorted, v) + } + sort.Slice(tasksPreSorted, func(i, j int) bool { + return tasksPreSorted[i].ID > tasksPreSorted[j].ID + }) + var tasksSorted []ExtendedPhabricatorTask phids := make([]string, 0, len(tasks)*2) for _, v := range tasks { phids = append(phids, v.AuthorPHID) + phids = append(phids, v.ProjectPHIDs...) } phids = removeDuplicates(phids) lookedUpPhids, err := phabricatorClient.PHIDLookup(requests.PHIDLookupRequest{ @@ -72,44 +171,65 @@ func runTaskServer() { } for _, priority := range prioritiesSorted { - for _, task := range tasks { + for _, task := range tasksPreSorted { author, ok := lookedUpPhids[task.AuthorPHID] authorName := "" if ok { authorName = author.FullName } - if task.Priority == priority { - tasksSorted = append(tasksSorted, ExtendedPhabricatorTask{ - PhabricatorTask: task, - AuthorName: authorName, - }) + rendered := mdToHTML(task.Description) + + // match tags like "{F602442}" + reg := regexp.MustCompile(`\{F\d+\}`) + rendered = reg.ReplaceAllStringFunc(rendered, func(s string) string { + s = strings.ReplaceAll(s, "{F", "") + s = strings.ReplaceAll(s, "}", "") + return fmt.Sprintf(``, s) + }) + + projectNames := make([]string, 0, len(task.ProjectPHIDs)) + isImportant := false + for _, projectPHID := range task.ProjectPHIDs { + project, ok := lookedUpPhids[projectPHID] + if ok { + if project.FullName == "[SUBSKRYBCYJNY] Wszystkie taski" { + continue + } + if project.FullName == "Na monitor" { + isImportant = true + } + projectNames = append(projectNames, project.FullName) + } } - } - } - // add all priority types that are not in the sorted list - for _, task := range tasks { - author, ok := lookedUpPhids[task.AuthorPHID] - authorName := "" - if ok { - authorName = author.FullName - } - found := false - for _, priority := range prioritiesSorted { - if task.Priority == priority { - found = true - break + if task.Priority == priority || priority == "*" { + // check if already added + found := false + for _, v := range tasksSorted { + if v.PhabricatorTask.Phid == task.Phid { + found = true + break + } + } + if !found { + tasksSorted = append(tasksSorted, ExtendedPhabricatorTask{ + PhabricatorTask: task, + AuthorName: authorName, + RenderedDescription: template.HTML(rendered), + ProjectNames: projectNames, + IsImportant: isImportant, + }) + } } } - if !found { - tasksSorted = append(tasksSorted, ExtendedPhabricatorTask{ - PhabricatorTask: task, - AuthorName: authorName, - }) - } } + // move is important to the top + sort.SliceStable(tasksSorted, func(i, j int) bool { + return tasksSorted[i].IsImportant + }) + // pretty print json // b, _ := json.MarshalIndent(tasks, "", " ") // fmt.Fprintf(w, "%s", b) diff --git a/tasks_template.html b/tasks_template.html index 4f524f9..a7fdb1a 100644 --- a/tasks_template.html +++ b/tasks_template.html @@ -72,6 +72,49 @@ padding: 10px; } +.task__project { + background-color: #ddd; + padding: 2px 5px; + border-radius: 3px; + font-size: 12px; + margin-right: 5px; +} + +.task.important { + border-color: red; + border-width: 4px; + animation: blinker 3s linear infinite; + /* horizontal stripe */ + background: linear-gradient( + 90deg, + #fff 0px, + #ff000044 50px, + #fff 100px + ); + background-position-x: -100px; +} +.task img { + max-height: 100px; +} +.task.important img { + max-height: 50vh; +} + +@keyframes blinker { + 0% { + border-color: red; + background-position-x: 0px; + } + 50% { + border-color: transparent; + background-position-x: 100vw; + } + 100% { + border-color: red; + background-position-x: 0px; + } +} + @@ -80,17 +123,20 @@ Tasks {{range .}} -
+
{{ .Priority }}
#{{.ID}}
{{.Title}}
+ {{range.ProjectNames}} +
{{.}}
+ {{end}}
Autor: {{.AuthorName}}
-
{{.Description}}
+
{{.RenderedDescription}}
{{end}} @@ -109,7 +155,7 @@ window.location.reload(); } - }, 1000 * 10); + }, 1000 * 60); diff --git a/types.go b/types.go index 0011a0c..eaad774 100644 --- a/types.go +++ b/types.go @@ -63,5 +63,37 @@ type PhabricatorTask struct { type ExtendedPhabricatorTask struct { PhabricatorTask - AuthorName string + AuthorName string + RenderedDescription any + ProjectNames []string + IsImportant bool +} + +type GetFileInfoRequest struct { + ID string `json:"id"` + requests.Request +} + +type GetFileInfoResp struct { + ID string `json:"id"` + Phid string `json:"phid"` + ObjectName string `json:"objectName"` + Name string `json:"name"` + MimeType string `json:"mimeType"` + ByteSize string `json:"byteSize"` + AuthorPHID string `json:"authorPHID"` + DateCreated string `json:"dateCreated"` + DateModified string `json:"dateModified"` + URI string `json:"uri"` +} + +type FileDownloadRequest struct { + requests.Request + Phid string `json:"phid"` +} + +type FileDownloadResp struct { + Result string `json:"result"` + ErrorCode any `json:"error_code"` + ErrorInfo any `json:"error_info"` }