feat: expose playlist sharing over HTTP
This commit is contained in:
1 parent
805db62264
commit
6a5d8b4ebd
13 files changed
+743
-19
No files matched your search
@@ -195,6 +195,14 @@ func DecodeEnableListenRecordingRequest(body []byte) (EnableListenRecordingReque
|
||||
return decodeEnableListenRecordingRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeShareMyPlaylistRequest(body []byte) (ShareMyPlaylistRequest, error) {
|
||||
return decodeShareMyPlaylistRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeCopySharedPlaylistRequest(body []byte) (CopySharedPlaylistRequest, error) {
|
||||
return decodeCopySharedPlaylistRequest(body, "")
|
||||
}
|
||||
|
||||
func decodeAddMyPlaylistItemRequest(raw json.RawMessage, path string) (AddMyPlaylistItemRequest, error) {
|
||||
var v AddMyPlaylistItemRequest
|
||||
m, err := decodeObject(raw, path, "track")
|
||||
@@ -504,6 +512,24 @@ func decodeCopyMyPlaylistItemsRequest(raw json.RawMessage, path string) (CopyMyP
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeCopySharedPlaylistRequest(raw json.RawMessage, path string) (CopySharedPlaylistRequest, error) {
|
||||
var v CopySharedPlaylistRequest
|
||||
m, err := decodeObject(raw, path, "name")
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = requireFields(m, path, "name"); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.Name, err = decodeString(m["name"], field(path, "name")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMaxLength(v.Name, field(path, "name"), 200); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeCreateMemberRequest(raw json.RawMessage, path string) (CreateMemberRequest, error) {
|
||||
var v CreateMemberRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
@@ -1152,6 +1178,12 @@ func decodeSettingsSetPlayModeRequest(raw json.RawMessage, path string) (Setting
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeShareMyPlaylistRequest(raw json.RawMessage, path string) (ShareMyPlaylistRequest, error) {
|
||||
var v ShareMyPlaylistRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func decodeSource(raw json.RawMessage, path string) (Source, error) {
|
||||
s, err := decodeString(raw, path)
|
||||
if err != nil {
|
||||
|
||||
+64
-2
@@ -186,9 +186,10 @@
|
||||
"trackCount": {"type": "integer"},
|
||||
"createdAtMs": {"type": "integer"},
|
||||
"updatedAtMs": {"type": "integer"},
|
||||
"coverUrl": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "歌单里排在最前、有封面的那首歌的封面;没有时为 null"}
|
||||
"coverUrl": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "歌单里排在最前、有封面的那首歌的封面;没有时为 null"},
|
||||
"shareId": {"anyOf": [{"type": "string"}, {"type": "null"}], "description": "分享链接 /s/<shareId> 中的 ID;没有分享时为 null"}
|
||||
},
|
||||
"required": ["id", "name", "favorites", "trackCount", "createdAtMs", "updatedAtMs", "coverUrl"]
|
||||
"required": ["id", "name", "favorites", "trackCount", "createdAtMs", "updatedAtMs", "coverUrl", "shareId"]
|
||||
},
|
||||
"MyPlaylistItem": {
|
||||
"type": "object",
|
||||
@@ -362,6 +363,18 @@
|
||||
"heardAtMs": {"type": "integer", "description": "最近一次听到这首歌时它的开播时间"}
|
||||
},
|
||||
"required": ["track", "meta", "heardAtMs"]
|
||||
},
|
||||
"SharedPlaylist": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"favorites": {"type": "boolean", "description": "是否是“我喜欢”"},
|
||||
"ownerName": {"type": "string", "description": "所有者的昵称"},
|
||||
"ownerAvatarHash": {"type": "string", "description": "所有者邮箱的 MD5,未设置邮箱时为空字符串"},
|
||||
"items": {"type": "array", "items": {"$ref": "#/$defs/MyPlaylistItem"}, "description": "新加入的在前"}
|
||||
},
|
||||
"required": ["name", "favorites", "ownerName", "ownerAvatarHash", "items"]
|
||||
}
|
||||
},
|
||||
"x-ws": {
|
||||
@@ -1624,6 +1637,55 @@
|
||||
"path": "/api/me/listen-recording",
|
||||
"auth": "member",
|
||||
"status": 204
|
||||
},
|
||||
{
|
||||
"name": "shareMyPlaylist",
|
||||
"description": "开启分享或重新生成分享链接:每次都生成新的 shareId,之前的链接随即失效;“我喜欢”也能分享",
|
||||
"method": "POST",
|
||||
"path": "/api/me/playlists/{id}/share",
|
||||
"auth": "member",
|
||||
"request": {"type": "object", "additionalProperties": false, "properties": {}},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"shareId": {"type": "string"}
|
||||
},
|
||||
"required": ["shareId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "unshareMyPlaylist",
|
||||
"description": "停止分享:分享链接随即失效;没有分享时也算成功",
|
||||
"method": "DELETE",
|
||||
"path": "/api/me/playlists/{id}/share",
|
||||
"auth": "member",
|
||||
"status": 204
|
||||
},
|
||||
{
|
||||
"name": "getSharedPlaylist",
|
||||
"description": "读取分享的歌单,不需要身份;不返回所有者的成员 ID 和用户名;停止分享、重新生成链接或删除歌单之后返回 NOT_FOUND",
|
||||
"method": "GET",
|
||||
"path": "/api/shared-playlists/{shareId}",
|
||||
"auth": "none",
|
||||
"response": {"$ref": "#/$defs/SharedPlaylist"}
|
||||
},
|
||||
{
|
||||
"name": "copySharedPlaylist",
|
||||
"description": "把分享的歌单复制成自己的新歌单,带上全部曲目和顺序;新歌单受“最多 50 个”的限制;分享已失效时返回 NOT_FOUND",
|
||||
"method": "POST",
|
||||
"path": "/api/shared-playlists/{shareId}/copies",
|
||||
"auth": "member",
|
||||
"status": 201,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {"$ref": "#/$defs/PlaylistName"}
|
||||
},
|
||||
"required": ["name"]
|
||||
},
|
||||
"response": {"$ref": "#/$defs/MyPlaylist"}
|
||||
}
|
||||
]
|
||||
}
|
||||
+258
-2
@@ -475,6 +475,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CopySharedPlaylistRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "歌单名:去掉首尾空白后 1 到 50 个字符",
|
||||
"maxLength": 200,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CreateMemberRequest": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
@@ -1087,6 +1101,17 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"shareId": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "分享链接 /s/<shareId> 中的 ID;没有分享时为 null"
|
||||
},
|
||||
"trackCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -1101,7 +1126,8 @@
|
||||
"trackCount",
|
||||
"createdAtMs",
|
||||
"updatedAtMs",
|
||||
"coverUrl"
|
||||
"coverUrl",
|
||||
"shareId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -1700,6 +1726,57 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ShareMyPlaylistRequest": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
},
|
||||
"ShareMyPlaylistResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"shareId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"shareId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SharedPlaylist": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"favorites": {
|
||||
"description": "是否是“我喜欢”",
|
||||
"type": "boolean"
|
||||
},
|
||||
"items": {
|
||||
"description": "新加入的在前",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MyPlaylistItem"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownerAvatarHash": {
|
||||
"description": "所有者邮箱的 MD5,未设置邮箱时为空字符串",
|
||||
"type": "string"
|
||||
},
|
||||
"ownerName": {
|
||||
"description": "所有者的昵称",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"favorites",
|
||||
"ownerName",
|
||||
"ownerAvatarHash",
|
||||
"items"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Source": {
|
||||
"enum": [
|
||||
"netease",
|
||||
@@ -1956,7 +2033,7 @@
|
||||
},
|
||||
"info": {
|
||||
"title": "Alisten",
|
||||
"version": "ae9a632d648f3840"
|
||||
"version": "cbfa923a9cbe9f0a"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
@@ -3811,6 +3888,93 @@
|
||||
"summary": "把若干首歌移出歌单,一次 1 到 1000 首;不在歌单里的忽略"
|
||||
}
|
||||
},
|
||||
"/api/me/playlists/{id}/share": {
|
||||
"delete": {
|
||||
"operationId": "unshareMyPlaylist",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "停止分享:分享链接随即失效;没有分享时也算成功"
|
||||
},
|
||||
"post": {
|
||||
"operationId": "shareMyPlaylist",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ShareMyPlaylistRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ShareMyPlaylistResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "开启分享或重新生成分享链接:每次都生成新的 shareId,之前的链接随即失效;“我喜欢”也能分享"
|
||||
}
|
||||
},
|
||||
"/api/members": {
|
||||
"post": {
|
||||
"operationId": "createMember",
|
||||
@@ -4608,6 +4772,98 @@
|
||||
"summary": "退出登录:发出请求的成员 token 随即失效"
|
||||
}
|
||||
},
|
||||
"/api/shared-playlists/{shareId}": {
|
||||
"get": {
|
||||
"operationId": "getSharedPlaylist",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "shareId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SharedPlaylist"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [],
|
||||
"summary": "读取分享的歌单,不需要身份;不返回所有者的成员 ID 和用户名;停止分享、重新生成链接或删除歌单之后返回 NOT_FOUND"
|
||||
}
|
||||
},
|
||||
"/api/shared-playlists/{shareId}/copies": {
|
||||
"post": {
|
||||
"operationId": "copySharedPlaylist",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "shareId",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CopySharedPlaylistRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MyPlaylist"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "把分享的歌单复制成自己的新歌单,带上全部曲目和顺序;新歌单受“最多 50 个”的限制;分享已失效时返回 NOT_FOUND"
|
||||
}
|
||||
},
|
||||
"/api/site": {
|
||||
"get": {
|
||||
"operationId": "getSite",
|
||||
|
||||
+32
-1
@@ -4,7 +4,7 @@ package protocol
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const Version = "ae9a632d648f3840"
|
||||
const Version = "cbfa923a9cbe9f0a"
|
||||
|
||||
type APIKey struct {
|
||||
Key string `json:"key"`
|
||||
@@ -181,6 +181,10 @@ type CopyMyPlaylistItemsResponse struct {
|
||||
Added int64 `json:"added"`
|
||||
}
|
||||
|
||||
type CopySharedPlaylistRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CreateMemberRequest struct{}
|
||||
|
||||
type CreateMemberResponse struct {
|
||||
@@ -437,6 +441,7 @@ type MyPlaylist struct {
|
||||
Favorites bool `json:"favorites"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ShareID *string `json:"shareId"`
|
||||
TrackCount int64 `json:"trackCount"`
|
||||
UpdatedAtMs int64 `json:"updatedAtMs"`
|
||||
}
|
||||
@@ -742,6 +747,28 @@ type SettingsUpdatedEvent struct {
|
||||
PlayMode PlayMode `json:"playMode"`
|
||||
}
|
||||
|
||||
type ShareMyPlaylistRequest struct{}
|
||||
|
||||
type ShareMyPlaylistResponse struct {
|
||||
ShareID string `json:"shareId"`
|
||||
}
|
||||
|
||||
type SharedPlaylist struct {
|
||||
Favorites bool `json:"favorites"`
|
||||
Items []MyPlaylistItem `json:"items"`
|
||||
Name string `json:"name"`
|
||||
OwnerAvatarHash string `json:"ownerAvatarHash"`
|
||||
OwnerName string `json:"ownerName"`
|
||||
}
|
||||
|
||||
func (v SharedPlaylist) MarshalJSON() ([]byte, error) {
|
||||
type plain SharedPlaylist
|
||||
if v.Items == nil {
|
||||
v.Items = []MyPlaylistItem{}
|
||||
}
|
||||
return json.Marshal(plain(v))
|
||||
}
|
||||
|
||||
type Source string
|
||||
|
||||
const (
|
||||
@@ -895,6 +922,10 @@ var (
|
||||
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}
|
||||
RouteShareMyPlaylist = Route{Pattern: "POST /api/me/playlists/{id}/share", Auth: AuthMember, Status: 200}
|
||||
RouteUnshareMyPlaylist = Route{Pattern: "DELETE /api/me/playlists/{id}/share", Auth: AuthMember, Status: 204}
|
||||
RouteGetSharedPlaylist = Route{Pattern: "GET /api/shared-playlists/{shareId}", Auth: AuthNone, Status: 200}
|
||||
RouteCopySharedPlaylist = Route{Pattern: "POST /api/shared-playlists/{shareId}/copies", Auth: AuthMember, Status: 201}
|
||||
)
|
||||
|
||||
var RoomRoutes = []RoomRoute{
|
||||
|
||||
Reference in new issue
Block a user