cmd/msrserve: initial drop-in replacement server
This is still quite ugly and only meant to get things going for now, as a proof of concept. It is usable though, and quite fast. Signed-off-by: Yonah <contrib@gensokyo.uk>
1
.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
*.dylib
|
||||
*.pkg
|
||||
/msrfetch
|
||||
/msrserve
|
||||
|
||||
# Content-addressed media files
|
||||
/data
|
||||
|
||||
187
cmd/msrserve/api.go
Normal file
@ -0,0 +1,187 @@
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
45
cmd/msrserve/data.go
Normal file
@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
flagFetchDirPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&flagFetchDirPath, "d", "data", "Path to content-addressed store")
|
||||
}
|
||||
|
||||
func handleDataNew(storePath string) http.HandlerFunc {
|
||||
// hash string to url
|
||||
urlMap := mustReadJSON[map[string]string](path.Join(storePath, "map"))
|
||||
|
||||
urlMapInverse := make(map[string]string, len(urlMap))
|
||||
for hs, u := range urlMap {
|
||||
urlMapInverse[u] = hs
|
||||
}
|
||||
log.Printf("inverted %d media entries", len(urlMap))
|
||||
|
||||
return func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL == nil {
|
||||
log.Printf("got invalid request %p", request)
|
||||
return
|
||||
}
|
||||
|
||||
fixedPath := strings.Replace(request.URL.Path, "/data/https:", "https:/", 1)
|
||||
if hs, ok := urlMapInverse[fixedPath]; !ok {
|
||||
verboseln("media path", fixedPath, "not found")
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
writeResp(writer, []byte("not found"))
|
||||
return
|
||||
} else {
|
||||
http.ServeFile(writer, request, path.Join(storePath, hs))
|
||||
}
|
||||
}
|
||||
}
|
||||
122
cmd/msrserve/main.go
Normal file
@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.gensokyo.uk/yonah/monstersirenfetch"
|
||||
)
|
||||
|
||||
const shutdownTimeout = 5 * time.Second
|
||||
|
||||
var (
|
||||
flagVerbose bool
|
||||
flagAddr string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagVerbose, "v", false, "Increase log verbosity")
|
||||
flag.StringVar(&flagAddr, "a", ":3000",
|
||||
`TCP address for the server to listen on, in the form "host:port".`)
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed all:static
|
||||
staticFS embed.FS
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("msrserve: ")
|
||||
flag.Parse()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", http.FileServerFS(mustSub(staticFS, "static")).ServeHTTP)
|
||||
|
||||
mux.HandleFunc("/{$}", serveAppShell)
|
||||
mux.HandleFunc("/about/{$}", serveAppShell)
|
||||
mux.HandleFunc("/music/", serveAppShell)
|
||||
mux.HandleFunc("/info/{$}", serveAppShell)
|
||||
mux.HandleFunc("/contact/{$}", serveAppShell)
|
||||
|
||||
mux.HandleFunc("GET /api/", handleAPINew(mustReadJSON[*monstersirenfetch.Metadata](flagMetadataPath)))
|
||||
mux.HandleFunc("/data/", handleDataNew(flagFetchDirPath))
|
||||
|
||||
s := http.Server{Addr: flagAddr, Handler: mux}
|
||||
sig := make(chan os.Signal, 2)
|
||||
go func() {
|
||||
defer signal.Stop(sig)
|
||||
v := <-sig
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||
defer cancel()
|
||||
log.Print(v)
|
||||
if err := s.Shutdown(ctx); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
|
||||
if err := s.ListenAndServe(); err != nil {
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveAppShell(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFileFS(w, r, staticFS, "static/appShell.html")
|
||||
}
|
||||
|
||||
func writeResp(writer http.ResponseWriter, data []byte) bool {
|
||||
if _, err := writer.Write(data); err != nil {
|
||||
verboseln(err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func mustMarshalJSON(v any) []byte {
|
||||
if data, err := json.Marshal(v); err != nil {
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
func mustReadJSON[T any](pathname string) T {
|
||||
var v T
|
||||
if r, err := os.OpenFile(pathname, os.O_RDONLY, 0); err != nil {
|
||||
log.Fatal(err)
|
||||
} else if err = json.NewDecoder(r).Decode(&v); err != nil {
|
||||
log.Fatal(err)
|
||||
} else if err = r.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func mustSub(fsys fs.FS, dir string) fs.FS {
|
||||
if sub, err := fs.Sub(fsys, dir); err != nil {
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
} else {
|
||||
return sub
|
||||
}
|
||||
}
|
||||
|
||||
func verboseln(v ...any) {
|
||||
if flagVerbose {
|
||||
log.Println(v...)
|
||||
}
|
||||
}
|
||||
30
cmd/msrserve/static/appShell.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
href="/favicon.ico"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/assets/umi.62693412.css"
|
||||
/>
|
||||
<script>
|
||||
window.routerBase = "/";
|
||||
</script>
|
||||
<script>
|
||||
//! umi version: 3.2.16
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/assets/umi.87fedd26.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 33 KiB |
1
cmd/msrserve/static/assets/hg_web_sdk/lib/sdk.entry.js
Normal file
@ -0,0 +1 @@
|
||||
/*! hg-web-sdk@2.10.11 Copyright (C) 2017 - 2025 Hypergryph Co.,Ltd. All Rights Reserved. Published at 2025-09-12 11:52:40 */!function(){"use strict";var e,t,n,r,o={7384:function(e){e.exports=void 0}},f={};function c(e){var t=f[e];if(void 0!==t)return t.exports;var n=f[e]={id:e,exports:{}};return o[e].call(n.exports,n,n.exports,c),n.exports}c.m=o,c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},t=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},c.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var o=Object.create(null);c.r(o);var f={};e=e||[null,t({}),t([]),t(t)];for(var u=2&r&&n;"object"==typeof u&&!~e.indexOf(u);u=t(u))Object.getOwnPropertyNames(u).forEach((function(e){f[e]=function(){return n[e]}}));return f.default=function(){return n},c.d(o,f),o},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return e+"."+{28:"8a36160d124e1fc0d695",93:"ded0c4ea6557e13e450a",121:"5496c920e3be96ec1c85",129:"262222be26cc102d11d9",306:"7ea4490145ac18b4b0f6",374:"dca7e29a1476aafa8ab1",389:"c4c636ec9a56be1b5965",533:"8647f436c1f4cfe38581",576:"91297a5c93a1b33d078e",590:"fe49eee5dbd7d2e738aa",592:"91c1d092dcffddfa3356",616:"9b2b6c931fdd0cfbcc34",623:"44d788954a39216e54ea",664:"76278e41ec6bc96a5535",694:"8f6120ad4997e6b2885a",754:"38b331149223a3b5db08",787:"24cdd1c202257bf4fadb",852:"9317493ac1a6da74b065",881:"28683b921b7e29e534be",987:"80ad9ecfa1a14168688b"}[e]+".js"},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n={},r="@hg/hg-web-sdk:",c.l=function(e,t,o,f){if(n[e])n[e].push(t);else{var u,i;if(void 0!==o)for(var a=document.getElementsByTagName("script"),d=0;d<a.length;d++){var b=a[d];if(b.getAttribute("src")==e||b.getAttribute("data-webpack")==r+o){u=b;break}}u||(i=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,c.nc&&u.setAttribute("nonce",c.nc),u.setAttribute("data-webpack",r+o),u.src=e),n[e]=[t];var l=function(t,r){u.onerror=u.onload=null,clearTimeout(s);var o=n[e];if(delete n[e],u.parentNode&&u.parentNode.removeChild(u),o&&o.forEach((function(e){return e(r)})),t)return t(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=l.bind(null,u.onerror),u.onload=l.bind(null,u.onload),i&&document.head.appendChild(u)}},c.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.p="/assets/hg_web_sdk/lib/",function(){c.b=document.baseURI||self.location.href;var e={650:0};c.f.j=function(t,n){var r=c.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else{var o=new Promise((function(n,o){r=e[t]=[n,o]}));n.push(r[2]=o);var f=c.p+c.u(t),u=new Error;c.l(f,(function(n){if(c.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var o=n&&("load"===n.type?"missing":n.type),f=n&&n.target&&n.target.src;u.message="Loading chunk "+t+" failed.\n("+o+": "+f+")",u.name="ChunkLoadError",u.type=o,u.request=f,r[1](u)}}),"chunk-"+t,t)}};var t=function(t,n){var r,o,f=n[0],u=n[1],i=n[2],a=0;if(f.some((function(t){return 0!==e[t]}))){for(r in u)c.o(u,r)&&(c.m[r]=u[r]);if(i)i(c)}for(t&&t(n);a<f.length;a++)o=f[a],c.o(e,o)&&e[o]&&e[o][0](),e[o]=0},n=self.webpackChunk_hg_hg_web_sdk=self.webpackChunk_hg_hg_web_sdk||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))}(),c.nc=void 0,c.e(129).then(c.bind(c,9129))}();
|
||||
BIN
cmd/msrserve/static/assets/static/Bender.0d3c9c62.eot
Normal file
BIN
cmd/msrserve/static/assets/static/Bender.11795b1b.woff
Normal file
BIN
cmd/msrserve/static/assets/static/Bender.39b9791f.ttf
Normal file
1
cmd/msrserve/static/assets/static/Bender.b9131ac7.svg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
cmd/msrserve/static/assets/static/Geometos.73852980.eot
Normal file
BIN
cmd/msrserve/static/assets/static/Geometos.76f01edc.woff
Normal file
1
cmd/msrserve/static/assets/static/Geometos.b4b69057.svg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
cmd/msrserve/static/assets/static/Geometos.f643a91a.ttf
Normal file
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 64 KiB |
BIN
cmd/msrserve/static/assets/static/SourceHanSansCN-Bold.eot
Normal file
|
After Width: | Height: | Size: 840 KiB |
BIN
cmd/msrserve/static/assets/static/SourceHanSansCN-Bold.ttf
Normal file
BIN
cmd/msrserve/static/assets/static/SourceHanSansCN-Bold.woff
Normal file
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 131 KiB |
BIN
cmd/msrserve/static/assets/static/SourceHanSansCN-Regular.eot
Normal file
|
After Width: | Height: | Size: 756 KiB |
BIN
cmd/msrserve/static/assets/static/SourceHanSansCN-Regular.ttf
Normal file
BIN
cmd/msrserve/static/assets/static/SourceHanSansCN-Regular.woff
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
BIN
cmd/msrserve/static/assets/static/bg-info.3be98aea.png
Normal file
|
After Width: | Height: | Size: 262 KiB |
BIN
cmd/msrserve/static/assets/static/bg-music-play.b00c7d0a.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
cmd/msrserve/static/assets/static/bg_about.96d7c1c3.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
cmd/msrserve/static/assets/static/bg_album_detail.b6a577d2.png
Normal file
|
After Width: | Height: | Size: 98 B |
BIN
cmd/msrserve/static/assets/static/bg_contact_h5.c8635cb5.png
Normal file
|
After Width: | Height: | Size: 918 KiB |
BIN
cmd/msrserve/static/assets/static/bg_h5.9013681d.png
Normal file
|
After Width: | Height: | Size: 414 KiB |
BIN
cmd/msrserve/static/assets/static/bg_index.e734f0e3.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
cmd/msrserve/static/assets/static/bg_index_h5.1b3d3a14.png
Normal file
|
After Width: | Height: | Size: 995 KiB |
BIN
cmd/msrserve/static/assets/static/bg_inner.3d1e0e7c.png
Normal file
|
After Width: | Height: | Size: 708 KiB |
BIN
cmd/msrserve/static/assets/static/bg_loading.6e8e3229.png
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
cmd/msrserve/static/assets/static/bg_menu_h5.1c19747b.png
Normal file
|
After Width: | Height: | Size: 937 KiB |
BIN
cmd/msrserve/static/assets/static/bg_modal.d4749766.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
cmd/msrserve/static/assets/static/bg_pole.bfa19473.png
Normal file
|
After Width: | Height: | Size: 659 KiB |
BIN
cmd/msrserve/static/assets/static/copyright.a1f8ed16.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<path fill="none" stroke="#c6c9ce" stroke-width="2px" d="M 4 4 l 8 24 l 19 -2 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 36 36"><path fill="#c6c9ce" d="M33 18A15 15 0 1118 3a15 15 0 0115 15zm-15-5a5 5 0 105 5 5 5 0 00-5-5z"/><path fill="#c6c9ce" d="M13 34c-7-2-8-3-11-11a22 22 0 0011 11z" /></svg>
|
||||
|
After Width: | Height: | Size: 252 B |
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<path fill="#c6c9ce" stroke="#c6c9ce" stroke-width="2px" d="M 4 4 l 8 24 l 19 -2 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 178 B |
BIN
cmd/msrserve/static/assets/static/share_logo.b11ee809.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
cmd/msrserve/static/assets/static/vinyl-pointer.75720e5f.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
1
cmd/msrserve/static/assets/umi.62693412.css
Normal file
1
cmd/msrserve/static/assets/umi.87fedd26.js
Normal file
BIN
cmd/msrserve/static/favicon.ico
Normal file
|
After Width: | Height: | Size: 162 KiB |