feat: read, remove and clear listening history and switch recording over HTTP
This commit is contained in:
1 parent
112527db83
commit
17e486209e
14 files changed
+722
-18
No files matched your search
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/dihemoe/alisten/internal/app/admin"
|
||||
"github.com/dihemoe/alisten/internal/app/catalog"
|
||||
"github.com/dihemoe/alisten/internal/app/identity"
|
||||
"github.com/dihemoe/alisten/internal/app/listen"
|
||||
"github.com/dihemoe/alisten/internal/app/lobby"
|
||||
"github.com/dihemoe/alisten/internal/app/media"
|
||||
"github.com/dihemoe/alisten/internal/app/playlist"
|
||||
@@ -52,6 +53,7 @@ type Services struct {
|
||||
Media *media.Library
|
||||
Playback *media.Playback
|
||||
Site *site.Service
|
||||
Listens *listen.Service
|
||||
|
||||
// 浏览器访问对象存储时使用的地址前缀,结尾没有 /。
|
||||
StorageURL string
|
||||
@@ -158,6 +160,11 @@ func New(s Services, metrics prometheus.Registerer, tracer trace.TracerProvider,
|
||||
srv.handle(mux, protocol.RouteAdminCloseRegistration, srv.adminCloseRegistration)
|
||||
srv.handle(mux, protocol.RouteAdminGetAccount, srv.adminGetAccount)
|
||||
srv.handle(mux, protocol.RouteAdminResetAccountPassword, srv.adminResetAccountPassword)
|
||||
srv.handle(mux, protocol.RouteListMyListens, srv.listMyListens)
|
||||
srv.handle(mux, protocol.RouteRemoveMyListen, srv.removeMyListen)
|
||||
srv.handle(mux, protocol.RouteClearMyListens, srv.clearMyListens)
|
||||
srv.handle(mux, protocol.RouteEnableListenRecording, srv.enableListenRecording)
|
||||
srv.handle(mux, protocol.RouteDisableListenRecording, srv.disableListenRecording)
|
||||
mux.HandleFunc("GET /ws", srv.websocket)
|
||||
mux.HandleFunc("GET /api/room/events", srv.roomEvents)
|
||||
mux.HandleFunc("GET /media/{source}/{id}", srv.redirectMedia)
|
||||
|
||||
@@ -42,6 +42,9 @@ func (s *server) getMe(ctx context.Context, c call) (any, error) {
|
||||
dto := accountDTO(account)
|
||||
resp.Account = &dto
|
||||
}
|
||||
if resp.RecordListens, err = s.Listens.Recording(ctx, c.member.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/dihemoe/alisten/protocol"
|
||||
)
|
||||
|
||||
func (s *server) listMyListens(ctx context.Context, c call) (any, error) {
|
||||
listens, err := s.Listens.List(ctx, c.member.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]protocol.MyListen, len(listens))
|
||||
for i, l := range listens {
|
||||
items[i] = protocol.MyListen{Track: trackDTO(l.Track), Meta: s.trackMeta(l.Meta), HeardAtMs: l.HeardAt.UnixMilli()}
|
||||
}
|
||||
return protocol.ListMyListensResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (s *server) removeMyListen(ctx context.Context, c call) (any, error) {
|
||||
req, err := protocol.DecodeRemoveMyListenRequest(c.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.Listens.Remove(ctx, c.member.ID, trackOf(req.Track))
|
||||
}
|
||||
|
||||
func (s *server) clearMyListens(ctx context.Context, c call) (any, error) {
|
||||
return nil, s.Listens.Clear(ctx, c.member.ID)
|
||||
}
|
||||
|
||||
func (s *server) enableListenRecording(ctx context.Context, c call) (any, error) {
|
||||
if _, err := protocol.DecodeEnableListenRecordingRequest(c.body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.Listens.SetRecording(ctx, c.member.ID, true)
|
||||
}
|
||||
|
||||
func (s *server) disableListenRecording(ctx context.Context, c call) (any, error) {
|
||||
return nil, s.Listens.SetRecording(ctx, c.member.ID, false)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package listen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/dihemoe/alisten/internal/app/port"
|
||||
"github.com/dihemoe/alisten/internal/domain/track"
|
||||
)
|
||||
|
||||
// 游客和已注册成员都有播放历史,不像歌单要求注册。
|
||||
type Service struct {
|
||||
listens port.Listens
|
||||
}
|
||||
|
||||
func New(listens port.Listens) *Service {
|
||||
return &Service{listens: listens}
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, memberID string) ([]port.Listen, error) {
|
||||
return s.listens.MemberListens(ctx, memberID)
|
||||
}
|
||||
|
||||
// 链接曲目的 t.ID 必须已经按 track.LinkID 规范化。
|
||||
func (s *Service) Remove(ctx context.Context, memberID string, t track.Track) error {
|
||||
return s.listens.DeleteListen(ctx, memberID, t)
|
||||
}
|
||||
|
||||
func (s *Service) Clear(ctx context.Context, memberID string) error {
|
||||
return s.listens.ClearListens(ctx, memberID)
|
||||
}
|
||||
|
||||
func (s *Service) Recording(ctx context.Context, memberID string) (bool, error) {
|
||||
return s.listens.ListenRecording(ctx, memberID)
|
||||
}
|
||||
|
||||
func (s *Service) SetRecording(ctx context.Context, memberID string, on bool) error {
|
||||
return s.listens.SetListenRecording(ctx, memberID, on)
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/dihemoe/alisten/internal/app/admin"
|
||||
"github.com/dihemoe/alisten/internal/app/catalog"
|
||||
"github.com/dihemoe/alisten/internal/app/identity"
|
||||
"github.com/dihemoe/alisten/internal/app/listen"
|
||||
"github.com/dihemoe/alisten/internal/app/lobby"
|
||||
"github.com/dihemoe/alisten/internal/app/media"
|
||||
"github.com/dihemoe/alisten/internal/app/playlist"
|
||||
@@ -90,6 +91,7 @@ func newPublic(cfg serveConfig, pool *pgxpool.Pool, rdb *goredis.Client, instanc
|
||||
Media: media.NewLibrary(store, store, objects),
|
||||
Playback: media.NewPlayback(src.playback, store),
|
||||
Site: site.New(store),
|
||||
Listens: listen.New(store),
|
||||
|
||||
StorageURL: cfg.storageURL,
|
||||
}
|
||||
|
||||
+6
-6
@@ -22,7 +22,7 @@ GET /api/me
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "account": {"username": "alice"}}
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "account": {"username": "alice"}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
PATCH /api/me
|
||||
@@ -38,7 +38,7 @@ GET /api/me
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}, "account": {"username": "alice", "email": "Alice@Example.COM"}}
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}, "account": {"username": "alice", "email": "Alice@Example.COM"}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
POST /api/account
|
||||
@@ -62,7 +62,7 @@ GET /api/me
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""}}
|
||||
{"member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
PATCH /api/me
|
||||
@@ -78,7 +78,7 @@ GET /api/me
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$bobId", "name": "$bobName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}}
|
||||
{"member": {"id": "$bobId", "name": "$bobName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
PATCH /api/me
|
||||
@@ -139,7 +139,7 @@ GET /api/me
|
||||
Authorization: Bearer $alice2
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}, "account": {"username": "alice", "email": "Alice@Example.COM"}}
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": "c160f8cc69a4f0bf2b0362752353d060"}, "account": {"username": "alice", "email": "Alice@Example.COM"}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
PATCH /api/me
|
||||
@@ -155,7 +155,7 @@ GET /api/me
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "account": {"username": "alice"}}
|
||||
{"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "account": {"username": "alice"}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
GET /api/me
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ GET /api/me
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "$guestName", "avatarHash": ""}}
|
||||
{"member": {"id": "$aliceId", "name": "$guestName", "avatarHash": ""}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
PATCH /api/me
|
||||
@@ -73,11 +73,11 @@ GET /api/me
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$aliceId", "name": "甲", "avatarHash": ""}}
|
||||
{"member": {"id": "$aliceId", "name": "甲", "avatarHash": ""}, "recordListens": true}
|
||||
|
||||
-- request --
|
||||
GET /api/me
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$bobId", "name": "乙", "avatarHash": ""}}
|
||||
{"member": {"id": "$bobId", "name": "乙", "avatarHash": ""}, "recordListens": true}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
播放历史:开播时记给房间里的成员,开播之后进来的成员也记上正在播放的歌,关闭了记录的成员不记,已有的历史保留;
|
||||
可以删除一首、清空;游客登录账号时,游客的历史并入账号。读取历史要有成员 token。
|
||||
|
||||
-- request --
|
||||
POST /api/members
|
||||
|
||||
{}
|
||||
-- response --
|
||||
201
|
||||
{"memberToken": "$alice", "member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}}
|
||||
|
||||
-- request --
|
||||
POST /api/account
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"username": "alice", "password": "correct horse"}
|
||||
-- response --
|
||||
201
|
||||
{"account": {"username": "alice"}}
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": []}
|
||||
|
||||
-- request --
|
||||
POST /api/members
|
||||
|
||||
{}
|
||||
-- response --
|
||||
201
|
||||
{"memberToken": "$bob", "member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""}}
|
||||
|
||||
-- request --
|
||||
DELETE /api/me/listen-recording
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
GET /api/me
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""}, "recordListens": false}
|
||||
|
||||
-- request --
|
||||
POST /api/admin/rooms
|
||||
Authorization: Bearer admin-secret
|
||||
|
||||
{"name": "大厅", "description": ""}
|
||||
-- response --
|
||||
201
|
||||
Location: /api/rooms/$room
|
||||
{"id": "$room"}
|
||||
|
||||
-- request --
|
||||
POST /api/rooms/$room/tickets
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{}
|
||||
-- response --
|
||||
201
|
||||
{"ticket": "$ticketA"}
|
||||
|
||||
-- connect alice --
|
||||
-- send alice --
|
||||
{"kind": "request", "type": "hello", "id": "h1", "data": {"roomId": "$room", "ticket": "$ticketA", "protocolVersion": "$protocolVersion"}}
|
||||
-- receive alice --
|
||||
{"kind": "response", "type": "hello", "id": "h1", "data": {
|
||||
"member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""},
|
||||
"serverTimeMs": 1,
|
||||
"room": {"term": 1, "seq": 1, "room": {"id": "$room", "name": "大厅", "description": "", "kind": "persistent"},
|
||||
"playMode": "sequential", "nowPlaying": null, "pending": null, "items": [],
|
||||
"members": {"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}], "onlineCount": 1}}}}
|
||||
|
||||
-- send alice --
|
||||
{"kind": "request", "type": "queue.add", "id": "a1", "data": {"track": {"source": "netease", "id": "186016"}}}
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "room.activity", "term": 1, "seq": 2, "data": {"kind": "added", "member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""},
|
||||
"entry": {"id": "1", "track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 0, "status": "preparing"},
|
||||
"message": "点了《晴天》"}}
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "queue.updated", "term": 1, "seq": 3, "data": {"items": [],
|
||||
"pending": {"id": "1", "track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 0, "status": "preparing"}}}
|
||||
-- receive alice --
|
||||
{"kind": "response", "type": "queue.add", "id": "a1", "data": {"entry": {"id": "1", "track": {"source": "netease", "id": "186016"},
|
||||
"meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269000},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 0, "status": "preparing"}}}
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "queue.updated", "term": 1, "seq": 4, "data": {"pending": null, "items": []}}
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "playback.updated", "term": 1, "seq": 5, "data": {"nowPlaying": {
|
||||
"entry": {"id": "1", "track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269500},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 0, "status": "ready"},
|
||||
"mediaUrl": "/media/netease/186016", "startedAtMs": 1, "durationMs": 269500, "gain": 1, "lyric": "[00:01.00]故事的小黄花"}}}
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269500}, "heardAtMs": 1}]}
|
||||
|
||||
-- request --
|
||||
POST /api/rooms/$room/tickets
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{}
|
||||
-- response --
|
||||
201
|
||||
{"ticket": "$ticketB"}
|
||||
|
||||
-- connect bob --
|
||||
-- send bob --
|
||||
{"kind": "request", "type": "hello", "id": "h1", "data": {"roomId": "$room", "ticket": "$ticketB", "protocolVersion": "$protocolVersion"}}
|
||||
-- receive bob --
|
||||
{"kind": "response", "type": "hello", "id": "h1", "data": {
|
||||
"member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""},
|
||||
"serverTimeMs": 1,
|
||||
"room": {"term": 1, "seq": 6, "room": {"id": "$room", "name": "大厅", "description": "", "kind": "persistent"},
|
||||
"playMode": "sequential", "pending": null, "items": [],
|
||||
"nowPlaying": {
|
||||
"entry": {"id": "1", "track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269500},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 0, "status": "ready"},
|
||||
"mediaUrl": "/media/netease/186016", "startedAtMs": 1, "durationMs": 269500, "gain": 1, "lyric": "[00:01.00]故事的小黄花"},
|
||||
"members": {"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, {"id": "$bobId", "name": "$bobName", "avatarHash": ""}], "onlineCount": 2}}}}
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "members.updated", "term": 1, "seq": 6, "data": {
|
||||
"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, {"id": "$bobId", "name": "$bobName", "avatarHash": ""}], "onlineCount": 2}}
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"items": []}
|
||||
|
||||
-- request --
|
||||
PUT /api/me/listen-recording
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{}
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
GET /api/me
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""}, "recordListens": true}
|
||||
|
||||
-- disconnect bob --
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "members.updated", "term": 1, "seq": 7, "data": {
|
||||
"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}], "onlineCount": 1}}
|
||||
|
||||
-- connect again --
|
||||
-- send again --
|
||||
{"kind": "request", "type": "hello", "id": "h1", "data": {"roomId": "$room", "ticket": "$ticketB", "protocolVersion": "$protocolVersion"}}
|
||||
-- receive again --
|
||||
{"kind": "response", "type": "hello", "id": "h1", "data": {
|
||||
"member": {"id": "$bobId", "name": "$bobName", "avatarHash": ""},
|
||||
"serverTimeMs": 1,
|
||||
"room": {"term": 1, "seq": 8, "room": {"id": "$room", "name": "大厅", "description": "", "kind": "persistent"},
|
||||
"playMode": "sequential", "pending": null, "items": [],
|
||||
"nowPlaying": {
|
||||
"entry": {"id": "1", "track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269500},
|
||||
"by": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "auto": false, "likes": 0, "status": "ready"},
|
||||
"mediaUrl": "/media/netease/186016", "startedAtMs": 1, "durationMs": 269500, "gain": 1, "lyric": "[00:01.00]故事的小黄花"},
|
||||
"members": {"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, {"id": "$bobId", "name": "$bobName", "avatarHash": ""}], "onlineCount": 2}}}}
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "members.updated", "term": 1, "seq": 8, "data": {
|
||||
"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, {"id": "$bobId", "name": "$bobName", "avatarHash": ""}], "onlineCount": 2}}
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $bob
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269500}, "heardAtMs": 1}]}
|
||||
|
||||
-- request --
|
||||
POST /api/me/listens/removals
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"track": {"source": "netease", "id": "186016"}}
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
POST /api/me/listens/removals
|
||||
Authorization: Bearer $alice
|
||||
|
||||
{"track": {"source": "qq", "id": "0039MnYb0qxYhV"}}
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": []}
|
||||
|
||||
-- request --
|
||||
POST /api/sessions
|
||||
Authorization: Bearer $bob
|
||||
|
||||
{"username": "alice", "password": "correct horse"}
|
||||
-- response --
|
||||
201
|
||||
{"memberToken": "$alice2", "member": {"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}, "account": {"username": "alice"}, "device": "$bobDevice"}
|
||||
-- closed again --
|
||||
4401
|
||||
-- receive alice --
|
||||
{"kind": "event", "type": "members.updated", "term": 1, "seq": 9, "data": {
|
||||
"sessions": [{"id": "$aliceId", "name": "$aliceName", "avatarHash": ""}], "onlineCount": 1}}
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $alice2
|
||||
-- response --
|
||||
200
|
||||
{"items": [{"track": {"source": "netease", "id": "186016"}, "meta": {"title": "晴天", "artist": "周杰伦", "album": "叶惠美", "coverUrl": "https://p1.music.126.net/example.jpg", "pageUrl": "https://music.163.com/#/song?id=186016", "durationMs": 269500}, "heardAtMs": 1}]}
|
||||
|
||||
-- request --
|
||||
DELETE /api/me/listens
|
||||
Authorization: Bearer $alice2
|
||||
-- response --
|
||||
204
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
Authorization: Bearer $alice
|
||||
-- response --
|
||||
200
|
||||
{"items": []}
|
||||
|
||||
-- request --
|
||||
GET /api/me/listens
|
||||
-- response --
|
||||
401
|
||||
{"error": {"code": "UNAUTHORIZED", "message": "成员 token 无效"}}
|
||||
@@ -187,6 +187,14 @@ func DecodeAdminResetAccountPasswordRequest(body []byte) (AdminResetAccountPassw
|
||||
return decodeAdminResetAccountPasswordRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeRemoveMyListenRequest(body []byte) (RemoveMyListenRequest, error) {
|
||||
return decodeRemoveMyListenRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeEnableListenRecordingRequest(body []byte) (EnableListenRecordingRequest, error) {
|
||||
return decodeEnableListenRecordingRequest(body, "")
|
||||
}
|
||||
|
||||
func decodeAddMyPlaylistItemRequest(raw json.RawMessage, path string) (AddMyPlaylistItemRequest, error) {
|
||||
var v AddMyPlaylistItemRequest
|
||||
m, err := decodeObject(raw, path, "track")
|
||||
@@ -570,6 +578,12 @@ func decodeCreateTicketRequest(raw json.RawMessage, path string) (CreateTicketRe
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeEnableListenRecordingRequest(raw json.RawMessage, path string) (EnableListenRecordingRequest, error) {
|
||||
var v EnableListenRecordingRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func decodeHelloRequest(raw json.RawMessage, path string) (HelloRequest, error) {
|
||||
var v HelloRequest
|
||||
m, err := decodeObject(raw, path, "protocolVersion", "roomId", "ticket")
|
||||
@@ -967,6 +981,21 @@ func decodeRegisterRequest(raw json.RawMessage, path string) (RegisterRequest, e
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeRemoveMyListenRequest(raw json.RawMessage, path string) (RemoveMyListenRequest, error) {
|
||||
var v RemoveMyListenRequest
|
||||
m, err := decodeObject(raw, path, "track")
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = requireFields(m, path, "track"); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.Track, err = decodeTrack(m["track"], field(path, "track")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeRemoveMyPlaylistItemsRequest(raw json.RawMessage, path string) (RemoveMyPlaylistItemsRequest, error) {
|
||||
var v RemoveMyPlaylistItemsRequest
|
||||
m, err := decodeObject(raw, path, "tracks")
|
||||
|
||||
+73
-2
@@ -352,6 +352,16 @@
|
||||
"message": {"type": "string", "description": "平台给出的说明,可能为空"}
|
||||
},
|
||||
"required": ["status", "message"]
|
||||
},
|
||||
"MyListen": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"track": {"$ref": "#/$defs/Track"},
|
||||
"meta": {"$ref": "#/$defs/TrackMeta", "description": "听到时的元数据"},
|
||||
"heardAtMs": {"type": "integer", "description": "最近一次听到这首歌时它的开播时间"}
|
||||
},
|
||||
"required": ["track", "meta", "heardAtMs"]
|
||||
}
|
||||
},
|
||||
"x-ws": {
|
||||
@@ -741,9 +751,10 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"member": {"$ref": "#/$defs/Member"},
|
||||
"account": {"$ref": "#/$defs/Account"}
|
||||
"account": {"$ref": "#/$defs/Account"},
|
||||
"recordListens": {"type": "boolean", "description": "是否把在房间里听到的歌记进播放历史"}
|
||||
},
|
||||
"required": ["member"]
|
||||
"required": ["member", "recordListens"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1553,6 +1564,66 @@
|
||||
},
|
||||
"required": ["password"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "listMyListens",
|
||||
"description": "我的播放历史:在房间里听到过的歌,每首只留最近一次,最近听到的在前,最多 500 首;同一账号的所有设备看到同一份",
|
||||
"method": "GET",
|
||||
"path": "/api/me/listens",
|
||||
"auth": "member",
|
||||
"response": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"items": {"type": "array", "items": {"$ref": "#/$defs/MyListen"}}
|
||||
},
|
||||
"required": ["items"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "removeMyListen",
|
||||
"description": "从播放历史中删除一首歌;不在历史里的忽略",
|
||||
"method": "POST",
|
||||
"path": "/api/me/listens/removals",
|
||||
"auth": "member",
|
||||
"status": 204,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"track": {"$ref": "#/$defs/Track"}
|
||||
},
|
||||
"required": ["track"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clearMyListens",
|
||||
"description": "清空播放历史",
|
||||
"method": "DELETE",
|
||||
"path": "/api/me/listens",
|
||||
"auth": "member",
|
||||
"status": 204
|
||||
},
|
||||
{
|
||||
"name": "enableListenRecording",
|
||||
"description": "开始把在房间里听到的歌记进播放历史",
|
||||
"method": "PUT",
|
||||
"path": "/api/me/listen-recording",
|
||||
"auth": "member",
|
||||
"status": 204,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "disableListenRecording",
|
||||
"description": "不再记录播放历史,已有的历史保留",
|
||||
"method": "DELETE",
|
||||
"path": "/api/me/listen-recording",
|
||||
"auth": "member",
|
||||
"status": 204
|
||||
}
|
||||
]
|
||||
}
|
||||
+213
-2
@@ -570,6 +570,10 @@
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
},
|
||||
"EnableListenRecordingRequest": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
},
|
||||
"Entry": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -690,10 +694,15 @@
|
||||
},
|
||||
"member": {
|
||||
"$ref": "#/components/schemas/Member"
|
||||
},
|
||||
"recordListens": {
|
||||
"description": "是否把在房间里听到的歌记进播放历史",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"member"
|
||||
"member",
|
||||
"recordListens"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -829,6 +838,21 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ListMyListensResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"items": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MyListen"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"items"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ListMyPlaylistItemsResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -1014,6 +1038,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MyListen": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"heardAtMs": {
|
||||
"description": "最近一次听到这首歌时它的开播时间",
|
||||
"type": "integer"
|
||||
},
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/TrackMeta",
|
||||
"description": "听到时的元数据"
|
||||
},
|
||||
"track": {
|
||||
"$ref": "#/components/schemas/Track"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"track",
|
||||
"meta",
|
||||
"heardAtMs"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MyPlaylist": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -1386,6 +1432,18 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RemoveMyListenRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"track": {
|
||||
"$ref": "#/components/schemas/Track"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"track"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RemoveMyPlaylistItemsRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -1898,7 +1956,7 @@
|
||||
},
|
||||
"info": {
|
||||
"title": "Alisten",
|
||||
"version": "ed36759d403f9894"
|
||||
"version": "ae9a632d648f3840"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
@@ -3035,6 +3093,159 @@
|
||||
"summary": "修改昵称和头像;email 用来计算头像哈希,已注册的账号同时保存它,去掉首尾空白后应形如 name@example.com,空字符串表示清除"
|
||||
}
|
||||
},
|
||||
"/api/me/listen-recording": {
|
||||
"delete": {
|
||||
"operationId": "disableListenRecording",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "不再记录播放历史,已有的历史保留"
|
||||
},
|
||||
"put": {
|
||||
"operationId": "enableListenRecording",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EnableListenRecordingRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "开始把在房间里听到的歌记进播放历史"
|
||||
}
|
||||
},
|
||||
"/api/me/listens": {
|
||||
"delete": {
|
||||
"operationId": "clearMyListens",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "清空播放历史"
|
||||
},
|
||||
"get": {
|
||||
"operationId": "listMyListens",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ListMyListensResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "我的播放历史:在房间里听到过的歌,每首只留最近一次,最近听到的在前,最多 500 首;同一账号的所有设备看到同一份"
|
||||
}
|
||||
},
|
||||
"/api/me/listens/removals": {
|
||||
"post": {
|
||||
"operationId": "removeMyListen",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RemoveMyListenRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "从播放历史中删除一首歌;不在历史里的忽略"
|
||||
}
|
||||
},
|
||||
"/api/me/playlist-links": {
|
||||
"post": {
|
||||
"operationId": "resolveMyPlaylistLink",
|
||||
|
||||
+33
-3
@@ -4,7 +4,7 @@ package protocol
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const Version = "ed36759d403f9894"
|
||||
const Version = "ae9a632d648f3840"
|
||||
|
||||
type APIKey struct {
|
||||
Key string `json:"key"`
|
||||
@@ -212,6 +212,8 @@ type CreateTicketResponse struct {
|
||||
|
||||
type Empty struct{}
|
||||
|
||||
type EnableListenRecordingRequest struct{}
|
||||
|
||||
type Entry struct {
|
||||
Auto bool `json:"auto"`
|
||||
By *Member `json:"by"`
|
||||
@@ -253,8 +255,9 @@ type ErrorInfo struct {
|
||||
}
|
||||
|
||||
type GetMeResponse struct {
|
||||
Account *Account `json:"account,omitempty"`
|
||||
Member Member `json:"member"`
|
||||
Account *Account `json:"account,omitempty"`
|
||||
Member Member `json:"member"`
|
||||
RecordListens bool `json:"recordListens"`
|
||||
}
|
||||
|
||||
type GetSiteResponse struct {
|
||||
@@ -313,6 +316,18 @@ type ImportedTrack struct {
|
||||
Track Track `json:"track"`
|
||||
}
|
||||
|
||||
type ListMyListensResponse struct {
|
||||
Items []MyListen `json:"items"`
|
||||
}
|
||||
|
||||
func (v ListMyListensResponse) MarshalJSON() ([]byte, error) {
|
||||
type plain ListMyListensResponse
|
||||
if v.Items == nil {
|
||||
v.Items = []MyListen{}
|
||||
}
|
||||
return json.Marshal(plain(v))
|
||||
}
|
||||
|
||||
type ListMyPlaylistItemsResponse struct {
|
||||
Items []MyPlaylistItem `json:"items"`
|
||||
}
|
||||
@@ -410,6 +425,12 @@ func (v MoveMyPlaylistItemsRequest) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(plain(v))
|
||||
}
|
||||
|
||||
type MyListen struct {
|
||||
HeardAtMs int64 `json:"heardAtMs"`
|
||||
Meta TrackMeta `json:"meta"`
|
||||
Track Track `json:"track"`
|
||||
}
|
||||
|
||||
type MyPlaylist struct {
|
||||
CoverURL *string `json:"coverUrl"`
|
||||
CreatedAtMs int64 `json:"createdAtMs"`
|
||||
@@ -581,6 +602,10 @@ type RegisterResponse struct {
|
||||
Account Account `json:"account"`
|
||||
}
|
||||
|
||||
type RemoveMyListenRequest struct {
|
||||
Track Track `json:"track"`
|
||||
}
|
||||
|
||||
type RemoveMyPlaylistItemsRequest struct {
|
||||
Tracks []Track `json:"tracks"`
|
||||
}
|
||||
@@ -865,6 +890,11 @@ var (
|
||||
RouteAdminCloseRegistration = Route{Pattern: "DELETE /api/admin/registration", Auth: AuthAdmin, Status: 204}
|
||||
RouteAdminGetAccount = Route{Pattern: "GET /api/admin/accounts/{username}", Auth: AuthAdmin, Status: 200}
|
||||
RouteAdminResetAccountPassword = Route{Pattern: "POST /api/admin/accounts/{username}/password", Auth: AuthAdmin, Status: 200}
|
||||
RouteListMyListens = Route{Pattern: "GET /api/me/listens", Auth: AuthMember, Status: 200}
|
||||
RouteRemoveMyListen = Route{Pattern: "POST /api/me/listens/removals", Auth: AuthMember, Status: 204}
|
||||
RouteClearMyListens = Route{Pattern: "DELETE /api/me/listens", Auth: AuthMember, Status: 204}
|
||||
RouteEnableListenRecording = Route{Pattern: "PUT /api/me/listen-recording", Auth: AuthMember, Status: 204}
|
||||
RouteDisableListenRecording = Route{Pattern: "DELETE /api/me/listen-recording", Auth: AuthMember, Status: 204}
|
||||
)
|
||||
|
||||
var RoomRoutes = []RoomRoute{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protocolgen from protocol/idl.json; DO NOT EDIT.
|
||||
|
||||
import type { APIKey, Account, AdminAccount, ErrorInfo, ImportedTrack, Member, MyPlaylist, MyPlaylistItem, QQLoginType, QRLogin, QRLoginResult, RoomListing, Source, SourceAccount, StoredMedia, Track } from '../realtime/protocol.gen'
|
||||
import type { APIKey, Account, AdminAccount, ErrorInfo, ImportedTrack, Member, MyListen, MyPlaylist, MyPlaylistItem, QQLoginType, QRLogin, QRLoginResult, RoomListing, Source, SourceAccount, StoredMedia, Track } from '../realtime/protocol.gen'
|
||||
|
||||
export interface AddMyPlaylistItemRequest {
|
||||
track: Track
|
||||
@@ -147,9 +147,12 @@ export interface CreateTicketResponse {
|
||||
ticket: string
|
||||
}
|
||||
|
||||
export type EnableListenRecordingRequest = Record<string, never>
|
||||
|
||||
export interface GetMeResponse {
|
||||
account?: Account
|
||||
member: Member
|
||||
recordListens: boolean
|
||||
}
|
||||
|
||||
export interface GetSiteResponse {
|
||||
@@ -178,6 +181,10 @@ export interface ImportMyPlaylistResponse {
|
||||
playlist: MyPlaylist
|
||||
}
|
||||
|
||||
export interface ListMyListensResponse {
|
||||
items: MyListen[]
|
||||
}
|
||||
|
||||
export interface ListMyPlaylistItemsResponse {
|
||||
items: MyPlaylistItem[]
|
||||
}
|
||||
@@ -225,6 +232,10 @@ export interface RegisterResponse {
|
||||
account: Account
|
||||
}
|
||||
|
||||
export interface RemoveMyListenRequest {
|
||||
track: Track
|
||||
}
|
||||
|
||||
export interface RemoveMyPlaylistItemsRequest {
|
||||
tracks: Track[]
|
||||
}
|
||||
@@ -318,5 +329,10 @@ export function httpClient(send: HttpSend) {
|
||||
adminCloseRegistration: () => send<void>({ method: 'DELETE', path: '/api/admin/registration', auth: 'admin' }),
|
||||
adminGetAccount: (username: string) => send<AdminAccount>({ method: 'GET', path: `/api/admin/accounts/${encodeURIComponent(username)}`, auth: 'admin' }),
|
||||
adminResetAccountPassword: (username: string) => send<AdminResetAccountPasswordResponse>({ method: 'POST', path: `/api/admin/accounts/${encodeURIComponent(username)}/password`, auth: 'admin', body: {} }),
|
||||
listMyListens: () => send<ListMyListensResponse>({ method: 'GET', path: '/api/me/listens', auth: 'member' }),
|
||||
removeMyListen: (body: RemoveMyListenRequest) => send<void>({ method: 'POST', path: '/api/me/listens/removals', auth: 'member', body }),
|
||||
clearMyListens: () => send<void>({ method: 'DELETE', path: '/api/me/listens', auth: 'member' }),
|
||||
enableListenRecording: () => send<void>({ method: 'PUT', path: '/api/me/listen-recording', auth: 'member', body: {} }),
|
||||
disableListenRecording: () => send<void>({ method: 'DELETE', path: '/api/me/listen-recording', auth: 'member' }),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protocolgen from protocol/idl.json; DO NOT EDIT.
|
||||
|
||||
export const PROTOCOL_VERSION = 'ed36759d403f9894'
|
||||
export const PROTOCOL_VERSION = 'ae9a632d648f3840'
|
||||
|
||||
export interface APIKey {
|
||||
key: string
|
||||
@@ -82,6 +82,12 @@ export interface Members {
|
||||
sessions: Member[]
|
||||
}
|
||||
|
||||
export interface MyListen {
|
||||
heardAtMs: number
|
||||
meta: TrackMeta
|
||||
track: Track
|
||||
}
|
||||
|
||||
export interface MyPlaylist {
|
||||
coverUrl: string | null
|
||||
createdAtMs: number
|
||||
|
||||
Reference in new issue
Block a user