Skip to content

Commit 0ecebcd

Browse files
committed
fix(hook): show local VIP badge by patching the account endpoints
1 parent b8bf8dc commit 0ecebcd

1 file changed

Lines changed: 111 additions & 52 deletions

File tree

‎src/hook.js‎

Lines changed: 111 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,23 @@ hook.target.path = new Set([
122122
'/api/vipauth/app/auth/query',
123123
'/api/music-vip-membership/client/vip/info',
124124
'/api/music-vip-membership/front/vip/info',
125+
'/api/nuser/account/get',
126+
'/api/w/nuser/account/get',
125127
]);
126128

127-
// The two interchangeable endpoints the client reads the membership state
128-
// from. `client` is the one the app has always used; `front` is the newer
129-
// variant, and either may show up standalone or inside a `/batch`.
129+
// The membership state reaches the client on two unrelated responses, and both
130+
// have to agree or the status looks unchanged: the entitlement endpoint says
131+
// what the account may play, while the account endpoint carries the flag the
132+
// badge is drawn from. Each has an older and a newer variant that are both
133+
// still served, and any of them may show up standalone or inside a `/batch`.
130134
const VIP_INFO_PATHS = [
131135
'/api/music-vip-membership/client/vip/info',
132136
'/api/music-vip-membership/front/vip/info',
133137
];
138+
const VIP_ACCOUNT_PATHS = [
139+
'/api/nuser/account/get',
140+
'/api/w/nuser/account/get',
141+
];
134142

135143
const domainList = [
136144
'music.163.com',
@@ -621,15 +629,56 @@ hook.negotiate.before = (ctx) => {
621629
};
622630

623631
/**
624-
* Rewrite a membership response so the client believes it is a paying member.
625-
* The state may arrive on its own or as one entry of a `/batch`, under either
626-
* of the two interchangeable endpoints.
632+
* Rewrite the membership responses so the client believes it is a paying
633+
* member. This has to cover both signals: the entitlement endpoint the player
634+
* consults for playback, and the account endpoint the VIP badge is drawn from.
635+
* Either may arrive on its own or as one entry of a `/batch`.
627636
*
628637
* @param {{ path: string, jsonBody: Record<string, any> }} netease
629638
*/
630639
const applyLocalVip = (netease) => {
631640
const batched = netease.path === '/batch' || netease.path === '/api/batch';
632-
if (!batched && !VIP_INFO_PATHS.includes(netease.path)) return;
641+
const entry = (path) =>
642+
batched
643+
? netease.jsonBody[path]
644+
: netease.path === path
645+
? netease.jsonBody
646+
: undefined;
647+
const store = (path, value) => {
648+
if (batched) netease.jsonBody[path] = value;
649+
else netease.jsonBody = value;
650+
};
651+
652+
VIP_INFO_PATHS.forEach((path) => {
653+
const info = entry(path);
654+
if (info === undefined) return;
655+
if (patchVipEntitlement(info)) {
656+
store(path, info);
657+
logger.debug(`Applied the local VIP on ${path}.`);
658+
}
659+
});
660+
661+
VIP_ACCOUNT_PATHS.forEach((path) => {
662+
const account = entry(path);
663+
if (account === undefined) return;
664+
if (patchVipAccount(account)) {
665+
store(path, account);
666+
logger.debug(`Applied the local VIP on ${path}.`);
667+
}
668+
});
669+
};
670+
671+
/**
672+
* The entitlement response: `associator`/`redplus`/`musicPackage` describe what
673+
* the account is allowed to play. `redplus` is the SVIP tier.
674+
*
675+
* @param {Record<string, any>} info
676+
* @return {boolean} Whether the response was patched.
677+
*/
678+
const patchVipEntitlement = (info) => {
679+
if (!info || !info.data) return false;
680+
if (LOCAL_VIP_UID.length !== 0 && !LOCAL_VIP_UID.includes(info.data.userId))
681+
return false;
633682

634683
const defaultPackage = {
635684
iconUrl: null,
@@ -641,63 +690,73 @@ const applyLocalVip = (netease) => {
641690
};
642691
const vipLevel = 7; // ? months
643692

644-
VIP_INFO_PATHS.forEach((vipPath) => {
645-
if (!batched && netease.path !== vipPath) return;
646-
const info = batched ? netease.jsonBody[vipPath] : netease.jsonBody;
647-
if (!info || !info.data) return;
648-
if (
649-
LOCAL_VIP_UID.length !== 0 &&
650-
!LOCAL_VIP_UID.includes(info.data.userId)
651-
)
652-
return;
693+
try {
694+
const nowTime = info.data.now || new Date().getTime();
695+
const expireTime = nowTime + 31622400000;
696+
info.data.redVipLevel = vipLevel;
697+
info.data.redVipAnnualCount = 1;
653698

654-
try {
655-
const nowTime = info.data.now || new Date().getTime();
656-
const expireTime = nowTime + 31622400000;
657-
info.data.redVipLevel = vipLevel;
658-
info.data.redVipAnnualCount = 1;
699+
info.data.musicPackage = {
700+
...defaultPackage,
701+
...info.data.musicPackage,
702+
vipCode: 230,
703+
vipLevel,
704+
expireTime,
705+
};
706+
707+
info.data.associator = {
708+
...defaultPackage,
709+
...info.data.associator,
710+
vipCode: 100,
711+
vipLevel,
712+
expireTime,
713+
};
659714

660-
info.data.musicPackage = {
715+
if (ENABLE_LOCAL_SVIP) {
716+
info.data.redplus = {
661717
...defaultPackage,
662-
...info.data.musicPackage,
663-
vipCode: 230,
718+
...info.data.redplus,
719+
vipCode: 300,
664720
vipLevel,
665721
expireTime,
666722
};
667723

668-
info.data.associator = {
724+
info.data.albumVip = {
669725
...defaultPackage,
670-
...info.data.associator,
671-
vipCode: 100,
672-
vipLevel,
726+
...info.data.albumVip,
727+
vipCode: 400,
728+
vipLevel: 0,
673729
expireTime,
674730
};
731+
}
732+
return true;
733+
} catch (error) {
734+
logger.debug({ err: error }, 'Unable to apply the local VIP.');
735+
return false;
736+
}
737+
};
675738

676-
if (ENABLE_LOCAL_SVIP) {
677-
info.data.redplus = {
678-
...defaultPackage,
679-
...info.data.redplus,
680-
vipCode: 300,
681-
vipLevel,
682-
expireTime,
683-
};
684-
685-
info.data.albumVip = {
686-
...defaultPackage,
687-
...info.data.albumVip,
688-
vipCode: 400,
689-
vipLevel: 0,
690-
expireTime,
691-
};
692-
}
739+
/**
740+
* The account response: `profile.vipType` is the flag the client draws the VIP
741+
* badge from. `11` is the ordinary black-vinyl VIP, `100` the SVIP tier. Left
742+
* untouched, the entitlement patch above shows nothing on the profile itself.
743+
*
744+
* @param {Record<string, any>} account
745+
* @return {boolean} Whether the response was patched.
746+
*/
747+
const patchVipAccount = (account) => {
748+
const profile = account && account.profile;
749+
if (!profile) return false;
750+
if (LOCAL_VIP_UID.length !== 0 && !LOCAL_VIP_UID.includes(profile.userId))
751+
return false;
693752

694-
if (batched) netease.jsonBody[vipPath] = info;
695-
else netease.jsonBody = info;
696-
logger.debug(`Applied the local VIP on ${vipPath}.`);
697-
} catch (error) {
698-
logger.debug({ err: error }, 'Unable to apply the local VIP.');
699-
}
700-
});
753+
try {
754+
profile.vipType = ENABLE_LOCAL_SVIP ? 100 : 11;
755+
return true;
756+
} catch (error) {
757+
logger.debug({ err: error }, 'Unable to apply the local VIP account.');
758+
return false;
759+
}
701760
};
702761

703762
const pretendPlay = (ctx) => {

0 commit comments

Comments
 (0)