From e9137a41c68ce805e679b20e3f4b0d607b5f9182 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Fri, 25 Jul 2025 19:33:45 +0100 Subject: [PATCH] Add received/sent friend requests to API proxy --- src/api/znc-proxy.ts | 13 +++++++++- src/cli/nso/http-server.ts | 50 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/api/znc-proxy.ts b/src/api/znc-proxy.ts index c63d436..6cdabbd 100644 --- a/src/api/znc-proxy.ts +++ b/src/api/znc-proxy.ts @@ -1,5 +1,5 @@ import { fetch, Response } from 'undici'; -import { ActiveEvent, CurrentUser, Event, Friend, Presence, PresencePermissions, User, WebServiceToken, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl, WebService_4, Media, Announcements_4, Friend_4, PresenceOnline_4, PresenceOnline, PresenceOffline, GetActiveEventResult } from './coral-types.js'; +import { ActiveEvent, CurrentUser, Event, Friend, PresencePermissions, User, WebServiceToken, CoralStatus, CoralSuccessResponse, FriendCodeUser, FriendCodeUrl, WebService_4, Media, Announcements_4, Friend_4, PresenceOnline_4, PresenceOnline, PresenceOffline, GetActiveEventResult, ReceivedFriendRequest, SentFriendRequest } from './coral-types.js'; import { defineResponse, ErrorResponse, ResponseSymbol } from './util.js'; import { AbstractCoralApi, CoralApiInterface, CoralAuthData, CorrelationIdSymbol, PartialCoralAuthData, RequestFlagAddPlatformSymbol, RequestFlagAddProductVersionSymbol, RequestFlagNoParameterSymbol, RequestFlagRequestIdSymbol, RequestFlags, ResponseDataSymbol, ResponseEncryptionSymbol, Result } from './coral.js'; import { NintendoAccountToken, NintendoAccountUser } from './na.js'; @@ -139,6 +139,16 @@ export default class ZncProxyApi extends AbstractCoralApi implements CoralApiInt return createResult(result, result.user); } + async getReceivedFriendRequests() { + const result = await this.fetchProxyApi<{friend_requests: ReceivedFriendRequest[]}>('friends/requests/received'); + return createResult(result, {friendRequests: result.friend_requests}); + } + + async getSentFriendRequests() { + const result = await this.fetchProxyApi<{friend_requests: SentFriendRequest[]}>('friends/requests/sent'); + return createResult(result, {friendRequests: result.friend_requests}); + } + async getFriendCodeUrl() { const result = await this.fetchProxyApi<{friendcode: FriendCodeUrl}>('friendcode'); return createResult(result, result.friendcode); @@ -233,6 +243,7 @@ export interface AuthPolicy { list_friends_presence?: boolean; friend?: boolean; friend_presence?: boolean; + list_friend_requests?: boolean; webservices?: boolean; activeevent?: boolean; chats?: boolean; diff --git a/src/cli/nso/http-server.ts b/src/cli/nso/http-server.ts index 2854501..ea19682 100644 --- a/src/cli/nso/http-server.ts +++ b/src/cli/nso/http-server.ts @@ -164,6 +164,11 @@ class Server extends HttpServer { app.get('/api/znc/friend/:nsaid/presence', this.authTokenMiddleware, this.localAuthMiddleware, this.createProxyRequestHandler(r => this.handleFriendPresenceRequest(r, r.req.params.nsaid))); + app.get('/api/znc/friends/requests/received', this.authTokenMiddleware, this.localAuthMiddleware, + this.createProxyRequestHandler(r => this.handleReceivedFriendRequestsRequest(r))); + app.get('/api/znc/friends/requests/sent', this.authTokenMiddleware, this.localAuthMiddleware, + this.createProxyRequestHandler(r => this.handleSentFriendRequestsRequest(r))); + app.get('/api/znc/webservices', this.authTokenMiddleware, this.localAuthMiddleware, this.createProxyRequestHandler(r => this.handleWebServicesRequest(r))); app.get('/api/znc/webservice/:id/token', @@ -665,6 +670,34 @@ class Server extends HttpServer { return friend.presence; } + async handleReceivedFriendRequestsRequest({req, res, policy}: RequestData) { + if (policy && !policy.list_friend_requests) { + throw new ResponseError(403, 'insufficient_scope'); + } + + const user = await this.getCoralUser(req); + + const friend_requests = await user.getReceivedFriendRequests(); + const updated = user.updated.fr_received!; + + res.setHeader('Cache-Control', 'private, immutable, max-age=' + cacheMaxAge(updated, this.update_interval)); + return {friend_requests, updated}; + } + + async handleSentFriendRequestsRequest({req, res, policy}: RequestData) { + if (policy && !policy.list_friend_requests) { + throw new ResponseError(403, 'insufficient_scope'); + } + + const user = await this.getCoralUser(req); + + const friend_requests = await user.getSentFriendRequests(); + const updated = user.updated.fr_sent!; + + res.setHeader('Cache-Control', 'private, immutable, max-age=' + cacheMaxAge(updated, this.update_interval)); + return {friend_requests, updated}; + } + async handleWebServicesRequest({req, res, policy}: RequestData) { if (policy && !policy.webservices) { throw new ResponseError(403, 'insufficient_scope'); @@ -766,6 +799,12 @@ class Server extends HttpServer { try { return await this._cache(friendcode, async (): Promise<[FriendCodeUser | null, string]> => { try { + // Always requested on the add friend page + Promise.all([ + coral.getReceivedFriendRequests(), + coral.getSentFriendRequests(), + ]); + const user = await coral.getUserByFriendCode(friendcode); return [user, id]; } catch (err) { @@ -800,8 +839,15 @@ class Server extends HttpServer { private cached_friendcodeurl = new Map(); getFriendCodeUrl(id: string, coral: CoralApiInterface) { - return this._cache(id, () => coral.getFriendCodeUrl(), - this.user_friendcodeurl_promise, this.cached_friendcodeurl); + return this._cache(id, async () => { + // Always requested on the add friend page + Promise.all([ + coral.getReceivedFriendRequests(), + coral.getSentFriendRequests(), + ]); + + return coral.getFriendCodeUrl(); + }, this.user_friendcodeurl_promise, this.cached_friendcodeurl); } async handleFriendCodeUrlRequest({res, user}: RequestDataWithUser) {