Skip to content

Commit

Permalink
add
Browse files Browse the repository at this point in the history
  • Loading branch information
walnuts1018 committed Sep 22, 2023
1 parent 4049f9d commit f4b454d
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 16 deletions.
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config_t struct {
SlackAccessToken string `env:"SLACK_ACCESS_TOKEN"`

ServerPort string
ServerURL string
}

var Config = Config_t{}
Expand All @@ -32,6 +33,7 @@ func LoadConfig() error {
serverport := flag.String("port", "8080", "server port")
flag.Parse()
Config.ServerPort = *serverport
Config.ServerURL = fmt.Sprintf("http://localhost:%v/", Config.ServerPort)

err := godotenv.Load(".env")
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3'
services:
postgres:
image: postgres:16
container_name: psql
container_name: wakatime-to-slack-psql
ports:
- "5432:5432"
volumes:
Expand Down
1 change: 1 addition & 0 deletions domain/slack.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package domain

type SlackClient interface {
SetUserCustomStatus(emoji string) error
}
3 changes: 2 additions & 1 deletion domain/wakatime.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type WakatimeClient interface {
Auth(state string) string
Callback(ctx context.Context, code string) (OAuth2Token, error)
SetToken(ctx context.Context, tokenStore TokenStore) error
Languages(ctx context.Context) ([]Language, error)
ListLanguages(ctx context.Context) ([]Language, error)
NowLanguage(ctx context.Context) (string, error)
}

type Language struct {
Expand Down
3 changes: 3 additions & 0 deletions emoji.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Go":"gopher"
}
3 changes: 0 additions & 3 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ func NewHandler(usecase *usecase.Usecase) (*gin.Engine, error) {

r.GET("/signin", signIn)
r.GET("/callback", callback)
r.GET("/languages", func(ctx *gin.Context) {
uc.Languages(ctx)
})

return r, nil
}
Expand Down
4 changes: 4 additions & 0 deletions infra/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package slack

import (
"fmt"
"strings"

"github.com/slack-go/slack"
"github.com/walnuts1018/wakatime-to-slack-profile/config"
Expand All @@ -18,6 +19,9 @@ func NewClient() *client {
}

func (c *client) SetUserCustomStatus(emoji string) error {
if !(strings.HasPrefix(emoji, ":") && strings.HasSuffix(emoji, ":")) {
emoji = ":" + emoji + ":"
}
err := c.slackClient.SetUserCustomStatus("", emoji, 0)
if err != nil {
return fmt.Errorf("error setting status: %w", err)
Expand Down
80 changes: 76 additions & 4 deletions infra/wakatime/wakatime.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"math"
"net/http"
"net/url"
"time"

"github.com/walnuts1018/wakatime-to-slack-profile/config"
"github.com/walnuts1018/wakatime-to-slack-profile/domain"
Expand All @@ -21,6 +25,7 @@ const (
var (
scopes = []string{
"read_stats",
"read_logged_time",
}
)

Expand All @@ -35,13 +40,14 @@ func NewOauth2Client() domain.WakatimeClient {
ClientID: config.Config.WakatimeAppID,
ClientSecret: config.Config.WakatimeAppSecret,
Endpoint: oauth2.Endpoint{AuthURL: AuthEndpoint, TokenURL: TokenEndpoint},
RedirectURL: config.Config.ServerURL + "callback",
Scopes: scopes,
},
}
}

func (c *client) Auth(state string) string {
url := c.cfg.AuthCodeURL(state, oauth2.AccessTypeOffline)
url := c.cfg.AuthCodeURL(state)
return url
}

Expand Down Expand Up @@ -84,7 +90,13 @@ func (c *client) SetToken(ctx context.Context, tokenStore domain.TokenStore) err
return nil
}

func (c *client) Languages(ctx context.Context) ([]domain.Language, error) {
type listLanguageResponce struct {
Data []domain.Language `json:"data"`
Total int `json:"total"`
TotalPages int `json:"total_pages"`
}

func (c *client) ListLanguages(ctx context.Context) ([]domain.Language, error) {
if c.wclient == nil {
return nil, fmt.Errorf("client is not set")
}
Expand All @@ -103,11 +115,71 @@ func (c *client) Languages(ctx context.Context) ([]domain.Language, error) {
return nil, fmt.Errorf("failed to read response body: %w", err)
}

var languages []domain.Language
var languages listLanguageResponce
err = json.Unmarshal(raw, &languages)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response body: %w", err)
}
return languages, nil
return languages.Data, nil
}

type nowLanguageResponce struct {
Data []struct {
Duration float64 `json:"duration"`
Language string `json:"language"`
Project string `json:"project"`
Time float64 `json:"time"`
} `json:"data"`
Start time.Time `json:"start"`
End time.Time `json:"end"`
Timezone string `json:"timezone"`
}

func (c *client) NowLanguage(ctx context.Context) (string, error) {
if c.wclient == nil {
return "", fmt.Errorf("client is not set")
}

endpoint, err := url.Parse("https://wakatime.com/api/v1/users/current/durations")
if err != nil {
return "", fmt.Errorf("failed to parse url: %w", err)
}
query := endpoint.Query()
query.Set("date", timeJST.Now().Format("2006-01-02"))
query.Set("slice_by", "language")
endpoint.RawQuery = query.Encode()

resp, err := c.wclient.Get(endpoint.String())
if err != nil {
return "", fmt.Errorf("failed to get languages: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to get languages: %v", resp.Status)
}

raw, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %w", err)
}

var languages nowLanguageResponce
err = json.Unmarshal(raw, &languages)
if err != nil {
return "", fmt.Errorf("failed to unmarshal response body: %w", err)
}

if len(languages.Data) == 0 {
slog.Warn("no language")
return "", nil
}

l := languages.Data[len(languages.Data)-1]
t := time.Unix(int64(math.Floor(l.Time)), 0)
if t.Before(timeJST.Now().Add(-10 * time.Minute)) {
slog.Warn("last language is too old", "lastLanguage", l.Language, "lastTime", t)
return "", nil
}
return l.Language, nil
}
30 changes: 29 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"fmt"
"log/slog"
"os"
"time"

"github.com/walnuts1018/wakatime-to-slack-profile/config"
"github.com/walnuts1018/wakatime-to-slack-profile/handler"
"github.com/walnuts1018/wakatime-to-slack-profile/infra/psql"
"github.com/walnuts1018/wakatime-to-slack-profile/infra/slack"
"github.com/walnuts1018/wakatime-to-slack-profile/infra/wakatime"
"github.com/walnuts1018/wakatime-to-slack-profile/usecase"
)
Expand All @@ -32,12 +34,38 @@ func main() {

wakatimeClient := wakatime.NewOauth2Client()

usecase := usecase.NewUsecase(wakatimeClient, psqClient)
slackClient := slack.NewClient()

usecase := usecase.NewUsecase(wakatimeClient, psqClient, slackClient)
err = usecase.SetToken(ctx)
if err != nil {
slog.Warn("failed to set token", "error", err)
}

go func() {
err := usecase.SetLanguage(ctx)
if err != nil {
slog.Error("Failed to set language", "error", err)
return
}

ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return
case <-ticker.C:
err := usecase.SetLanguage(ctx)
if err != nil {
slog.Error("Failed to set language", "error", err)
return
}
}
}
}()

handler, err := handler.NewHandler(usecase)
if err != nil {
slog.Error("Error loading handler: %v", "error", err)
Expand Down
45 changes: 45 additions & 0 deletions usecase/emoji.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package usecase

import (
"fmt"
"log/slog"
"strings"
)

func (u *Usecase) SetUserCustomStatus(language string) error {
if language == "" {
err := u.slackClient.SetUserCustomStatus("sloth")
if err == nil {
slog.Info("set user custom status", "emoji", "🦥")
return nil
}
}

override, ok := u.emojiOverides[language]
if ok {
err := u.slackClient.SetUserCustomStatus(override)
if err == nil {
slog.Info("set user custom status", "emoji", override)
return nil
}
}

err := u.slackClient.SetUserCustomStatus(language)
if err == nil {
slog.Info("set user custom status", "emoji", language)
return nil
}
err = u.slackClient.SetUserCustomStatus(strings.ToLower(language))
if err == nil {
slog.Info("set user custom status", "emoji", language)
return nil
}

err = u.slackClient.SetUserCustomStatus("question")
if err == nil {
slog.Info("set user custom status", "emoji", "❓")
return nil
}

return fmt.Errorf("failed to find emoji: %v", language)
}
19 changes: 15 additions & 4 deletions usecase/languages.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ import (
"fmt"
)

func (u *Usecase) Languages(ctx context.Context) error {
langs, err := u.wakatimeClient.Languages(ctx)
func (u *Usecase) SetLanguage(ctx context.Context) error {
language, err := u.wakatimeClient.NowLanguage(ctx)
if err != nil {
return err
return fmt.Errorf("failed to get now language: %w", err)
}
fmt.Printf("%#v\n", langs)

if u.lastLanguage != nil {
if *u.lastLanguage == language {
return nil
}
}

err = u.SetUserCustomStatus(language)
if err != nil {
return fmt.Errorf("failed to set user custom status: %w", err)
}
u.lastLanguage = &language
return nil
}
22 changes: 20 additions & 2 deletions usecase/usecase.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
package usecase

import "github.com/walnuts1018/wakatime-to-slack-profile/domain"
import (
"encoding/json"
"log/slog"
"os"

"github.com/walnuts1018/wakatime-to-slack-profile/domain"
)

type Usecase struct {
wakatimeClient domain.WakatimeClient
tokenStore domain.TokenStore
slackClient domain.SlackClient
emojiOverides map[string]string
lastLanguage *string
}

func NewUsecase(wakatimeClient domain.WakatimeClient, tokenStore domain.TokenStore) *Usecase {
func NewUsecase(wakatimeClient domain.WakatimeClient, tokenStore domain.TokenStore, slackClient domain.SlackClient) *Usecase {
var emojis map[string]string
b, err := os.ReadFile("emoji.json")
if err != nil {
slog.Warn("emoji.json is not found")
} else {
json.Unmarshal(b, &emojis)
}
return &Usecase{
wakatimeClient: wakatimeClient,
tokenStore: tokenStore,
slackClient: slackClient,
emojiOverides: emojis,
}
}

0 comments on commit f4b454d

Please sign in to comment.