@neteasecloudmusicapienhanced/api 4.32.0 → 4.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.MD +248 -251
- package/app.js +18 -18
- package/data/china_ip_ranges.txt +4147 -4147
- package/data/deviceid.txt +24641 -24641
- package/generateConfig.js +24 -24
- package/interface.d.ts +1843 -1836
- package/main.js +63 -63
- package/module/activate_init_profile.js +9 -9
- package/module/aidj_content_rcmd.js +28 -28
- package/module/album.js +6 -6
- package/module/album_detail.js +12 -12
- package/module/album_detail_dynamic.js +12 -12
- package/module/album_list.js +16 -16
- package/module/album_list_style.js +15 -15
- package/module/album_new.js +11 -11
- package/module/album_newest.js +6 -6
- package/module/album_privilege.js +9 -9
- package/module/album_songsaleboard.js +19 -19
- package/module/album_sub.js +10 -10
- package/module/album_sublist.js +11 -11
- package/module/api.js +21 -21
- package/module/artist_album.js +15 -15
- package/module/artist_desc.js +9 -9
- package/module/artist_detail.js +10 -10
- package/module/artist_detail_dynamic.js +9 -9
- package/module/artist_fans.js +11 -11
- package/module/artist_follow_count.js +13 -13
- package/module/artist_list.js +33 -33
- package/module/artist_mv.js +12 -12
- package/module/artist_new_mv.js +12 -12
- package/module/artist_new_song.js +12 -12
- package/module/artist_songs.js +12 -12
- package/module/artist_sub.js +11 -11
- package/module/artist_sublist.js +11 -11
- package/module/artist_top_song.js +8 -8
- package/module/artist_video.js +15 -15
- package/module/artists.js +6 -6
- package/module/audio_match.js +19 -19
- package/module/avatar_upload.js +22 -22
- package/module/banner.js +16 -16
- package/module/batch.js +12 -12
- package/module/broadcast_category_region_get.js +11 -11
- package/module/broadcast_channel_collect_list.js +12 -12
- package/module/broadcast_channel_currentinfo.js +13 -13
- package/module/broadcast_channel_list.js +13 -13
- package/module/broadcast_sub.js +12 -12
- package/module/calendar.js +8 -8
- package/module/captcha_sent.js +11 -11
- package/module/captcha_verify.js +11 -11
- package/module/cellphone_existence_check.js +10 -10
- package/module/check_music.js +30 -30
- package/module/cloud.js +164 -164
- package/module/cloud_import.js +39 -39
- package/module/cloud_lyric_get.js +11 -11
- package/module/cloud_match.js +13 -13
- package/module/cloud_upload_complete.js +72 -72
- package/module/cloud_upload_token.js +111 -111
- package/module/cloudsearch.js +13 -13
- package/module/comment.js +30 -30
- package/module/comment_album.js +16 -16
- package/module/comment_dj.js +16 -16
- package/module/comment_event.js +15 -15
- package/module/comment_floor.js +16 -16
- package/module/comment_hot.js +18 -18
- package/module/comment_hug_list.js +20 -20
- package/module/comment_info_list.js +30 -30
- package/module/comment_like.js +20 -20
- package/module/comment_music.js +16 -16
- package/module/comment_mv.js +16 -16
- package/module/comment_new.js +37 -37
- package/module/comment_playlist.js +16 -16
- package/module/{comment_delete.js → comment_report.js} +11 -10
- package/module/comment_video.js +16 -16
- package/module/countries_code_list.js +6 -6
- package/module/creator_authinfo_get.js +6 -6
- package/module/daily_signin.js +16 -16
- package/module/digitalAlbum_detail.js +13 -13
- package/module/digitalAlbum_ordering.js +22 -22
- package/module/digitalAlbum_purchased.js +15 -15
- package/module/digitalAlbum_sales.js +13 -13
- package/module/djRadio_top.js +15 -15
- package/module/dj_banner.js +6 -6
- package/module/dj_category_excludehot.js +10 -10
- package/module/dj_category_recommend.js +10 -10
- package/module/dj_catelist.js +6 -6
- package/module/dj_detail.js +9 -9
- package/module/dj_difm_all_style_channel.js +9 -9
- package/module/dj_difm_channel_subscribe.js +9 -9
- package/module/dj_difm_channel_unsubscribe.js +9 -9
- package/module/dj_difm_playing_tracks_list.js +11 -11
- package/module/dj_difm_subscribe_channels_get.js +13 -13
- package/module/dj_hot.js +10 -10
- package/module/dj_paygift.js +15 -15
- package/module/dj_personalize_recommend.js +12 -12
- package/module/dj_program.js +12 -12
- package/module/dj_program_detail.js +9 -9
- package/module/dj_program_toplist.js +10 -10
- package/module/dj_program_toplist_hours.js +13 -13
- package/module/dj_radio_hot.js +11 -11
- package/module/dj_recommend.js +6 -6
- package/module/dj_recommend_type.js +32 -32
- package/module/dj_sub.js +10 -10
- package/module/dj_sublist.js +11 -11
- package/module/dj_subscriber.js +12 -12
- package/module/dj_today_perfered.js +13 -13
- package/module/dj_toplist.js +14 -14
- package/module/dj_toplist_hours.js +10 -10
- package/module/dj_toplist_newcomer.js +9 -9
- package/module/dj_toplist_pay.js +9 -9
- package/module/dj_toplist_popular.js +10 -10
- package/module/eapi_decrypt.js +27 -27
- package/module/event.js +10 -10
- package/module/event_del.js +9 -9
- package/module/event_forward.js +11 -11
- package/module/fanscenter_basicinfo_age_get.js +6 -6
- package/module/fanscenter_basicinfo_gender_get.js +10 -10
- package/module/fanscenter_basicinfo_province_get.js +10 -10
- package/module/fanscenter_overview_get.js +6 -6
- package/module/fanscenter_trend_list.js +10 -10
- package/module/fm_trash.js +11 -11
- package/module/follow.js +11 -11
- package/module/get_userids.js +7 -7
- package/module/history_recommend_songs.js +11 -11
- package/module/history_recommend_songs_detail.js +13 -13
- package/module/homepage_block_page.js +8 -8
- package/module/homepage_dragon_ball.js +10 -10
- package/module/hot_topic.js +10 -10
- package/module/hug_comment.js +16 -16
- package/module/inner_version.js +16 -16
- package/module/like.js +13 -13
- package/module/likelist.js +9 -9
- package/module/listen_data_realtime_report.js +11 -11
- package/module/listen_data_report.js +12 -12
- package/module/listen_data_today_song.js +9 -9
- package/module/listen_data_total.js +9 -9
- package/module/listen_data_year_report.js +9 -9
- package/module/listentogether_accept.js +13 -13
- package/module/listentogether_end.js +9 -9
- package/module/listentogether_heatbeat.js +12 -12
- package/module/listentogether_play_command.js +21 -21
- package/module/listentogether_room_check.js +9 -9
- package/module/listentogether_room_create.js +9 -9
- package/module/listentogether_status.js +10 -10
- package/module/listentogether_sync_list_command.js +26 -26
- package/module/listentogether_sync_playlist_get.js +13 -13
- package/module/login.js +41 -41
- package/module/login_cellphone.js +40 -40
- package/module/login_qr_check.js +29 -29
- package/module/login_qr_create.js +30 -30
- package/module/login_qr_key.js +19 -19
- package/module/login_refresh.js +21 -21
- package/module/login_status.js +21 -21
- package/module/logout.js +6 -6
- package/module/lyric.js +14 -14
- package/module/lyric_new.js +17 -17
- package/module/mlog_music_rcmd.js +13 -13
- package/module/mlog_to_video.js +13 -13
- package/module/mlog_url.js +11 -11
- package/module/msg_comments.js +17 -17
- package/module/msg_forwards.js +11 -11
- package/module/msg_notices.js +10 -10
- package/module/msg_private.js +11 -11
- package/module/msg_private_history.js +12 -12
- package/module/msg_recentcontact.js +11 -11
- package/module/music_first_listen_info.js +13 -13
- package/module/musician_cloudbean.js +7 -7
- package/module/musician_cloudbean_obtain.js +14 -14
- package/module/musician_data_overview.js +11 -11
- package/module/musician_play_trend.js +14 -14
- package/module/musician_sign.js +7 -7
- package/module/musician_tasks.js +11 -11
- package/module/musician_tasks_new.js +11 -11
- package/module/musician_vip_tasks.js +11 -11
- package/module/mv_all.js +16 -16
- package/module/mv_detail.js +9 -9
- package/module/mv_detail_info.js +14 -14
- package/module/mv_exclusive_rcmd.js +10 -10
- package/module/mv_first.js +12 -12
- package/module/mv_sub.js +11 -11
- package/module/mv_sublist.js +15 -15
- package/module/mv_url.js +14 -14
- package/module/nickname_check.js +7 -7
- package/module/personal_fm.js +6 -6
- package/module/personal_fm_mode.js +14 -14
- package/module/personalized.js +16 -16
- package/module/personalized_djprogram.js +10 -10
- package/module/personalized_mv.js +6 -6
- package/module/personalized_newsong.js +15 -15
- package/module/personalized_privatecontent.js +10 -10
- package/module/personalized_privatecontent_list.js +15 -15
- package/module/pl_count.js +6 -6
- package/module/playlist_category_list.js +11 -11
- package/module/playlist_catlist.js +6 -6
- package/module/playlist_cover_update.js +32 -32
- package/module/playlist_create.js +11 -11
- package/module/playlist_delete.js +9 -9
- package/module/playlist_desc_update.js +10 -10
- package/module/playlist_detail.js +11 -11
- package/module/playlist_detail_dynamic.js +11 -11
- package/module/playlist_detail_rcmd_get.js +11 -11
- package/module/playlist_highquality_tags.js +10 -10
- package/module/playlist_hot.js +6 -6
- package/module/playlist_import_name_task_create.js +62 -62
- package/module/playlist_import_task_status.js +11 -11
- package/module/playlist_mylike.js +12 -12
- package/module/playlist_name_update.js +10 -10
- package/module/playlist_order_update.js +13 -13
- package/module/playlist_privacy.js +10 -10
- package/module/playlist_subscribe.js +14 -14
- package/module/playlist_subscribers.js +11 -11
- package/module/playlist_tags_update.js +10 -10
- package/module/playlist_track_add.js +16 -16
- package/module/playlist_track_all.js +31 -31
- package/module/playlist_track_delete.js +20 -20
- package/module/playlist_tracks.js +45 -45
- package/module/playlist_update.js +13 -13
- package/module/playlist_update_playcount.js +9 -9
- package/module/playlist_video_recent.js +9 -9
- package/module/playmode_intelligence_list.js +13 -13
- package/module/playmode_song_vector.js +8 -8
- package/module/program_recommend.js +15 -15
- package/module/radio_sport_get.js +9 -9
- package/module/rebind.js +16 -16
- package/module/recent_listen_list.js +7 -7
- package/module/recommend_resource.js +10 -10
- package/module/recommend_songs.js +13 -11
- package/module/recommend_songs_dislike.js +14 -14
- package/module/record_recent_album.js +11 -11
- package/module/record_recent_dj.js +11 -11
- package/module/record_recent_playlist.js +11 -11
- package/module/record_recent_song.js +11 -11
- package/module/record_recent_video.js +11 -11
- package/module/record_recent_voice.js +11 -11
- package/module/register_anonimous.js +53 -53
- package/module/register_cellphone.js +15 -15
- package/module/related_allvideo.js +14 -14
- package/module/related_playlist.js +32 -32
- package/module/resource_like.js +14 -14
- package/module/sati_resource_list.js +11 -11
- package/module/sati_resource_list_more.js +13 -13
- package/module/sati_resource_sub.js +10 -10
- package/module/sati_resource_sub_list.js +7 -7
- package/module/sati_tag_list.js +7 -7
- package/module/sati_timescene_resources_get.js +13 -13
- package/module/scrobble.js +26 -26
- package/module/search.js +21 -21
- package/module/search_default.js +6 -6
- package/module/search_hot.js +9 -9
- package/module/search_hot_detail.js +6 -6
- package/module/search_match.js +18 -18
- package/module/search_multimatch.js +14 -14
- package/module/search_suggest.js +14 -14
- package/module/search_suggest_pc.js +13 -13
- package/module/send_album.js +12 -12
- package/module/send_playlist.js +12 -12
- package/module/send_song.js +12 -12
- package/module/send_text.js +11 -11
- package/module/setting.js +7 -7
- package/module/share_resource.js +11 -11
- package/module/sheet_list.js +9 -9
- package/module/sheet_preview.js +8 -8
- package/module/sign_happy_info.js +5 -5
- package/module/signin_progress.js +13 -13
- package/module/simi_artist.js +12 -12
- package/module/simi_mv.js +9 -9
- package/module/simi_playlist.js +15 -15
- package/module/simi_song.js +15 -15
- package/module/simi_user.js +11 -11
- package/module/song_chorus.js +11 -11
- package/module/song_copyright_rcmd.js +9 -0
- package/module/song_creators.js +9 -9
- package/module/song_detail.js +11 -11
- package/module/song_downlist.js +11 -11
- package/module/song_download_url.js +10 -10
- package/module/song_download_url_v1.js +13 -13
- package/module/song_dynamic_cover.js +9 -9
- package/module/song_like.js +12 -12
- package/module/song_like_check.js +9 -9
- package/module/song_lyrics_mark.js +9 -9
- package/module/song_lyrics_mark_add.js +12 -12
- package/module/song_lyrics_mark_del.js +9 -9
- package/module/song_lyrics_mark_user_page.js +14 -14
- package/module/song_monthdownlist.js +11 -11
- package/module/song_music_detail.js +9 -9
- package/module/song_order_update.js +12 -12
- package/module/song_purchased.js +14 -14
- package/module/song_red_count.js +9 -9
- package/module/song_singledownlist.js +11 -11
- package/module/song_url.js +26 -26
- package/module/song_url_match.js +38 -38
- package/module/song_url_ncmget.js +5 -5
- package/module/song_url_v1.js +57 -57
- package/module/song_url_v1_302.js +53 -53
- package/module/song_wiki_summary.js +8 -8
- package/module/starpick_comments_summary.js +12 -12
- package/module/style_album.js +16 -16
- package/module/style_artist.js +16 -16
- package/module/style_detail.js +9 -9
- package/module/style_list.js +7 -7
- package/module/style_playlist.js +16 -16
- package/module/style_preference.js +11 -11
- package/module/style_song.js +12 -12
- package/module/summary_annual.js +12 -12
- package/module/threshold_detail_get.js +10 -10
- package/module/top_album.js +22 -22
- package/module/top_artists.js +11 -11
- package/module/top_list.js +20 -20
- package/module/top_mv.js +12 -12
- package/module/top_playlist.js +22 -22
- package/module/top_playlist_highquality.js +16 -16
- package/module/top_song.js +16 -16
- package/module/topic_detail.js +7 -7
- package/module/topic_detail_event_hot.js +7 -7
- package/module/topic_sublist.js +11 -11
- package/module/toplist.js +6 -6
- package/module/toplist_artist.js +12 -12
- package/module/toplist_detail.js +6 -6
- package/module/toplist_detail_v2.js +6 -6
- package/module/ugc_album_get.js +8 -8
- package/module/ugc_artist_get.js +8 -8
- package/module/ugc_artist_search.js +10 -10
- package/module/ugc_detail.js +17 -17
- package/module/ugc_mv_get.js +8 -8
- package/module/ugc_song_get.js +8 -8
- package/module/ugc_user_devote.js +6 -6
- package/module/user_account.js +5 -5
- package/module/user_audio.js +9 -9
- package/module/user_binding.js +9 -9
- package/module/user_bindingcellphone.js +15 -15
- package/module/user_cloud.js +10 -10
- package/module/user_cloud_del.js +9 -9
- package/module/user_cloud_detail.js +10 -10
- package/module/user_comment_history.js +15 -15
- package/module/user_detail.js +15 -15
- package/module/user_detail_new.js +20 -20
- package/module/user_dj.js +14 -14
- package/module/user_event.js +12 -12
- package/module/user_follow_mixed.js +23 -23
- package/module/user_followeds.js +17 -17
- package/module/user_follows.js +15 -15
- package/module/user_level.js +7 -7
- package/module/user_medal.js +11 -11
- package/module/user_mutualfollow_get.js +9 -9
- package/module/user_playlist.js +12 -12
- package/module/user_playlist_collect.js +14 -14
- package/module/user_playlist_create.js +14 -14
- package/module/user_record.js +10 -10
- package/module/user_replacephone.js +14 -14
- package/module/user_social_status.js +11 -11
- package/module/user_social_status_edit.js +16 -16
- package/module/user_social_status_rcmd.js +5 -5
- package/module/user_social_status_support.js +5 -5
- package/module/user_subcount.js +6 -6
- package/module/user_update.js +15 -15
- package/module/verify_getQr.js +39 -39
- package/module/verify_qrcodestatus.js +12 -12
- package/module/video_category_list.js +15 -15
- package/module/video_detail.js +13 -13
- package/module/video_detail_info.js +14 -14
- package/module/video_group.js +16 -16
- package/module/video_group_list.js +11 -11
- package/module/video_sub.js +14 -14
- package/module/video_timeline_all.js +17 -17
- package/module/video_timeline_recommend.js +13 -13
- package/module/video_url.js +10 -10
- package/module/vip_growthpoint.js +11 -11
- package/module/vip_growthpoint_details.js +14 -14
- package/module/vip_growthpoint_get.js +13 -13
- package/module/vip_info.js +12 -12
- package/module/vip_info_v2.js +12 -12
- package/module/vip_sign.js +7 -11
- package/module/vip_sign_info.js +11 -11
- package/module/vip_tasks.js +11 -11
- package/module/vip_timemachine.js +17 -17
- package/module/voice_delete.js +7 -7
- package/module/voice_detail.js +7 -7
- package/module/voice_lyric.js +7 -7
- package/module/voice_upload.js +200 -200
- package/module/voicelist_detail.js +11 -11
- package/module/voicelist_list.js +13 -13
- package/module/voicelist_list_search.js +14 -14
- package/module/voicelist_my_created.js +13 -13
- package/module/voicelist_search.js +11 -11
- package/module/voicelist_trans.js +15 -15
- package/module/weblog.js +10 -10
- package/module/yunbei.js +6 -6
- package/module/yunbei_expense.js +8 -8
- package/module/yunbei_info.js +5 -5
- package/module/yunbei_rcmd_song.js +17 -17
- package/module/yunbei_rcmd_song_history.js +16 -16
- package/module/yunbei_receipt.js +8 -8
- package/module/yunbei_sign.js +5 -5
- package/module/yunbei_task_finish.js +12 -12
- package/module/yunbei_tasks.js +9 -9
- package/module/yunbei_tasks_todo.js +9 -9
- package/module/yunbei_today.js +5 -5
- package/package.json +8 -8
- package/plugins/songUpload.js +109 -109
- package/plugins/upload.js +35 -35
- package/public/api.html +222 -222
- package/public/audio_match_demo/afp.js +1626 -1626
- package/public/audio_match_demo/afp.wasm.js +5 -5
- package/public/audio_match_demo/index.html +367 -367
- package/public/audio_match_demo/rec.js +49 -49
- package/public/avatar_update.html +326 -326
- package/public/cloud.html +578 -578
- package/public/docs/_coverpage.md +13 -13
- package/public/docs/home.md +5318 -5325
- package/public/docs/index.html +129 -47
- package/public/docs/logo.svg +6 -6
- package/public/docs/netease.png +0 -0
- package/public/docs/sw.js +90 -90
- package/public/eapi_decrypt.html +264 -264
- package/public/home.html +40 -40
- package/public/index.html +124 -124
- package/public/listen_together_host.html +552 -552
- package/public/login.html +219 -219
- package/public/playlist_cover_update.html +323 -323
- package/public/playlist_import.html +416 -416
- package/public/qrlogin-nocookie.html +199 -199
- package/public/qrlogin.html +199 -199
- package/public/static/2169.png +0 -0
- package/public/static/neteaselogo.png +0 -0
- package/public/unblock_test.html +153 -153
- package/public/voice_upload.html +326 -326
- package/server.js +454 -454
- package/util/apicache.js +836 -836
- package/util/client-sign.js +169 -169
- package/util/config.json +20 -20
- package/util/crypto.js +153 -153
- package/util/fileHelper.js +88 -88
- package/util/index.js +233 -233
- package/util/logger.js +42 -42
- package/util/memory-cache.js +71 -71
- package/util/option.js +14 -14
- package/util/request.js +367 -367
- package/module/comment_add.js +0 -15
- package/module/comment_reply.js +0 -13
package/server.js
CHANGED
|
@@ -1,454 +1,454 @@
|
|
|
1
|
-
require('dotenv').config()
|
|
2
|
-
const fs = require('fs')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const express = require('express')
|
|
5
|
-
const request = require('./util/request')
|
|
6
|
-
const packageJSON = require('./package.json')
|
|
7
|
-
const exec = require('child_process').exec
|
|
8
|
-
const cache = require('./util/apicache').middleware
|
|
9
|
-
const { cookieToJson } = require('./util/index')
|
|
10
|
-
const fileUpload = require('express-fileupload')
|
|
11
|
-
const decode = require('safe-decode-uri-component')
|
|
12
|
-
const logger = require('./util/logger.js')
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The version check result.
|
|
16
|
-
* @readonly
|
|
17
|
-
* @enum {number}
|
|
18
|
-
*/
|
|
19
|
-
const VERSION_CHECK_RESULT = {
|
|
20
|
-
FAILED: -1,
|
|
21
|
-
NOT_LATEST: 0,
|
|
22
|
-
LATEST: 1,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @typedef {{
|
|
27
|
-
* identifier?: string,
|
|
28
|
-
* route: string,
|
|
29
|
-
* module: any
|
|
30
|
-
* }} ModuleDefinition
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @typedef {{
|
|
35
|
-
* port?: number,
|
|
36
|
-
* host?: string,
|
|
37
|
-
* checkVersion?: boolean,
|
|
38
|
-
* moduleDefs?: ModuleDefinition[]
|
|
39
|
-
* }} NcmApiOptions
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @typedef {{
|
|
44
|
-
* status: VERSION_CHECK_RESULT,
|
|
45
|
-
* ourVersion?: string,
|
|
46
|
-
* npmVersion?: string,
|
|
47
|
-
* }} VersionCheckResult
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @typedef {{
|
|
52
|
-
* server?: import('http').Server,
|
|
53
|
-
* }} ExpressExtension
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get the module definitions dynamically.
|
|
58
|
-
*
|
|
59
|
-
* @param {string} modulesPath The path to modules (JS).
|
|
60
|
-
* @param {Record<string, string>} [specificRoute] The specific route of specific modules.
|
|
61
|
-
* @param {boolean} [doRequire] If true, require() the module directly.
|
|
62
|
-
* Otherwise, print out the module path. Default to true.
|
|
63
|
-
* @returns {Promise<ModuleDefinition[]>} The module definitions.
|
|
64
|
-
*
|
|
65
|
-
* @example getModuleDefinitions("./module", {"album_new.js": "/album/create"})
|
|
66
|
-
*/
|
|
67
|
-
async function getModulesDefinitions(
|
|
68
|
-
modulesPath,
|
|
69
|
-
specificRoute,
|
|
70
|
-
doRequire = true,
|
|
71
|
-
) {
|
|
72
|
-
const files = await fs.promises.readdir(modulesPath)
|
|
73
|
-
const parseRoute = (/** @type {string} */ fileName) =>
|
|
74
|
-
specificRoute && fileName in specificRoute
|
|
75
|
-
? specificRoute[fileName]
|
|
76
|
-
: `/${fileName.replace(/\.js$/i, '').replace(/_/g, '/')}`
|
|
77
|
-
|
|
78
|
-
const modules = files
|
|
79
|
-
.reverse()
|
|
80
|
-
.filter((file) => file.endsWith('.js'))
|
|
81
|
-
.map((file) => {
|
|
82
|
-
const identifier = file.split('.').shift()
|
|
83
|
-
const route = parseRoute(file)
|
|
84
|
-
const modulePath = path.join(modulesPath, file)
|
|
85
|
-
const module = doRequire ? require(modulePath) : modulePath
|
|
86
|
-
|
|
87
|
-
return { identifier, route, module }
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
return modules
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check if the version of this API is latest.
|
|
95
|
-
*
|
|
96
|
-
* @returns {Promise<VersionCheckResult>} If true, this API is up-to-date;
|
|
97
|
-
* otherwise, this API should be upgraded and you would
|
|
98
|
-
* need to notify users to upgrade it manually.
|
|
99
|
-
*/
|
|
100
|
-
async function checkVersion() {
|
|
101
|
-
return new Promise((resolve) => {
|
|
102
|
-
exec('npm info NeteaseCloudMusicApiEnhanced version', (err, stdout) => {
|
|
103
|
-
if (!err) {
|
|
104
|
-
let version = stdout.trim()
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* @param {VERSION_CHECK_RESULT} status
|
|
108
|
-
*/
|
|
109
|
-
const resolveStatus = (status) =>
|
|
110
|
-
resolve({
|
|
111
|
-
status,
|
|
112
|
-
ourVersion: packageJSON.version,
|
|
113
|
-
npmVersion: version,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
resolveStatus(
|
|
117
|
-
packageJSON.version < version
|
|
118
|
-
? VERSION_CHECK_RESULT.NOT_LATEST
|
|
119
|
-
: VERSION_CHECK_RESULT.LATEST,
|
|
120
|
-
)
|
|
121
|
-
} else {
|
|
122
|
-
resolve({
|
|
123
|
-
status: VERSION_CHECK_RESULT.FAILED,
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function parseCorsAllowOrigins(corsAllowOrigin) {
|
|
131
|
-
if (!corsAllowOrigin) {
|
|
132
|
-
return null
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const origins = corsAllowOrigin
|
|
136
|
-
.split(',')
|
|
137
|
-
.map((origin) => origin.trim())
|
|
138
|
-
.filter(Boolean)
|
|
139
|
-
|
|
140
|
-
return origins.length > 0 ? origins : null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function getCorsAllowOrigin(allowOrigins, requestOrigin) {
|
|
144
|
-
if (!allowOrigins) {
|
|
145
|
-
return requestOrigin || '*'
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (allowOrigins.includes('*')) {
|
|
149
|
-
return '*'
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (requestOrigin && allowOrigins.includes(requestOrigin)) {
|
|
153
|
-
return requestOrigin
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return null
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function createConsoleSpinner(message = '启动中') {
|
|
160
|
-
if (!process.stdout.isTTY) {
|
|
161
|
-
return {
|
|
162
|
-
stop() {},
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
167
|
-
let index = 0
|
|
168
|
-
process.stdout.write(`${frames[index]} ${message}...`)
|
|
169
|
-
const timer = setInterval(() => {
|
|
170
|
-
index = (index + 1) % frames.length
|
|
171
|
-
process.stdout.write(`\r${frames[index]} ${message}...`)
|
|
172
|
-
}, 80)
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
stop() {
|
|
176
|
-
clearInterval(timer)
|
|
177
|
-
process.stdout.write(`\r✔ ${message} 完成。\n`)
|
|
178
|
-
},
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Construct the server of NCM API.
|
|
184
|
-
*
|
|
185
|
-
* @param {ModuleDefinition[]} [moduleDefs] Customized module definitions [advanced]
|
|
186
|
-
* @returns {Promise<import("express").Express>} The server instance.
|
|
187
|
-
*/
|
|
188
|
-
async function constructServer(moduleDefs) {
|
|
189
|
-
const app = express()
|
|
190
|
-
const { CORS_ALLOW_ORIGIN } = process.env
|
|
191
|
-
const allowOrigins = parseCorsAllowOrigins(CORS_ALLOW_ORIGIN)
|
|
192
|
-
app.set('trust proxy', true)
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Serving static files
|
|
196
|
-
*/
|
|
197
|
-
app.use(express.static(path.join(__dirname, 'public')))
|
|
198
|
-
/**
|
|
199
|
-
* CORS & Preflight request
|
|
200
|
-
*/
|
|
201
|
-
app.use((req, res, next) => {
|
|
202
|
-
if (req.path !== '/' && !req.path.includes('.')) {
|
|
203
|
-
const corsAllowOrigin = getCorsAllowOrigin(
|
|
204
|
-
allowOrigins,
|
|
205
|
-
req.headers.origin,
|
|
206
|
-
)
|
|
207
|
-
const shouldSetVaryHeader = corsAllowOrigin && corsAllowOrigin !== '*'
|
|
208
|
-
res.set({
|
|
209
|
-
'Access-Control-Allow-Credentials': true,
|
|
210
|
-
...(corsAllowOrigin
|
|
211
|
-
? { 'Access-Control-Allow-Origin': corsAllowOrigin }
|
|
212
|
-
: {}),
|
|
213
|
-
...(shouldSetVaryHeader ? { Vary: 'Origin' } : {}),
|
|
214
|
-
'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
|
|
215
|
-
'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
|
|
216
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
req.method === 'OPTIONS' ? res.status(204).end() : next()
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Cookie Parser
|
|
224
|
-
*/
|
|
225
|
-
app.use((req, _, next) => {
|
|
226
|
-
req.cookies = {}
|
|
227
|
-
//;(req.headers.cookie || '').split(/\s*;\s*/).forEach((pair) => { // Polynomial regular expression //
|
|
228
|
-
;(req.headers.cookie || '').split(/;\s+|(?<!\s)\s+$/g).forEach((pair) => {
|
|
229
|
-
let crack = pair.indexOf('=')
|
|
230
|
-
if (crack < 1 || crack == pair.length - 1) return
|
|
231
|
-
req.cookies[decode(pair.slice(0, crack)).trim()] = decode(
|
|
232
|
-
pair.slice(crack + 1),
|
|
233
|
-
).trim()
|
|
234
|
-
})
|
|
235
|
-
next()
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Body Parser and File Upload
|
|
240
|
-
*/
|
|
241
|
-
const MAX_UPLOAD_SIZE_MB = 500
|
|
242
|
-
const MAX_UPLOAD_SIZE_BYTES = MAX_UPLOAD_SIZE_MB * 1024 * 1024
|
|
243
|
-
|
|
244
|
-
app.use(express.json({ limit: `${MAX_UPLOAD_SIZE_MB}mb` }))
|
|
245
|
-
app.use(
|
|
246
|
-
express.urlencoded({ extended: false, limit: `${MAX_UPLOAD_SIZE_MB}mb` }),
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
app.use(
|
|
250
|
-
fileUpload({
|
|
251
|
-
limits: {
|
|
252
|
-
fileSize: MAX_UPLOAD_SIZE_BYTES,
|
|
253
|
-
},
|
|
254
|
-
useTempFiles: true,
|
|
255
|
-
tempFileDir: require('os').tmpdir(),
|
|
256
|
-
abortOnLimit: true,
|
|
257
|
-
parseNested: true,
|
|
258
|
-
}),
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Cache
|
|
263
|
-
*/
|
|
264
|
-
app.use(cache('2 minutes', (_, res) => res.statusCode === 200))
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Special Routers
|
|
268
|
-
*/
|
|
269
|
-
const special = {
|
|
270
|
-
'daily_signin.js': '/daily_signin',
|
|
271
|
-
'fm_trash.js': '/fm_trash',
|
|
272
|
-
'personal_fm.js': '/personal_fm',
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Load every modules in this directory
|
|
277
|
-
*/
|
|
278
|
-
const moduleDefinitions =
|
|
279
|
-
moduleDefs ||
|
|
280
|
-
(await getModulesDefinitions(path.join(__dirname, 'module'), special))
|
|
281
|
-
|
|
282
|
-
for (const moduleDef of moduleDefinitions) {
|
|
283
|
-
// Register the route.
|
|
284
|
-
app.all(moduleDef.route, async (req, res) => {
|
|
285
|
-
;[req.query, req.body].forEach((item) => {
|
|
286
|
-
// item may be undefined (some environments / middlewares).
|
|
287
|
-
// Guard access to avoid "Cannot read properties of undefined (reading 'cookie')".
|
|
288
|
-
if (item && typeof item.cookie === 'string') {
|
|
289
|
-
item.cookie = cookieToJson(decode(item.cookie))
|
|
290
|
-
}
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
let query = Object.assign(
|
|
294
|
-
{},
|
|
295
|
-
{ cookie: req.cookies },
|
|
296
|
-
req.query,
|
|
297
|
-
req.body,
|
|
298
|
-
req.files,
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const moduleResponse = await moduleDef.module(query, (...params) => {
|
|
303
|
-
// 参数注入客户端IP
|
|
304
|
-
const obj = [...params]
|
|
305
|
-
const options = obj[2] || {}
|
|
306
|
-
if (!options.randomCNIP) {
|
|
307
|
-
let ip = req.ip
|
|
308
|
-
|
|
309
|
-
if (ip.substring(0, 7) == '::ffff:') {
|
|
310
|
-
ip = ip.substring(7)
|
|
311
|
-
}
|
|
312
|
-
if (ip == '::1') {
|
|
313
|
-
ip = global.cnIp
|
|
314
|
-
}
|
|
315
|
-
// logger.info('Requested from ip:', ip)
|
|
316
|
-
obj[2] = {
|
|
317
|
-
...options,
|
|
318
|
-
ip,
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return request(...obj)
|
|
323
|
-
})
|
|
324
|
-
logger.info(`Request Success: ${decode(req.originalUrl)}`)
|
|
325
|
-
|
|
326
|
-
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
|
327
|
-
if (
|
|
328
|
-
req.baseUrl === '/song/url/v1' &&
|
|
329
|
-
process.env.ENABLE_GENERAL_UNBLOCK === 'true'
|
|
330
|
-
) {
|
|
331
|
-
const song = moduleResponse.body.data[0]
|
|
332
|
-
if (
|
|
333
|
-
song.freeTrialInfo !== null ||
|
|
334
|
-
!song.url ||
|
|
335
|
-
[1, 4].includes(song.fee)
|
|
336
|
-
) {
|
|
337
|
-
const {
|
|
338
|
-
matchID,
|
|
339
|
-
} = require('@neteasecloudmusicapienhanced/unblockmusic-utils')
|
|
340
|
-
logger.info('Starting unblock(uses general unblock):', req.query.id)
|
|
341
|
-
const result = await matchID(req.query.id)
|
|
342
|
-
song.url = result.data.url
|
|
343
|
-
song.freeTrialInfo = null
|
|
344
|
-
logger.info('Unblock success! url:', song.url)
|
|
345
|
-
}
|
|
346
|
-
if (song.url && song.url.includes('kuwo')) {
|
|
347
|
-
const proxy = process.env.PROXY_URL
|
|
348
|
-
const useProxy = process.env.ENABLE_PROXY || 'false'
|
|
349
|
-
if (useProxy === 'true' && proxy) {
|
|
350
|
-
song.proxyUrl = proxy + song.url
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const cookies = moduleResponse.cookie
|
|
356
|
-
if (!query.noCookie) {
|
|
357
|
-
if (Array.isArray(cookies) && cookies.length > 0) {
|
|
358
|
-
if (req.protocol === 'https') {
|
|
359
|
-
// Try to fix CORS SameSite Problem
|
|
360
|
-
res.append(
|
|
361
|
-
'Set-Cookie',
|
|
362
|
-
cookies.map((cookie) => {
|
|
363
|
-
return cookie + '; SameSite=None; Secure'
|
|
364
|
-
}),
|
|
365
|
-
)
|
|
366
|
-
} else {
|
|
367
|
-
res.append('Set-Cookie', cookies)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (moduleResponse.redirectUrl) {
|
|
372
|
-
res.redirect(moduleResponse.status || 302, moduleResponse.redirectUrl)
|
|
373
|
-
return
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
res.status(moduleResponse.status).send(moduleResponse.body)
|
|
377
|
-
} catch (/** @type {*} */ moduleResponse) {
|
|
378
|
-
logger.error(`${decode(req.originalUrl)}`, {
|
|
379
|
-
status: moduleResponse.status,
|
|
380
|
-
body: moduleResponse.body,
|
|
381
|
-
})
|
|
382
|
-
if (!moduleResponse.body) {
|
|
383
|
-
res.status(404).send({
|
|
384
|
-
code: 404,
|
|
385
|
-
data: null,
|
|
386
|
-
msg: 'Not Found',
|
|
387
|
-
})
|
|
388
|
-
return
|
|
389
|
-
}
|
|
390
|
-
if (moduleResponse.body.code == '301')
|
|
391
|
-
moduleResponse.body.msg = '需要登录'
|
|
392
|
-
if (!query.noCookie) {
|
|
393
|
-
res.append('Set-Cookie', moduleResponse.cookie)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
res.status(moduleResponse.status).send(moduleResponse.body)
|
|
397
|
-
}
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return app
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Serve the NCM API.
|
|
406
|
-
* @param {NcmApiOptions} options
|
|
407
|
-
* @returns {Promise<import('express').Express & ExpressExtension>}
|
|
408
|
-
*/
|
|
409
|
-
async function serveNcmApi(options) {
|
|
410
|
-
const port = Number(options.port || process.env.PORT || '3000')
|
|
411
|
-
const host = options.host || process.env.HOST || ''
|
|
412
|
-
|
|
413
|
-
const spinner = createConsoleSpinner('服务启动中')
|
|
414
|
-
|
|
415
|
-
const checkVersionSubmission =
|
|
416
|
-
options.checkVersion &&
|
|
417
|
-
checkVersion().then(({ npmVersion, ourVersion, status }) => {
|
|
418
|
-
if (status == VERSION_CHECK_RESULT.NOT_LATEST) {
|
|
419
|
-
logger.info(
|
|
420
|
-
`最新版本: ${npmVersion}, 当前版本: ${ourVersion}, 请及时更新`,
|
|
421
|
-
)
|
|
422
|
-
}
|
|
423
|
-
})
|
|
424
|
-
const constructServerSubmission = constructServer(options.moduleDefs)
|
|
425
|
-
|
|
426
|
-
const [_, app] = await Promise.all([
|
|
427
|
-
checkVersionSubmission,
|
|
428
|
-
constructServerSubmission,
|
|
429
|
-
])
|
|
430
|
-
|
|
431
|
-
spinner.stop()
|
|
432
|
-
|
|
433
|
-
/** @type {import('express').Express & ExpressExtension} */
|
|
434
|
-
const appExt = app
|
|
435
|
-
appExt.server = app.listen(port, host, () => {
|
|
436
|
-
console.log(`
|
|
437
|
-
╔═╗╔═╗╦ ╔═╗╔╗╔╦ ╦╔═╗╔╗╔╔═╗╔═╗╔╦╗
|
|
438
|
-
╠═╣╠═╝║ ║╣ ║║║╠═╣╠═╣║║║║ ║╣ ║║
|
|
439
|
-
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
|
440
|
-
`)
|
|
441
|
-
logger.info(`
|
|
442
|
-
- Server started successfully @ http://${host ? host : 'localhost'}:${port}
|
|
443
|
-
- Environment: ${process.env.NODE_ENV || 'development'}
|
|
444
|
-
- Node Version: ${process.version}
|
|
445
|
-
- Process ID: ${process.pid}`)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
return appExt
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
module.exports = {
|
|
452
|
-
serveNcmApi,
|
|
453
|
-
getModulesDefinitions,
|
|
454
|
-
}
|
|
1
|
+
require('dotenv').config()
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const express = require('express')
|
|
5
|
+
const request = require('./util/request')
|
|
6
|
+
const packageJSON = require('./package.json')
|
|
7
|
+
const exec = require('child_process').exec
|
|
8
|
+
const cache = require('./util/apicache').middleware
|
|
9
|
+
const { cookieToJson } = require('./util/index')
|
|
10
|
+
const fileUpload = require('express-fileupload')
|
|
11
|
+
const decode = require('safe-decode-uri-component')
|
|
12
|
+
const logger = require('./util/logger.js')
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The version check result.
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {number}
|
|
18
|
+
*/
|
|
19
|
+
const VERSION_CHECK_RESULT = {
|
|
20
|
+
FAILED: -1,
|
|
21
|
+
NOT_LATEST: 0,
|
|
22
|
+
LATEST: 1,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{
|
|
27
|
+
* identifier?: string,
|
|
28
|
+
* route: string,
|
|
29
|
+
* module: any
|
|
30
|
+
* }} ModuleDefinition
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {{
|
|
35
|
+
* port?: number,
|
|
36
|
+
* host?: string,
|
|
37
|
+
* checkVersion?: boolean,
|
|
38
|
+
* moduleDefs?: ModuleDefinition[]
|
|
39
|
+
* }} NcmApiOptions
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {{
|
|
44
|
+
* status: VERSION_CHECK_RESULT,
|
|
45
|
+
* ourVersion?: string,
|
|
46
|
+
* npmVersion?: string,
|
|
47
|
+
* }} VersionCheckResult
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {{
|
|
52
|
+
* server?: import('http').Server,
|
|
53
|
+
* }} ExpressExtension
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the module definitions dynamically.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} modulesPath The path to modules (JS).
|
|
60
|
+
* @param {Record<string, string>} [specificRoute] The specific route of specific modules.
|
|
61
|
+
* @param {boolean} [doRequire] If true, require() the module directly.
|
|
62
|
+
* Otherwise, print out the module path. Default to true.
|
|
63
|
+
* @returns {Promise<ModuleDefinition[]>} The module definitions.
|
|
64
|
+
*
|
|
65
|
+
* @example getModuleDefinitions("./module", {"album_new.js": "/album/create"})
|
|
66
|
+
*/
|
|
67
|
+
async function getModulesDefinitions(
|
|
68
|
+
modulesPath,
|
|
69
|
+
specificRoute,
|
|
70
|
+
doRequire = true,
|
|
71
|
+
) {
|
|
72
|
+
const files = await fs.promises.readdir(modulesPath)
|
|
73
|
+
const parseRoute = (/** @type {string} */ fileName) =>
|
|
74
|
+
specificRoute && fileName in specificRoute
|
|
75
|
+
? specificRoute[fileName]
|
|
76
|
+
: `/${fileName.replace(/\.js$/i, '').replace(/_/g, '/')}`
|
|
77
|
+
|
|
78
|
+
const modules = files
|
|
79
|
+
.reverse()
|
|
80
|
+
.filter((file) => file.endsWith('.js'))
|
|
81
|
+
.map((file) => {
|
|
82
|
+
const identifier = file.split('.').shift()
|
|
83
|
+
const route = parseRoute(file)
|
|
84
|
+
const modulePath = path.join(modulesPath, file)
|
|
85
|
+
const module = doRequire ? require(modulePath) : modulePath
|
|
86
|
+
|
|
87
|
+
return { identifier, route, module }
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return modules
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if the version of this API is latest.
|
|
95
|
+
*
|
|
96
|
+
* @returns {Promise<VersionCheckResult>} If true, this API is up-to-date;
|
|
97
|
+
* otherwise, this API should be upgraded and you would
|
|
98
|
+
* need to notify users to upgrade it manually.
|
|
99
|
+
*/
|
|
100
|
+
async function checkVersion() {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
exec('npm info NeteaseCloudMusicApiEnhanced version', (err, stdout) => {
|
|
103
|
+
if (!err) {
|
|
104
|
+
let version = stdout.trim()
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {VERSION_CHECK_RESULT} status
|
|
108
|
+
*/
|
|
109
|
+
const resolveStatus = (status) =>
|
|
110
|
+
resolve({
|
|
111
|
+
status,
|
|
112
|
+
ourVersion: packageJSON.version,
|
|
113
|
+
npmVersion: version,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
resolveStatus(
|
|
117
|
+
packageJSON.version < version
|
|
118
|
+
? VERSION_CHECK_RESULT.NOT_LATEST
|
|
119
|
+
: VERSION_CHECK_RESULT.LATEST,
|
|
120
|
+
)
|
|
121
|
+
} else {
|
|
122
|
+
resolve({
|
|
123
|
+
status: VERSION_CHECK_RESULT.FAILED,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseCorsAllowOrigins(corsAllowOrigin) {
|
|
131
|
+
if (!corsAllowOrigin) {
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const origins = corsAllowOrigin
|
|
136
|
+
.split(',')
|
|
137
|
+
.map((origin) => origin.trim())
|
|
138
|
+
.filter(Boolean)
|
|
139
|
+
|
|
140
|
+
return origins.length > 0 ? origins : null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getCorsAllowOrigin(allowOrigins, requestOrigin) {
|
|
144
|
+
if (!allowOrigins) {
|
|
145
|
+
return requestOrigin || '*'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (allowOrigins.includes('*')) {
|
|
149
|
+
return '*'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (requestOrigin && allowOrigins.includes(requestOrigin)) {
|
|
153
|
+
return requestOrigin
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createConsoleSpinner(message = '启动中') {
|
|
160
|
+
if (!process.stdout.isTTY) {
|
|
161
|
+
return {
|
|
162
|
+
stop() {},
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
167
|
+
let index = 0
|
|
168
|
+
process.stdout.write(`${frames[index]} ${message}...`)
|
|
169
|
+
const timer = setInterval(() => {
|
|
170
|
+
index = (index + 1) % frames.length
|
|
171
|
+
process.stdout.write(`\r${frames[index]} ${message}...`)
|
|
172
|
+
}, 80)
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
stop() {
|
|
176
|
+
clearInterval(timer)
|
|
177
|
+
process.stdout.write(`\r✔ ${message} 完成。\n`)
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Construct the server of NCM API.
|
|
184
|
+
*
|
|
185
|
+
* @param {ModuleDefinition[]} [moduleDefs] Customized module definitions [advanced]
|
|
186
|
+
* @returns {Promise<import("express").Express>} The server instance.
|
|
187
|
+
*/
|
|
188
|
+
async function constructServer(moduleDefs) {
|
|
189
|
+
const app = express()
|
|
190
|
+
const { CORS_ALLOW_ORIGIN } = process.env
|
|
191
|
+
const allowOrigins = parseCorsAllowOrigins(CORS_ALLOW_ORIGIN)
|
|
192
|
+
app.set('trust proxy', true)
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Serving static files
|
|
196
|
+
*/
|
|
197
|
+
app.use(express.static(path.join(__dirname, 'public')))
|
|
198
|
+
/**
|
|
199
|
+
* CORS & Preflight request
|
|
200
|
+
*/
|
|
201
|
+
app.use((req, res, next) => {
|
|
202
|
+
if (req.path !== '/' && !req.path.includes('.')) {
|
|
203
|
+
const corsAllowOrigin = getCorsAllowOrigin(
|
|
204
|
+
allowOrigins,
|
|
205
|
+
req.headers.origin,
|
|
206
|
+
)
|
|
207
|
+
const shouldSetVaryHeader = corsAllowOrigin && corsAllowOrigin !== '*'
|
|
208
|
+
res.set({
|
|
209
|
+
'Access-Control-Allow-Credentials': true,
|
|
210
|
+
...(corsAllowOrigin
|
|
211
|
+
? { 'Access-Control-Allow-Origin': corsAllowOrigin }
|
|
212
|
+
: {}),
|
|
213
|
+
...(shouldSetVaryHeader ? { Vary: 'Origin' } : {}),
|
|
214
|
+
'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
|
|
215
|
+
'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
|
|
216
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
req.method === 'OPTIONS' ? res.status(204).end() : next()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Cookie Parser
|
|
224
|
+
*/
|
|
225
|
+
app.use((req, _, next) => {
|
|
226
|
+
req.cookies = {}
|
|
227
|
+
//;(req.headers.cookie || '').split(/\s*;\s*/).forEach((pair) => { // Polynomial regular expression //
|
|
228
|
+
;(req.headers.cookie || '').split(/;\s+|(?<!\s)\s+$/g).forEach((pair) => {
|
|
229
|
+
let crack = pair.indexOf('=')
|
|
230
|
+
if (crack < 1 || crack == pair.length - 1) return
|
|
231
|
+
req.cookies[decode(pair.slice(0, crack)).trim()] = decode(
|
|
232
|
+
pair.slice(crack + 1),
|
|
233
|
+
).trim()
|
|
234
|
+
})
|
|
235
|
+
next()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Body Parser and File Upload
|
|
240
|
+
*/
|
|
241
|
+
const MAX_UPLOAD_SIZE_MB = 500
|
|
242
|
+
const MAX_UPLOAD_SIZE_BYTES = MAX_UPLOAD_SIZE_MB * 1024 * 1024
|
|
243
|
+
|
|
244
|
+
app.use(express.json({ limit: `${MAX_UPLOAD_SIZE_MB}mb` }))
|
|
245
|
+
app.use(
|
|
246
|
+
express.urlencoded({ extended: false, limit: `${MAX_UPLOAD_SIZE_MB}mb` }),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
app.use(
|
|
250
|
+
fileUpload({
|
|
251
|
+
limits: {
|
|
252
|
+
fileSize: MAX_UPLOAD_SIZE_BYTES,
|
|
253
|
+
},
|
|
254
|
+
useTempFiles: true,
|
|
255
|
+
tempFileDir: require('os').tmpdir(),
|
|
256
|
+
abortOnLimit: true,
|
|
257
|
+
parseNested: true,
|
|
258
|
+
}),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Cache
|
|
263
|
+
*/
|
|
264
|
+
app.use(cache('2 minutes', (_, res) => res.statusCode === 200))
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Special Routers
|
|
268
|
+
*/
|
|
269
|
+
const special = {
|
|
270
|
+
'daily_signin.js': '/daily_signin',
|
|
271
|
+
'fm_trash.js': '/fm_trash',
|
|
272
|
+
'personal_fm.js': '/personal_fm',
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Load every modules in this directory
|
|
277
|
+
*/
|
|
278
|
+
const moduleDefinitions =
|
|
279
|
+
moduleDefs ||
|
|
280
|
+
(await getModulesDefinitions(path.join(__dirname, 'module'), special))
|
|
281
|
+
|
|
282
|
+
for (const moduleDef of moduleDefinitions) {
|
|
283
|
+
// Register the route.
|
|
284
|
+
app.all(moduleDef.route, async (req, res) => {
|
|
285
|
+
;[req.query, req.body].forEach((item) => {
|
|
286
|
+
// item may be undefined (some environments / middlewares).
|
|
287
|
+
// Guard access to avoid "Cannot read properties of undefined (reading 'cookie')".
|
|
288
|
+
if (item && typeof item.cookie === 'string') {
|
|
289
|
+
item.cookie = cookieToJson(decode(item.cookie))
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
let query = Object.assign(
|
|
294
|
+
{},
|
|
295
|
+
{ cookie: req.cookies },
|
|
296
|
+
req.query,
|
|
297
|
+
req.body,
|
|
298
|
+
req.files,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const moduleResponse = await moduleDef.module(query, (...params) => {
|
|
303
|
+
// 参数注入客户端IP
|
|
304
|
+
const obj = [...params]
|
|
305
|
+
const options = obj[2] || {}
|
|
306
|
+
if (!options.randomCNIP) {
|
|
307
|
+
let ip = req.ip
|
|
308
|
+
|
|
309
|
+
if (ip.substring(0, 7) == '::ffff:') {
|
|
310
|
+
ip = ip.substring(7)
|
|
311
|
+
}
|
|
312
|
+
if (ip == '::1') {
|
|
313
|
+
ip = global.cnIp
|
|
314
|
+
}
|
|
315
|
+
// logger.info('Requested from ip:', ip)
|
|
316
|
+
obj[2] = {
|
|
317
|
+
...options,
|
|
318
|
+
ip,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return request(...obj)
|
|
323
|
+
})
|
|
324
|
+
logger.info(`Request Success: ${decode(req.originalUrl)}`)
|
|
325
|
+
|
|
326
|
+
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
|
327
|
+
if (
|
|
328
|
+
req.baseUrl === '/song/url/v1' &&
|
|
329
|
+
process.env.ENABLE_GENERAL_UNBLOCK === 'true'
|
|
330
|
+
) {
|
|
331
|
+
const song = moduleResponse.body.data[0]
|
|
332
|
+
if (
|
|
333
|
+
song.freeTrialInfo !== null ||
|
|
334
|
+
!song.url ||
|
|
335
|
+
[1, 4].includes(song.fee)
|
|
336
|
+
) {
|
|
337
|
+
const {
|
|
338
|
+
matchID,
|
|
339
|
+
} = require('@neteasecloudmusicapienhanced/unblockmusic-utils')
|
|
340
|
+
logger.info('Starting unblock(uses general unblock):', req.query.id)
|
|
341
|
+
const result = await matchID(req.query.id)
|
|
342
|
+
song.url = result.data.url
|
|
343
|
+
song.freeTrialInfo = null
|
|
344
|
+
logger.info('Unblock success! url:', song.url)
|
|
345
|
+
}
|
|
346
|
+
if (song.url && song.url.includes('kuwo')) {
|
|
347
|
+
const proxy = process.env.PROXY_URL
|
|
348
|
+
const useProxy = process.env.ENABLE_PROXY || 'false'
|
|
349
|
+
if (useProxy === 'true' && proxy) {
|
|
350
|
+
song.proxyUrl = proxy + song.url
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const cookies = moduleResponse.cookie
|
|
356
|
+
if (!query.noCookie) {
|
|
357
|
+
if (Array.isArray(cookies) && cookies.length > 0) {
|
|
358
|
+
if (req.protocol === 'https') {
|
|
359
|
+
// Try to fix CORS SameSite Problem
|
|
360
|
+
res.append(
|
|
361
|
+
'Set-Cookie',
|
|
362
|
+
cookies.map((cookie) => {
|
|
363
|
+
return cookie + '; SameSite=None; Secure'
|
|
364
|
+
}),
|
|
365
|
+
)
|
|
366
|
+
} else {
|
|
367
|
+
res.append('Set-Cookie', cookies)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (moduleResponse.redirectUrl) {
|
|
372
|
+
res.redirect(moduleResponse.status || 302, moduleResponse.redirectUrl)
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
res.status(moduleResponse.status).send(moduleResponse.body)
|
|
377
|
+
} catch (/** @type {*} */ moduleResponse) {
|
|
378
|
+
logger.error(`${decode(req.originalUrl)}`, {
|
|
379
|
+
status: moduleResponse.status,
|
|
380
|
+
body: moduleResponse.body,
|
|
381
|
+
})
|
|
382
|
+
if (!moduleResponse.body) {
|
|
383
|
+
res.status(404).send({
|
|
384
|
+
code: 404,
|
|
385
|
+
data: null,
|
|
386
|
+
msg: 'Not Found',
|
|
387
|
+
})
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
if (moduleResponse.body.code == '301')
|
|
391
|
+
moduleResponse.body.msg = '需要登录'
|
|
392
|
+
if (!query.noCookie) {
|
|
393
|
+
res.append('Set-Cookie', moduleResponse.cookie)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
res.status(moduleResponse.status).send(moduleResponse.body)
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return app
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Serve the NCM API.
|
|
406
|
+
* @param {NcmApiOptions} options
|
|
407
|
+
* @returns {Promise<import('express').Express & ExpressExtension>}
|
|
408
|
+
*/
|
|
409
|
+
async function serveNcmApi(options) {
|
|
410
|
+
const port = Number(options.port || process.env.PORT || '3000')
|
|
411
|
+
const host = options.host || process.env.HOST || ''
|
|
412
|
+
|
|
413
|
+
const spinner = createConsoleSpinner('服务启动中')
|
|
414
|
+
|
|
415
|
+
const checkVersionSubmission =
|
|
416
|
+
options.checkVersion &&
|
|
417
|
+
checkVersion().then(({ npmVersion, ourVersion, status }) => {
|
|
418
|
+
if (status == VERSION_CHECK_RESULT.NOT_LATEST) {
|
|
419
|
+
logger.info(
|
|
420
|
+
`最新版本: ${npmVersion}, 当前版本: ${ourVersion}, 请及时更新`,
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
const constructServerSubmission = constructServer(options.moduleDefs)
|
|
425
|
+
|
|
426
|
+
const [_, app] = await Promise.all([
|
|
427
|
+
checkVersionSubmission,
|
|
428
|
+
constructServerSubmission,
|
|
429
|
+
])
|
|
430
|
+
|
|
431
|
+
spinner.stop()
|
|
432
|
+
|
|
433
|
+
/** @type {import('express').Express & ExpressExtension} */
|
|
434
|
+
const appExt = app
|
|
435
|
+
appExt.server = app.listen(port, host, () => {
|
|
436
|
+
console.log(`
|
|
437
|
+
╔═╗╔═╗╦ ╔═╗╔╗╔╦ ╦╔═╗╔╗╔╔═╗╔═╗╔╦╗
|
|
438
|
+
╠═╣╠═╝║ ║╣ ║║║╠═╣╠═╣║║║║ ║╣ ║║
|
|
439
|
+
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
|
440
|
+
`)
|
|
441
|
+
logger.info(`
|
|
442
|
+
- Server started successfully @ http://${host ? host : 'localhost'}:${port}
|
|
443
|
+
- Environment: ${process.env.NODE_ENV || 'development'}
|
|
444
|
+
- Node Version: ${process.version}
|
|
445
|
+
- Process ID: ${process.pid}`)
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
return appExt
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
module.exports = {
|
|
452
|
+
serveNcmApi,
|
|
453
|
+
getModulesDefinitions,
|
|
454
|
+
}
|