package main import ( "flag" "log" "net/http" "strconv" "strings" "git.gensokyo.uk/yonah/monstersirenfetch" ) var ( flagMetadataPath string flagAutoplay string ) func init() { flag.StringVar(&flagMetadataPath, "m", "data/metadata", "Path to enriched metadata") flag.StringVar(&flagAutoplay, "autoplay", "048794", "Value to set for autoplay field in /api/songs") } func handleAPINew(metadata *monstersirenfetch.Metadata) http.HandlerFunc { if metadata == nil { log.Fatal("invalid metadata") } const ( // TODO(ophestra): stub these out with local artwork fontsetResp = `{"code":0,"msg":"","data":{"Sans-Regular":{"tt":"/static/SourceHanSansCN-Regular.ttf","eot":"/static/SourceHanSansCN-Regular.eot","svg":"/static/SourceHanSansCN-Regular.svg","woff":"/static/SourceHanSansCN-Regular.woff"},"Sans-Bold":{"tt":"/static/SourceHanSansCN-Bold.ttf","eot":"/static/SourceHanSansCN-Bold.eot","svg":"/static/SourceHanSansCN-Bold.svg","woff":"/static/SourceHanSansCN-Bold.woff"}}}` recommendsResp = `{"code":0,"msg":"","data":[{"title":"#AUS小屋","coverUrl":"https://web.hycdn.cn/siren/pic/20230228/ba2bf4c42b3852a8b35e9721a8c8bbb7.jpg","cover":{"private":false,"path":"siren/pic/20230228/ba2bf4c42b3852a8b35e9721a8c8bbb7.jpg"},"description":"\"嗨,各位,这个冬天过得怎么样?\n我们一直呆在室内,眼看着春天到来,再不出门,雪就要化了,于是大家都被dan拉着去比赛堆雪人。虽然能动弹动弹是很不错,但“这是为了阻止alty祸害厨房”这句话就很多余了不是吗!\n我和frost堆到一半,决定给雪人戴一个耳朵头箍,frost还把围巾也围上去了,造就了一个可爱的卡特斯。就在我们以为赢定了的时候,回头看见dan正站在一个巨大的雪人旁边手舞足蹈。呃,不得不说气势很惊人,有一瞬间甘拜下风的念头。\n但是看了看被惊到的aya旁边的……小雪人,我觉得我和frost的雪人至少","type":2,"data":"750452"},{"title":"#EMPEROR","coverUrl":"https://web.hycdn.cn/siren/pic/20230122/89969a75098c07cd8e289bad831ea4a2.jpg","cover":{"private":false,"path":"siren/pic/20230122/89969a75098c07cd8e289bad831ea4a2.jpg"},"description":"","type":2,"data":"578833"},{"title":"#AUS小屋","coverUrl":"https://web.hycdn.cn/siren/pic/20221128/ede2a04423632c17b702a656414a8c04.jpg","cover":{"private":false,"path":"siren/pic/20221128/ede2a04423632c17b702a656414a8c04.jpg"},"description":"一起用被炉(有点热!)","type":2,"data":"750452"},{"title":"#AUS小屋","coverUrl":"https://web.hycdn.cn/siren/pic/20220930/f4c7886eabcc1f3f178e7d4a9e5f7a21.jpg","cover":{"private":false,"path":"siren/pic/20220930/f4c7886eabcc1f3f178e7d4a9e5f7a21.jpg"},"description":"","type":2,"data":"336217"},{"title":"#D.D.D.PHOTO","coverUrl":"https://web.hycdn.cn/siren/pic/20221228/93393e28e1d0caff9b61c951eaf6a5f5.jpg","cover":{"private":false,"path":"siren/pic/20221228/93393e28e1d0caff9b61c951eaf6a5f5.jpg"},"description":"","type":2,"data":"241307"},{"title":"live演出","coverUrl":"https://web.hycdn.cn/siren/pic/20230427/b524e7b99982a972609de43998f61b46.jpg","cover":{"private":false,"path":"siren/pic/20230427/b524e7b99982a972609de43998f61b46.jpg"},"description":"\"春天好,各位。AUS与仲春一起归来了。\n今天我们刚结束了新live的最后一场演出。我站在舞台中央,被音浪和节奏包围着,即使晃眼的灯光遮挡了视线,也能感受到大家在台下拼命挥舞双手,这同样让我很激动。\n我唱哑了嗓子,alty弹到指头隐隐作痛,frost不时就会揉揉手腕,一向精力充沛的dan回工作室后也累瘫了,但我们一路笑得很大声,连frost也在旁边勾着嘴角。毕竟对于创作者来说,欣赏者的热爱足以融化春寒和创作的倦怠,对吧?\"","type":2,"data":"605965"}]}` ) var ( fontsetRespData = []byte(fontsetResp) recommendsRespData = []byte(recommendsResp) ) var albumsRespData []byte { resp := monstersirenfetch.AlbumsResponse{Data: make(monstersirenfetch.AlbumsData, len(metadata.Albums))} for i := range metadata.Albums { if !metadata.Albums[i].Copy(&resp.Data[i], monstersirenfetch.AlbumVariantBase) { log.Fatal("cannot copy album metadata for base variant") } } albumsRespData = mustMarshalJSON(resp) } // data, detail albumCidData := make(map[int][2][]byte, len(metadata.Albums)) for i := range metadata.Albums { var ( a = &metadata.Albums[i] v monstersirenfetch.AlbumResponse d [2][]byte ) if !a.Copy(&v.Data, monstersirenfetch.AlbumVariantData) { log.Fatal("cannot copy album metadata for data variant") } d[0] = mustMarshalJSON(&v) if !a.Copy(&v.Data, monstersirenfetch.AlbumVariantDetail) { log.Fatal("cannot copy album metadata for detail variant") } d[1] = mustMarshalJSON(&v) albumCidData[int(a.CID)] = d } if len(albumCidData) != len(metadata.Albums) { log.Fatalf("album has duplicate cid: %d != %d", len(albumCidData), len(metadata.Albums)) } var songsRespData []byte { resp := monstersirenfetch.SongsResponse{Data: monstersirenfetch.SongsData{ List: make([]monstersirenfetch.Song, len(metadata.Songs)), Autoplay: flagAutoplay, }} for i := range metadata.Songs { if !metadata.Songs[i].Copy(&resp.Data.List[i], monstersirenfetch.SongVariantBase) { log.Fatal("cannot copy song metadata for base variant") } } songsRespData = mustMarshalJSON(resp) } songsCidData := make(map[int][]byte, len(metadata.Songs)) for i := range metadata.Songs { var ( s = metadata.Songs[i] v monstersirenfetch.SongResponse ) if !s.Copy(&v.Data, monstersirenfetch.SongVariantFull) { log.Fatal("cannot copy song metadata for full variant") } songsCidData[int(s.CID)] = mustMarshalJSON(&v) } if len(songsCidData) != len(metadata.Songs) { log.Fatalf("song has duplicate cid: %d != %d", len(songsCidData), len(metadata.Songs)) } log.Printf("loaded %d albums and %d songs", len(metadata.Albums), len(metadata.Songs)) return func(writer http.ResponseWriter, request *http.Request) { if request.URL == nil { log.Printf("got invalid request %p", request) return } const ( prefix = "/api/" endpointAlbum = prefix + "album/" endpointSong = prefix + "song/" endpointAlbumSuffixData = "/data" endpointAlbumSuffixDetail = "/detail" ) switch request.URL.Path { case prefix + "fontset": writeResp(writer, fontsetRespData) case prefix + "recommends": writeResp(writer, recommendsRespData) case prefix + "albums": writeResp(writer, albumsRespData) case prefix + "songs": writeResp(writer, songsRespData) default: if strings.HasPrefix(request.URL.Path, endpointAlbum) { v := request.URL.Path[len(endpointAlbum):] detail := strings.HasSuffix(v, endpointAlbumSuffixDetail) if detail { v = v[:len(v)-len(endpointAlbumSuffixDetail)] } else if strings.HasSuffix(v, endpointAlbumSuffixData) { v = v[:len(v)-len(endpointAlbumSuffixData)] } else { writer.WriteHeader(http.StatusNotFound) writeResp(writer, []byte("unsupported album endpoint")) return } if c, err := strconv.Atoi(v); err != nil { writer.WriteHeader(http.StatusBadRequest) writeResp(writer, []byte("invalid cid")) return } else if albumData, ok := albumCidData[c]; !ok { writer.WriteHeader(http.StatusNotFound) writeResp(writer, []byte("not found")) return } else if detail { writeResp(writer, albumData[1]) return } else { writeResp(writer, albumData[0]) return } } if strings.HasPrefix(request.URL.Path, endpointSong) { v := request.URL.Path[len(endpointSong):] if c, err := strconv.Atoi(v); err != nil { writer.WriteHeader(http.StatusBadRequest) writeResp(writer, []byte("invalid cid")) return } else if songData, ok := songsCidData[c]; !ok { writer.WriteHeader(http.StatusNotFound) writeResp(writer, []byte("not found")) return } else { writeResp(writer, songData) return } } verboseln("api endpoint", request.URL.Path, "not implemented") writer.WriteHeader(http.StatusNotFound) writeResp(writer, []byte("null")) } } }