diff --git a/bot.go b/bot.go index 0924574..70c7462 100644 --- a/bot.go +++ b/bot.go @@ -17,6 +17,7 @@ type Bot struct { *discordgo.Session mentionRe *regexp.Regexp db *Db + api *Osuapi requests chan int } @@ -37,7 +38,7 @@ func NewBot(token string, db *Db, requests chan int) (bot *Bot, err error) { return } - bot = &Bot{s, re, db, requests} + bot = &Bot{s, re, db, db.api, requests} s.AddHandler(bot.errWrap(bot.newMessageHandler)) return } @@ -66,6 +67,89 @@ func (bot *Bot) errWrap(fn interface{}) interface{} { return newFunc.Interface() } +func (bot *Bot) NotifyNewEvent(channelId string, newMaps []Event) (err error) { + for _, event := range newMaps { + var ( + gotBeatmapInfo = false + beatmapSet Beatmapset + gotDownloadedBeatmap = false + downloadedBeatmap BeatmapsetDownloaded + ) + beatmapSet, err = bot.getBeatmapsetInfo(event) + if err != nil { + log.Println("failed to retrieve beatmap info:", err) + } else { + gotBeatmapInfo = true + downloadedBeatmap, err = bot.downloadBeatmap(&beatmapSet) + if err != nil { + log.Println("failed to download beatmap:", err) + } else { + gotDownloadedBeatmap = true + } + } + + log.Println("BEATMAP SET", beatmapSet) + embed := &discordgo.MessageEmbed{ + URL: "https://osu.ppy.sh" + event.Beatmapset.URL, + Title: event.Type + ": " + event.Beatmapset.Title, + Timestamp: event.CreatedAt, + Footer: &discordgo.MessageEmbedFooter{ + Text: fmt.Sprintf("Event ID: %d", event.ID), + }, + } + if gotBeatmapInfo { + embed.Author = &discordgo.MessageEmbedAuthor{ + URL: "https://osu.ppy.sh/u/" + strconv.Itoa(beatmapSet.UserId), + Name: beatmapSet.Creator, + IconURL: fmt.Sprintf( + "https://a.ppy.sh/%d?%d.png", + beatmapSet.UserId, + time.Now().Unix, + ), + } + embed.Thumbnail = &discordgo.MessageEmbedThumbnail{ + URL: beatmapSet.Covers.SlimCover2x, + } + + if gotDownloadedBeatmap { + log.Println(downloadedBeatmap) + } + } + bot.ChannelMessageSendEmbed(channelId, embed) + } + + return +} + +type BeatmapsetDownloaded struct { + Path string +} + +func (bot *Bot) downloadBeatmap(beatmapSet *Beatmapset) (downloadedBeatmap BeatmapsetDownloaded, err error) { + beatmapFile, err := bot.api.BeatmapsetDownload(beatmapSet.Id) + if err != nil { + return + } + + downloadedBeatmap.Path = beatmapFile + return +} + +func (bot *Bot) getBeatmapsetInfo(event Event) (beatmapSet Beatmapset, err error) { + beatmapSetId, err := strconv.Atoi(strings.TrimPrefix(event.Beatmapset.URL, "/s/")) + if err != nil { + return + } + + log.Println("beatmap set id", beatmapSetId) + beatmapSet, err = bot.api.GetBeatmapSet(beatmapSetId) + if err != nil { + return + } + + return +} + func (bot *Bot) newMessageHandler(s *discordgo.Session, m *discordgo.MessageCreate) (err error) { mentionsMe := false for _, user := range m.Mentions { diff --git a/config.go b/config.go index 6a842fe..d9a5a7f 100644 --- a/config.go +++ b/config.go @@ -12,6 +12,7 @@ type Config struct { BotToken string `toml:"bot_token"` ClientId int `toml:"client_id"` ClientSecret string `toml:"client_secret"` + Repos string `toml:"repos"` } func ReadConfig(path string) (config Config, err error) { diff --git a/models.go b/models.go index b8db5a4..db480cd 100644 --- a/models.go +++ b/models.go @@ -1,31 +1,66 @@ package main +type User struct { + Id int `json:"id"` + Username string `json:"username"` + CountryCode string `json:"country_code"` +} + +type Beatmapset struct { + Id int `json:"id"` + + Artist string `json:"artist"` + ArtistUnicode string `json:"artist_unicode"` + Title string `json:"title"` + TitleUnicode string `json:"title_unicode"` + Creator string `json:"creator"` + UserId int `json:"user_id"` + + Covers BeatmapCovers `json:"covers"` + Beatmaps []Beatmap `json:"beatmaps,omitempty"` +} + +type Beatmap struct { + Id int `json:"id"` + DifficultyRating float64 `json:"difficulty_rating"` + DifficultyName string `json:"version"` +} + +type BeatmapCovers struct { + Cover string `json:"cover"` + Cover2x string `json:"cover@2x"` + Card string `json:"card"` + Card2x string `json:"card@2x"` + SlimCover string `json:"slimcover"` + SlimCover2x string `json:"slimcover@2x"` +} + type Event struct { CreatedAt string `json:"created_at"` ID int `json:"id"` Type string `json:"type"` // type: achievement - Achievement Achievement `json:"achievement,omitempty"` + Achievement EventAchievement `json:"achievement,omitempty"` // type: beatmapsetApprove // type: beatmapsetDelete // type: beatmapsetRevive // type: beatmapsetUpdate // type: beatmapsetUpload - Beatmapset Beatmapset `json:"beatmapset,omitempty"` + Beatmapset EventBeatmapset `json:"beatmapset,omitempty"` - User User `json:"user,omitempty"` + User EventUser `json:"user,omitempty"` } -type Achievement struct{} +type EventAchievement struct{} -type Beatmapset struct { +type EventBeatmapset struct { Title string `json:"title"` URL string `json:"url"` } -type User struct { +type EventUser struct { Username string `json:"username"` URL string `json:"url"` PreviousUsername string `json:"previousUsername,omitempty"` diff --git a/osuapi.go b/osuapi.go index 97a3778..0880e37 100644 --- a/osuapi.go +++ b/osuapi.go @@ -4,9 +4,11 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "log" "net/http" + "os" "strings" "time" @@ -64,7 +66,7 @@ func (api *Osuapi) Token() (token string, err error) { return } -func (api *Osuapi) Request(action string, url string, result interface{}) (err error) { +func (api *Osuapi) Request0(action string, url string) (resp *http.Response, err error) { err = api.lock.Acquire(context.TODO(), 1) if err != nil { return @@ -82,10 +84,36 @@ func (api *Osuapi) Request(action string, url string, result interface{}) (err e return } - resp, err := http.DefaultClient.Do(req) + resp, err = http.DefaultClient.Do(req) if err != nil { return } + + if resp.StatusCode != 200 { + var respBody []byte + respBody, err = ioutil.ReadAll(resp.Body) + if err != nil { + return + } + + err = fmt.Errorf("not 200: %s", string(respBody)) + return + } + + // release the lock after 1 minute + go func() { + time.Sleep(time.Minute) + api.lock.Release(1) + }() + return +} + +func (api *Osuapi) Request(action string, url string, result interface{}) (err error) { + resp, err := api.Request0(action, url) + if err != nil { + return + } + data, err := ioutil.ReadAll(resp.Body) if err != nil { return @@ -96,11 +124,48 @@ func (api *Osuapi) Request(action string, url string, result interface{}) (err e return } - // release the lock after 1 minute - go func() { - time.Sleep(time.Minute) - api.lock.Release(1) - }() + return +} + +func (api *Osuapi) GetBeatmapSet(beatmapSetId int) (beatmapSet Beatmapset, err error) { + url := fmt.Sprintf("/beatmapsets/%d", beatmapSetId) + err = api.Request("GET", url, &beatmapSet) + if err != nil { + return + } + + return +} + +func (api *Osuapi) BeatmapsetDownload(beatmapSetId int) (path string, err error) { + url := fmt.Sprintf("/beatmapsets/%d/download", beatmapSetId) + resp, err := api.Request0("GET", url) + if err != nil { + return + } + + file, err := ioutil.TempFile(os.TempDir(), "beatmapsetDownload") + if err != nil { + return + } + + _, err = io.Copy(file, resp.Body) + if err != nil { + return + } + file.Close() + + path = file.Name() + return +} + +func (api *Osuapi) GetUser(userId int) (user User, err error) { + url := fmt.Sprintf("/users/%d", userId) + err = api.Request("GET", url, &user) + if err != nil { + return + } + return } diff --git a/scrape.go b/scrape.go index 16ae8fd..015444a 100644 --- a/scrape.go +++ b/scrape.go @@ -23,9 +23,7 @@ func RunScraper(bot *Bot, db *Db, api *Osuapi, requests chan int) { } db.IterTrackingChannels(userId, func(channelId string) error { - for _, beatmap := range newMaps { - bot.ChannelMessageSend(channelId, fmt.Sprintf("new beatmap event [%s](%s)", beatmap.Title, beatmap.URL)) - } + bot.NotifyNewEvent(channelId, newMaps) return nil }) @@ -37,10 +35,10 @@ func RunScraper(bot *Bot, db *Db, api *Osuapi, requests chan int) { } } -func getNewMaps(db *Db, api *Osuapi, userId int) (newMaps []Beatmapset, err error) { +func getNewMaps(db *Db, api *Osuapi, userId int) (newMaps []Event, err error) { // see if there's a last event hasLastEvent, lastEventId := db.MapperLastEvent(userId) - newMaps = make([]Beatmapset, 0) + newMaps = make([]Event, 0) var ( events []Event newLatestEvent = 0 @@ -75,7 +73,7 @@ func getNewMaps(db *Db, api *Osuapi, userId int) (newMaps []Beatmapset, err erro if event.Type == "beatmapsetUpload" || event.Type == "beatmapsetRevive" || event.Type == "beatmapsetUpdate" { - newMaps = append(newMaps, event.Beatmapset) + newMaps = append(newMaps, event) } } @@ -97,11 +95,14 @@ func getNewMaps(db *Db, api *Osuapi, userId int) (newMaps []Beatmapset, err erro if event.Type == "beatmapsetUpload" || event.Type == "beatmapsetRevive" || event.Type == "beatmapsetUpdate" { - newMaps = append(newMaps, event.Beatmapset) + newMaps = append(newMaps, event) } } } + // TODO: debug + // updateLatestEvent = false + if updateLatestEvent { err = db.UpdateMapperLatestEvent(userId, newLatestEvent) if err != nil {