Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
该 PR 主要围绕“在线播放”能力补全:引入在线平台(网易云/QQ 音乐/酷狗)搜索与曲目建模、在线音频 URL 解析(官方接口 + 插件兜底)、在线歌词拉取与 TTML 升级策略,并新增网易云登录与用户侧入口;同时加入一个可选的外部控制 API(HTTP + WebSocket)以及若干 UI/组件能力调整来支撑上述功能在界面端落地。
Changes:
- 新增在线平台的数据类型/转换工具、搜索 API 与“合集/歌手页”按来源加载服务,打通在线内容浏览链路。
- 在线播放地址解析接入(网易云官方 song_url + 插件解析兜底),并扩展错误码与缓存防护(拒绝 HTML/JSON 冒充音频)。
- 新增网易云登录(二维码/网页登录/手动 Cookie)与外部 API 服务(REST + WS),并调整若干 UI 组件/导航入口。
Reviewed changes
Copilot reviewed 109 out of 115 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| uno.config.ts | 新增 dialog 顶部弹出动画 keyframes/duration/easing |
| src/utils/time.ts | 时间格式化支持 h:mm:ss |
| src/utils/netease.ts | 网易云 raw 数据到应用层模型的转换工具 |
| src/utils/navigate.ts | 导航工具增强(非本地源 ID 约束 + 新增歌单跳转) |
| src/utils/format.ts | 新增数字紧凑格式化(Intl compact) |
| src/utils/errors.ts | 扩展可跳过错误码集合 |
| src/types/user.ts | 新增用户资料/订阅计数类型 |
| src/types/netease.ts | 新增网易云接口原始类型定义 |
| src/stores/status.ts | 新增搜索平台/我的歌单来源状态并持久化 |
| src/stores/data.ts | 新增通用本地数据 store(搜索历史) |
| src/settings/categories/services.ts | 新增“外部 API(Beta)”设置区块与面板 |
| src/services/lyricLoader.ts | 在线歌词偏好策略增强 + TTML overlay 多平台候选 |
| src/services/collectionLoader.ts | 新增合集加载服务(local/streaming/netease) |
| src/services/audioSource.ts | 在线音源 URL 解析接入(官方+插件)与缓存 key 扩展 |
| src/services/artistLoader.ts | 新增歌手页加载服务(local/streaming/netease) |
| src/router/index.ts | 新增 /search 路由 |
| src/pages/Streaming/Playlists.vue | 歌单点击改用统一 navigateToPlaylist |
| src/pages/Folders.vue | selectedFolder 改 shallowRef + toRaw 避免深层代理影响 |
| src/pages/Collection.vue | 合集页改走 collectionLoader + 增量更新/Abort/Loading 态 |
| src/pages/Artist.vue | 歌手页改走 artistLoader + 网易云歌曲触底分页 + Loading 态 |
| src/layouts/components/SideBarLogo.vue | 删除旧布局目录下的 SideBarLogo(迁移) |
| src/i18n/locales/zh-CN.json | 新增搜索/登录/外部 API 文案与错误提示更新 |
| src/i18n/locales/en-US.json | 新增搜索/登录/外部 API 文案与错误提示更新 |
| src/components/ui/SVirtualList.vue | paddingBottom 处理调整(底部 spacer)+ footer 结构调整 |
| src/components/ui/SInput.vue | 支持 textarea/rows/resize + 清空按钮交互优化 |
| src/components/ui/SDialog.vue | 支持 top 偏移与 contentStyle,并新增顶部弹出动画分支 |
| src/components/ui/SAlert.vue | 新增通用 Alert 组件 |
| src/components/settings/custom/LyricSourceOrderConfig.vue | 平台显示名改用 PLATFORM_SHORT_NAME |
| src/components/settings/custom/ExternalApiPanel.vue | 外部 API 状态展示与重启按钮面板 |
| src/components/player/TrackInfo.vue | 播放栏歌手支持按来源可跳转判断 |
| src/components/player/FullPlayer/PlayerData.vue | 全屏页歌手/专辑跳转约束 + sourceLabel 改进 |
| src/components/modals/LoginDialog.vue | 新增网易云登录弹窗(二维码+自动获取+手动入口) |
| src/components/modals/LoginCookieDialog.vue | 新增手动 Cookie 登录弹窗 |
| src/components/list/SongList.vue | 列表跳转约束、付费标识、触底加载 footer 态支持 |
| src/components/list/PlaylistPanel.vue | 播放列表点击播放改用 player.playAtIndex |
| src/components/list/CoverList.vue | 支持触底加载与 footer 状态(loading/noMore) |
| src/components/layout/WindowControls.vue | 新增窗口控制按钮组件 |
| src/components/layout/SideBarLogo.vue | SideBar Logo 迁移到新目录并改点击回首页 |
| src/components/layout/SideBar.vue | 侧栏“我的歌单”支持本地/在线切换与订阅歌单分组 |
| src/components/layout/NavUser.vue | 新增用户入口(登录态/统计/退出) |
| src/components/layout/NavHeader.vue | 头部加入搜索与用户入口 |
| src/apis/user/netease.ts | 新增网易云用户数据拉取(歌单/收藏/专辑/歌手/喜欢等) |
| src/apis/song/netease.ts | 新增 song_detail 批量取歌 + song_url 播放地址解析 |
| src/apis/search/suggest.ts | 新增网易云搜索建议 |
| src/apis/search/qqmusic.ts | 新增 QQ 音乐搜索(歌曲)渲染端适配 |
| src/apis/search/netease.ts | 新增网易云云搜(歌曲/专辑/歌手/歌单) |
| src/apis/search/kugou.ts | 新增酷狗搜索(歌曲)渲染端适配 |
| src/apis/search/index.ts | 新增跨平台搜索分发入口 |
| src/apis/search/hot.ts | 新增网易云热搜(TTL 缓存) |
| src/apis/netease.ts | 渲染端 Netease Proxy 调整(泛型/注释) |
| src/apis/login/netease.ts | 新增网易云登录相关 API(qrKey/qrCheck/status/refresh/logout) |
| shared/types/settings.ts | 新增 ExternalApiSettings/Status 并挂到 SystemConfig |
| shared/types/plugin.ts | SandboxOut 新增 sourcesUpdate 消息 |
| shared/types/player.ts | TrackSource 改为 Platform 并扩展 TrackFee/extId 等 |
| shared/types/platform.ts | 新增 PLATFORM_SHORT_NAME/ALL_PLATFORMS/isPlatform |
| shared/types/errors.ts | 新增 URL_RESOLVE_FAILED/NO_PLUGIN_AVAILABLE 错误码 |
| shared/types/apis.ts | ApisApi 增加 openLoginWeb/setCookie |
| shared/defaults/settings.ts | 增加 externalApi 默认配置 |
| package.json | 引入 hono/@hono/node-server/ws/uqr 及 @types/ws |
| native/audio-engine/src/player.rs | stop() 清理 current_source,避免 Stopped 复活上一首 |
| electron/preload/index.ts | 新增 apis.openLoginWeb/apis.setCookie 与 externalApi IPC |
| electron/preload/index.d.ts | window.api.externalApi 类型声明补全 |
| electron/main/window/login.ts | 新增网易云网页登录窗口(独立 session + 轮询 cookie) |
| electron/main/utils/logger.ts | 新增 serverLog/pluginLog scope |
| electron/main/services/songCache.ts | 缓存下载增加 MIME/文件头拦截,防错误页入缓存 |
| electron/main/services/nowPlaying.ts | 注释/参数说明补全(功能未改) |
| electron/main/server/ws.ts | 新增 WS 指令分发与 ack/error 协议 |
| electron/main/server/routes.ts | 新增外部 API REST 路由(status/now-playing/controls) |
| electron/main/server/index.ts | 新增外部 API server 启停与状态管理 |
| electron/main/server/gate.ts | 新增 externalApi.enabled/wsEnabled 门禁中间件 |
| electron/main/server/broadcast.ts | 新增 WS 客户端注册与事件广播(高频过滤) |
| electron/main/plugins/sandbox.worker.ts | IPC 数据 sanitize + sourcesUpdate + btoa/atob 注入 |
| electron/main/plugins/sandbox.ts | Sandbox 事件新增 onSourcesUpdate |
| electron/main/plugins/router.ts | resolveUrl 调用路径调整并增强日志 |
| electron/main/plugins/registry.ts | 合并异步 sourcesUpdate 到 runtime status |
| electron/main/plugins/lx-shim.ts | lx.request headers/body 归一 + musicUrl 回包形状校验 |
| electron/main/ipc/player.ts | player 事件同时 wsBroadcast + 远端封面拉取逻辑泛化 |
| electron/main/ipc/nowPlaying.ts | nowPlaying 事件同时广播到外部 WS |
| electron/main/ipc/lyrics.ts | 在线 Track 的 id/extId 处理调整 + 注释增强 |
| electron/main/ipc/index.ts | 注册 externalApi IPC |
| electron/main/ipc/externalApi.ts | 新增 externalApi:restart/getStatus IPC |
| electron/main/ipc/apis.ts | 新增网页登录与手动 cookie 写入 IPC |
| electron/main/core/index.ts | 应用启动/退出时启动/停止外部 API 服务 |
| electron/main/apis/qqmusic/modules/search.ts | QM 搜索端点调整并限制仅歌曲搜索 |
| electron/main/apis/netease/modules/song_url.ts | 新增网易云 song_url(v1) 模块 |
| electron/main/apis/netease/modules/song_detail.ts | 新增网易云 song_detail 模块 |
| electron/main/apis/netease/modules/playlist_detail.ts | 新增网易云 playlist_detail 模块 |
| electron/main/apis/netease/modules/likelist.ts | 新增喜欢列表模块 |
| electron/main/apis/netease/modules/like.ts | 新增 like/取消 like 模块 |
| electron/main/apis/netease/modules/index.ts | 汇总新增模块导出 |
| electron/main/apis/netease/modules/artists.ts | 新增歌手信息模块 |
| electron/main/apis/netease/modules/artist_sublist.ts | 新增收藏歌手列表模块 |
| electron/main/apis/netease/modules/artist_songs.ts | 新增歌手歌曲分页模块 |
| electron/main/apis/netease/modules/artist_album.ts | 新增歌手专辑模块 |
| electron/main/apis/netease/modules/album.ts | 新增专辑详情模块 |
| electron/main/apis/netease/modules/album_sublist.ts | 新增收藏专辑列表模块 |
| electron/main/apis/netease/core/request.ts | 失败抛 NeteaseRequestError(保留 response) |
| electron/main/apis/kugou/index.ts | 搜索缓存:空结果不缓存 |
| electron/main/apis/kugou/core/request.ts | KG 请求注释与成功码说明更新 |
| electron/main/apis/kugou/core/config.ts | 新增 mobilecdn 主搜索端点常量 |
| electron/main/apis/common/lyric/utils.ts | 候选匹配增加“时长差距过大”硬性过滤 |
| electron/main/apis/common/lyric/qqmusic.ts | QQMusic 歌词接口注释澄清 songID/mid |
| electron.vite.config.ts | 组件自动导入目录调整(移除旧 layouts/components) |
| docs/plugins-usage.md | lx 垫片能力说明更新 |
| docs/plugins-development.md | 插件全局能力/level1 约束/错误码说明更新 |
| components.d.ts | 自动组件声明更新(含新增组件/路径变更) |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
src/services/audioSource.ts:166
- 同上:在线源解析失败时 catch 分支把 err.message 当作错误码传入 handleError(),会导致只提示 UNKNOWN。建议改为 handleError(ErrorCode.URL_RESOLVE_FAILED/NETWORK_ERROR 等),并额外记录 err 细节到日志。
Comment on lines
+77
to
+78
| // 启动外部 API 服务(fire-and-forget;监听结果通过 getStatus 暴露给渲染端) | ||
| void startServer(); |
Comment on lines
+35
to
+47
| const port = store.get("externalApi.port"); | ||
| const hostname = "0.0.0.0"; | ||
|
|
||
| const app = new Hono(); | ||
| app.use("/api/*", externalControlGate); | ||
| app.route("/api", buildRoutes()); | ||
| app.get( | ||
| "/ws", | ||
| externalControlGate, | ||
| wsGate, | ||
| upgradeWebSocket(() => wsHandlers), | ||
| ); | ||
| app.get("/", (c) => c.text("SPlayer Next external API")); |
Comment on lines
+61
to
+65
| key: "externalApiWs", | ||
| type: "switch", | ||
| binding: { store: "settings", path: "system.externalApi.wsEnabled" }, | ||
| defaultValue: true, | ||
| }, |
Comment on lines
147
to
150
| } catch (err) { | ||
| handleError(err instanceof Error ? err.message : String(err)); | ||
| return null; | ||
| } |
Comment on lines
+14
to
+18
| <div class="flex items-center justify-center h-16 shrink-0 px-4"> | ||
| <div | ||
| class="inline-flex items-center cursor-pointer transition-transform duration-300 hover:scale-105 active:scale-100" | ||
| @click="goHome" | ||
| > |
Comment on lines
+88
to
+96
| webPreferences: { | ||
| session: ses, | ||
| // sandbox 模式下 NCM 的 JS 渲染极慢;登录窗口里没有自家代码,关闭沙箱影响可控 | ||
| sandbox: false, | ||
| spellcheck: false, | ||
| backgroundThrottling: false, | ||
| nodeIntegration: false, | ||
| contextIsolation: true, | ||
| }, |
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 133 out of 139 changed files in this pull request and generated 10 comments.
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
src/services/audioSource.ts:154
- 这里把
async () => { ... }赋给 cacheRequest(且调度器/调用方不会 await),容易出现未处理的 Promise 与类型不匹配问题。建议用同步包装(内部void (async () => { ... })())或让调度器支持/await Promise。
Comment on lines
+120
to
+124
| export interface ResolvedTrackSource { | ||
| source: string; | ||
| fromCache: boolean; | ||
| } | null> => { | ||
| cacheRequest?: () => void; | ||
| } |
| const cacheUrl = await store.getStreamUrl(track, { | ||
| playSessionId: crypto.randomUUID(), | ||
| }); | ||
| void window.api.cache.song.fetch(cacheKey, "streaming", cacheUrl); |
Comment on lines
+77
to
+78
| // 启动外部 API 服务(fire-and-forget;监听结果通过 getStatus 暴露给渲染端) | ||
| void startServer(); |
Comment on lines
+35
to
+37
| const port = store.get("externalApi.port"); | ||
| const hostname = "0.0.0.0"; | ||
|
|
Comment on lines
+10
to
+12
| * @param albumName - 专辑名称(本地用作聚合 key;非本地用于 query 兜底显示) | ||
| * @param options.source - 来源(local/streaming/online);默认为 local | ||
| * @param options.albumId - 真实专辑 ID(streaming/online 必填) |
Comment on lines
+126
to
+131
| /** 订阅计数(/user/subcount)→ 应用层 UserSubcount */ | ||
| export const toSubcount = (raw: any): UserSubcount => ({ | ||
| createdPlaylistCount: raw.createdPlaylistCount ?? 0, | ||
| subPlaylistCount: raw.subPlaylistCount ?? 0, | ||
| artistCount: raw.artistCount ?? 0, | ||
| }); |
Comment on lines
+14
to
+18
| <div class="flex items-center justify-center h-16 shrink-0 px-4"> | ||
| <div | ||
| class="inline-flex items-center cursor-pointer transition-transform duration-300 hover:scale-105 active:scale-100" | ||
| @click="goHome" | ||
| > |
Comment on lines
+61
to
+65
| key: "externalApiWs", | ||
| type: "switch", | ||
| binding: { store: "settings", path: "system.externalApi.wsEnabled" }, | ||
| defaultValue: true, | ||
| }, |
Comment on lines
+45
to
+48
| upgradeWebSocket(() => wsHandlers), | ||
| ); | ||
| app.get("/", (c) => c.text("SPlayer Next external API")); | ||
|
|
Comment on lines
+35
to
+47
| const port = store.get("externalApi.port"); | ||
| const hostname = "0.0.0.0"; | ||
|
|
||
| const app = new Hono(); | ||
| app.use("/api/*", externalControlGate); | ||
| app.route("/api", buildRoutes()); | ||
| app.get( | ||
| "/ws", | ||
| externalControlGate, | ||
| wsGate, | ||
| upgradeWebSocket(() => wsHandlers), | ||
| ); | ||
| app.get("/", (c) => c.text("SPlayer Next external API")); |
Comment on lines
+77
to
+78
| // 启动外部 API 服务(fire-and-forget;监听结果通过 getStatus 暴露给渲染端) | ||
| void startServer(); |
Comment on lines
+49
to
+58
| const candidates = plugins.list.filter( | ||
| (info) => | ||
| info.enabled && | ||
| info.status.state === "ready" && | ||
| info.status.sources[pluginSource]?.actions.includes("musicUrl"), | ||
| ); | ||
| if (candidates.length === 0) { | ||
| handleError(ErrorCode.NO_PLUGIN_AVAILABLE); | ||
| return null; | ||
| } |
Comment on lines
+24
to
+49
| const readFromDisk = async (): Promise<HistoryEntry[]> => { | ||
| const cached = await db.getItem<HistoryEntry[]>(HISTORY_KEY).catch(() => null); | ||
| return Array.isArray(cached) ? cached : []; | ||
| }; | ||
|
|
||
| const apply = async (next: HistoryEntry[]): Promise<void> => { | ||
| entries.value = next; | ||
| await db.setItem(HISTORY_KEY, toRaw(next)).catch(() => {}); | ||
| }; | ||
|
|
||
| const load = async (): Promise<void> => { | ||
| entries.value = await readFromDisk(); | ||
| }; | ||
|
|
||
| /** | ||
| * 记录一次播放:每次都以硬盘为真值源,避免内存未 load 时覆盖旧历史 | ||
| * @param track 当前播放曲目 | ||
| */ | ||
| const record = async (track: Track): Promise<void> => { | ||
| if (!track?.id) return; | ||
| const key = keyOf(track); | ||
| const list = await readFromDisk(); | ||
| const filtered = list.filter((item) => keyOf(item.track) !== key); | ||
| const next = [{ track, playedAt: Date.now() }, ...filtered].slice(0, MAX_HISTORY); | ||
| await apply(next); | ||
| }; |
Comment on lines
+34
to
+44
| export const wsBroadcast = (event: { type: string; data?: unknown }): void => { | ||
| if (wsClients.size === 0) return; | ||
| if (HIGH_FREQ_EVENTS.has(event.type)) return; | ||
| const payload = JSON.stringify({ kind: "event", ...event }); | ||
| for (const ws of wsClients) { | ||
| try { | ||
| ws.send(payload); | ||
| } catch { | ||
| // 单个客户端发送失败不影响其他人 | ||
| } | ||
| } |
Comment on lines
+35
to
+40
| const port = store.get("externalApi.port"); | ||
| const hostname = "0.0.0.0"; | ||
|
|
||
| const app = new Hono(); | ||
| app.use("/api/*", externalControlGate); | ||
| app.route("/api", buildRoutes()); |
Comment on lines
+6
to
+12
| interface Pending { | ||
| trackId: string; | ||
| fire: () => void; | ||
| } | ||
|
|
||
| let pending: Pending | null = null; | ||
|
|
Comment on lines
43
to
47
| return { | ||
| ...meta, | ||
| cover: tracks.find((t) => t.cover)?.cover, | ||
| tracks, | ||
| trackCount: tracks.length, | ||
| }; |
Comment on lines
+15
to
+20
| <div | ||
| role="link" | ||
| tabindex="0" | ||
| class="inline-flex items-center cursor-pointer transform-gpu transition-transform duration-300 hover:scale-105 active:scale-100" | ||
| @click="goHome" | ||
| > |
Comment on lines
96
to
100
| }; | ||
| const fingerprint = buildFingerprint(track); | ||
| const cached = getMatchedId(fingerprint, platform); | ||
| // QM mid 放前面 | ||
| // QM mid 放前面(AMLL DB 早期 QM 条目以 mid 为文件名的居多) | ||
| if (platform === "qqmusic") push(cached?.extra?.mid); |
Comment on lines
+35
to
+37
| const port = store.get("externalApi.port"); | ||
| const hostname = "0.0.0.0"; | ||
|
|
Comment on lines
+77
to
79
| // 启动外部 API 服务(fire-and-forget;监听结果通过 getStatus 暴露给渲染端) | ||
| void startServer(); | ||
| app.on("activate", () => { |
Comment on lines
+38
to
+48
| // 当前播放完整快照 | ||
| api.get("/now-playing", (c) => { | ||
| const snap = nowPlaying.snapshot(); | ||
| return c.json({ | ||
| track: snap.track, | ||
| lyric: snap.lyric, | ||
| source: snap.source, | ||
| position: snap.position, | ||
| playing: snap.playing, | ||
| lyricOffsetMs: snap.lyricOffsetMs, | ||
| }); |
Comment on lines
+10
to
+16
| /** 总开关 */ | ||
| export const externalControlGate: MiddlewareHandler = async (c, next) => { | ||
| if (!store.get("externalApi.enabled")) { | ||
| return c.json({ error: "external API disabled" }, 403); | ||
| } | ||
| await next(); | ||
| return; |
Comment on lines
+88
to
+96
| webPreferences: { | ||
| session: ses, | ||
| // sandbox 模式下 NCM 的 JS 渲染极慢;登录窗口里没有自家代码,关闭沙箱影响可控 | ||
| sandbox: false, | ||
| spellcheck: false, | ||
| backgroundThrottling: false, | ||
| nodeIntegration: false, | ||
| contextIsolation: true, | ||
| }, |
Comment on lines
+15
to
+20
| <div | ||
| role="link" | ||
| tabindex="0" | ||
| class="inline-flex items-center cursor-pointer transform-gpu transition-transform duration-300 hover:scale-105 active:scale-100" | ||
| @click="goHome" | ||
| > |
Comment on lines
54
to
57
| const setTrack = (newTrack: Track, newDetail?: TrackDetail): void => { | ||
| track.value = newTrack; | ||
| detail.value = newDetail ?? null; | ||
| if (newDetail) detail.value = newDetail; | ||
| }; |
Comment on lines
54
to
57
| const setTrack = (newTrack: Track, newDetail?: TrackDetail): void => { | ||
| track.value = newTrack; | ||
| detail.value = newDetail ?? null; | ||
| if (newDetail) detail.value = newDetail; | ||
| }; |
Comment on lines
+77
to
+78
| // 启动外部 API 服务(fire-and-forget;监听结果通过 getStatus 暴露给渲染端) | ||
| void startServer(); |
Comment on lines
+35
to
+47
| const port = store.get("externalApi.port"); | ||
| const hostname = "0.0.0.0"; | ||
|
|
||
| const app = new Hono(); | ||
| app.use("/api/*", externalControlGate); | ||
| app.route("/api", buildRoutes()); | ||
| app.get( | ||
| "/ws", | ||
| externalControlGate, | ||
| wsGate, | ||
| upgradeWebSocket(() => wsHandlers), | ||
| ); | ||
| app.get("/", (c) => c.text("SPlayer Next external API")); |
Comment on lines
+36
to
+40
| api.get("/volume", (c) => c.json({ volume: getPlayer().getVolume() })); | ||
|
|
||
| // 当前播放完整快照 | ||
| api.get("/now-playing", (c) => c.json(nowPlaying.snapshot())); | ||
|
|
Comment on lines
+15
to
+20
| <div | ||
| role="link" | ||
| tabindex="0" | ||
| class="inline-flex items-center cursor-pointer transform-gpu transition-transform duration-300 hover:scale-105 active:scale-100" | ||
| @click="goHome" | ||
| > |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.