diff --git a/apps/api/package.json b/apps/api/package.json deleted file mode 100644 index 3ef6b9448..000000000 --- a/apps/api/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@npwd/api", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "scripts": { - "dev1": "cross-env IS_BROWSER_DEV=1 nodemon src/index.ts" - }, - "devDependencies": { - "@types/body-parser": "^1.19.2", - "@types/cors": "^2.8.13", - "@types/express": "^4.17.17", - "@types/node": "^18.13.0", - "cross-env": "^7.0.3", - "nodemon": "^2.0.20", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - }, - "dependencies": { - "@npwd/database": "workspace:^1.0.0", - "body-parser": "^1.20.1", - "cors": "^2.8.5", - "express": "^4.18.2", - "mysql2": "^3.1.2" - } -} diff --git a/apps/api/src/contacts/contact-service.ts b/apps/api/src/contacts/contact-service.ts deleted file mode 100644 index bb87bfc90..000000000 --- a/apps/api/src/contacts/contact-service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ContactsDB, _ContactsDB } from '@npwd/database'; -import { Contact } from '@typings/contact'; -import { PLAYER_IDENITIFER } from '../database/data-source'; - -export class ContactService { - private readonly _contactDB: _ContactsDB; - - constructor() { - this._contactDB = ContactsDB; - } - - async getContacts(): Promise { - const contacts = await this._contactDB.fetchAllContacts(PLAYER_IDENITIFER); - if (!contacts || contacts.length == 0) return null; - - return contacts; - } - - async addContact(contact: Contact): Promise { - console.log('CONTACT', contact); - - try { - const newContact = await this._contactDB.addContact(PLAYER_IDENITIFER, contact); - return newContact; - } catch (e) { - console.error(e); - return null; - } - } -} diff --git a/apps/api/src/database/data-source.ts b/apps/api/src/database/data-source.ts deleted file mode 100644 index 9adfcdafd..000000000 --- a/apps/api/src/database/data-source.ts +++ /dev/null @@ -1 +0,0 @@ -export const PLAYER_IDENITIFER = '1234'; diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts deleted file mode 100644 index fb3468933..000000000 --- a/apps/api/src/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import './database/data-source'; -import express from 'express'; -import { ContactService } from './contacts/contact-service'; -import cors from 'cors'; -import bodyParser from 'body-parser'; - -const app = express(); - -app.use(cors()); -app.use(bodyParser.json()); - -const contactService = new ContactService(); - -app.post('/npwd-contact-getAll', async (req, res) => { - const contacts = await contactService.getContacts(); - res.send({ status: 'ok', data: contacts }); -}); - -app.post('/npwd-contact-add', async (req, res) => { - const contact = req.body; - const newContact = await contactService.addContact(contact); - - res.json({ status: 'ok', data: newContact }); -}); - -app.listen(6001, () => console.log('Server is running')); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json deleted file mode 100644 index 47b82277e..000000000 --- a/apps/api/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "@typings/*": ["../../typings/*"], - "@game/*": ["../../apps/game/*"] - }, - "outDir": "./", - "noImplicitAny": true, - "module": "commonjs", - "target": "ES2020", - "allowJs": false, - "lib": ["es2020"], - "types": ["@types/node"], - "moduleResolution": "node", - "sourceMap": true, - "resolveJsonModule": true, - "esModuleInterop": true - }, - "include": ["./**/*"], - "exclude": ["**/node_modules"] -} diff --git a/apps/game/client/animations/animation.controller.ts b/apps/game/client/animations/animation.controller.ts deleted file mode 100644 index 163668eee..000000000 --- a/apps/game/client/animations/animation.controller.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AnimationService } from './animation.service'; - -export const animationService = new AnimationService(); diff --git a/apps/game/client/animations/animation.service.ts b/apps/game/client/animations/animation.service.ts deleted file mode 100644 index c1f3eaab3..000000000 --- a/apps/game/client/animations/animation.service.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { newPhoneProp, removePhoneProp } from '../functions'; -import { Delay } from '../../utils/fivem'; - -export enum AnimationState { - ON_CALL, - PHONE_OPEN, - ON_CAMERA, -} - -export class AnimationService { - private animationInterval: NodeJS.Timeout; - private onCall = false; - private phoneOpen = false; - private onCamera = false; - - private createAnimationInterval() { - this.animationInterval = setInterval(async () => { - const playerPed = PlayerPedId(); - if (this.onCall) { - this.handleCallAnimation(playerPed); - } else if (this.phoneOpen && !this.onCamera) { - this.handleOpenAnimation(playerPed); - } - }, 250); - } - - private setPhoneState(state: AnimationState, stateValue: boolean) { - switch (state) { - case AnimationState.ON_CALL: - this.onCall = stateValue; - break; - case AnimationState.PHONE_OPEN: - this.phoneOpen = stateValue; - break; - case AnimationState.ON_CAMERA: - this.onCamera = stateValue; - break; - } - - if (!this.onCall && !this.phoneOpen) { - if (this.animationInterval) { - clearInterval(this.animationInterval); - this.animationInterval = null; - } - } else if (!this.animationInterval) { - this.createAnimationInterval(); - } - } - - private handleCallAnimation(playerPed: number) { - if (IsPedInAnyVehicle(playerPed, true)) { - this.handleOnCallInVehicle(playerPed); - } else { - this.handleOnCallNormal(playerPed); - } - } - - private handleOpenAnimation(playerPed: number) { - if (IsPedInAnyVehicle(playerPed, true)) { - this.handleOpenVehicleAnim(playerPed); - } else { - this.handleOpenNormalAnim(playerPed); - } - } - - private handleCallEndAnimation(playerPed: number) { - if (IsPedInAnyVehicle(playerPed, true)) { - this.handleCallEndVehicleAnim(playerPed); - } else { - this.handleCallEndNormalAnim(playerPed); - } - } - - private handleCloseAnimation(playerPed: number) { - if (IsPedInAnyVehicle(playerPed, true)) { - this.handleCloseVehicleAnim(playerPed); - } else { - this.handleCloseNormalAnim(playerPed); - } - } - - async openPhone(): Promise { - newPhoneProp(); - if (!this.onCall) { - this.handleOpenAnimation(PlayerPedId()); - } - this.setPhoneState(AnimationState.PHONE_OPEN, true); - } - - async closePhone(): Promise { - removePhoneProp(); - this.setPhoneState(AnimationState.PHONE_OPEN, false); - if (!this.onCall) { - this.handleCloseAnimation(PlayerPedId()); - } - } - - async startPhoneCall(): Promise { - this.handleCallAnimation(PlayerPedId()); - this.setPhoneState(AnimationState.ON_CALL, true); - } - - async endPhoneCall(): Promise { - this.handleCallEndAnimation(PlayerPedId()); - this.setPhoneState(AnimationState.ON_CALL, false); - } - - async openCamera() { - this.setPhoneState(AnimationState.ON_CAMERA, true); - } - - async closeCamera() { - this.setPhoneState(AnimationState.ON_CAMERA, false); - } - - private async loadAnimDict(dict: any) { - //-- Loads the animation dict. Used in the anim functions. - RequestAnimDict(dict); - while (!HasAnimDictLoaded(dict)) { - await Delay(100); - } - } - - private async handleOpenVehicleAnim(playerPed: number): Promise { - const dict = 'anim@cellphone@in_car@ps'; - const anim = 'cellphone_text_in'; - await this.loadAnimDict(dict); - - if (!IsEntityPlayingAnim(playerPed, dict, anim, 3)) { - SetCurrentPedWeapon(playerPed, 0xa2719263, true); - TaskPlayAnim(playerPed, dict, anim, 7.0, -1, -1, 50, 0, false, false, false); - } - } - - private async handleOpenNormalAnim(playerPed: number): Promise { - //While not in a vehicle it will use this dict. - const dict = 'cellphone@'; - const anim = 'cellphone_text_in'; - await this.loadAnimDict(dict); - - if (!IsEntityPlayingAnim(playerPed, dict, anim, 3)) { - SetCurrentPedWeapon(playerPed, 0xa2719263, true); - TaskPlayAnim(playerPed, dict, anim, 8.0, -1, -1, 50, 0, false, false, false); - } - } - - private async handleCloseVehicleAnim(playerPed: number): Promise { - const DICT = 'anim@cellphone@in_car@ps'; - StopAnimTask(playerPed, DICT, 'cellphone_text_in', 1.0); // Do both incase they were on the phone. - StopAnimTask(playerPed, DICT, 'cellphone_call_to_text', 1.0); - removePhoneProp(); - } - - private async handleCloseNormalAnim(playerPed: number): Promise { - const DICT = 'cellphone@'; - const ANIM = 'cellphone_text_out'; - StopAnimTask(playerPed, DICT, 'cellphone_text_in', 1.0); - await Delay(100); - await this.loadAnimDict(DICT); - TaskPlayAnim(playerPed, DICT, ANIM, 7.0, -1, -1, 50, 0, false, false, false); - await Delay(200); - StopAnimTask(playerPed, DICT, ANIM, 1.0); - removePhoneProp(); - } - - private async handleOnCallInVehicle(playerPed: number): Promise { - const DICT = 'anim@cellphone@in_car@ps'; - const ANIM = 'cellphone_call_listen_base'; - - if (!IsEntityPlayingAnim(playerPed, DICT, ANIM, 3)) { - await this.loadAnimDict(DICT); - TaskPlayAnim(playerPed, DICT, ANIM, 3.0, 3.0, -1, 49, 0, false, false, false); - } - } - - private async handleOnCallNormal(playerPed: number): Promise { - const DICT = 'cellphone@'; - const ANIM = 'cellphone_call_listen_base'; - if (!IsEntityPlayingAnim(playerPed, DICT, ANIM, 3)) { - await this.loadAnimDict(DICT); - TaskPlayAnim(playerPed, DICT, ANIM, 3.0, 3.0, -1, 49, 0, false, false, false); - } - } - - private async handleCallEndVehicleAnim(playerPed: number): Promise { - const DICT = 'anim@cellphone@in_car@ps'; - const ANIM = 'cellphone_call_to_text'; - StopAnimTask(playerPed, DICT, 'cellphone_call_listen_base', 1.0); - await this.loadAnimDict(DICT); - TaskPlayAnim(playerPed, DICT, ANIM, 1.3, 5.0, -1, 50, 0, false, false, false); - } - - private async handleCallEndNormalAnim(playerPed: number): Promise { - const DICT = 'cellphone@'; - const ANIM = 'cellphone_call_to_text'; - - if (IsEntityPlayingAnim(playerPed, 'cellphone@', 'cellphone_call_listen_base', 49)) { - await this.loadAnimDict(DICT); - TaskPlayAnim(playerPed, DICT, ANIM, 2.5, 8.0, -1, 50, 0, false, false, false); - } - } -} diff --git a/apps/game/client/calls/cl_calls.controller.ts b/apps/game/client/calls/cl_calls.controller.ts deleted file mode 100644 index 9b74c96cf..000000000 --- a/apps/game/client/calls/cl_calls.controller.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - ActiveCall, - ActiveCallRaw, - CallEvents, - EndCallDTO, - InitializeCallDTO, - StartCallEventData, - TransmitterNumDTO, -} from '@typings/call'; -import { IAlertProps } from '@typings/alerts'; -import { callService, CallService } from './cl_calls.service'; -import { animationService } from '../animations/animation.controller'; -import { emitNetTyped, onNetTyped } from '../../server/utils/miscUtils'; -import { RegisterNuiCB, RegisterNuiProxy } from '../cl_utils'; -import { ClUtils } from '../client'; -import { ServerPromiseResp } from '@typings/common'; -import { NuiCallbackFunc } from '@project-error/pe-utils'; - -export const initializeCallHandler = async (data: InitializeCallDTO, cb?: NuiCallbackFunc) => { - if (callService.isInCall()) return; - - try { - const serverRes = await ClUtils.emitNetPromise>( - CallEvents.INITIALIZE_CALL, - data, - ); - - animationService.startPhoneCall(); - // If something went wrong lets inform the client - if (serverRes.status !== 'ok') { - return cb?.(serverRes); - } - const { transmitter, isTransmitter, receiver, isUnavailable, isAnonymous } = serverRes.data; - // Start the process of giving NUI feedback by opening NUI modal - callService.handleStartCall(transmitter, receiver, isTransmitter, isUnavailable, isAnonymous); - cb?.(serverRes); - } catch (e) { - console.error(e); - cb?.({ status: 'error', errorMsg: 'CLIENT_TIMED_OUT' }); - } -}; - -// Will trigger whenever somebody initializes a call to any number -RegisterNuiCB(CallEvents.INITIALIZE_CALL, initializeCallHandler); - -onNetTyped(CallEvents.START_CALL, async (data: ActiveCall) => { - // TODO: add isAnonymous to the call for the client that is being called - const { transmitter, isTransmitter, receiver, isUnavailable, isAnonymous } = data; - callService.handleStartCall(transmitter, receiver, isTransmitter, isUnavailable, isAnonymous); -}); - -RegisterNuiCB(CallEvents.ACCEPT_CALL, (data, cb) => { - animationService.startPhoneCall(); - emitNetTyped(CallEvents.ACCEPT_CALL, data); - cb({}); -}); - -onNetTyped(CallEvents.WAS_ACCEPTED, (callData) => { - callService.handleCallAccepted(callData); -}); - -// Rejected call -RegisterNuiCB(CallEvents.REJECTED, (data, cb) => { - emitNetTyped(CallEvents.REJECTED, data); - cb({}); -}); - -onNet(CallEvents.WAS_REJECTED, async (currentCall: ActiveCallRaw) => { - callService.handleRejectCall(currentCall.receiver); - animationService.endPhoneCall(); - CallService.sendDialerAction(CallEvents.WAS_REJECTED, currentCall); -}); - -RegisterNuiCB(CallEvents.END_CALL, async (data, cb) => { - try { - const serverRes: ServerPromiseResp = await ClUtils.emitNetPromise( - CallEvents.END_CALL, - data, - ); - if (serverRes.status === 'error') return console.error(serverRes.errorMsg); - cb({}); - } catch (e) { - console.error(e); - cb({ status: 'error', errorMsg: 'CLIENT_TIMED_OUT' }); - } - animationService.endPhoneCall(); -}); - -onNet(CallEvents.WAS_ENDED, (callStarter: number, currentCall?: ActiveCallRaw) => { - if (callService.isInCall() && !callService.isCurrentCall(callStarter)) return; - callService.handleEndCall(); - animationService.endPhoneCall(); - if (currentCall) { - CallService.sendDialerAction(CallEvents.WAS_REJECTED, currentCall); - } -}); - -// Simple fetch so lets just proxy it -RegisterNuiProxy(CallEvents.FETCH_CALLS); - -onNet(CallEvents.SEND_ALERT, (alert: IAlertProps) => { - callService.handleSendAlert(alert); -}); - -RegisterNuiCB(CallEvents.TOGGLE_MUTE_CALL, (data: { call: ActiveCall; state: boolean }, cb) => { - const { state, call } = data; - callService.handleMute(state, call); - - cb({}); -}); diff --git a/apps/game/client/calls/cl_calls.service.ts b/apps/game/client/calls/cl_calls.service.ts deleted file mode 100644 index e9aff0207..000000000 --- a/apps/game/client/calls/cl_calls.service.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { checkHasPhone } from '../cl_main'; -import { IAlertProps } from '@typings/alerts'; -import { ActiveCall, CallEvents, CallRejectReasons } from '@typings/call'; -import { Sound } from '../sounds/client-sound.class'; -import { Ringtone } from '../sounds/client-ringtone.class'; -import { KvpItems } from '@typings/settings'; -import KvpService from '../settings/client-kvp.service'; - -const exp = global.exports; - -export class CallService { - private currentCall: number; - private currentPendingCall: string | null; - private callSound: Sound; - private ringtone: Ringtone; - private callSoundName = 'Remote_Ring'; - private hangUpSoundName = 'Hang_Up'; - private soundSet = 'Phone_SoundSet_Default'; - - // Using the Micheal set for hang up, since the default is awful. - private hangUpSoundSet = 'Phone_SoundSet_Michael'; - - constructor() { - this.currentCall = 0; - } - - static sendCallAction(method: CallEvents, data: T): void { - SendNUIMessage({ - app: 'CALL', - method, - data, - }); - } - - static sendDialerAction(method: CallEvents, data: T): void { - SendNUIMessage({ - app: 'DIALER', - method, - data, - }); - } - - isInCall() { - return this.currentCall !== 0; - } - - isCurrentCall(tgtCall: number) { - return this.currentCall === tgtCall; - } - - getCurrentCall() { - return this.currentPendingCall; - } - - isInPendingCall() { - return !!this.currentPendingCall; - } - - isCurrentPendingCall(target: string) { - return target === this.currentPendingCall; - } - - openCallModal(show: boolean) { - CallService.sendCallAction(CallEvents.SET_CALL_MODAL, show); - } - - handleRejectCall(receiver: string) { - // we don't want to reset our UI if we're in a call already or if we're currently starting a call that hasn't been canceled - if (this.isInCall() || !this.isCurrentPendingCall(receiver)) return; - if (this.callSound) this.callSound.stop(); - if (Ringtone.isPlaying()) this.ringtone.stop(); - this.currentPendingCall = null; - this.openCallModal(false); - CallService.sendCallAction(CallEvents.SET_CALL_INFO, null); - - const hangUpSound = new Sound(this.hangUpSoundName, this.hangUpSoundSet); - hangUpSound.play(); - } - - async handleStartCall( - transmitter: string, - receiver: string, - isTransmitter: boolean, - isUnavailable: boolean, - isAnonymous: boolean, - ) { - // If we're already in a call we want to automatically reject - if (this.isInCall() || !(await checkHasPhone()) || this.currentPendingCall) - return emitNet( - CallEvents.REJECTED, - { transmitterNumber: transmitter }, - CallRejectReasons.BUSY_LINE, - ); - - this.currentPendingCall = receiver; - - this.openCallModal(true); - - if (isTransmitter) { - this.callSound = new Sound(this.callSoundName, this.soundSet); - this.callSound.play(); - } - - if (!isTransmitter) { - const ringtone = KvpService.getKvpString(KvpItems.NPWD_RINGTONE); - this.ringtone = new Ringtone(ringtone); - this.ringtone.play(); - } - - CallService.sendCallAction(CallEvents.SET_CALL_INFO, { - active: true, - transmitter: transmitter, - receiver: receiver, - isTransmitter: isTransmitter, - accepted: false, - isUnavailable: isUnavailable, - isAnonymous: isAnonymous, - }); - } - - handleCallAccepted(callData: ActiveCall) { - this.currentCall = callData.channelId; - if (this.callSound) this.callSound.stop(); - if (Ringtone.isPlaying()) this.ringtone.stop(); - exp['pma-voice'].setCallChannel(callData.channelId); - CallService.sendCallAction(CallEvents.SET_CALL_INFO, callData); - } - - handleEndCall() { - if (this.callSound) this.callSound.stop(); - this.currentCall = 0; - exp['pma-voice'].setCallChannel(0); - this.currentPendingCall = null; - - this.openCallModal(false); - CallService.sendCallAction(CallEvents.SET_CALL_INFO, null); - - const hangUpSound = new Sound(this.hangUpSoundName, this.hangUpSoundSet); - hangUpSound.play(); - } - - handleMute(state: boolean, callData: ActiveCall) { - if (state) { - exp['pma-voice'].setCallChannel(0); - } else { - exp['pma-voice'].setCallChannel(callData.channelId); - } - } - - handleSendAlert(alert: IAlertProps) { - SendNUIMessage({ - app: 'DIALER', - method: CallEvents.SEND_ALERT, - data: alert, - }); - } -} - -export const callService = new CallService(); diff --git a/apps/game/client/cl_config.ts b/apps/game/client/cl_config.ts deleted file mode 100644 index 1719ffe3a..000000000 --- a/apps/game/client/cl_config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ResourceConfig } from '@typings/config'; -import { deepMergeObjects } from '@shared/deepMergeObjects'; -import defaultConfig from '../../../config.default.json'; - -// Setup and export the config for the resource -export const config = (() => { - const resourceName = GetCurrentResourceName(); - const config: ResourceConfig = JSON.parse(LoadResourceFile(resourceName, 'config.json')); - - let phoneAsItem = GetConvar('npwd:phoneAsItem', '') as string; - if (phoneAsItem !== '') { - phoneAsItem = JSON.parse(phoneAsItem) as any; - Object.entries(config.PhoneAsItem).forEach(([key, value]) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (phoneAsItem[key] && typeof value === typeof phoneAsItem[key]) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - config.PhoneAsItem[key] = phoneAsItem[key]; - } - }); - } - - return deepMergeObjects({}, defaultConfig, config) as any; -})(); diff --git a/apps/game/client/cl_contacts.ts b/apps/game/client/cl_contacts.ts deleted file mode 100644 index 4644e323f..000000000 --- a/apps/game/client/cl_contacts.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ContactEvents } from '@typings/contact'; -import { RegisterNuiProxy } from './cl_utils'; -import { sendContactsEvent } from "../utils/messages"; - -RegisterNuiProxy(ContactEvents.PAY_CONTACT); -RegisterNuiProxy(ContactEvents.GET_CONTACTS); -RegisterNuiProxy(ContactEvents.ADD_CONTACT); -RegisterNuiProxy(ContactEvents.DELETE_CONTACT); -RegisterNuiProxy(ContactEvents.UPDATE_CONTACT); -RegisterNuiProxy(ContactEvents.LOCAL_SHARE) - -const exp = global.exports - -onNet("npwd:contacts:receiveContact", (data: unknown) => { - sendContactsEvent(ContactEvents.ADD_CONTACT_EXPORT, data); -}) \ No newline at end of file diff --git a/apps/game/client/cl_controls.lua b/apps/game/client/cl_controls.lua deleted file mode 100644 index 397ab5608..000000000 --- a/apps/game/client/cl_controls.lua +++ /dev/null @@ -1,63 +0,0 @@ -local disableKeys - -local DisableControlAction = DisableControlAction -local Wait = Wait -local CreateThread = CreateThread - -CreateThread(function() - while true do - if disableKeys then - Wait(0) - DisableControlAction(0, 0, true) -- Next Camera - DisableControlAction(0, 1, true) -- Look Left/Right - DisableControlAction(0, 2, true) -- Look up/Down - DisableControlAction(0, 16, true) -- Next Weapon - DisableControlAction(0, 17, true) -- Select Previous Weapon - DisableControlAction(0, 22, true) -- Jump - DisableControlAction(0, 24, true) -- Attack - DisableControlAction(0, 25, true) -- Aim - DisableControlAction(0, 26, true) -- Look Behind - DisableControlAction(0, 36, true) -- Input Duck/Sneak - DisableControlAction(0, 37, true) -- Weapon Wheel - DisableControlAction(0, 44, true) -- Cover - DisableControlAction(0, 47, true) -- Detonate - DisableControlAction(0, 55, true) -- Dive - DisableControlAction(0, 75, true) -- Exit Vehicle - DisableControlAction(0, 76, true) -- Vehicle Handbrake - DisableControlAction(0, 81, true) -- Next Radio (Vehicle) - DisableControlAction(0, 82, true) -- Previous Radio (Vehicle) - DisableControlAction(0, 91, true) -- Passenger Aim (Vehicle) - DisableControlAction(0, 92, true) -- Passenger Attack (Vehicle) - DisableControlAction(0, 99, true) -- Select Next Weapon (Vehicle) - DisableControlAction(0, 106, true) -- Control Override (Vehicle) - DisableControlAction(0, 114, true) -- Fly Attack (Flying) - DisableControlAction(0, 115, true) -- Next Weapon (Flying) - DisableControlAction(0, 121, true) -- Fly Camera (Flying) - DisableControlAction(0, 122, true) -- Control OVerride (Flying) - DisableControlAction(0, 135, true) -- Control OVerride (Sub) - DisableControlAction(0, 140, true) -- Melee attack light - DisableControlAction(0, 200, true) -- Pause Menu - DisableControlAction(0, 245, true) -- Chat - else - Wait(100) - end - end -end) - --- Handles pause menu state -CreateThread(function() - while true do - Wait(500) - local isPauseOpen = IsPauseMenuActive() ~= false - local isPhoneVisible = exports.npwd:isPhoneVisible() - -- Handle if the phone is already visible and escape menu is opened - if isPauseOpen and isPhoneVisible then - exports.npwd:setPhoneVisible(false) - end - end -end) - - -AddEventHandler('npwd:disableControlActions', function(bool) - disableKeys = bool -end) diff --git a/apps/game/client/cl_exports.ts b/apps/game/client/cl_exports.ts deleted file mode 100644 index 75fa75fb5..000000000 --- a/apps/game/client/cl_exports.ts +++ /dev/null @@ -1,151 +0,0 @@ -import {sendContactsEvent, sendMessage, sendNotesEvent, sendPhoneEvent} from '../utils/messages'; -import {PhoneEvents} from '@typings/phone'; -import {verifyExportArgType} from './cl_utils'; -import {initializeCallHandler} from './calls/cl_calls.controller'; -import {AddContactExportData, ContactEvents} from '@typings/contact'; -import {AddNoteExportData, NotesEvents} from '@typings/notes'; -import {hidePhone, showPhone} from './cl_main'; -import {ClUtils} from './client'; -import {CallService, callService} from './calls/cl_calls.service'; -import {animationService} from './animations/animation.controller'; -import {CallEvents} from '@typings/call'; -import { - CreateNotificationDTO, - NotificationEvents, - SystemNotificationDTO, -} from '@typings/notifications'; -import {NotificationFuncRefs} from './cl_notifications'; - -const exps = global.exports; - -// Will open an app based on ID -exps('openApp', (app: string) => { - verifyExportArgType('openApp', app, ['string']); - - sendMessage('PHONE', PhoneEvents.OPEN_APP, app); -}); - -// Will set the phone to open or closed based on based value -exps('setPhoneVisible', async (bool: boolean | number) => { - verifyExportArgType('setPhoneVisible', bool, ['boolean', 'number']); - - const isPhoneDisabled = global.isPhoneDisabled; - const isPhoneOpen = global.isPhoneOpen; - // We need to make sure that the phone isn't disabled before we use the setter - if (isPhoneDisabled && !bool && isPhoneOpen) return; - - const coercedType = !!bool; - - if (coercedType) await showPhone(); - else await hidePhone(); -}); - -// Getter equivalent of above -exps('isPhoneVisible', () => global.isPhoneOpen); - -// Will prevent the phone from being opened -exps('setPhoneDisabled', (bool: boolean | number) => { - verifyExportArgType('setPhoneVisible', bool, ['boolean', 'number']); - const coercedType = !!bool; - global.isPhoneDisabled = coercedType; - sendPhoneEvent(PhoneEvents.IS_PHONE_DISABLED, bool); -}); - -exps('isPhoneDisabled', () => global.isPhoneDisabled); - -// Takes in a number to start the call with -exps('startPhoneCall', (number: string, isAnonymous = false) => { - verifyExportArgType('startPhoneCall', number, ['string']); - - initializeCallHandler({receiverNumber: number, isAnonymous}); -}); - -// Will automatically open the contacts app start the new contact process -// filling in all of the fields with passed data. If this number already exists, -// it will edit it. -// -// Data Struct -// interface AddContactExportData { -// name?: string; -// number: string; -// avatar?: string; -// } -exps('fillNewContact', (contactData: AddContactExportData) => { - verifyExportArgType('fillNewContact', contactData, ['object']); - sendContactsEvent(ContactEvents.ADD_CONTACT_EXPORT, contactData); -}); - -// Will automatically open the notes app and start the new note process -// filling in all of the fields with passed data. If this number already exists, -// it will edit it. -// -// Data Struct -// interface AddNoteExportData { -// title?: string; -// content?: string; -// } -exps('fillNewNote', (noteData: AddNoteExportData) => { - verifyExportArgType('fillNewNote', noteData, ['object']); - sendNotesEvent(NotesEvents.ADD_NOTE_EXPORT, noteData); -}); - -exps('getPhoneNumber', async () => { - if (!global.clientPhoneNumber) { - const res = await ClUtils.emitNetPromise(PhoneEvents.GET_PHONE_NUMBER); - global.clientPhoneNumber = res.data; - } - return global.clientPhoneNumber; -}); - -exps('isInCall', () => { - return callService.isInCall(); -}); - -exps('endCall', async () => { - if (callService.isInCall()) { - CallService.sendCallAction(CallEvents.END_CALL, null); - animationService.endPhoneCall(); - } -}); - -// @deprecated Use `setPhoneVisible` instead -exps('sendUIMessage', (action: { type: string; payload: unknown }) => { - console.log('sendUIMessage is deprecated, use sendNPWDMessage instead') - SendNUIMessage(action); -}); - -exps("sendNPWDMessage", (app: string, method: string, data: any) => { - sendMessage(app, method, data); -}) - -exps('createNotification', (dto: CreateNotificationDTO) => { - verifyExportArgType('createSystemNotification', dto, ['object']); - verifyExportArgType('createSystemNotification', dto.notisId, ['string']); - - sendMessage('PHONE', NotificationEvents.CREATE_NOTIFICATION, dto); -}); - -exps('createSystemNotification', (dto: SystemNotificationDTO) => { - verifyExportArgType('createSystemNotification', dto, ['object']); - verifyExportArgType('createSystemNotification', dto.uniqId, ['string']); - - const actionSet = dto?.onConfirm || dto?.onCancel; - - if (dto.controls && !dto.keepOpen) - return console.log('Notification must be set to keeOpen in order to use notifcation actions'); - - if (!dto.controls && actionSet) - return console.log('Controls must be set to true in order to use notifcation actions'); - - if (dto.controls) { - NotificationFuncRefs.set(`${dto.uniqId}:confirm`, dto.onConfirm); - NotificationFuncRefs.set(`${dto.uniqId}:cancel`, dto.onCancel); - } - - sendMessage('SYSTEM', NotificationEvents.CREATE_SYSTEM_NOTIFICATION, dto); -}); - -exps('removeSystemNotification', (uniqId: string) => { - verifyExportArgType('createSystemNotification', uniqId, ['string']); - sendMessage('SYSTEM', NotificationEvents.REMOVE_SYSTEM_NOTIFICATION, {uniqId}); -}); diff --git a/apps/game/client/cl_main.ts b/apps/game/client/cl_main.ts deleted file mode 100644 index 34340c6f6..000000000 --- a/apps/game/client/cl_main.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { sendMessage } from '../utils/messages'; -import { PhoneEvents } from '@typings/phone'; -import { config } from './cl_config'; -import { animationService } from './animations/animation.controller'; -import { RegisterNuiCB } from './cl_utils'; - -// All main globals that are set and used across files -global.isPhoneOpen = false; -global.isPhoneDisabled = false; -global.isPlayerLoaded = false; -global.clientPhoneNumber = null; -global.phoneProp = null; - -const exps = global.exports; - -/* * * * * * * * * * * * * - * - * Phone initialize data - * - * * * * * * * * * * * * */ - -onNet(PhoneEvents.SET_PLAYER_LOADED, (state: boolean) => { - global.isPlayerLoaded = state; - // Whenever a player is unloaded, we need to communicate this to the NUI layer. - // resetting the global state. - if (!state) { - sendMessage('PHONE', PhoneEvents.UNLOAD_CHARACTER, {}); - } -}); - -RegisterKeyMapping( - config.general.toggleCommand, - 'Toggle Phone', - 'keyboard', - config.general.toggleKey, -); - -setTimeout(() => { - emit( - 'chat:addSuggestion', - `/${config.general.toggleCommand}`, - 'Toggle displaying your cellphone', - ); -}, 1000); - -const getCurrentGameTime = () => { - let hour: string | number = GetClockHours(); - - let minute: string | number = GetClockMinutes(); - - // Format time if need be - if (hour < 10) hour = `0${hour}`; - if (minute < 10) minute = `0${minute}`; - - return `${hour}:${minute}`; -}; - -/* * * * * * * * * * * * * - * - * Phone Visibility Handling - * - * * * * * * * * * * * * */ - -export const showPhone = async (): Promise => { - global.isPhoneOpen = true; - const time = getCurrentGameTime(); - await animationService.openPhone(); // Animation starts before the phone is open - emitNet(PhoneEvents.FETCH_CREDENTIALS); - SetCursorLocation(0.9, 0.922); //Experimental - sendMessage('PHONE', PhoneEvents.SET_VISIBILITY, true); - sendMessage('PHONE', PhoneEvents.SET_TIME, time); - SetNuiFocus(true, true); - SetNuiFocusKeepInput(true); - emit('npwd:disableControlActions', true); -}; - -export const hidePhone = async (): Promise => { - global.isPhoneOpen = false; - sendMessage('PHONE', PhoneEvents.SET_VISIBILITY, false); - await animationService.closePhone(); - SetNuiFocus(false, false); - SetNuiFocusKeepInput(false); - emit('npwd:disableControlActions', false); -}; - -/* * * * * * * * * * * * * - * - * Register Command and Keybinding - * - * * * * * * * * * * * * */ -RegisterCommand( - config.general.toggleCommand, - async () => { - //-- Toggles Phone - // Check to see if the phone is marked as disabled - if (!global.isPhoneDisabled && !IsPauseMenuActive()) await togglePhone(); - }, - false, -); - -RegisterCommand( - 'phone:restart', - async () => { - await hidePhone(); - sendMessage('PHONE', 'phoneRestart', {}); - }, - false, -); - -/* * * * * * * * * * * * * - * - * Misc. Helper Functions - * - * * * * * * * * * * * * */ - -export const checkHasPhone = async (): Promise => { - if (!config.PhoneAsItem.enabled) return true; - const exportResp = await Promise.resolve( - exps[config.PhoneAsItem.exportResource][config.PhoneAsItem.exportFunction](), - ); - if (typeof exportResp !== 'number' && typeof exportResp !== 'boolean') { - throw new Error('You must return either a boolean or number from your export function'); - } - - return !!exportResp; -}; - -async function togglePhone(): Promise { - const canAccess = await checkHasPhone(); - if (!canAccess) return; - if (global.isPhoneOpen) return await hidePhone(); - await showPhone(); -} - -onNet( - PhoneEvents.SEND_CREDENTIALS, - (number: string, playerSource: number, playerIdentifier: string) => { - global.clientPhoneNumber = number; - sendMessage('SIMCARD', PhoneEvents.SET_NUMBER, number); - sendMessage('PHONE', PhoneEvents.SEND_PLAYER_SOURCE, playerSource); - sendMessage('PHONE', PhoneEvents.SEND_PLAYER_IDENTIFIER, playerIdentifier); - }, -); - -on('onResourceStop', (resource: string) => { - if (resource === GetCurrentResourceName()) { - sendMessage('PHONE', PhoneEvents.SET_VISIBILITY, false); - SetNuiFocus(false, false); - animationService.endPhoneCall(); - animationService.closePhone(); - ClearPedTasks(PlayerPedId()); //Leave here until launch as it'll fix any stuck animations. - } -}); - -/* * * * * * * * * * * * * - * - * NUI Service Callback Registration - * - * * * * * * * * * * * * */ -RegisterNuiCB(PhoneEvents.CLOSE_PHONE, async (_, cb) => { - await hidePhone(); - cb(); -}); - -// NOTE: This probably has an edge case when phone is closed for some reason -// and we need to toggle keep input off -RegisterNuiCB<{ keepGameFocus: boolean }>( - PhoneEvents.TOGGLE_KEYS, - async ({ keepGameFocus }, cb) => { - // We will only - if (global.isPhoneOpen) SetNuiFocusKeepInput(keepGameFocus); - cb({}); - }, -); - - -// If you want to remove controls from a external application this is the way to do it. -// chip - commenting this out because it crashed the phone for some reason, even though it's not used anywhere??? like...we dont emit it -/* on(PhoneEvents.SET_GAME_FOCUS, (keepGameFocus: boolean) => { - if (global.isPhoneOpen) SetNuiFocusKeepInput(keepGameFocus); -}); */ - -/* * * * * * * * * * * * * - * - * PhoneAsItem Export Checker - * - * * * * * * * * * * * * */ -if (config.PhoneAsItem.enabled) { - setTimeout(() => { - let doesExportExist = false; - - const { exportResource, exportFunction } = config.PhoneAsItem; - emit(`__cfx_export_${exportResource}_${exportFunction}`, () => { - doesExportExist = true; - }); - - if (!doesExportExist) { - console.log('\n^1Incorrect PhoneAsItem configuration detected. Export does not exist.^0\n'); - } - }, 100); -} - -// Will update the phone's time even while its open -setInterval(() => { - const time = getCurrentGameTime() as string; - sendMessage('PHONE', PhoneEvents.SET_TIME, time); -}, 2000); diff --git a/apps/game/client/cl_marketplace.ts b/apps/game/client/cl_marketplace.ts deleted file mode 100644 index bf54b24f9..000000000 --- a/apps/game/client/cl_marketplace.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - MarketplaceBroadcastAddDTO, - MarketplaceDeleteDTO, - MarketplaceEvents, -} from '@typings/marketplace'; -import { RegisterNuiProxy } from './cl_utils'; -import { sendMarketplaceEvent } from '../utils/messages'; - -RegisterNuiProxy(MarketplaceEvents.FETCH_LISTING); -RegisterNuiProxy(MarketplaceEvents.ADD_LISTING); - -RegisterNuiProxy(MarketplaceEvents.DELETE_LISTING); -RegisterNuiProxy(MarketplaceEvents.REPORT_LISTING); - -onNet(MarketplaceEvents.BROADCAST_ADD, (broadcastEvent: MarketplaceBroadcastAddDTO) => { - sendMarketplaceEvent(MarketplaceEvents.BROADCAST_ADD, broadcastEvent); -}); - -onNet(MarketplaceEvents.BROADCAST_DELETE, (broadcastEvent: MarketplaceDeleteDTO) => { - sendMarketplaceEvent(MarketplaceEvents.BROADCAST_DELETE, broadcastEvent); -}); diff --git a/apps/game/client/cl_match.ts b/apps/game/client/cl_match.ts deleted file mode 100644 index ecebeb770..000000000 --- a/apps/game/client/cl_match.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CreateMatchBroadcast, FormattedProfile, MatchEvents } from '@typings/match'; -import { sendMatchEvent } from '../utils/messages'; -import { RegisterNuiProxy } from './cl_utils'; - -RegisterNuiProxy(MatchEvents.GET_PROFILES); -RegisterNuiProxy(MatchEvents.GET_MY_PROFILE); -RegisterNuiProxy(MatchEvents.GET_MATCHES); -RegisterNuiProxy(MatchEvents.SAVE_LIKES); -RegisterNuiProxy(MatchEvents.CREATE_MY_PROFILE); -RegisterNuiProxy(MatchEvents.UPDATE_MY_PROFILE); - -onNet(MatchEvents.SAVE_LIKES_BROADCAST, (result: CreateMatchBroadcast) => { - sendMatchEvent(MatchEvents.SAVE_LIKES_BROADCAST, result); -}); - -onNet(MatchEvents.CREATE_MATCH_ACCOUNT_BROADCAST, (result: FormattedProfile) => { - sendMatchEvent(MatchEvents.CREATE_MATCH_ACCOUNT_BROADCAST, result); -}); diff --git a/apps/game/client/cl_messages.ts b/apps/game/client/cl_messages.ts deleted file mode 100644 index 8b10316a8..000000000 --- a/apps/game/client/cl_messages.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { - CreateMessageBroadcast, - MessageConversationResponse, - MessageEvents, - PreDBMessage, -} from '@typings/messages'; -import { sendMessageEvent } from '../utils/messages'; -import { RegisterNuiProxy, RegisterNuiCB } from './cl_utils'; - -RegisterNuiProxy(MessageEvents.FETCH_MESSAGE_CONVERSATIONS); -RegisterNuiProxy(MessageEvents.DELETE_MESSAGE); -RegisterNuiProxy(MessageEvents.FETCH_MESSAGES); -RegisterNuiProxy(MessageEvents.CREATE_MESSAGE_CONVERSATION); -RegisterNuiProxy(MessageEvents.DELETE_CONVERSATION); -RegisterNuiProxy(MessageEvents.SEND_MESSAGE); -RegisterNuiProxy(MessageEvents.SET_MESSAGE_READ); -RegisterNuiProxy(MessageEvents.GET_MESSAGE_LOCATION); - -RegisterNuiCB(MessageEvents.MESSAGES_SET_WAYPOINT, ({ coords }: { coords: number[] }) => { - SetNewWaypoint(coords[0], coords[1]); -}); - -onNet(MessageEvents.SEND_MESSAGE_SUCCESS, (messageDto: PreDBMessage) => { - sendMessageEvent(MessageEvents.SEND_MESSAGE_SUCCESS, messageDto); -}); - -onNet(MessageEvents.CREATE_MESSAGE_BROADCAST, (result: CreateMessageBroadcast) => { - sendMessageEvent(MessageEvents.CREATE_MESSAGE_BROADCAST, result); -}); - -onNet(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, (result: MessageConversationResponse) => { - sendMessageEvent(MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, result); -}); diff --git a/apps/game/client/cl_notes.ts b/apps/game/client/cl_notes.ts deleted file mode 100644 index 55b381e6a..000000000 --- a/apps/game/client/cl_notes.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NotesEvents } from '@typings/notes'; -import { RegisterNuiProxy } from './cl_utils'; - -RegisterNuiProxy(NotesEvents.ADD_NOTE); -RegisterNuiProxy(NotesEvents.FETCH_ALL_NOTES); -RegisterNuiProxy(NotesEvents.UPDATE_NOTE); -RegisterNuiProxy(NotesEvents.DELETE_NOTE); diff --git a/apps/game/client/cl_notifications.ts b/apps/game/client/cl_notifications.ts deleted file mode 100644 index 2952c9f70..000000000 --- a/apps/game/client/cl_notifications.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { RegisterNuiCB } from './cl_utils'; -import { AlertEvents } from '@typings/alerts'; -import KvpService from './settings/client-kvp.service'; -import { KvpItems } from '@typings/settings'; -import { Sound } from './sounds/client-sound.class'; - -export const NotificationFuncRefs = new Map(); - -RegisterNuiCB(AlertEvents.PLAY_ALERT, () => { - const notifSoundset = KvpService.getKvpString(KvpItems.NPWD_NOTIFICATION); - const sound = new Sound('Text_Arrive_Tone', notifSoundset); - sound.play(); -}); - -RegisterNuiCB('npwd:onNotificationConfirm', (notisId, cb) => { - const funcRef = NotificationFuncRefs.get(`${notisId}:confirm`); - if (!funcRef) - return console.log(`NPWD could not find any function ref for notification: ${notisId}`); - funcRef(); - - // delete as controls are only available when the notifiation persist. - // if we need a option to store these notis in the top bar, this should be removed. - NotificationFuncRefs.delete(`${notisId}:confirm`); - cb({}); -}); - -RegisterNuiCB('npwd:onNotificationCancel', (notisId, cb) => { - const funcRef = NotificationFuncRefs.get(`${notisId}:cancel`); - if (!funcRef) - return console.log(`NPWD could not find any function ref for notification: ${notisId}`); - funcRef(); - - // delete as controls are only available when the notifiation persist. - // if we need a option to store these notis in the top bar, this should be removed. - NotificationFuncRefs.delete(`${notisId}:cancel`); - cb({}); -}); diff --git a/apps/game/client/cl_photo.ts b/apps/game/client/cl_photo.ts deleted file mode 100644 index 03830715e..000000000 --- a/apps/game/client/cl_photo.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { PhotoEvents } from '@typings/photo'; -import { Delay } from '../utils/fivem'; -import { sendCameraEvent, sendMessage } from '../utils/messages'; -import { PhoneEvents } from '@typings/phone'; -import { ClUtils } from './client'; -import { animationService } from './animations/animation.controller'; -import { RegisterNuiCB, RegisterNuiProxy } from './cl_utils'; - -let inCameraMode = false; -let canToggleHUD = false; -let canToggleRadar = false; - -function closePhoneTemp() { - SetNuiFocus(false, false); - sendMessage('PHONE', PhoneEvents.SET_VISIBILITY, false); -} - -function openPhoneTemp() { - SetNuiFocus(true, true); - sendMessage('PHONE', PhoneEvents.SET_VISIBILITY, true); -} - -function CellFrontCamActivate(activate: boolean) { - return Citizen.invokeNative('0x2491A93618B7D838', activate); -} - -const displayHelperText = () => { - BeginTextCommandDisplayHelp('THREESTRINGS'); - AddTextComponentString('Exit Camera Mode: ~INPUT_CELLPHONE_CANCEL~'); - AddTextComponentString('Toggle Front/Back: ~INPUT_PHONE~'); - AddTextComponentString('Take Picture: ~INPUT_CELLPHONE_SELECT~'); - EndTextCommandDisplayHelp(0, true, false, -1); -}; - -// TODO: The flow here seems a little convuluted, we need to take a look at it. -RegisterNuiCB(PhotoEvents.TAKE_PHOTO, async (_, cb) => { - await animationService.openCamera(); - emit('npwd:disableControlActions', false); - // Create Phone Prop - let frontCam = false; - CreateMobilePhone(1); - // Active Camera Change - CellCamActivate(true, true); - // Hide phone from rendering temporary - closePhoneTemp(); - SetNuiFocus(false, false); - - inCameraMode = true; - - // If hud is not already hidden (by another resource), Hide the hud - if (!IsHudHidden()) { - canToggleHUD = true; - DisplayHud(false); - } else { - canToggleHUD = false; - } - - // If radar is not already hidden (by another resource), Hide the radar - if (!IsRadarHidden()) { - canToggleRadar = true; - DisplayRadar(false); - } else { - canToggleRadar = false; - } - - // We want to emit this event for UI handling in other resources - // We hide nothing in NPWD by default - emit(PhotoEvents.NPWD_PHOTO_MODE_STARTED); - - while (inCameraMode) { - await Delay(0); - - // Arrow Up Key, Toggle Front/Back Camera - if (IsControlJustPressed(1, 27)) { - frontCam = !frontCam; - CellFrontCamActivate(frontCam); - // Enter Key, Take Photo - } else if (IsControlJustPressed(1, 176)) { - const resp = await handleTakePicture(); - cb(resp); - break; - } else if (IsControlJustPressed(1, 194)) { - await handleCameraExit(); - break; - } - displayHelperText(); - } - - ClearHelp(true); - // We can now signal to other resources for ending photo mode - // and redisplaying HUD components - emit(PhotoEvents.NPWD_PHOTO_MODE_ENDED); - - emit('npwd:disableControlActions', true); - await animationService.closeCamera(); -}); - -const handleTakePicture = async () => { - // Wait a frame so we don't draw the display helper text - ClearHelp(true); - await Delay(0); - /* - * If we don't do this janky work around players get stuck in their camera - * until the entire server callback has happened, which doesn't matter for - * people with fast internet but a lot of people still have slow internet - */ - - const resp = await ClUtils.emitNetPromise(PhotoEvents.UPLOAD_PHOTO); - DestroyMobilePhone(); - CellCamActivate(false, false); - openPhoneTemp(); - animationService.openPhone(); - emit('npwd:disableControlActions', true); - inCameraMode = false; - - // If hud is already hidden (by this resource), Show the hud - if (canToggleHUD) { - DisplayHud(true); - canToggleHUD = false; - } - - // If radar is already hidden (by this resource), Show the radar - if (canToggleRadar) { - DisplayRadar(true); - canToggleRadar = false; - } - - return resp; -}; - -const handleCameraExit = async () => { - sendCameraEvent(PhotoEvents.CAMERA_EXITED); - ClearHelp(true); - await animationService.closeCamera(); - emit('npwd:disableControlActions', true); - DestroyMobilePhone(); - CellCamActivate(false, false); - openPhoneTemp(); - inCameraMode = false; - - // If hud is already hidden (by this resource), Show the hud - if (canToggleHUD) { - DisplayHud(true); - canToggleHUD = false; - } - - // If radar is already hidden (by this resource), Show the radar - if (canToggleRadar) { - DisplayRadar(true); - canToggleRadar = false; - } -}; - -RegisterNuiProxy(PhotoEvents.FETCH_PHOTOS); -RegisterNuiProxy(PhotoEvents.DELETE_PHOTO); -RegisterNuiProxy(PhotoEvents.SAVE_IMAGE); -RegisterNuiProxy(PhotoEvents.DELETE_MULTIPLE_PHOTOS); diff --git a/apps/game/client/cl_twitter.ts b/apps/game/client/cl_twitter.ts deleted file mode 100644 index ea74e915f..000000000 --- a/apps/game/client/cl_twitter.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { TwitterEvents } from '@typings/twitter'; -import { sendTwitterMessage } from '../utils/messages'; -import { RegisterNuiProxy } from './cl_utils'; - -RegisterNuiProxy(TwitterEvents.GET_OR_CREATE_PROFILE); -RegisterNuiProxy(TwitterEvents.DELETE_TWEET); -RegisterNuiProxy(TwitterEvents.UPDATE_PROFILE); -RegisterNuiProxy(TwitterEvents.CREATE_PROFILE); -RegisterNuiProxy(TwitterEvents.FETCH_TWEETS); -RegisterNuiProxy(TwitterEvents.CREATE_TWEET); -RegisterNuiProxy(TwitterEvents.FETCH_TWEETS_FILTERED); -RegisterNuiProxy(TwitterEvents.TOGGLE_LIKE); -RegisterNuiProxy(TwitterEvents.REPORT); -RegisterNuiProxy(TwitterEvents.RETWEET); - -onNet(TwitterEvents.CREATE_TWEET_BROADCAST, (tweet: any) => { - sendTwitterMessage(TwitterEvents.CREATE_TWEET_BROADCAST, tweet); -}); - -onNet(TwitterEvents.TWEET_LIKED_BROADCAST, ( - tweetId: number, - isAddLike: boolean, - likedByProfileName: string -) => { - sendTwitterMessage(TwitterEvents.TWEET_LIKED_BROADCAST, {tweetId, isAddLike, likedByProfileName}); -}); - -onNet(TwitterEvents.DELETE_TWEET_BROADCAST, (tweetId: number) => { - sendTwitterMessage(TwitterEvents.DELETE_TWEET_BROADCAST, tweetId); -}); diff --git a/apps/game/client/cl_utils.ts b/apps/game/client/cl_utils.ts deleted file mode 100644 index 37177708b..000000000 --- a/apps/game/client/cl_utils.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { uuidv4 } from '../utils/fivem'; -import { ClUtils } from './client'; - -interface ISettings { - promiseTimeout: number; -} - -interface ISettingsParams { - promiseTimeout?: number; -} - -export default class ClientUtils { - private _settings: ISettings; - private _defaultSettings: ISettings = { - promiseTimeout: 15000, - }; - - constructor(settings?: ISettingsParams) { - this.setSettings(settings); - } - - public setSettings(settings: ISettingsParams) { - this._settings = { - ...this._defaultSettings, - ...settings, - }; - } - - public emitNetPromise(eventName: string, ...args: any[]): Promise { - return new Promise((resolve, reject) => { - let hasTimedOut = false; - - setTimeout(() => { - hasTimedOut = true; - reject(`${eventName} has timed out after ${this._settings.promiseTimeout} ms`); - }, this._settings.promiseTimeout); - - // Have to use this as the regular uuid refused to work here for some - // fun reason - const uniqId = uuidv4(); - - const listenEventName = `${eventName}:${uniqId}`; - - emitNet(eventName, listenEventName, ...args); - - const handleListenEvent = (data: T) => { - removeEventListener(listenEventName, handleListenEvent); - if (hasTimedOut) return; - resolve(data); - }; - onNet(listenEventName, handleListenEvent); - }); - } -} - -type CallbackFn = (data: T, cb: Function) => void; - -/** - * A wrapper for handling NUI Callbacks - * @param event - The event name to listen for - * @param callback - The callback function - */ -export const RegisterNuiCB = (event: string, callback: CallbackFn) => { - RegisterNuiCallbackType(event); - on(`__cfx_nui:${event}`, callback); -}; - -/** - * Returns a promise that will be resolved once the client has been loaded. - */ -export const playerLoaded = () => { - return new Promise((resolve) => { - const id = setInterval(() => { - if (global.isPlayerLoaded) resolve(id); - }, 50); - }).then((id) => clearInterval(id)); -}; - -/** - * Will Register an NUI event listener that will immediately - * proxy to a server side event of the same name and wait - * for the response. - * @param event - The event name to listen for - */ -export const RegisterNuiProxy = (event: string) => { - RegisterNuiCallbackType(event); - on(`__cfx_nui:${event}`, async (data: unknown, cb: Function) => { - if (!global.isPlayerLoaded) await playerLoaded(); - try { - const res = await ClUtils.emitNetPromise(event, data); - - cb(res); - } catch (e) { - console.error('Error encountered while listening to resp. Error:', e); - cb({ status: 'error' }); - } - }); -}; - -type MsgpackTypes = - | 'string' - | 'number' - | 'bigint' - | 'boolean' - | 'symbol' - | 'undefined' - | 'function' - | 'object'; - -type WrapperNetEventCb = (...args: T) => void; - -/** - * Wrapped onNet so we can use generic types on return values from server - * @param event - The event name to listen to - * @param cb - The callback function to execute - */ -export const onNpwdEvent = (event: string, cb: WrapperNetEventCb) => { - onNet(event, cb); -}; -export const verifyExportArgType = ( - exportName: string, - passedArg: unknown, - validTypes: MsgpackTypes[], -): void => { - const passedArgType = typeof passedArg; - - if (!validTypes.includes(passedArgType)) - throw new Error( - `Export ${exportName} was called with incorrect argument type (${validTypes.join( - ', ', - )}. Passed: ${passedArg}, Type: ${passedArgType})`, - ); -}; diff --git a/apps/game/client/client-audio.ts b/apps/game/client/client-audio.ts deleted file mode 100644 index c18fd03f9..000000000 --- a/apps/game/client/client-audio.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RegisterNuiProxy } from './cl_utils'; -import { AudioEvents } from '@typings/audio'; - -RegisterNuiProxy(AudioEvents.UPLOAD_AUDIO); diff --git a/apps/game/client/client.ts b/apps/game/client/client.ts deleted file mode 100644 index ce874e7a5..000000000 --- a/apps/game/client/client.ts +++ /dev/null @@ -1,20 +0,0 @@ -import ClientUtils from './cl_utils'; -import './cl_config'; - -import './cl_main'; -import './cl_twitter'; -import './cl_contacts'; -import './cl_marketplace'; -import './cl_notes'; -import './cl_photo'; -import './cl_messages'; -import './calls/cl_calls.controller'; -import './cl_match'; -import './darkchat-client'; -import './client-audio'; -import './functions'; -import './cl_exports'; -import './settings/client-settings'; -import './cl_notifications'; - -export const ClUtils = new ClientUtils(); diff --git a/apps/game/client/darkchat-client.ts b/apps/game/client/darkchat-client.ts deleted file mode 100644 index fc818654b..000000000 --- a/apps/game/client/darkchat-client.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { RegisterNuiProxy } from './cl_utils'; -import { - ChannelMessageProps, - DarkchatEvents, - OwnerTransferResp, - UpdateLabelDto, -} from '@typings/darkchat'; -import { sendMessage } from '../utils/messages'; - -RegisterNuiProxy(DarkchatEvents.FETCH_CHANNELS); -RegisterNuiProxy(DarkchatEvents.FETCH_MESSAGES); -RegisterNuiProxy(DarkchatEvents.ADD_CHANNEL); -RegisterNuiProxy(DarkchatEvents.SEND_MESSAGE); -RegisterNuiProxy(DarkchatEvents.LEAVE_CHANNEL); -RegisterNuiProxy(DarkchatEvents.UPDATE_CHANNEL_LABEL); -RegisterNuiProxy(DarkchatEvents.FETCH_MEMBERS); -RegisterNuiProxy(DarkchatEvents.TRANSFER_OWNERSHIP); -RegisterNuiProxy(DarkchatEvents.DELETE_CHANNEL); - -onNet(DarkchatEvents.BROADCAST_MESSAGE, (data: ChannelMessageProps) => { - sendMessage('DARKCHAT', DarkchatEvents.BROADCAST_MESSAGE, data); -}); - -onNet(DarkchatEvents.BROADCAST_LABEL_UPDATE, (data: UpdateLabelDto) => { - sendMessage('DARKCHAT', DarkchatEvents.BROADCAST_LABEL_UPDATE, data); -}); - -onNet(DarkchatEvents.TRANSFER_OWNERSHIP_SUCCESS, (dto: OwnerTransferResp) => { - sendMessage('DARKCHAT', DarkchatEvents.TRANSFER_OWNERSHIP_SUCCESS, dto); -}); - -onNet(DarkchatEvents.DELETE_CHANNEL_SUCCESS, (dto: any) => { - sendMessage('DARKCHAT', DarkchatEvents.DELETE_CHANNEL_SUCCESS, dto); -}); diff --git a/apps/game/client/functions.ts b/apps/game/client/functions.ts deleted file mode 100644 index acfe0294f..000000000 --- a/apps/game/client/functions.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { KvpItems } from '@typings/settings'; -import KvpService from './settings/client-kvp.service'; -import { Delay } from '../utils/fivem'; - -global.phoneProp = 0; -let propCreated = false; - -/* * * * * * * * * * * * * - * - * Prop Deletion/Creation handling - * - * * * * * * * * * * * * */ - -// TODO: add a option to make server side for people who use entity lockdown. - -export const newPhoneProp = async () => { - const hasNPWDProps = GetConvarInt('NPWD_PROPS', 0); - let phoneModel; - if (hasNPWDProps) { - phoneModel = 'dolu_npwd_phone'; - } else { - phoneModel = 'prop_amb_phone'; - } - removePhoneProp(); //deletes the already existing prop before creating another. - if (!propCreated) { - RequestModel(phoneModel); - - while (!HasModelLoaded(phoneModel)) { - await Delay(1); - } - - const playerPed = PlayerPedId(); - const [x, y, z] = GetEntityCoords(playerPed, true); - global.phoneProp = CreateObject(GetHashKey(phoneModel), x, y, z + 0.2, true, true, true); - const boneIndex = GetPedBoneIndex(playerPed, 28422); - AttachEntityToEntity( - global.phoneProp, - playerPed, - boneIndex, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - true, - true, - false, - false, - 2, - true - ); //-- Attaches the phone to the player. - propCreated = true; - - let txtVariation; - - if (hasNPWDProps) { - txtVariation = KvpService.getKvpInt(KvpItems.NPWD_FRAME); - } - - SetObjectTextureVariation(global.phoneProp, txtVariation || 7); - - } else if (propCreated) { - console.log('prop already created'); - } -}; - -export function removePhoneProp() { - //-- Triggered in newphoneProp function. Only way to destory the prop correctly. - if (global.phoneProp != 0) { - DeleteEntity(global.phoneProp); - global.phoneProp = 0; - propCreated = false; - } -} diff --git a/apps/game/client/router-client.ts b/apps/game/client/router-client.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/game/client/settings/client-kvp.service.ts b/apps/game/client/settings/client-kvp.service.ts deleted file mode 100644 index 057a75213..000000000 --- a/apps/game/client/settings/client-kvp.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -export class _KvpService { - setKvp(key: string, value: string): void { - SetResourceKvp(key, value); - } - - setKvpFloat(key: string, value: number): void { - SetResourceKvpFloat(key, value); - } - - setKvpInt(key: string, value: number): void { - SetResourceKvpInt(key, value); - } - - getKvpString(key: string): string { - return GetResourceKvpString(key); - } - - getKvpInt(key: string): number { - return GetResourceKvpInt(key); - } - - getKvpFloat(key: string): number { - return GetResourceKvpFloat(key); - } -} -const KvpService = new _KvpService(); -export default KvpService; diff --git a/apps/game/client/settings/client-settings.ts b/apps/game/client/settings/client-settings.ts deleted file mode 100644 index e4c7f7828..000000000 --- a/apps/game/client/settings/client-settings.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { RegisterNuiCB } from '../cl_utils'; -import { IPhoneSettings, KvpItems, SettingEvents } from '@typings/settings'; -import KvpService from './client-kvp.service'; -import { Sound } from '../sounds/client-sound.class'; -import { Ringtone } from '../sounds/client-ringtone.class'; - -const PHONE_PROPS: Record = Object.freeze({ - gold: 3, - default: 7, - minimal: 7, - blue: 0, - pink: 6, - white: 4, -}); - -// 1 - green -// 2 - red -// 3 - gold -// 4 - white / gray -// 5 - purple -// 6 - pink -// 7 - black - -// This will run once we first load NUI settings stored in localStorage, and every time -// we update it. -RegisterNuiCB(SettingEvents.NUI_SETTINGS_UPDATED, (cfg, cb) => { - global.exports['pma-voice'].setCallVolume(cfg.callVolume); - - KvpService.setKvp(KvpItems.NPWD_RINGTONE, cfg.ringtone.value); - KvpService.setKvp(KvpItems.NPWD_NOTIFICATION, cfg.notiSound.value); - const frameValue: string = cfg.frame.value; - const frameName = frameValue.substr(0, frameValue.lastIndexOf('.')); - KvpService.setKvpInt(KvpItems.NPWD_FRAME, PHONE_PROPS[frameName]); - SetObjectTextureVariation(global.phoneProp, PHONE_PROPS[frameName]); - cb({}); -}); - -// Play an alert when previewing notification sound -RegisterNuiCB(SettingEvents.PREVIEW_ALERT, () => { - const notifSoundset = KvpService.getKvpString(KvpItems.NPWD_NOTIFICATION); - const sound = new Sound('Text_Arrive_Tone', notifSoundset); - sound.play(); -}); - -// Play ringtone for 3 seconds when previewing ringtone -RegisterNuiCB(SettingEvents.PREVIEW_RINGTONE, () => { - if (Ringtone.isPlaying()) return; - - const ringtoneSound = KvpService.getKvpString(KvpItems.NPWD_RINGTONE); - const ringtone = new Ringtone(ringtoneSound); - ringtone.play(); - setTimeout(ringtone.stop, 3000); -}); diff --git a/apps/game/client/sounds/client-ringtone.class.ts b/apps/game/client/sounds/client-ringtone.class.ts deleted file mode 100644 index aa30f26f7..000000000 --- a/apps/game/client/sounds/client-ringtone.class.ts +++ /dev/null @@ -1,15 +0,0 @@ -export class Ringtone { - constructor(private readonly ringtoneName: string) {} - - play(): void { - PlayPedRingtone(this.ringtoneName, PlayerPedId(), true); - } - - stop(): void { - StopPedRingtone(PlayerPedId()); - } - - static isPlaying(): boolean { - return IsPedRingtonePlaying(PlayerPedId()); - } -} diff --git a/apps/game/client/sounds/client-sound.class.ts b/apps/game/client/sounds/client-sound.class.ts deleted file mode 100644 index fb163a1b5..000000000 --- a/apps/game/client/sounds/client-sound.class.ts +++ /dev/null @@ -1,29 +0,0 @@ -const SoundIdsByName: { [k: string]: number } = {}; -export class Sound { - private readonly _soundName: string; - private readonly _soundSetName: string; - readonly _soundId: number; - - constructor(soundName: string, soundSetName: string) { - this._soundName = soundName; - this._soundSetName = soundSetName; - - if (SoundIdsByName[this._soundName]) { - ReleaseSoundId(SoundIdsByName[this._soundName]); - delete SoundIdsByName[this._soundName]; - } - - this._soundId = GetSoundId(); - SoundIdsByName[this._soundName] = this._soundId; - } - - play() { - PlaySoundFrontend(this._soundId, this._soundName, this._soundSetName, false); - } - - stop() { - StopSound(this._soundId); - ReleaseSoundId(this._soundId); - if (SoundIdsByName[this._soundName]) delete SoundIdsByName[this._soundName]; - } -} \ No newline at end of file diff --git a/apps/game/client/tsconfig.json b/apps/game/client/tsconfig.json deleted file mode 100644 index 741f9a093..000000000 --- a/apps/game/client/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "*": ["types/*"], - "@shared/*": ["../../../shared/*"], - "@typings/*": ["../../../typings/*"] - }, - "outDir": "./", - "noImplicitAny": true, - "module": "commonjs", - "target": "es6", - "allowJs": true, - "lib": ["es2017"], - "types": ["@citizenfx/client", "@types/node", "jest"], - "moduleResolution": "node", - "resolveJsonModule": true, - "esModuleInterop": true - }, - "include": ["./**/*", "../../config.default.json"], - "exclude": ["**/node_modules", "**/__tests__/*"] -} diff --git a/apps/game/client/types/global.d.ts b/apps/game/client/types/global.d.ts deleted file mode 100644 index 00df0ce43..000000000 --- a/apps/game/client/types/global.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare global { - namespace NodeJS { - interface Global { - isPhoneOpen: boolean; - isPhoneDisabled: boolean; - isPlayerLoaded: boolean; - clientPhoneNumber: string | null; - phoneProp: number | null; - } - } -} -export {}; diff --git a/apps/game/scripts/build.js b/apps/game/scripts/build.js deleted file mode 100644 index 823efd725..000000000 --- a/apps/game/scripts/build.js +++ /dev/null @@ -1,49 +0,0 @@ -const { build } = require('esbuild'); - -const { copy } = require('esbuild-plugin-copy'); - -build({ - entryPoints: ['server/server.ts'], - outfile: '../../dist/game/server/server.js', - bundle: true, - loader: { - '.ts': 'ts', - '.js': 'js', - }, - write: true, - platform: 'node', - target: 'es2020', -}) - .then(() => { - console.log('Server built successfully'); - }) - .catch(() => process.exit(1)); - -build({ - entryPoints: ['client/client.ts'], - outfile: '../../dist/game/client/client.js', - bundle: true, - loader: { - '.ts': 'ts', - '.js': 'js', - }, - write: true, - platform: 'browser', - target: 'es2016', - minify: true, - plugins: [ - copy({ - resolveFrom: 'cwd', - verbose: true, - copyOnStart: true, - assets: { - from: ['client/cl_controls.lua'], - to: ['../../dist/game/client/'], - }, - }), - ], -}) - .then(() => { - console.log('Client built successfully'); - }) - .catch(() => process.exit(1)); diff --git a/apps/game/scripts/watch.js b/apps/game/scripts/watch.js deleted file mode 100644 index 0dd1d8810..000000000 --- a/apps/game/scripts/watch.js +++ /dev/null @@ -1,59 +0,0 @@ -const { build } = require('esbuild'); -const { copy } = require('esbuild-plugin-copy'); - -build({ - entryPoints: ['server/server.ts'], - outfile: '../../dist/game/server/server.js', - bundle: true, - loader: { - '.ts': 'ts', - '.js': 'js', - }, - write: true, - watch: { - onRebuild(error, result) { - if (error) console.error('Server watch build failed:', error); - else console.log('Server watch build succeeded:', result); - }, - }, - platform: 'node', - target: 'es2020', -}) - .then(() => { - console.log('Server built successfully'); - }) - .catch(() => process.exit(1)); - -build({ - entryPoints: ['client/client.ts'], - outfile: '../../dist/game/client/client.js', - bundle: true, - loader: { - '.ts': 'ts', - '.js': 'js', - }, - watch: { - onRebuild(error, result) { - if (error) console.error('Client watch build failed:', error); - else console.log('Client watch build succeeded:', result); - }, - }, - write: true, - platform: 'browser', - target: 'es2016', - plugins: [ - copy({ - resolveFrom: 'cwd', - verbose: true, - copyOnStart: true, - assets: { - from: ['client/cl_controls.lua'], - to: ['../../dist/game/client/cl_controls.lua'], - }, - }), - ], -}) - .then(() => { - console.log('Client built successfully'); - }) - .catch(() => process.exit(1)); diff --git a/apps/game/server/audio/audio.controller.ts b/apps/game/server/audio/audio.controller.ts deleted file mode 100644 index 57b6715f3..000000000 --- a/apps/game/server/audio/audio.controller.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; -import { AudioEvents, AudioRequest, AudioResponse } from '@typings/audio'; -import AudioService from './audio.service'; -import { audioLogger } from './audio.utils'; - -onNetPromise(AudioEvents.UPLOAD_AUDIO, (reqObj, resp) => { - AudioService.uploadAudio(reqObj, resp).catch((e) => { - audioLogger.error( - `Error occurred in audio upload event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); diff --git a/apps/game/server/audio/audio.service.ts b/apps/game/server/audio/audio.service.ts deleted file mode 100644 index 2f1e64232..000000000 --- a/apps/game/server/audio/audio.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { config } from '@npwd/config/server'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import { AudioRequest, AudioResponse } from '../../../typings/audio'; -import { audioLogger } from './audio.utils'; -import fetch, { FormData, fileFromSync } from 'node-fetch'; -import * as fs from 'fs'; -import { v4 as uuidv4 } from 'uuid'; - -const path = GetResourcePath('npwd') + '/uploads'; - -class _AudioService { - private readonly TOKEN: string; - - constructor() { - this.TOKEN = GetConvar('NPWD_AUDIO_TOKEN', ''); - } - - async uploadAudio(reqObj: PromiseRequest, resp: PromiseEventResp) { - if (!this.TOKEN) { - audioLogger.error('Could not find upload token for audio! Please add the token the convar.'); - return resp({ status: 'error', errorMsg: 'No upload token found!' }); - } - - if (!fs.existsSync(path)) fs.mkdirSync(path); - const filePath = `${path}/recording-${uuidv4()}.ogg`; - - fs.writeFileSync( - filePath, - Buffer.from(reqObj.data.file.replace('data:audio/ogg;base64,', ''), 'base64'), - ); - - try { - const form_data = new FormData(); - const blob = fileFromSync(filePath, 'audio/ogg'); - form_data.append('recording', blob); - - const res = await fetch(config.voiceMessage.url, { - method: 'POST', - headers: { - [config.voiceMessage.authorizationHeader]: this.TOKEN, - }, - body: form_data, - }); - - fs.rmSync(filePath); - - if (res.status !== 200) { - const errorText = await res.text(); - audioLogger.error(`Failed to upload audio. Error: ${errorText}`); - return resp({ status: 'error', errorMsg: 'Failed to upload audio' }); - } - - const response: any = await res.json(); - - let recordingUrl = ''; - for (const index of config.voiceMessage.returnedDataIndexes) { - recordingUrl = response[index] as string; - } - - resp({ status: 'ok', data: { url: recordingUrl } }); - } catch (err) { - console.error('Error when trying to upload image', err.message); - resp({ status: 'error', errorMsg: 'Failed to upload audio' }); - } - } -} - -const AudioService = new _AudioService(); -export default AudioService; diff --git a/apps/game/server/audio/audio.utils.ts b/apps/game/server/audio/audio.utils.ts deleted file mode 100644 index f67a6a159..000000000 --- a/apps/game/server/audio/audio.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const audioLogger = mainLogger.child({ module: 'audio' }); diff --git a/apps/game/server/boot/boot.controller.ts b/apps/game/server/boot/boot.controller.ts deleted file mode 100644 index e415e0de9..000000000 --- a/apps/game/server/boot/boot.controller.ts +++ /dev/null @@ -1,7 +0,0 @@ -import BootService from './boot.service'; - -on('onServerResourceStart', async (resource: string) => { - if (resource === GetCurrentResourceName()) { - await BootService.handleResourceStarting(); - } -}); diff --git a/apps/game/server/boot/boot.db.ts b/apps/game/server/boot/boot.db.ts deleted file mode 100644 index 2d53e7dd7..000000000 --- a/apps/game/server/boot/boot.db.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {DbInterface, getDbConfig} from '@npwd/database'; -import {config} from '@npwd/config/server'; - -export class _BootDb { - /** - * Check if the player table described in the config exists. - * - * @returns Boolean - If the player table exists. - **/ - async doesPlayerTableExist(): Promise { - const tableSchema = getDbConfig(); - const query = `SELECT COUNT(*) as count - FROM information_schema.tables - WHERE table_schema = ? - AND table_name = ?` - - const [results] = await DbInterface._rawExec(query, [tableSchema.database, config.database.playerTable]); - - const result = <{ count: number }[]>results; - - return result[0].count > 0; - } - - /** - * Fetches all columns from the player table described in the config. - * - * @returns Array - String array of column names. - **/ - async getPlayerTableColumns(): Promise { - const query = `SHOW COLUMNS IN ${config.database.playerTable}`; - const [results] = await DbInterface._rawExec(query, []); - - const columnResults = <{ Field: string }[]>results; - - return columnResults.map((columnData) => columnData.Field); - } -} - -const BootDb = new _BootDb(); - -export default BootDb; diff --git a/apps/game/server/boot/boot.service.ts b/apps/game/server/boot/boot.service.ts deleted file mode 100644 index 9098b9968..000000000 --- a/apps/game/server/boot/boot.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import BootDb, { _BootDb } from './boot.db'; -import { bootLogger, fatalDbError } from './boot.utils'; -import { config } from '@npwd/config/server'; -import { frameworkDependencies } from './boot.utils'; - -export class _BootService { - private readonly bootDb: _BootDb; - - constructor() { - this.bootDb = BootDb; - bootLogger.debug('Boot service started'); - } - - /** - * onResourceStart event handler. - */ - async handleResourceStarting(): Promise { - await this.validateDatabaseSchema(); - } - - /** - * Validates that the player table and required columns exist. - */ - async validateDatabaseSchema(): Promise { - bootLogger.debug('Beginning database schema validation'); - - const doesPlayerTableExist = await this.bootDb.doesPlayerTableExist(); - - if (!doesPlayerTableExist) { - fatalDbError( - `Player table "${config.database.playerTable}" does not exist in the configured database.`, - ); - } - - const columnData = await this.bootDb.getPlayerTableColumns(); - - const { identifierColumn, phoneNumberColumn } = config.database; - const requiredDbColumns = [identifierColumn, phoneNumberColumn]; - - if (!requiredDbColumns.every((elem) => columnData.includes(elem))) { - const missingColumns = requiredDbColumns.filter((elem) => !columnData.includes(elem)); - - fatalDbError(`Player table is missing required columns: [${missingColumns.join(', ')}]`); - } - - bootLogger.debug('Database schema successfully validated'); - } - - - /** - * Check if various framework wrappers are started if applicable. - */ - checkFrameworkDependencies(): void { - bootLogger.debug('Checking for missing framework dependencies'); - - const startedResources = new Set(); - const errorsDetected = new Set(); - - const numOfResources = GetNumResources(); - for (let i = 0; i < numOfResources; i++) { - const resourceName = GetResourceByFindIndex(i); - - if (GetResourceState(resourceName) === 'started') { - startedResources.add(resourceName); - } - } - - for (const [resourceName, depList] of Object.entries(frameworkDependencies)) { - if (startedResources.has(resourceName)) { - if (!depList.every((elem) => startedResources.has(elem))) { - const missingDependencies = depList.filter((depName) => !startedResources.has(depName)); - - errorsDetected.add( - `Missing ${resourceName} dependencies detected: ${missingDependencies.join(', ')}`, - ); - } - } - } - - if (errorsDetected.size) { - errorsDetected.forEach((errorString) => bootLogger.error(errorString)); - } else { - bootLogger.debug('No missing dependencies were detected'); - } - } -} - -const BootService = new _BootService(); - -export default BootService; diff --git a/apps/game/server/boot/boot.utils.ts b/apps/game/server/boot/boot.utils.ts deleted file mode 100644 index 0465e29e0..000000000 --- a/apps/game/server/boot/boot.utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const bootLogger = mainLogger.child({ - module: 'boot', -}); - -export const fatalDbError = (reason: string) => { - throw new Error( - '\n^1==============================================\n\n' + - '!!! NPWD WAS UNABLE TO VALIDATE YOUR DATABASE AND FINISH STARTING !!!\n\n' + - `${reason}\n\n` + - 'This error is most likely caused by incorrect values in the config.json file.\n\n' + - '==============================================^0', - ); -}; - -export const frameworkDependencies = { - ['es_extended']: ['esx-npwd'], - ['qb-core']: ['qb-npwd'], -}; diff --git a/apps/game/server/bridge/bridge.utils.ts b/apps/game/server/bridge/bridge.utils.ts deleted file mode 100644 index de58b6f87..000000000 --- a/apps/game/server/bridge/bridge.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const bridgeLogger = mainLogger.child({ module: 'bridge' }); diff --git a/apps/game/server/bridge/esx/esx-server.ts b/apps/game/server/bridge/esx/esx-server.ts deleted file mode 100644 index db61085e4..000000000 --- a/apps/game/server/bridge/esx/esx-server.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { config } from '@npwd/config/server'; -import PlayerService from '../../players/player.service'; -import { mainLogger } from '../../sv_logger'; -import { Strategy } from '../framework-strategy'; - -export class ESXFramework implements Strategy { - constructor() { - mainLogger.info('Loading ESX bridge....'); - - config.general.useResourceIntegration = true; - config.database.identifierColumn = 'identifier'; - config.database.phoneNumberColumn = 'phone_number'; - config.database.playerTable = 'users'; - config.database.identifierType = 'license'; - } - - init(): void { - on('esx:playerLoaded', async (playerId: number, xPlayer: any) => { - const ESX = global.exports['es_extended'].getSharedObject(); - - if (!xPlayer) { - xPlayer = ESX.GetPlayerFromId(playerId); - } - - await PlayerService.handleNewPlayerEvent({ - identifier: xPlayer.identifier, - source: playerId, - firstname: xPlayer.firstname || xPlayer.variables?.lastName || xPlayer.get('firstName'), - lastname: xPlayer.lastname || xPlayer.variables?.lastName || xPlayer.get('lastName'), - }); - }); - - on("esx:playerLogout", async () => { - await PlayerService.handleUnloadPlayerEvent(global.source); - }) - - mainLogger.info('ESX bridge initialized'); - } - - onStart(): void { - on('onServerResourceStart', async (resource: string) => { - const ESX = global.exports['es_extended'].getSharedObject(); - - if (resource === GetCurrentResourceName()) { - const xPlayers = ESX.GetPlayers(); - - for (const xPlayer of xPlayers) { - await PlayerService.handleNewPlayerEvent({ - identifier: xPlayer.identifier, - source: xPlayer.source, - firstname: xPlayer.firstname || xPlayer.variables?.lastName || xPlayer.get('firstName'), - lastname: xPlayer.lastname || xPlayer.variables?.lastName || xPlayer.get('lastName'), - }); - } - } - }); - } -} diff --git a/apps/game/server/bridge/framework-strategy.ts b/apps/game/server/bridge/framework-strategy.ts deleted file mode 100644 index b22e7995b..000000000 --- a/apps/game/server/bridge/framework-strategy.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ESXFramework } from './esx/esx-server'; -import { NDCoreFramework } from './ndcore/ndcore-server'; -import { QBCoreFramework } from './qb/qbcore-server'; -import { QBXFramework } from './qbx/qbx-server'; -import { Standalone } from './standalone/standalone-server'; - - -export type Framework = 'qbcore' | 'qbx' | 'esx' | 'standalone' | 'ndcore'; - -export interface Strategy { - onStart(): void; - init(): void; -} - -export class FrameworkStrategy { - private strategy: Strategy | null; - - constructor(strategy: Framework) { - switch (strategy) { - case 'qbcore': - this.strategy = new QBCoreFramework(); - break; - case 'qbx': - this.strategy = new QBXFramework(); - break; - case 'esx': - this.strategy = new ESXFramework(); - break; - case 'ndcore': - this.strategy = new NDCoreFramework(); - break; - case 'standalone': - this.strategy = new Standalone(); - break; - default: - this.strategy = new Standalone(); - break; - } - } - - public onStart(): void { - this.strategy.onStart(); - } - - public init(): void { - this.strategy.init(); - } -} - -const framework = GetConvar('npwd:framework', 'standalone') as Framework - -const strategy = new FrameworkStrategy(framework); - -if (strategy) { - strategy.init(); - strategy.onStart(); -} diff --git a/apps/game/server/bridge/ndcore/ndcore-server.ts b/apps/game/server/bridge/ndcore/ndcore-server.ts deleted file mode 100644 index 7fce0b26f..000000000 --- a/apps/game/server/bridge/ndcore/ndcore-server.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { config } from "@npwd/config/server"; -import PlayerService from "../../players/player.service"; -import { mainLogger } from "../../sv_logger"; -import { Strategy } from "../framework-strategy"; - -type NDPlayer = { - id: number; - source: number; - identifier: string; - nickname: string; - user: string; - roles: unknown; - name: string; - firstname: string; - lastname: string; - fullname: string; - dob: string; - gender: string; - phonenumber: string; - cash: number; - bank: number; - groups: unknown; - job: string; - jobInfo: unknown; - label: string; - rankName: string; - rank: number; - metadata: unknown; - inventory: unknown; -}; - -// and this phone was supposed to be a 100% standalone my ass -export class NDCoreFramework implements Strategy { - constructor() { - mainLogger.info("Loading NDCore bridge...."); - - config.general.useResourceIntegration = true; - config.database.identifierColumn = "charid"; - config.database.phoneNumberColumn = "phonenumber"; - config.database.playerTable = "nd_characters"; - config.database.identifierType = "license"; - } - - init(): void { - on("ND:characterLoaded", async (player: NDPlayer) => { - const playerIdent = player.id; - const phoneNumber = player?.phonenumber; - const playerSrc = player.source; - - await PlayerService.handleNewPlayerEvent({ - identifier: playerIdent.toString(), - source: playerSrc, - phoneNumber: phoneNumber ?? null, - firstname: player.firstname, - lastname: player.lastname, - }); - }); - - on("ND:characterUnloaded", async (source: number) => { - await PlayerService.handleUnloadPlayerEvent(source); - }); - - mainLogger.info("NDCore bridge initialized"); - } - - onStart(): void { - on("onServerResourceStart", async (resource: string) => { - const NDCore = global.exports["ND_Core"]; - - if (resource === GetCurrentResourceName()) { - const onlinePlayers = NDCore.getPlayers() as NDPlayer[]; - for (const player of onlinePlayers) { - const phoneNumber = player?.phonenumber; - - await PlayerService.handleNewPlayerEvent({ - source: player.source, - identifier: player.id.toString(), - phoneNumber: phoneNumber ?? null, - firstname: player.firstname, - lastname: player.lastname, - }); - } - } - }); - } -} diff --git a/apps/game/server/bridge/qb/qbcore-server.ts b/apps/game/server/bridge/qb/qbcore-server.ts deleted file mode 100644 index 677dc8a57..000000000 --- a/apps/game/server/bridge/qb/qbcore-server.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { config } from '@npwd/config/server'; -import PlayerService from '../../players/player.service'; -import { mainLogger } from '../../sv_logger'; -import { Strategy } from '../framework-strategy'; - -export class QBCoreFramework implements Strategy { - constructor() { - mainLogger.info('Loading QBCore bridge....'); - - config.general.useResourceIntegration = true; - config.database.identifierColumn = 'citizenid'; - config.database.phoneNumberColumn = 'phone_number'; - config.database.playerTable = 'players'; - config.database.identifierType = 'license'; - } - - init(): void { - on('QBCore:Server:PlayerLoaded', async (qbPlayer: any) => { - const playerIdent = qbPlayer.PlayerData.citizenid; - const phoneNumber = qbPlayer.PlayerData.charinfo.phone; - const charInfo = qbPlayer.PlayerData.charinfo; - const playerSrc = qbPlayer.PlayerData.source; - - await PlayerService.handleNewPlayerEvent({ - identifier: playerIdent, - source: playerSrc, - phoneNumber: phoneNumber.toString(), - firstname: charInfo.firstname, - lastname: charInfo.lastname, - }); - }); - - on("QBCore:Server:OnPlayerUnload", async () => { - await PlayerService.handleUnloadPlayerEvent(global.source); - }) - - mainLogger.info('QBCore bridge initialized'); - } - - onStart(): void { - on('onServerResourceStart', async (resource: string) => { - const QBCore = global.exports['qb-core'].GetCoreObject(); - - if (resource === GetCurrentResourceName()) { - const onlinePlayers = QBCore.Functions.GetQBPlayers(); - for (const player of onlinePlayers) { - await PlayerService.handleNewPlayerEvent({ - source: player.PlayerData.source, - identifier: player.PlayerData.citizenid, - phoneNumber: player.PlayerData.charinfo.phone.toString(), - firstname: player.PlayerData.charinfo.firstname, - lastname: player.PlayerData.charinfo.lastname, - }); - } - } - }); - } -} diff --git a/apps/game/server/bridge/qbx/qbx-server.ts b/apps/game/server/bridge/qbx/qbx-server.ts deleted file mode 100644 index a6f2c9834..000000000 --- a/apps/game/server/bridge/qbx/qbx-server.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { config } from '@npwd/config/server'; -import PlayerService from '../../players/player.service'; -import { mainLogger } from '../../sv_logger'; -import { Strategy } from '../framework-strategy'; - -export class QBXFramework implements Strategy { - constructor() { - mainLogger.info('Loading QBX bridge....'); - - config.general.useResourceIntegration = true; - config.database.identifierColumn = 'citizenid'; - config.database.phoneNumberColumn = 'phone_number'; - config.database.playerTable = 'players'; - config.database.identifierType = 'license'; - } - - init(): void { - on('QBCore:Server:PlayerLoaded', async (qbPlayer: any) => { - const playerIdent = qbPlayer.PlayerData.citizenid; - const phoneNumber = qbPlayer.PlayerData.charinfo.phone; - const charInfo = qbPlayer.PlayerData.charinfo; - const playerSrc = qbPlayer.PlayerData.source; - - await PlayerService.handleNewPlayerEvent({ - identifier: playerIdent, - source: playerSrc, - phoneNumber: phoneNumber.toString(), - firstname: charInfo.firstname, - lastname: charInfo.lastname, - }); - }); - - on('QBCore:Server:OnPlayerUnload', async () => { - await PlayerService.handleUnloadPlayerEvent(global.source); - }); - - mainLogger.info('QBX bridge initialized'); - } - - onStart(): void { - on('onServerResourceStart', async (resource: string) => { - const QBCore = global.exports['qb-core'].GetCoreObject(); - - if (resource === GetCurrentResourceName()) { - const onlinePlayers = QBCore.Functions.GetQBPlayers(); - for (const player of onlinePlayers) { - await PlayerService.handleNewPlayerEvent({ - source: player.PlayerData.source, - identifier: player.PlayerData.citizenid, - phoneNumber: player.PlayerData.charinfo.phone.toString(), - firstname: player.PlayerData.charinfo.firstname, - lastname: player.PlayerData.charinfo.lastname, - }); - } - } - }); - } -} diff --git a/apps/game/server/bridge/standalone/standalone-server.ts b/apps/game/server/bridge/standalone/standalone-server.ts deleted file mode 100644 index ffab519f2..000000000 --- a/apps/game/server/bridge/standalone/standalone-server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { mainLogger } from '../../sv_logger'; -import { Strategy } from '../framework-strategy'; - -export class Standalone implements Strategy { - constructor() { - mainLogger.info('Loading NPWD Standalone'); - } - - init(): void { - return; - } - - onStart(): void { - return; - } -} diff --git a/apps/game/server/bridge/sv_exports.ts b/apps/game/server/bridge/sv_exports.ts deleted file mode 100644 index c7081458d..000000000 --- a/apps/game/server/bridge/sv_exports.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { generateUniquePhoneNumber } from '../misc/generateUniquePhoneNumber'; -import { bridgeLogger } from './bridge.utils'; -import { config } from '@npwd/config/server'; -import { ExportedPlayerData, PlayerAddData } from '../players/player.interfaces'; -import { playerLogger } from '../players/player.utils'; -import PlayerService from '../players/player.service'; -import { PhoneEvents } from '@typings/phone'; -import { Player } from '../players/player.class'; -import callsService from '../calls/calls.service'; - -const exp = global.exports; - -const logExport = (exportName: string, msg: string) => { - bridgeLogger.debug(`[${exportName}] ${msg}`); -}; - -// Will generate and return a unique phone number -exp('generatePhoneNumber', async () => { - const num = await generateUniquePhoneNumber(); - logExport('generatePhoneNumber', num); - return num; -}); - -// For multicharacter frameworks, we enable these events for -// instantiating/deleting a player. The config option must be set to true -// for these to be available -if (config.general.useResourceIntegration) { - exp('newPlayer', async (playerDTO: PlayerAddData) => { - if (typeof playerDTO.source !== 'number') { - return playerLogger.error('Source must be passed as a number when loading a player'); - } - await PlayerService.handleNewPlayerEvent(playerDTO); - emitNet(PhoneEvents.SET_PLAYER_LOADED, playerDTO.source, true); - }); - - exp('unloadPlayer', async (src: number) => { - if (typeof src !== 'number') { - return playerLogger.error('Source must be passed as a number when unloading a player'); - } - playerLogger.debug(`Received unloadPlayer event for ${src}`); - await PlayerService.handleUnloadPlayerEvent(src); - }); -} - -exp('getPhoneNumber', (src: number): string => { - playerLogger.error( - '[DEPRECATION WARNING]: deprecated export getPhoneNumber called, please use getPlayerData', - ); - return PlayerService.getPlayer(src).getPhoneNumber(); -}); - -interface PlayerDataExportArgs { - source?: string | number; - identifier?: string; - phoneNumber?: string; -} - -/** - * Returns an object containing various player data that NPWD stores - * @param locator: Object that must contain 1 of either a source, identifier or phone number - */ -exp('getPlayerData', async (locator: PlayerDataExportArgs): Promise => { - let player: Player; - - if (locator.source) { - player = PlayerService.getPlayer( - typeof locator.source === 'string' ? parseInt(locator.source) : locator.source, - ); - } - - if (locator.identifier) { - player = PlayerService.getPlayerFromIdentifier(locator.identifier); - } - - if (locator.phoneNumber) { - const identifier = await PlayerService.getIdentifierFromPhoneNumber(locator.phoneNumber, false); - player = PlayerService.getPlayerFromIdentifier(identifier); - } - - if (!player) return null; - - return { - phoneNumber: player.getPhoneNumber(), - firstName: player.getFirstName(), - lastName: player.getLastName(), - name: player.getName(), - identifier: player.getIdentifier(), - source: player.source, - }; -}); - -exp('isPlayerBusy', (src: number): boolean => { - return PlayerService.isBusy(src); -}); - - -exp("isPhoneNumberBusy", (phoneNumber: string) => { - return callsService.isPhoneNumberInCall(phoneNumber); -}) \ No newline at end of file diff --git a/apps/game/server/calls/calls.controller.ts b/apps/game/server/calls/calls.controller.ts deleted file mode 100644 index cd411eb29..000000000 --- a/apps/game/server/calls/calls.controller.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - CallEvents, - CallHistoryItem, - TransmitterNumDTO, - EndCallDTO, - InitializeCallDTO, - ActiveCall, -} from '@typings/call'; -import { getSource, onNetTyped } from '../utils/miscUtils'; -import CallService from './calls.service'; -import { callLogger } from './calls.utils'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; -import OncallService from "./middleware/oncall.service"; -import './middleware' - -onNetPromise(CallEvents.INITIALIZE_CALL, async (reqObj, resp) => { - await OncallService.handle(reqObj, resp) - - CallService.handleInitializeCall(reqObj, resp).catch((e) => { - resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); - callLogger.error(`Error occured handling init call: ${e.message}`); - }); -}); - -onNetTyped(CallEvents.ACCEPT_CALL, ({ transmitterNumber }) => { - const src = getSource(); - CallService.handleAcceptCall(src, transmitterNumber).catch((e) => - callLogger.error( - `Error occured in accept call event (${transmitterNumber}), Error: ${e.message}`, - ), - ); -}); - -// Fire and forget event, client doesn't care what response is we reject no matter what. -// thats the reason its not promise -onNetTyped(CallEvents.REJECTED, (data) => { - const src = getSource(); - CallService.handleRejectCall(src, data.transmitterNumber).catch((e) => - callLogger.error( - `Error occured in rejectcall event (${data.transmitterNumber}), Error: ${e.message}`, - ), - ); -}); - -onNetPromise(CallEvents.END_CALL, (reqObj, resp) => { - CallService.handleEndCall(reqObj, resp).catch((e) => { - callLogger.error( - `Error occured in end call event (${reqObj.data.transmitterNumber}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); - }); -}); - -onNetPromise(CallEvents.FETCH_CALLS, (reqObj, resp) => { - CallService.handleFetchCalls(reqObj, resp).catch((e) => { - resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); - callLogger.error(`Error occured in fetch call event, Error: ${e.message}`); - }); -}); diff --git a/apps/game/server/calls/calls.database.ts b/apps/game/server/calls/calls.database.ts deleted file mode 100644 index b1cc853cc..000000000 --- a/apps/game/server/calls/calls.database.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { CallHistoryItem } from '@typings/call'; -import { DbInterface } from '@npwd/database'; -import { FetchDefaultLimits } from '../utils/ServerConstants'; - -export class _CallsRepo { - async saveCall(call: CallHistoryItem): Promise { - const query = - 'INSERT INTO npwd_calls (identifier, transmitter, receiver, isAnonymous, `start`) VALUES (?, ?, ?, ?, ?)'; - await DbInterface._rawExec(query, [ - call.identifier, - call.transmitter, - call.receiver, - call.isAnonymous ?? false, - call.start, - ]); - } - - async updateCall(call: CallHistoryItem, isAccepted: boolean, end: number): Promise { - const query = 'UPDATE npwd_calls SET is_accepted=?, end=? WHERE identifier = ?'; - await DbInterface._rawExec(query, [isAccepted, end, call.identifier]); - } - - async fetchCalls( - phoneNumber: string, - limit = FetchDefaultLimits.CALLS_FETCH_LIMIT, - ): Promise { - const query = - 'SELECT * FROM npwd_calls WHERE receiver = ? OR transmitter = ? ORDER BY id DESC LIMIT ?'; - const [result] = await DbInterface._rawExec(query, [ - phoneNumber, - phoneNumber, - limit.toString(), - ]); - - return result; - } -} - -export const CallsRepo = new _CallsRepo(); diff --git a/apps/game/server/calls/calls.interfaces.ts b/apps/game/server/calls/calls.interfaces.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/game/server/calls/calls.service.ts b/apps/game/server/calls/calls.service.ts deleted file mode 100644 index ff60f63ae..000000000 --- a/apps/game/server/calls/calls.service.ts +++ /dev/null @@ -1,297 +0,0 @@ -import Collection from '@discordjs/collection'; -import { - CallEvents, - CallHistoryItem, - InitializeCallDTO, - EndCallDTO, - ActiveCall, - ActiveCallRaw, -} from '@typings/call'; -import { v4 as uuidv4 } from 'uuid'; -import PlayerService from '../players/player.service'; -import { callLogger } from './calls.utils'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import { emitNetTyped } from '../utils/miscUtils'; -import { mainLogger } from '../sv_logger'; -import { CallsRepo, _CallsRepo } from './calls.database'; - -class CallsService { - public callMap: Collection; - private readonly callsDB: _CallsRepo; - - constructor() { - this.callMap = new Collection(); - this.callsDB = CallsRepo; - callLogger.debug('Call service started'); - } - - private setCallInMap(transmitterNumber: string, callObj: ActiveCallRaw): void { - this.callMap.set(transmitterNumber, callObj); - callLogger.debug(`Call obj set with key ${transmitterNumber}, value:`); - callLogger.debug(callObj); - } - - isPlayerInCall(source: number): boolean { - const phoneNunber = PlayerService.getPlayer(source).getPhoneNumber(); - - return this.isPhoneNumberInCall(phoneNunber); - } - - isPhoneNumberInCall(phoneNumber: string): boolean { - const calls = this.callMap.find((call) => { - return call.transmitter === phoneNumber || call.receiver === phoneNumber; - }); - - return !!calls; - } - - async handleInitializeCall( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - // Create initial call data - const transmittingPlayer = PlayerService.getPlayer(reqObj.source); - const transmitterNumber = transmittingPlayer.getPhoneNumber(); - const receiverIdentifier = await PlayerService.getIdentifierFromPhoneNumber( - reqObj.data.receiverNumber, - true, - ); - - const startCallTimeUnix = Math.floor(new Date().getTime() / 1000); - const callIdentifier = uuidv4(); - - // Used when the number is invalid or the player if offline. - const tempSaveCallObj = { - identifier: callIdentifier, - transmitter: transmitterNumber, - receiver: reqObj.data.receiverNumber, - is_accepted: false, - start: startCallTimeUnix.toString(), - isAnonymous: reqObj.data.isAnonymous, - }; - - // If not online we immediately let the caller know that is an invalid - // number - if (!receiverIdentifier) { - await this.callsDB.saveCall(tempSaveCallObj); - return resp({ - status: 'ok', - data: { - transmitter: transmitterNumber, - isTransmitter: true, - receiver: reqObj.data.receiverNumber, - isUnavailable: true, - is_accepted: false, - start: startCallTimeUnix.toString(), - identifier: callIdentifier, - }, - }); - } - - // Will be null if the player is offline - const receivingPlayer = PlayerService.getPlayerFromIdentifier(receiverIdentifier); - - // Now if the player is offline, we send the same resp - // as before - if (!receivingPlayer) { - await this.callsDB.saveCall(tempSaveCallObj); - return resp({ - status: 'ok', - data: { - is_accepted: false, - transmitter: transmitterNumber, - isTransmitter: true, - receiver: reqObj.data.receiverNumber, - isUnavailable: true, - start: startCallTimeUnix.toString(), - identifier: callIdentifier, - }, - }); - } - - callLogger.debug(`Receiving Identifier: ${receiverIdentifier}`); - callLogger.debug(`Receiving source: ${receivingPlayer.source} `); - - // We call this here, so that w - const callObj: ActiveCallRaw = { - identifier: callIdentifier, - transmitter: transmitterNumber, - transmitterSource: transmittingPlayer.source, - receiver: reqObj.data.receiverNumber, - receiverSource: receivingPlayer.source, - start: startCallTimeUnix.toString(), - is_accepted: false, - isAnonymous: reqObj.data.isAnonymous, - }; - - // Now we can add the call to our memory map - this.setCallInMap(callObj.transmitter, callObj); - - try { - await this.callsDB.saveCall(callObj); - } catch (e) { - callLogger.error( - `Unable to save call object for transmitter number ${transmitterNumber}. Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'DATABASE_ERROR' }); - } - - // At this point we return back to the client that the player contacted - // is technically available and therefore initialization process ic complete - resp({ - status: 'ok', - data: { - is_accepted: false, - transmitter: transmitterNumber, - receiver: reqObj.data.receiverNumber, - isTransmitter: true, - start: startCallTimeUnix.toString(), - identifier: callIdentifier, - }, - }); - - emitNetTyped( - CallEvents.START_CALL, - { - is_accepted: false, - transmitter: transmitterNumber, - receiver: reqObj.data.receiverNumber, - isTransmitter: false, - isAnonymous: reqObj.data.isAnonymous, - }, - receivingPlayer.source, - ); - } - - async handleAcceptCall(src: number, transmitterNumber: string): Promise { - // We retrieve the call that was accepted from the current calls map - const targetCallItem = this.callMap.get(transmitterNumber); - // We update its reference - targetCallItem.is_accepted = true; - // Use the callers source so we can handle rejection if the phone is already in a call - const channelId = targetCallItem.transmitterSource; - - await this.callsDB.updateCall(targetCallItem, true, null); - callLogger.debug(`Call with key ${transmitterNumber} was updated to be accepted`); - - // player who is being called - emitNetTyped( - CallEvents.WAS_ACCEPTED, - { - is_accepted: true, - transmitter: transmitterNumber, - receiver: targetCallItem.receiver, - isTransmitter: false, - isAnonymous: targetCallItem.isAnonymous, - channelId, - }, - targetCallItem.receiverSource, - ); - - mainLogger.debug(targetCallItem); - - // player who is calling - emitNetTyped( - CallEvents.WAS_ACCEPTED, - { - is_accepted: true, - transmitter: transmitterNumber, - receiver: targetCallItem.receiver, - isTransmitter: true, - channelId, - }, - targetCallItem.transmitterSource, - ); - } - - async handleFetchCalls( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - try { - const player = PlayerService.getPlayer(reqObj.source); - const srcPlayerNumber = player.getPhoneNumber(); - const calls = await this.callsDB.fetchCalls(srcPlayerNumber); - resp({ status: 'ok', data: calls }); - } catch (e) { - resp({ status: 'error', errorMsg: 'DATABASE_ERROR' }); - console.error(`Error while fetching calls, ${e.message}`); - } - } - - async handleRejectCall(src: number, transmitterNumber: string): Promise { - const currentCall = this.callMap.get(transmitterNumber); - - const endCallTimeUnix = Math.floor(new Date().getTime() / 1000); - - if (!currentCall) { - callLogger.error( - `Call with transmitter number ${transmitterNumber} does not exist in current calls map!`, - ); - return; - } - - // player who is calling and recieved the rejection. - emitNet(CallEvents.WAS_REJECTED, currentCall.receiverSource, currentCall); - emitNet(CallEvents.WAS_REJECTED, currentCall.transmitterSource, currentCall); - - // Update our database - await this.callsDB.updateCall(currentCall, false, endCallTimeUnix); - - // Remove from active memory map - this.callMap.delete(transmitterNumber); - } - - async handleEndCall(reqObj: PromiseRequest, resp: PromiseEventResp) { - const transmitterNumber = reqObj.data.transmitterNumber; - const endCallTimeUnix = Math.floor(new Date().getTime() / 1000); - - if (reqObj.data.isUnavailable) { - // TODO: Need to get receiever number so we can return this as WAS_REJECTED so it gets added to the call history - emitNet(CallEvents.WAS_ENDED, reqObj.source); - resp({ status: 'ok' }); - return; - } - - const currentCall = this.callMap.get(transmitterNumber); - - if (!currentCall) { - callLogger.error( - `Call with transmitter number ${transmitterNumber} does not exist in current calls map!`, - ); - return resp({ status: 'error', errorMsg: 'DOES_NOT_EXIST' }); - } - - // Just in case currentCall for some reason at this point is falsy - // lets protect against that - if (currentCall) { - // We don't want to send the WAS_ENDED if the call was never accepted, send a rejected instead which handles properly if they're in a call. - if (currentCall.is_accepted) { - emitNet( - CallEvents.WAS_ENDED, - currentCall.receiverSource, - currentCall.transmitterSource, - currentCall, - ); - emitNet( - CallEvents.WAS_ENDED, - currentCall.transmitterSource, - currentCall.transmitterSource, - currentCall, - ); - } else { - emitNet(CallEvents.WAS_REJECTED, currentCall.receiverSource, currentCall); - emitNet(CallEvents.WAS_REJECTED, currentCall.transmitterSource, currentCall); - } - } - // player who is calling (transmitter) - resp({ status: 'ok' }); - - // We should be fine once here but sentry was acting up idk - await this.callsDB.updateCall(currentCall, currentCall?.is_accepted, endCallTimeUnix); - // Clear from memory - this.callMap.delete(transmitterNumber); - } -} - -export default new CallsService(); diff --git a/apps/game/server/calls/calls.utils.ts b/apps/game/server/calls/calls.utils.ts deleted file mode 100644 index fe50407f8..000000000 --- a/apps/game/server/calls/calls.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const callLogger = mainLogger.child({ module: 'calls' }); diff --git a/apps/game/server/calls/middleware/index.ts b/apps/game/server/calls/middleware/index.ts deleted file mode 100644 index be78d5c38..000000000 --- a/apps/game/server/calls/middleware/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - ActiveCall, - CallMiddlewareInvokable, - IncomingCallerCtx, - InitializeCallDTO, - OnCallExportCtx, OnCallStatus -} from "@typings/call"; -import OncallService from './oncall.service'; -import MessagesService from '../../messages/messages.service'; -import CallService from '../calls.service'; -import { callLogger } from '../calls.utils'; -import { PromiseEventResp, PromiseRequest } from '../../lib/PromiseNetEvents/promise.types'; - -const exp = global.exports; - -export const OnCallMap = new Map void>(); - -/* - Will add middleware for targeted numbers with the following context: - interface IncomingCallerCtx { - source: number; - number: string; - name: string; - } - - export interface OnCallExportCtx { - incomingCaller: IncomingCallerCtx; - exit: () => void; - next: () => void; - reply: (msg: string) => void; - forward: (tgt: string) => void; - } - */ -exp('onCall', (tgtNumber: string, cb: CallMiddlewareInvokable) => { - const resourceName = GetInvokingResource() - const handler = new CallMiddleware(cb, resourceName, tgtNumber.toString()) - callLogger.debug(`Resource [${resourceName}] registered an onCall handler for number [${tgtNumber}]`) - OncallService.addHandler(handler) -}); - -export class CallMiddleware { - constructor( - private funcRef: CallMiddlewareInvokable, - public hostResource: string, - public target: string, - ) {} - - invoke( - incomingCaller: IncomingCallerCtx, - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - return new Promise((resolve, reject) => - this.funcRef({ - receiverNumber: reqObj.data.receiverNumber, - incomingCaller, - next: () => { - resolve(OnCallStatus.NEXT); - return; - }, - exit: () => { - reject(); - return; - }, - reply: (message) => { - MessagesService.handleEmitMessage({ - senderNumber: reqObj.data.receiverNumber, - targetNumber: incomingCaller.number, - message, - }); - }, - forward: (receiverNumber: string, isAnonymous = false) => { - CallService.handleInitializeCall( - { ...reqObj, data: { receiverNumber, isAnonymous } }, - resp, - ) - .catch((e) => { - resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); - callLogger.error(`Error occured handling init call: ${e.message}`); - }) - .then(() => { - resolve(OnCallStatus.FORWARD) - return; - }) - .catch(reject); - }, - }), - ); - } -} - -on("onResourceStop", (resource: string) => { - OncallService.resetResource(resource) -}) diff --git a/apps/game/server/calls/middleware/oncall.service.ts b/apps/game/server/calls/middleware/oncall.service.ts deleted file mode 100644 index 6d625985b..000000000 --- a/apps/game/server/calls/middleware/oncall.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { ActiveCall, CallEvents, InitializeCallDTO, OnCallStatus } from "@typings/call"; -import { CallMiddleware } from "./index"; -import { PromiseEventResp, PromiseRequest } from "../../lib/PromiseNetEvents/promise.types"; -import PlayerService from "../../players/player.service"; -import { uuidv4 } from "../../../utils/fivem"; -import { callLogger } from "../calls.utils"; - -class OnCallService { - private callHandlers: Map - private resourcesTracked: Set - - constructor() { - this.callHandlers = new Map() - this.resourcesTracked = new Set() - } - - addHandler(handler: CallMiddleware) { - this.resourcesTracked.add(handler.hostResource) - - if (this.callHandlers.has(handler.target)){ - const handlerList = this.callHandlers.get(handler.target) - handlerList.push(handler) - - return - } - - this.callHandlers.set(handler.target, [handler]) - } - - // Keep a set containing all the resources we are tracking so that we are not doing an O(n^2) compute unnecessarily - resetResource(resource: string) { - if (!this.resourcesTracked.has(resource)) return; - - this.callHandlers.forEach((value, key, map) => { - const newList = value.filter(c => c.hostResource !== resource) - map.set(key, newList) - }) - } - - async handle( - reqObj: PromiseRequest, - resp: PromiseEventResp - ) { - callLogger.debug("invoking onCall for number", reqObj.data.receiverNumber) - if (!this.callHandlers.has(reqObj.data.receiverNumber)) { - return - } - - const caller = PlayerService.getPlayer(reqObj.source); - - const incomingCaller = { - source: reqObj.source, - name: caller.getName(), - number: caller.getPhoneNumber(), - }; - - const handlerList = this.callHandlers.get(reqObj.data.receiverNumber) - console.log(handlerList.length) - for (const handler of handlerList) { - try { - const status = await handler.invoke(incomingCaller, reqObj, resp) - if (status === OnCallStatus.FORWARD) { - break; - } - } catch (e) { - const tempSaveCallObj = { - identifier: uuidv4(), - isTransmitter: true, - transmitter: incomingCaller.number, - receiver: reqObj.data.receiverNumber, - is_accepted: false, - isUnavailable: true, - start: Math.floor(new Date().getTime() / 1000).toString(), - }; - - resp({ - status: 'ok', - data: tempSaveCallObj, - }); - - return setTimeout(() => { - emitNet(CallEvents.WAS_REJECTED, reqObj.source, tempSaveCallObj); - }, 2000); - } - } - } -} - -export default new OnCallService() \ No newline at end of file diff --git a/apps/game/server/commands/registerCommands.ts b/apps/game/server/commands/registerCommands.ts deleted file mode 100644 index 3cdbe1b6c..000000000 --- a/apps/game/server/commands/registerCommands.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { mainLogger } from '../sv_logger'; -import { config } from '@npwd/config/server'; -import { CONNECTION_STRING, DbInterface, parseUri2 } from '@npwd/database'; - -const mysqlConnectionString = GetConvar(CONNECTION_STRING, 'none'); - -const npwdDebugDumpCommand = async (src: number): Promise => { - // We require this be called from the server console. - if (src !== 0) return; - - const tableSchema = parseUri2(mysqlConnectionString).database; - - if (config.debug.level === 'error') { - console.log('SET DEBUG LEVEL TO INFO/SILLY TO SEE LOGS'); - } - - mainLogger.info('NPWD DEBUG DUMP STARTED, THIS WILL WRITE TO THE SV_NPWD.LOG FILE'); - mainLogger.info('Resource Config >'); - mainLogger.info(config); - - const versionInfo = GetResourceMetadata(GetCurrentResourceName(), 'version', 0); - mainLogger.info(`Manifest VERSION > ${versionInfo}`); - - const fxServerVersion = GetConvar('version', 'unknown'); - mainLogger.info(`FXServer VERSION > ${fxServerVersion}`); - - const activePlayerCount = GetNumPlayerIndices(); - mainLogger.info(`Connected Player Count > ${activePlayerCount}`); - - try { - const playerTableResults = await DbInterface._rawExec( - `SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE - FROM information_schema.COLUMNS - WHERE TABLE_NAME = ? - AND TABLE_SCHEMA = ?`, - [config.database.playerTable, tableSchema], - ); - - const tableExists = playerTableResults.length > 0; - - if (tableExists) { - mainLogger.info('Player Table Info >'); - mainLogger.info(playerTableResults[0]); - } else { - mainLogger.error( - `Unable to locate schema metadata for specified player table of ${config.database.playerTable}. Maybe it doesn't exist?`, - ); - } - } catch (e) { - mainLogger.error(`Failed to collect debug info for player table, ${e.message}`); - } -}; - -export const registerCommands = (): void => { - RegisterCommand('npwd-debug-dump', npwdDebugDumpCommand, false); -}; diff --git a/apps/game/server/config.ts b/apps/game/server/config.ts deleted file mode 100644 index d03895229..000000000 --- a/apps/game/server/config.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Setup and export config loaded at runtime -import { getConfig } from './utils/config'; - -export const config = (() => { - const config = getConfig(); - - let database: Record | string = GetConvar('npwd:database', ''); - if (database !== '') { - database = JSON.parse(database) as Record; - Object.entries(config.database).forEach(([key, value]) => { - const record = database as Record; - const configDb = config.database as unknown as Record; - - if (record[key] && typeof value === typeof record[key]) { - configDb[key] = record[key]; - } - }); - } - - if (GetConvar('npwd:useResourceIntegration', '') == 'true') { - config.general.useResourceIntegration = true; - } - - return config; -})(); diff --git a/apps/game/server/contacts/contacts.controller.ts b/apps/game/server/contacts/contacts.controller.ts deleted file mode 100644 index e985a3f1e..000000000 --- a/apps/game/server/contacts/contacts.controller.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { config } from '@npwd/config/server'; -import { Contact, ContactDeleteDTO, ContactEvents, PreDBContact, ContactPay } from '@typings/contact'; -import PlayerService from '../players/player.service'; -import ContactService from './contacts.service'; -import { contactsLogger } from './contacts.utils'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; -import { distanceBetweenCoords } from "../utils/miscUtils"; -const exps = global.exports; - -onNetPromise(ContactEvents.PAY_CONTACT, (reqObj, resp) => { - PlayerService.getIdentifierByPhoneNumber(reqObj.data.number, true).then((response) => { - const resourceExport = exps[config.contacts.payResource ?? '']; - if (resourceExport) { - //dont wait for a response.. devs can add their own "notification" triggers in their exports if they wish - exps[config.contacts.payResource][config.contacts.payFunction](reqObj.source, response, reqObj.data.amount) - } else { - contactsLogger.error(`No such resource exists for ${config.contacts.payResource} (${reqObj.source})`); - return resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - } - resp({ status: 'ok', errorMsg: '' }); - }).catch((e) => { - contactsLogger.error( - `Error occured in fetch identifier by phone event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }) -}); - -onNetPromise(ContactEvents.GET_CONTACTS, (reqObj, resp) => { - ContactService.handleFetchContacts(reqObj, resp).catch((e) => { - contactsLogger.error( - `Error occured in fetch contacts event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(ContactEvents.ADD_CONTACT, (reqObj, resp) => { - ContactService.handleAddContact(reqObj, resp).catch((e) => { - contactsLogger.error( - `Error occured in fetch contacts event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(ContactEvents.UPDATE_CONTACT, (reqObj, resp) => { - ContactService.handleUpdateContact(reqObj, resp).catch((e) => { - contactsLogger.error( - `Error occured in update contact event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(ContactEvents.DELETE_CONTACT, (reqObj, resp) => { - ContactService.handleDeleteContact(reqObj, resp).catch((e) => { - contactsLogger.error( - `Error occured in delete contact event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(ContactEvents.LOCAL_SHARE, (reqObj, resp) => { - ContactService.handleLocalShare(reqObj, resp).catch((e) => { - resp({status: 'error', errorMsg: 'INTERNAL_ERROR'}) - }) -}) \ No newline at end of file diff --git a/apps/game/server/contacts/contacts.database.ts b/apps/game/server/contacts/contacts.database.ts deleted file mode 100644 index e45e4b6a0..000000000 --- a/apps/game/server/contacts/contacts.database.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Contact, PreDBContact } from '@typings/contact'; -import { ResultSetHeader } from 'mysql2'; -import { DbInterface } from '@npwd/database'; -import { config } from '@npwd/config/server'; - -export class _ContactsDB { - private readonly defaultContacts: Contact[] = config.defaultContacts.map((contact) => ({ - id: `${contact.display}:${contact.number}`, - ...contact, - })); - - async fetchAllContacts(identifier: string): Promise { - const query = 'SELECT * FROM npwd_phone_contacts WHERE identifier = ? ORDER BY display ASC'; - const [results] = await DbInterface._rawExec(query, [identifier]); - - return this.defaultContacts.concat(results); - } - - async addContact( - identifier: string, - { display, avatar, number }: PreDBContact, - ): Promise { - const query = - 'INSERT INTO npwd_phone_contacts (identifier, number, display, avatar) VALUES (?, ?, ?, ?)'; - - const [setResult] = await DbInterface._rawExec(query, [identifier, number, display, avatar]); - - return { - id: (setResult).insertId, - number, - avatar, - display, - }; - } - - async updateContact(contact: Contact, identifier: string): Promise { - const query = - 'UPDATE npwd_phone_contacts SET number = ?, display = ?, avatar = ? WHERE id = ? AND identifier = ?'; - await DbInterface._rawExec(query, [ - contact.number, - contact.display, - contact.avatar, - contact.id, - identifier, - ]); - } - - async deleteContact(contactId: number, identifier: string): Promise { - const query = 'DELETE FROM npwd_phone_contacts WHERE id = ? AND identifier = ?'; - await DbInterface._rawExec(query, [contactId, identifier]); - } -} - -export const ContactsDB = new _ContactsDB(); diff --git a/apps/game/server/contacts/contacts.service.ts b/apps/game/server/contacts/contacts.service.ts deleted file mode 100644 index e0dba9a16..000000000 --- a/apps/game/server/contacts/contacts.service.ts +++ /dev/null @@ -1,107 +0,0 @@ -import PlayerService from '../players/player.service'; -import { contactsLogger } from './contacts.utils'; -import { Contact, ContactDeleteDTO, ContactResp, PreDBContact } from '@typings/contact'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import { checkAndFilterImage } from './../utils/imageFiltering'; -import { _ContactsDB, ContactsDB } from './contacts.database'; -import { distanceBetweenCoords } from "../utils/miscUtils"; -import { generateProfileName } from "../utils/generateProfileName"; - -class _ContactService { - private readonly contactsDB: _ContactsDB; - - constructor() { - this.contactsDB = ContactsDB; - contactsLogger.debug('Contacts service started'); - } - - async handleUpdateContact( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const imageUrl = checkAndFilterImage(reqObj.data.avatar); - if (imageUrl == null) { - return resp({ status: 'error', errorMsg: 'GENERIC_INVALID_IMAGE_HOST' }); - } - reqObj.data.avatar = imageUrl; - await this.contactsDB.updateContact(reqObj.data, identifier); - resp({ status: 'ok' }); - } catch (e) { - contactsLogger.error(`Error in handleUpdateContact (${identifier}), ${e.message}`); - resp({ status: 'error', errorMsg: ContactResp.UPDATE_FAILED }); - } - } - async handleDeleteContact( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - await this.contactsDB.deleteContact(reqObj.data.id, identifier); - resp({ status: 'ok' }); - } catch (e) { - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - contactsLogger.error(`Error in handleDeleteContact (${identifier}), ${e.message}`); - } - } - async handleAddContact( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const imageUrl = checkAndFilterImage(reqObj.data.avatar); - if (imageUrl == null) { - return resp({ status: 'error', errorMsg: 'GENERIC_INVALID_IMAGE_HOST' }); - } - reqObj.data.avatar = imageUrl; - const contact = await this.contactsDB.addContact(identifier, reqObj.data); - - resp({ status: 'ok', data: contact }); - } catch (e) { - contactsLogger.error(`Error in handleAddContact, ${e.message}`); - resp({ status: 'error', errorMsg: ContactResp.ADD_FAILED }); - } - } - async handleFetchContacts( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const contacts = await this.contactsDB.fetchAllContacts(identifier); - resp({ status: 'ok', data: contacts }); - } catch (e) { - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - contactsLogger.error(`Error in handleFetchContact (${identifier}), ${e.message}`); - } - } - - async handleLocalShare( - reqObj: PromiseRequest, - resp: PromiseEventResp - ): Promise { - const source = reqObj.source.toString() - const sourceCoords = GetEntityCoords(GetPlayerPed(source)) - - const player = PlayerService.getPlayer(reqObj.source) - const name = player.getName() - const number = player.getPhoneNumber() - - getPlayers()?.forEach(src => { - if (src === source) return; - - const dist = distanceBetweenCoords(sourceCoords, GetEntityCoords(GetPlayerPed(src))) - if (dist <=3 ){ - emitNet('npwd:contacts:receiveContact', src, {name, number}) - } - }) - - resp({status: "ok"}) - } -} - -const ContactService = new _ContactService(); -export default ContactService; diff --git a/apps/game/server/contacts/contacts.utils.ts b/apps/game/server/contacts/contacts.utils.ts deleted file mode 100644 index 0f0c69de4..000000000 --- a/apps/game/server/contacts/contacts.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const contactsLogger = mainLogger.child({ module: 'contact' }); diff --git a/apps/game/server/darkchat/darkchat.controller.ts b/apps/game/server/darkchat/darkchat.controller.ts deleted file mode 100644 index bad7da880..000000000 --- a/apps/game/server/darkchat/darkchat.controller.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; -import { - ChannelItemProps, - ChannelMember, - ChannelMessageProps, - DarkchatEvents, - JoinChannelDTO, - MessageDTO, - OwnerTransferReq, - OwnerTransferResp, - UpdateLabelDto, -} from '@typings/darkchat'; -import DarkchatService from './darkchat.service'; -import { darkchatLogger } from './darkchat.utils'; - -onNetPromise(DarkchatEvents.FETCH_CHANNELS, async (reqObj, resp) => { - await DarkchatService.handleGetAllChannels(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in fetch channels event (${reqObj.source}). Error: ${err.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise<{ channelId: number }, ChannelMessageProps[]>( - DarkchatEvents.FETCH_MESSAGES, - async (reqObj, resp) => { - DarkchatService.handleGetChannelMessage(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in fetch channel messages (${reqObj.source}). Error: ${err.message}`, - ); - }); - }, -); - -onNetPromise(DarkchatEvents.ADD_CHANNEL, async (reqObj, resp) => { - DarkchatService.handleJoinChannel(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in join channel event (${reqObj.source}). Error: ${err.message}`, - ); - }); -}); - -onNetPromise(DarkchatEvents.SEND_MESSAGE, async (reqObj, resp) => { - DarkchatService.handleCreateMessage(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in send message event (${reqObj.source}). Error: ${err.message}`, - ); - }); -}); - -onNetPromise<{ channelId: number }, void>(DarkchatEvents.LEAVE_CHANNEL, async (reqObj, resp) => { - DarkchatService.handleLeaveChannel(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in leave channel event (${reqObj.source}). Error: ${err.message}`, - ); - }); -}); - -onNetPromise(DarkchatEvents.UPDATE_CHANNEL_LABEL, async (reqObj, resp) => { - DarkchatService.handleUpdateChannelLabel(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in update channel label event (${reqObj.source}). Error: ${err.message}`, - ); - }); -}); - -onNetPromise<{ channelId: number }, void>(DarkchatEvents.DELETE_CHANNEL, async (reqObj, resp) => { - DarkchatService.deleteChannel(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in delete channel event (${reqObj.source}). Error: ${err.message}`, - ); - }); -}); - -onNetPromise( - DarkchatEvents.TRANSFER_OWNERSHIP, - async (reqObj, resp) => { - DarkchatService.handleTransferOwnership(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in transfer ownership event (${reqObj.source}). Error: ${err.message}`, - ); - }); - }, -); - -onNetPromise<{ channelId: number }, ChannelMember[]>( - DarkchatEvents.FETCH_MEMBERS, - async (reqObj, resp) => { - DarkchatService.handleGetMembers(reqObj, resp).catch((err) => { - darkchatLogger.error( - `Error occurred in delete channel event (${reqObj.source}). Error: ${err.message}`, - ); - }); - }, -); diff --git a/apps/game/server/darkchat/darkchat.service.ts b/apps/game/server/darkchat/darkchat.service.ts deleted file mode 100644 index c7ff40ded..000000000 --- a/apps/game/server/darkchat/darkchat.service.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { DarkchatDB, _DarkchatDB } from '@npwd/database'; -import { - ChannelItemProps, - ChannelMember, - ChannelMessageProps, - DarkchatEvents, - JoinChannelDTO, - MessageDTO, - OwnerTransferReq, - OwnerTransferResp, - UpdateLabelDto, -} from '@typings/darkchat'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import PlayerService from '../players/player.service'; -import { darkchatLogger } from './darkchat.utils'; -import { emitNetTyped } from '../utils/miscUtils'; - -class _DarkchatService { - darkchatDB: _DarkchatDB; - - constructor() { - this.darkchatDB = DarkchatDB; - } - - async handleGetAllChannels( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const channels = await this.darkchatDB.getAllChannels(identifier); - - resp({ status: 'ok', data: channels }); - } catch (err) { - darkchatLogger.error(`Failed to fetch channels. Error: ${err.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleGetMembers( - reqObj: PromiseRequest<{ channelId: number }>, - resp: PromiseEventResp, - ): Promise { - try { - const members = await this.darkchatDB.getMembers(reqObj.data.channelId); - - resp({ status: 'ok', data: members }); - } catch (err) { - darkchatLogger.error(`Failed to fetch members. Error: ${err.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleTransferOwnership( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - // the current owner - const ownerIdentifier = PlayerService.getIdentifier(reqObj.source); - try { - await this.darkchatDB.transferChannelOwnership( - ownerIdentifier, - reqObj.data.userIdentifier, - reqObj.data.channelId, - ); - - resp({ - status: 'ok', - data: { - ownerPhoneNumber: reqObj.data.newOwnerPhoneNumber, - channelId: reqObj.data.channelId, - }, - }); - - const newOwnerPlayer = PlayerService.getPlayerFromIdentifier(reqObj.data.userIdentifier); - if (newOwnerPlayer) { - emitNetTyped( - DarkchatEvents.TRANSFER_OWNERSHIP_SUCCESS, - { - ownerPhoneNumber: reqObj.data.newOwnerPhoneNumber, - channelId: reqObj.data.channelId, - }, - newOwnerPlayer.source, - ); - } - } catch (err) { - darkchatLogger.error(`Failed to transfer ownership. Error: ${err.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleGetChannelMessage( - reqObj: PromiseRequest<{ channelId: number }>, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const rawMessages = await this.darkchatDB.getChannelMessages(reqObj.data.channelId); - const mappedMessages = rawMessages.map((msg) => { - if (msg.identifier === identifier) { - return { - ...msg, - isMine: true, - type: (msg.type && 'image') || 'text', - }; - } - - return msg; - }); - - resp({ status: 'ok', data: mappedMessages }); - } catch (err) { - darkchatLogger.error(`Failed to fetch channel messages. Error: ${err.message}`); - resp({ status: 'error', errorMsg: err.message }); - } - } - - // TODO: When we join channel, we'll have to check if exists, and if there are a current owner of the channel. - // TODO: If not, we'll set ourselves as the owner - async handleJoinChannel( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const channelExists = await this.darkchatDB.doesChannelExist(reqObj.data.channelIdentifier); - let isPlayerOwner = false; - - if (!channelExists) { - await this.darkchatDB.createChannel(reqObj.data.channelIdentifier); - isPlayerOwner = true; - } - - const { id, label } = await this.darkchatDB.getChannelIdAndLabel( - reqObj.data.channelIdentifier, - ); - const members = await this.darkchatDB.getChannelMembers(id); - - const hasOwner = members.find((member) => member.isOwner); - if (!hasOwner) isPlayerOwner = true; - - const channelId = await this.darkchatDB.joinChannel( - reqObj.data.channelIdentifier, - identifier, - isPlayerOwner, - ); - - const channelOwner = await this.darkchatDB.getChannelOwner(id); - const ownerPhoneNumber = await PlayerService.getPhoneNumberFromIdentifier(channelOwner); - - const resData: ChannelItemProps = { - id: channelId, - identifier: reqObj.data.channelIdentifier, - label: label ?? reqObj.data.channelIdentifier, - owner: ownerPhoneNumber, - }; - - resp({ status: 'ok', data: resData }); - } catch (err) { - darkchatLogger.error(`Failed to join channel. Error: ${err.message}`); - resp({ status: 'error', errorMsg: err.message }); - } - } - - async handleCreateMessage( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const userIdentifier = PlayerService.getIdentifier(reqObj.source); - const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); - const messageData = reqObj.data; - try { - const message = await this.darkchatDB.createMessage( - messageData.channelId, - userIdentifier, - messageData.message, - messageData.type === 'image', - ); - - const resObj: ChannelMessageProps = { - ...message, - channelId: messageData.channelId, - isMine: messageData.phoneNumber === phoneNumber, - //type: messageData.type, - }; - - resp({ status: 'ok', data: resObj }); - - const members = await this.darkchatDB.getChannelMembers(messageData.channelId); - for (const member of members) { - if (member.identifier !== userIdentifier) { - const participant = PlayerService.getPlayerFromIdentifier(member.identifier); - if (participant) { - const resObj: ChannelMessageProps = { - ...message, - channelId: messageData.channelId, - isMine: messageData.phoneNumber === participant.getPhoneNumber(), - //type: messageData.type, - }; - - emitNetTyped( - DarkchatEvents.BROADCAST_MESSAGE, - resObj, - participant.source, - ); - } - } - } - } catch (err) { - darkchatLogger.error(`Failed to create message. Error: ${err.message}`); - resp({ status: 'error', errorMsg: err.message }); - } - } - - async handleLeaveChannel( - reqObj: PromiseRequest<{ channelId: number }>, - resp: PromiseEventResp, - ): Promise { - const userIdentifier = PlayerService.getIdentifier(reqObj.source); - - try { - await this.darkchatDB.leaveChannel(reqObj.data.channelId, userIdentifier); - - resp({ status: 'ok' }); - } catch (err) { - darkchatLogger.error(`Failed to leave channel. Error: ${err.message}`); - resp({ status: 'error', errorMsg: err.message }); - } - } - - async handleUpdateChannelLabel( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const userIdentifier = PlayerService.getIdentifier(reqObj.source); - try { - const channelData = reqObj.data; - await this.darkchatDB.updateChannelLabel(reqObj.data); - - resp({ status: 'ok' }); - - const members = await this.darkchatDB.getChannelMembers(reqObj.data.channelId); - for (const member of members) { - if (member.identifier !== userIdentifier) { - const participant = PlayerService.getPlayerFromIdentifier(member.identifier); - if (participant) { - emitNetTyped( - DarkchatEvents.BROADCAST_MESSAGE, - channelData, - participant.source, - ); - } - } - } - } catch (err) { - darkchatLogger.error(`Failed to update channel label. Error: ${err.message}`); - resp({ status: 'error', errorMsg: err.message }); - } - } - - async deleteChannel( - reqObj: PromiseRequest<{ channelId: number }>, - resp: PromiseEventResp, - ): Promise { - const userIdentifier = PlayerService.getIdentifier(reqObj.source); - - try { - const owner = await this.darkchatDB.getChannelOwner(reqObj.data.channelId); - const members = await this.darkchatDB.getMembers(reqObj.data.channelId); - if (owner === userIdentifier) { - await this.darkchatDB.deleteChannel(reqObj.data.channelId); - resp({ status: 'ok' }); - - for (const member of members) { - if (member.identifier !== userIdentifier) { - const participant = PlayerService.getPlayerFromIdentifier(member.identifier); - if (participant) { - emitNet(DarkchatEvents.DELETE_CHANNEL_SUCCESS, participant.source, { - channelId: reqObj.data.channelId, - }); - } - } - } - } - - return resp({ status: 'error', errorMsg: 'NOT_OWNER' }); - } catch (err) { - darkchatLogger.error(`Failed to delete channel. Error: ${err.message}`); - resp({ status: 'error', errorMsg: err.message }); - } - } -} - -const DarkchatService = new _DarkchatService(); -export default DarkchatService; diff --git a/apps/game/server/darkchat/darkchat.utils.ts b/apps/game/server/darkchat/darkchat.utils.ts deleted file mode 100644 index a1daeb994..000000000 --- a/apps/game/server/darkchat/darkchat.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const darkchatLogger = mainLogger.child({ module: 'darkchat' }); diff --git a/apps/game/server/lib/GlobalRateLimiter.ts b/apps/game/server/lib/GlobalRateLimiter.ts deleted file mode 100644 index f3f3ebae4..000000000 --- a/apps/game/server/lib/GlobalRateLimiter.ts +++ /dev/null @@ -1,33 +0,0 @@ -interface Limiter { - limiters: Map; - options: LimiterOptions; -} - -export interface LimiterOptions { - rateLimit?: number; -} - -export class GlobalRateLimiter { - private rateLimits: Map = new Map(); - private timeBetweenRequests: number; - constructor(timeBetweenReq: number = 250) { - this.timeBetweenRequests = timeBetweenReq; - } - - registerNewEvent(event: string, options?: LimiterOptions) { - this.rateLimits.set(event, { limiters: new Map(), options }); - } - - isPlayerRateLimited(event: string, source: number) { - return !!this.rateLimits?.get(event).limiters.get(source); - } - - rateLimitPlayer(event: string, source: number) { - let rateLimiter = this.rateLimits.get(event); - rateLimiter.limiters.set(source, true); - - setTimeout(() => { - rateLimiter.limiters.delete(source); - }, rateLimiter.options?.rateLimit || this.timeBetweenRequests); - } -} diff --git a/apps/game/server/lib/PromiseNetEvents/onNetPromise.ts b/apps/game/server/lib/PromiseNetEvents/onNetPromise.ts deleted file mode 100644 index dde76a71f..000000000 --- a/apps/game/server/lib/PromiseNetEvents/onNetPromise.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { getSource } from '../../utils/miscUtils'; -import { mainLogger } from '../../sv_logger'; -import { CBSignature, PromiseEventResp, PromiseRequest } from './promise.types'; -import { ServerPromiseResp } from '../../../../typings/common'; -import { GlobalRateLimiter, LimiterOptions } from '../GlobalRateLimiter'; - -const netEventLogger = mainLogger.child({ module: 'events' }); - -const globalRateLimiter = new GlobalRateLimiter(250); - -export function onNetPromise( - eventName: string, - cb: CBSignature, - options: LimiterOptions = null, -): void { - globalRateLimiter.registerNewEvent(eventName, options); - onNet(eventName, async (respEventName: string, data: T) => { - const startTime = process.hrtime.bigint(); - const src = getSource(); - - if (!respEventName) { - return netEventLogger.warn( - `Promise event (${eventName}) was called with wrong struct by ${src} (maybe originator wasn't a promiseEvent`, - ); - } - - const promiseRequest: PromiseRequest = { - source: src, - data, - }; - - netEventLogger.silly(`netPromise > ${eventName} > RequestObj`); - netEventLogger.silly(promiseRequest); - - const promiseResp: PromiseEventResp

= (data: ServerPromiseResp

) => { - const endTime = process.hrtime.bigint(); - const totalTime = Number(endTime - startTime) / 1e6; - emitNet(respEventName, src, data); - netEventLogger.silly(`Response Promise Event ${respEventName} (${totalTime}ms), Data >>`); - netEventLogger.silly(data); - }; - - if (globalRateLimiter.isPlayerRateLimited(eventName, src)) { - return promiseResp({ status: 'error', errorMsg: 'ERROR_RATE_LIMITED' }); - } else { - globalRateLimiter.rateLimitPlayer(eventName, source); - } - - // In case the cb is a promise, we use Promise.resolve - Promise.resolve(cb(promiseRequest, promiseResp)).catch((e) => { - netEventLogger.error( - `An error occured for a onNetPromise (${eventName}), Error: ${e.message}`, - ); - - promiseResp({ status: 'error', errorMsg: 'UNKNOWN_ERROR' }); - }); - }); -} diff --git a/apps/game/server/lib/PromiseNetEvents/promise.types.ts b/apps/game/server/lib/PromiseNetEvents/promise.types.ts deleted file mode 100644 index b3df91e3d..000000000 --- a/apps/game/server/lib/PromiseNetEvents/promise.types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ServerPromiseResp } from '../../../../typings/common'; - -export interface PromiseRequest { - data: T; - source: number; -} - -export type PromiseEventResp = (returnData: ServerPromiseResp) => void; - -export type CBSignature = (reqObj: PromiseRequest, resp: PromiseEventResp

) => void; diff --git a/apps/game/server/lib/http-service.ts b/apps/game/server/lib/http-service.ts deleted file mode 100644 index 94f220628..000000000 --- a/apps/game/server/lib/http-service.ts +++ /dev/null @@ -1,77 +0,0 @@ -import fetch, { FormData } from 'node-fetch'; -import { config } from '@npwd/config/server'; -import { Player } from '../players/player.class'; - -export async function tweetDiscordPost(url: string, body: string): Promise { - return new Promise((resolve, reject) => - fetch(url, { - method: 'POST', - body, - headers: { - ['Content-Type']: 'application/json', - }, - }) - .then(async (res) => { - const response: any = await res.json(); - resolve(response); - }) - .catch((err) => { - reject(err); - }) - ); -} - -export async function apiPhotoUpload(body: string | FormData, token: string): Promise { - return new Promise((resolve, reject) => - fetch(config.images.url, { - method: 'POST', - body, - headers: { - [config.images.useAuthorization && - config.images.authorizationHeader]: `${config.images.authorizationPrefix} ${token}`, - [config.images.useContentType && 'Content-Type']: config.images.contentType, - }, - }).then(async (result) => { - if (result.status !== 200) { - const err = await result.text(); - reject({ errorText: err, statusCode: result.status }); - } - - const res = await result.json(); - resolve(res); - }), - ); -} - -export async function webhookPhotoUpload( - webhook: string, - imagePath: string, - blob: any, - player: Player, -): Promise { - const form: FormData = new FormData(); - - form.append('file0', blob, `fivemscreenshot.${config.images.imageEncoding}`); - - form.append( - 'payload_json', - JSON.stringify({ - content: `**NPWD:** Image uploaded by ${player.getIdentifier()}`, - }), - ); - - return new Promise((resolve, reject) => - fetch(webhook, { - method: 'POST', - body: form, - }) - .then(async (res) => { - // fuck discord, not my fault I am using any here - const response: any = await res.json(); - resolve(response.attachments[0].proxy_url); - }) - .catch((err) => { - reject(err); - }), - ); -} \ No newline at end of file diff --git a/apps/game/server/marketplace/marketplace.controller.ts b/apps/game/server/marketplace/marketplace.controller.ts deleted file mode 100644 index be5e760f3..000000000 --- a/apps/game/server/marketplace/marketplace.controller.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - MarketplaceResp, - MarketplaceEvents, - MarketplaceListing, - MarketplaceListingBase, - MarketplaceReportDTO, - MarketplaceDeleteDTO, -} from '@typings/marketplace'; -import { marketplaceLogger } from './marketplace.utils'; -import MarketplaceService from './marketplace.service'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; - -onNetPromise(MarketplaceEvents.FETCH_LISTING, async (reqObj, resp) => { - MarketplaceService.handleFetchListings(reqObj, resp).catch((e) => { - marketplaceLogger.error( - `Error occurred in fetch listing event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise( - MarketplaceEvents.ADD_LISTING, - async (reqObj, resp) => { - MarketplaceService.handleAddListing(reqObj, resp).catch((e) => { - marketplaceLogger.error( - `Error occurred in add listing event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); - }, -); - -onNetPromise(MarketplaceEvents.DELETE_LISTING, async (reqObj, resp) => { - MarketplaceService.handleDeleteListing(reqObj, resp).catch((e) => { - marketplaceLogger.error( - `Error occurred in delete listing event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(MarketplaceEvents.REPORT_LISTING, async (reqObj, resp) => { - // TODO: Needs a permission check of some sort here eventually - MarketplaceService.handleReportListing(reqObj, resp).catch((e) => { - marketplaceLogger.error( - `Error occurred in report listing event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -on(MarketplaceEvents.DELETE_LISTINGS_ON_DROP, (identifier: string) => { - MarketplaceService.handleDeleteListingsOnDrop(identifier).catch((e) => { - marketplaceLogger.error( - `Error occurred when deleting listing on player drop event, Error: ${e.message}`, - ); - }); -}); diff --git a/apps/game/server/marketplace/marketplace.database.ts b/apps/game/server/marketplace/marketplace.database.ts deleted file mode 100644 index d529614e5..000000000 --- a/apps/game/server/marketplace/marketplace.database.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - MarketplaceDeleteDTO, - MarketplaceListing, - MarketplaceListingBase, - ReportListingDTO, -} from '@typings/marketplace'; -import { ResultSetHeader } from 'mysql2'; -import { DbInterface } from '@npwd/database'; - -export class _MarketplaceDB { - async addListing( - identifier: string, - username: string, - name: string, - number: string, - listing: MarketplaceListingBase, - ): Promise { - const query = - 'INSERT INTO npwd_marketplace_listings (identifier, username, name, number, title, url, description) VALUES (?, ?, ?, ?, ?, ?, ?)'; - - const [result] = await DbInterface._rawExec(query, [ - identifier, - username, - name, - number, - listing.title, - listing.url, - listing.description, - ]); - - const resultCast = result as ResultSetHeader; - - return resultCast.insertId; - } - - async fetchListings(): Promise { - const query = 'SELECT * FROM npwd_marketplace_listings WHERE reported = 0 ORDER BY id DESC'; - - const [results] = await DbInterface._rawExec(query); - return results; - } - - async deleteListing(listingId: number, identifier: string): Promise { - const query = 'DELETE FROM npwd_marketplace_listings WHERE id = ? AND identifier = ?'; - - await DbInterface._rawExec(query, [listingId, identifier]); - } - - async deleteListingsOnDrop(identifier: string) { - const query = `DELETE FROM npwd_marketplace_listings WHERE identifier = ? AND reported = 0`; - await DbInterface._rawExec(query, [identifier]); - } - - async getListing(listingId: number): Promise { - const query = `SELECT * FROM npwd_marketplace_listings WHERE id = ?`; - const [results] = await DbInterface._rawExec(query, [listingId]); - const listings = results; - return listings[0]; - } - - async getListingIdsByIdentifier(identifier: string): Promise { - const query = `SELECT id FROM npwd_marketplace_listings WHERE identifier = ?`; - const [results] = await DbInterface._rawExec(query, [identifier]); - - return results; - } - - async reportListing(listing: ReportListingDTO): Promise { - const query = `UPDATE npwd_marketplace_listings SET reported = 1 WHERE id = ?`; - await DbInterface._rawExec(query, [listing.id]); - } - - async doesListingExist(listing: MarketplaceListingBase, identifier: string): Promise { - const query = `SELECT * FROM npwd_marketplace_listings WHERE title = ? AND identifier = ?`; - const [results] = await DbInterface._rawExec(query, [listing.title, identifier]); - const listings = results; - - return listings.length > 0; - } - - async doesReportExist(listingId: number, profile: string): Promise { - const query = `SELECT * FROM npwd_marketplace_listings WHERE id = ? AND username = ? AND reported = 1`; - const [results] = await DbInterface._rawExec(query, [listingId, profile]); - const result = results; - - return result.length > 0; - } -} - -export const MarketplaceDB = new _MarketplaceDB(); diff --git a/apps/game/server/marketplace/marketplace.service.ts b/apps/game/server/marketplace/marketplace.service.ts deleted file mode 100644 index 02cdeece9..000000000 --- a/apps/game/server/marketplace/marketplace.service.ts +++ /dev/null @@ -1,153 +0,0 @@ -import PlayerService from '../players/player.service'; -import { marketplaceLogger } from './marketplace.utils'; -import { MarketplaceDB, _MarketplaceDB } from './marketplace.database'; -import { - MarketplaceResp, - MarketplaceDeleteDTO, - MarketplaceEvents, - MarketplaceListing, - MarketplaceListingBase, - MarketplaceReportDTO, -} from '@typings/marketplace'; -import { reportListingToDiscord } from '../misc/discord'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import { checkAndFilterImage } from './../utils/imageFiltering'; - -class _MarketplaceService { - private readonly marketplaceDB: _MarketplaceDB; - - constructor() { - this.marketplaceDB = MarketplaceDB; - marketplaceLogger.debug('Marketplace service started'); - } - - async handleAddListing( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - marketplaceLogger.debug('Handling add listing, listing:'); - marketplaceLogger.debug(reqObj.data); - - const player = PlayerService.getPlayer(reqObj.source); - const identifier = player.getIdentifier(); - - try { - const doesListingExist = await this.marketplaceDB.doesListingExist(reqObj.data, identifier); - if (doesListingExist) return resp({ status: 'error', errorMsg: MarketplaceResp.DUPLICATE }); - const imageUrl = checkAndFilterImage(reqObj.data.url); - if (imageUrl == null) { - return resp({ status: 'error', errorMsg: MarketplaceResp.INVALID_IMAGE_HOST }); - } - reqObj.data.url = imageUrl; - - const listingId = await this.marketplaceDB.addListing( - player.getIdentifier(), - player.username, - player.getName(), - player.getPhoneNumber(), - reqObj.data, - ); - // Respond with success to original source - resp({ status: 'ok' }); - - const returnObj: MarketplaceListing = { - id: listingId, - identifier: player.getIdentifier(), - name: player.getName(), - number: player.getPhoneNumber(), - url: reqObj.data.url, - username: player.username, - description: reqObj.data.description, - title: reqObj.data.title, - }; - // Broadcast to everyone we are adding a listing - emitNet(MarketplaceEvents.BROADCAST_ADD, -1, { type: 'ADD', listing: returnObj }); - } catch (e) { - marketplaceLogger.error(`Failed to add listing ${e.message}`, { - source: reqObj.source, - }); - - resp({ status: 'error', errorMsg: MarketplaceResp.CREATE_FAILED }); - } - } - - async handleFetchListings( - req: PromiseRequest, - resp: PromiseEventResp, - ) { - try { - const listings = await this.marketplaceDB.fetchListings(); - - resp({ data: listings, status: 'ok' }); - } catch (e) { - marketplaceLogger.error(`Failed to fetch listings, ${e.message}`, { - source: req.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleDeleteListing( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - await this.marketplaceDB.deleteListing(reqObj.data.id, identifier); - - resp({ status: 'ok' }); - - const returnObj = reqObj.data.id; - - emitNet(MarketplaceEvents.BROADCAST_DELETE, -1, [returnObj]); - } catch (e) { - marketplaceLogger.error(`Error in handleDeleteListing, ${e.message}`); - - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleDeleteListingsOnDrop(identifier: string) { - try { - const listingIds = await this.marketplaceDB.getListingIdsByIdentifier(identifier); - - emitNet(MarketplaceEvents.BROADCAST_DELETE, -1, listingIds); - - await this.marketplaceDB.deleteListingsOnDrop(identifier); - } catch (e) { - marketplaceLogger.error(`Error when deleting listings on player drop, ${e.message}`); - } - } - - async handleReportListing( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - try { - const rListing = await this.marketplaceDB.getListing(reqObj.data.id); - const reportExists = await this.marketplaceDB.doesReportExist( - reqObj.data.id, - rListing.username, - ); - - const reportingPlayer = GetPlayerName(reqObj.source.toString()); - - if (reportExists) { - marketplaceLogger.error(`This listing has already been reported`); - resp({ status: 'error', errorMsg: 'REPORT_EXISTS' }); - return; - } - - await this.marketplaceDB.reportListing(rListing); - await reportListingToDiscord(rListing, reportingPlayer); - } catch (e) { - marketplaceLogger.error(`Failed to report listing ${e.message}`, { - source: reqObj.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } -} - -const MarketplaceService = new _MarketplaceService(); -export default MarketplaceService; diff --git a/apps/game/server/marketplace/marketplace.utils.ts b/apps/game/server/marketplace/marketplace.utils.ts deleted file mode 100644 index 251c7bd0d..000000000 --- a/apps/game/server/marketplace/marketplace.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const marketplaceLogger = mainLogger.child({ module: 'marketplace' }); diff --git a/apps/game/server/match/match.controller.ts b/apps/game/server/match/match.controller.ts deleted file mode 100644 index 59b936dcb..000000000 --- a/apps/game/server/match/match.controller.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { FormattedMatch, FormattedProfile, Like, MatchEvents, Profile } from '@typings/match'; -import MatchService from './match.service'; -import { getSource } from '../utils/miscUtils'; -import { matchLogger } from './match.utils'; -import { config } from '@npwd/config/server'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; - -onNetPromise(MatchEvents.GET_PROFILES, (reqObj, resp) => { - MatchService.handleGetProfiles(reqObj, resp).catch((e) => { - matchLogger.error( - `Error occurred in fetch profiles event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(MatchEvents.GET_MY_PROFILE, (reqObj, resp) => { - MatchService.handleGetMyProfile(reqObj, resp).catch((e) => { - matchLogger.error( - `Error occurred in fetch my profile event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise<{ page: number }, FormattedMatch[]>(MatchEvents.GET_MATCHES, (reqObj, resp) => { - MatchService.handleGetMatches(reqObj, resp).catch((e) => { - matchLogger.error( - `Error occurred in fetch matches event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(MatchEvents.SAVE_LIKES, (reqObj, resp) => { - MatchService.handleSaveLikes(reqObj, resp).catch((e) => { - matchLogger.error(`Error occurred in save likes event (${reqObj.source}), Error: ${e.message}`); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(MatchEvents.CREATE_MY_PROFILE, (reqObj, resp) => { - MatchService.handleCreateMyProfile(reqObj, resp).catch((e) => { - matchLogger.error( - `Error occurred in create my profile event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(MatchEvents.UPDATE_MY_PROFILE, (reqObj, resp) => { - MatchService.handleUpdateMyProfile(reqObj, resp).catch((e) => { - matchLogger.error( - `Error occurred in update my profile event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNet(MatchEvents.INITIALIZE, () => { - const _source = getSource(); - MatchService.handleInitialize(_source).catch((e) => - matchLogger.error(`Error occurred in initialize event (${_source}), Error: ${e.message}`), - ); -}); - -if (!config.match.allowEditableProfileName && !config.match.generateProfileNameFromUsers) { - const warning = - `Both allowEdtiableProfileName and generateProfileNameFromUsers ` + - `are set false - this means users will likely not have profile names ` + - `for the Match App and won't be able to use it!`; - matchLogger.warn(warning); -} diff --git a/apps/game/server/match/match.database.ts b/apps/game/server/match/match.database.ts deleted file mode 100644 index 65b8f5341..000000000 --- a/apps/game/server/match/match.database.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { Like, Match, NewProfile, Profile } from '@typings/match'; -import { DbInterface, pool } from '@npwd/database'; -import { ResultSetHeader } from 'mysql2'; -import { config } from '@npwd/config/server'; -import { matchLogger } from './match.utils'; -import { generateProfileName } from '../utils/generateProfileName'; - -const DEFAULT_IMAGE = 'https://upload.wikimedia.org/wikipedia/commons/a/ac/No_image_available.svg'; -const MATCHES_PER_PAGE = 20; - -export class _MatchDB { - /** - * Select profiles that: - * 1. aren't the current player - * 2. haven't been viewed before - * 3. have been viewed before but that was at least 7 days ago - * @param identifier - players identifier - * @returns - array of profiles - */ - async getPotentialProfiles(identifier: string): Promise { - const query = ` - SELECT npwd_match_profiles.*, - UNIX_TIMESTAMP(npwd_match_profiles.updatedAt) AS lastActive, - MaxDates.lastSeen, - MaxDates.liked - FROM npwd_match_profiles - LEFT OUTER JOIN ( - SELECT id, profile, liked, MAX(createdAt) AS lastSeen - FROM npwd_match_views - WHERE identifier = ? - GROUP BY id, profile, liked - ) AS MaxDates ON npwd_match_profiles.id = MaxDates.profile - WHERE npwd_match_profiles.identifier != ? - AND (MaxDates.lastSeen IS NULL OR MaxDates.lastSeen < NOW() - INTERVAL 7 DAY) - AND MaxDates.liked IS NULL - ORDER BY npwd_match_profiles.updatedAt DESC - LIMIT 25 - `; - const [results] = await DbInterface._rawExec(query, [identifier, identifier]); - return results; - } - - /** - * Save a list of Like objects - * @param identifier - player's identifier - * @param likes - likes to be saved to the database - * @returns ResultSet - */ - async saveLikes(identifier: string, like: Like): Promise { - const query = `INSERT INTO npwd_match_views (identifier, profile, liked) - VALUES (?, ?, ?)`; - - const results = await DbInterface._rawExec(query, [identifier, like.id, like.liked]); - return results; - } - - /** - * Determines if a profile we just liked has already liked us in the past which - * indicates a match has occurred - * @param identifier - player's identifier - * @param id - profile ID we just liked - * @returns Profile[] - list of profiles that are a match - */ - async checkForMatchById(identifier: string, id: number): Promise { - const query = ` - SELECT targetProfile.*, - UNIX_TIMESTAMP(targetProfile.updatedAt) AS lastActive - FROM npwd_match_views - LEFT OUTER JOIN npwd_match_profiles AS playerProfile ON npwd_match_views.profile = playerProfile.id - LEFT OUTER JOIN npwd_match_profiles AS targetProfile - ON npwd_match_views.identifier = targetProfile.identifier - WHERE playerProfile.identifier = ? - AND targetProfile.id = ? - AND liked = 1 - `; - const [results] = await DbInterface._rawExec(query, [identifier, id]); - return results; - } - - /** - * Return all matches associated with a player regardless of date/time. A - * match is a case where two profiles have liked each other. - * @param identifier - player's identifier - * @param page - * @returns Match[] - all matches associated with the current player - */ - async findAllMatches(identifier: string, page: number): Promise { - const offset = MATCHES_PER_PAGE * page; - - const query = ` - SELECT targetProfile.*, - UNIX_TIMESTAMP(targetProfile.updatedAt) AS lastActive, - UNIX_TIMESTAMP(GREATEST(npwd_match_views.createdAt, targetViews.createdAt)) AS matchedAt, - targetUser.${config.database.phoneNumberColumn} AS phoneNumber - FROM npwd_match_views - LEFT OUTER JOIN npwd_match_profiles AS targetProfile ON npwd_match_views.profile = targetProfile.id - LEFT OUTER JOIN npwd_match_profiles AS myProfile ON npwd_match_views.identifier = myProfile.identifier - LEFT OUTER JOIN npwd_match_views AS targetViews - ON targetProfile.identifier = targetViews.identifier AND - targetViews.profile = myProfile.id - LEFT OUTER JOIN ${config.database.playerTable} AS targetUser ON targetProfile.identifier = targetUser.${config.database.identifierColumn} - WHERE npwd_match_views.identifier = ? - AND npwd_match_views.liked = 1 - AND targetViews.liked = 1 - ORDER BY matchedAt DESC - LIMIT ? OFFSET ? - `; - const [results] = await DbInterface._rawExec(query, [ - identifier, - MATCHES_PER_PAGE.toString(), - offset.toString(), - ]); - return results; - } - - /** - * Retrieve the current player's profile - * @param identifier - player's identifier - * @returns Profile - player's current profile - */ - async getPlayerProfile(identifier: string): Promise { - const query = ` - SELECT *, - UNIX_TIMESTAMP(updatedAt) AS lastActive - FROM npwd_match_profiles - WHERE identifier = ? - LIMIT 1 - `; - const [results] = await DbInterface._rawExec(query, [identifier]); - const profiles = results; - return profiles[0]; - } - - /** - * Create a profile and associate it with the current player - * @param identifier - player's identifier - * @param profile - profile we are going to create - * @returns Profile - the created profile - */ - async createProfile(identifier: string, profile: NewProfile): Promise { - const { name, image, bio, location, job, tags, voiceMessage } = profile; - const query = ` - INSERT INTO npwd_match_profiles (identifier, name, image, bio, location, job, tags, voiceMessage) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `; - - await pool.execute(query, [identifier, name, image, bio, location, job, tags, voiceMessage]); - return await this.getPlayerProfile(identifier); - } - - /** - * Update the current player's profile - * @param identifier - player's identifier - * @param profile Profile - player's updated profile - */ - async updateProfile(identifier: string, profile: Profile): Promise { - const { image, name, bio, location, job, tags, voiceMessage } = profile; - const query = ` - UPDATE npwd_match_profiles - SET image = ?, - name = ?, - bio = ?, - location = ?, - job = ?, - tags = ?, - voiceMessage = ? - WHERE identifier = ? - `; - await pool.execute(query, [image, name, bio, location, job, tags, voiceMessage, identifier]); - return await this.getPlayerProfile(identifier); - } - - /** - * Create a default profile for a player based on information - * associated with them (name, phone number, etc.) - * @param identifier - player's identifier - * @returns Profile | null - the player's profile or null if it does not exist - * and could not be created - */ - async createDefaultProfile(identifier: string): Promise { - // case where the server owner wants players to select their own names - if (!config.match.generateProfileNameFromUsers) return null; - - const defaultProfileName = await generateProfileName(identifier, ' '); - // case where we tried to generate a profile name but failed due to - // some database misconfiguration or error - if (!defaultProfileName) return null; - - const defaultProfile: NewProfile = { - name: defaultProfileName, - image: DEFAULT_IMAGE, - bio: '', - location: '', - job: '', - tags: '', - voiceMessage: null, - }; - - matchLogger.info(`Creating default match profile ${defaultProfileName} for ${identifier}`); - return await this.createProfile(identifier, defaultProfile); - } - - /** - * Given a list of likes determine if any of the profiles we liked have also - * liked us which indicates a match has occurred. - * @param identifier - player's identifier - * @param likes - list of new Likes - * @returns Profile - list of profiles we matched with - */ - async checkIfMatched(identifier: string, like: Like): Promise { - const matchedProfiles = await this.checkForMatchById(identifier, like.id); - - return matchedProfiles[0]; - } - - /** - * Retrieve the player's profile by their identifier if it exists. If - * not, create it and return it. - * @param identifier - player's identifier - * @returns Profile - player's profile - */ - async getOrCreateProfile(identifier: string): Promise { - const profile = await this.getPlayerProfile(identifier); - return profile || (await this.createDefaultProfile(identifier)); - } - - /** - * We track when the player last used the app so other players can - * be aware of how active that player is when they are liking/disliking - * @param identifier - player's identifier - */ - async updateLastActive(identifier: string): Promise { - const query = ` - UPDATE npwd_match_profiles - SET updatedAt = CURRENT_TIMESTAMP() - WHERE identifier = ? - `; - await pool.execute(query, [identifier]); - } -} - -export const MatchDB = new _MatchDB(); diff --git a/apps/game/server/match/match.service.ts b/apps/game/server/match/match.service.ts deleted file mode 100644 index 960d5ebdd..000000000 --- a/apps/game/server/match/match.service.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { formatMatches, formatProfile, matchLogger } from './match.utils'; -import { MatchDB, _MatchDB } from './match.database'; -import { - FormattedMatch, - FormattedProfile, - Like, - MatchEvents, - MatchResp, - Profile, -} from '@typings/match'; -import PlayerService from '../players/player.service'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import { checkAndFilterImage } from './../utils/imageFiltering'; - -class _MatchService { - private readonly matchDB: _MatchDB; - - constructor() { - this.matchDB = MatchDB; - matchLogger.debug('Match service started'); - } - - async handleGetProfiles( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const profiles = await this.dispatchProfiles(identifier); - resp({ status: 'ok', data: profiles }); - } catch (e) { - matchLogger.error(`Error in handleGetProfiles, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleGetMyProfile( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const profile = await this.dispatchPlayerProfile(identifier); - emitNet(MatchEvents.CREATE_MATCH_ACCOUNT_BROADCAST, -1, profile); - resp({ status: 'ok', data: profile }); - } catch (e) { - matchLogger.error(`Error in handleGetMyProfile, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleSaveLikes(reqObj: PromiseRequest, resp: PromiseEventResp) { - const player = PlayerService.getPlayer(reqObj.source); - const identifier = player.getIdentifier(); - matchLogger.debug(`Saving likes for identifier ${identifier}`); - - try { - await this.matchDB.saveLikes(identifier, reqObj.data); - } catch (e) { - matchLogger.error(`Failed to save likes, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - - try { - const newMatches = await this.matchDB.checkIfMatched(identifier, reqObj.data); - - if (newMatches) { - resp({ status: 'ok', data: true }); - - const matchedPlayer = PlayerService.getPlayerFromIdentifier(newMatches.identifier); - - if (matchedPlayer) { - emitNet(MatchEvents.SAVE_LIKES_BROADCAST, matchedPlayer.source, { - name: player.getName(), - }); - } - } else { - resp({ status: 'ok', data: false }); - } - } catch (e) { - matchLogger.error(`Failed to find new matches, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleGetMatches( - reqObj: PromiseRequest<{ page: number }>, - resp: PromiseEventResp, - ) { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const matchedProfiles = await this.matchDB.findAllMatches(identifier, reqObj.data.page); - const formattedMatches = matchedProfiles.map(formatMatches); - resp({ status: 'ok', data: formattedMatches }); - } catch (e) { - matchLogger.error(`Failed to retrieve matches, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleCreateMyProfile( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const identifier = PlayerService.getIdentifier(reqObj.source); - const profile = reqObj.data; - - matchLogger.debug(`Creating profile for identifier: ${identifier}`); - matchLogger.debug(profile); - - try { - if (!profile.name || !profile.name.trim()) { - throw new Error('Profile name must not be blank'); - } - - const newProfile = await this.matchDB.createProfile(identifier, profile); - const formattedProfile = formatProfile(newProfile); - - resp({ status: 'ok', data: formattedProfile }); - } catch (e) { - matchLogger.error(`Failed to update profile for identifier ${identifier}, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleUpdateMyProfile( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const identifier = PlayerService.getIdentifier(reqObj.source); - const profile = reqObj.data; - - matchLogger.debug(`Updating profile for identifier: ${identifier}`); - matchLogger.debug(profile); - - try { - if (!profile.name || !profile.name.trim()) { - throw new Error('Profile name must not be blank'); - } - - const imageUrl = checkAndFilterImage(reqObj.data.image); - if (imageUrl == null) { - return resp({ status: 'error', errorMsg: 'GENERIC_INVALID_IMAGE_HOST' }); - } - reqObj.data.image = imageUrl; - - const updatedProfile = await this.matchDB.updateProfile(identifier, profile); - const formattedProfile = formatProfile(updatedProfile); - - resp({ status: 'ok', data: formattedProfile }); - } catch (e) { - matchLogger.error(`Failed to update profile for identifier ${identifier}, ${e.message}`); - resp({ status: 'error', errorMsg: MatchResp.UPDATE_FAILED }); - } - } - - async dispatchPlayerProfile(identifier: string): Promise { - try { - const profile = await this.matchDB.getOrCreateProfile(identifier); - return formatProfile(profile); - } catch (e) { - matchLogger.error(`Failed to get player profile, ${e.message}`); - } - } - - async dispatchProfiles(identifier: string): Promise { - try { - const profiles = await this.matchDB.getPotentialProfiles(identifier); - return profiles.map(formatProfile); - } catch (e) { - matchLogger.error(`Failed to retrieve profiles, ${e.message}`); - } - } - - async handleInitialize(src: number) { - const identifier = PlayerService.getIdentifier(src); - matchLogger.debug(`Initializing match for identifier: ${identifier}`); - await this.matchDB.updateLastActive(identifier); - } -} - -const MatchService = new _MatchService(); - -export default MatchService; diff --git a/apps/game/server/match/match.utils.ts b/apps/game/server/match/match.utils.ts deleted file mode 100644 index 26914b581..000000000 --- a/apps/game/server/match/match.utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { mainLogger } from '../sv_logger'; -import { FormattedMatch, FormattedProfile, Match, Profile } from '../../../typings/match'; -import dayjs from 'dayjs'; - -export const matchLogger = mainLogger.child({ module: 'match' }); - -export function formatProfile(profile: Profile): FormattedProfile | null { - return { - ...profile, - tagList: profile.tags.split(',').filter((t) => t), // remove any empty tags - lastActiveFormatted: dayjs.unix(profile.lastActive).toString(), - viewed: false, - }; -} - -export function formatMatches(match: Match): FormattedMatch | null { - return { - ...match, - tagList: match.tags.split(',').filter((t) => t), // remove any empty tags - lastActiveFormatted: dayjs.unix(match.lastActive).toString(), - matchedAtFormatted: dayjs.unix(match.matchedAt).toString(), - }; -} diff --git a/apps/game/server/messages/messages.controller.ts b/apps/game/server/messages/messages.controller.ts deleted file mode 100644 index 6f5765da2..000000000 --- a/apps/game/server/messages/messages.controller.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { getSource } from '../utils/miscUtils'; -import { - DeleteConversationRequest, - Message, - MessageConversation, - MessageEvents, - PreDBConversation, - PreDBMessage, -} from '@typings/messages'; -import MessagesService from './messages.service'; -import { messagesLogger } from './messages.utils'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; -import { OnMessageExportMap } from './middleware/onMessage'; - -onNetPromise( - MessageEvents.FETCH_MESSAGE_CONVERSATIONS, - async (reqObj, resp) => { - MessagesService.handleFetchMessageConversations(reqObj, resp).catch((e) => { - messagesLogger.error( - `Error occurred in fetch message conversations (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); - }, -); - -onNetPromise( - MessageEvents.CREATE_MESSAGE_CONVERSATION, - async (reqObj, resp) => { - MessagesService.handleCreateMessageConversation(reqObj, resp).catch((e) => { - messagesLogger.error( - `Error occurred on creating message conversation (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); - }, -); - -onNetPromise<{ conversationId: string; page: number }, Message[]>( - MessageEvents.FETCH_MESSAGES, - async (reqObj, resp) => { - MessagesService.handleFetchMessages(reqObj, resp).catch((e) => { - messagesLogger.error( - `Error occurred in fetch messages (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); - }, -); - -onNetPromise(MessageEvents.SEND_MESSAGE, async (reqObj, resp) => { - MessagesService.handleSendMessage(reqObj, resp) - .then(async () => { - // Get the phone numbers from conversation list since front end seems to be borked - const numbers = reqObj.data.conversationList - .split('+') - .filter((x) => x !== reqObj.data.sourcePhoneNumber); - - // Allow only messages in 1 on 1 DM and not from group chats - if (numbers.length > 1) return; - const tgtPhoneNumber = numbers[0]; - - const funcRef = OnMessageExportMap.get(tgtPhoneNumber); - if (funcRef) { - try { - await funcRef({ data: { ...reqObj.data, tgtPhoneNumber }, source: reqObj.source }); - } catch (e) { - messagesLogger.error( - `Failed to find a callback reference for onMessage. Probably because the resource(s) using the export was stopped or restarted. Please restart said resource(s). Error: ${e.message}`, - ); - } - } - }) - .catch((e) => { - messagesLogger.error( - `Error occurred while sending message (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise( - MessageEvents.DELETE_CONVERSATION, - async (reqObj, resp) => { - MessagesService.handleDeleteConversation(reqObj, resp).catch((e) => { - messagesLogger.error( - `Error occurred while deleting conversation (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); - }, -); - -onNetPromise(MessageEvents.DELETE_MESSAGE, async (reqObj, resp) => { - MessagesService.handleDeleteMessage(reqObj, resp).catch((e) => { - messagesLogger.error( - `Error occurred while deleting message (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(MessageEvents.SET_MESSAGE_READ, async (reqObj, resp) => { - const src = getSource(); - MessagesService.handleSetMessageRead(reqObj, resp).catch((e) => - messagesLogger.error(`Error occurred in set message read event (${src}), Error: ${e.message}`), - ); -}); - -onNetPromise(MessageEvents.GET_MESSAGE_LOCATION, async (reqObj, resp) => { - const src = getSource(); - MessagesService.handleGetLocation(reqObj, resp).catch((e) => { - messagesLogger.error(`Error occurred in get location event (${src}), Error: ${e.message}`); - }); -}); diff --git a/apps/game/server/messages/messages.database.ts b/apps/game/server/messages/messages.database.ts deleted file mode 100644 index df5079703..000000000 --- a/apps/game/server/messages/messages.database.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { DbInterface } from "@npwd/database"; -import { - CreateMessageDTO, - Message, - MessageConversation, - MessagesRequest, -} from "@typings/messages"; -import { ResultSetHeader } from "mysql2"; -import { messagesLogger } from "../../../../apps/game/server/messages/messages.utils"; - -const MESSAGES_PER_PAGE = 20; - -export class _MessagesDB { - async getConversations(phoneNumber: string): Promise { - const query = `SELECT npwd_messages_conversations.id, - npwd_messages_conversations.conversation_list as conversationList, - npwd_messages_participants.unread_count as unreadCount, - npwd_messages_conversations.is_group_chat as isGroupChat, - npwd_messages_conversations.label, - UNIX_TIMESTAMP(npwd_messages_conversations.updatedAt) as updatedAt, - npwd_messages_participants.participant - FROM npwd_messages_conversations - INNER JOIN npwd_messages_participants - on npwd_messages_conversations.id = npwd_messages_participants.conversation_id - WHERE npwd_messages_participants.participant = ?`; - - const [results] = await DbInterface._rawExec(query, [phoneNumber]); - - return results; - } - - async getConversation(conversationId: number): Promise { - const query = `SELECT npwd_messages_conversations.id, - npwd_messages_conversations.conversation_list as conversationList, - npwd_messages_conversations.is_group_chat as isGroupChat, - npwd_messages_conversations.label, - UNIX_TIMESTAMP(npwd_messages_conversations.createdAt) as createdAt, - UNIX_TIMESTAMP(npwd_messages_conversations.updatedAt) as updatedAt - FROM npwd_messages_conversations - WHERE id = ? - LIMIT 1`; - const [results] = await DbInterface._rawExec(query, [conversationId]); - - const result = results; - return result[0]; - } - - async getMessages(dto: MessagesRequest): Promise { - const offset = MESSAGES_PER_PAGE * dto.page; - - const query = `SELECT npwd_messages.id, - npwd_messages.conversation_id, - npwd_messages.author, - npwd_messages.message, - npwd_messages.is_embed, - UNIX_TIMESTAMP(npwd_messages.createdAt) as createdAt, - npwd_messages.embed - FROM npwd_messages - WHERE conversation_id = ? - ORDER BY createdAt DESC - LIMIT ? OFFSET ?`; - - const [results] = await DbInterface._rawExec(query, [ - dto.conversationId, - MESSAGES_PER_PAGE.toString(), - offset.toString(), - ]); - return results; - } - - async createConversation( - participants: string[], - conversationList: string, - conversationLabel: string, - isGroupChat: boolean, - ) { - const conversationQuery = `INSERT INTO npwd_messages_conversations (conversation_list, label, is_group_chat) - VALUES (?, ?, ?)`; - const participantQuery = `INSERT INTO npwd_messages_participants (conversation_id, participant) - VALUES (?, ?)`; - - const [results] = await DbInterface._rawExec(conversationQuery, [ - conversationList, - isGroupChat ? conversationLabel : "", - isGroupChat, - ]); - const result = results; - - const conversationId = result.insertId; - - for (const participant of participants) { - await DbInterface._rawExec(participantQuery, [ - conversationId, - participant, - ]); - } - - return conversationId; - } - - async addParticipantToConversation( - conversationList: string, - phoneNumber: string, - ) { - const conversationId = await this.getConversationId(conversationList); - - const participantQuery = `INSERT INTO npwd_messages_participants (conversation_id, participant) - VALUES (?, ?)`; - - await DbInterface._rawExec(participantQuery, [conversationId, phoneNumber]); - - return conversationId; - } - - async createMessage(dto: CreateMessageDTO): Promise { - const query = `INSERT INTO npwd_messages (message, user_identifier, conversation_id, author, is_embed, embed) - VALUES (?, ?, ?, ?, ?, ?)`; - - const [results] = await DbInterface._rawExec(query, [ - dto.message || "", - dto.userIdentifier, - dto.conversationId, - dto.authorPhoneNumber, - dto.is_embed || false, - dto.embed || "", - ]); - - const result = results; - - const updateConversation = `UPDATE npwd_messages_conversations - SET updatedAt = current_timestamp() - WHERE id = ?`; - - // We await here so we're not blocking the return call - setImmediate(async () => { - await DbInterface._rawExec(updateConversation, [ - dto.conversationId, - ]).catch((err) => - messagesLogger.error( - `Error occurred in message update Error: ${err.message}`, - ), - ); - }); - - return await this.getMessageFromId(result.insertId); - } - - async setMessageUnread(conversationId: number, tgtPhoneNumber: string) { - const query = `UPDATE npwd_messages_participants - SET unread_count = unread_count + 1 - WHERE conversation_id = ? - AND participant = ?`; - - await DbInterface._rawExec(query, [conversationId, tgtPhoneNumber]); - } - - async setMessageRead(conversationId: number, participantNumber: string) { - const query = `UPDATE npwd_messages_participants - SET unread_count = 0 - WHERE conversation_id = ? - AND participant = ?`; - - await DbInterface._rawExec(query, [conversationId, participantNumber]); - } - - async deleteMessage(message: Message) { - const query = `DELETE - FROM npwd_messages - WHERE id = ?`; - - await DbInterface._rawExec(query, [message.id]); - } - - async deleteConversation(conversationId: number, phoneNumber: string) { - const query = `DELETE - FROM npwd_messages_participants - WHERE conversation_id = ? - AND participant = ?`; - - await DbInterface._rawExec(query, [conversationId, phoneNumber]); - } - - async doesConversationExist(conversationList: string): Promise { - const query = `SELECT COUNT(*) as count - FROM npwd_messages_conversations - INNER JOIN npwd_messages_participants - on npwd_messages_conversations.id = npwd_messages_participants.conversation_id - WHERE conversation_list = ?`; - - const [results] = await DbInterface._rawExec(query, [conversationList]); - const result = results; - const count = result[0].count; - - return count > 0; - } - - async doesConversationExistForPlayer( - conversationList: string, - phoneNumber: string, - ): Promise { - const query = `SELECT COUNT(*) as count - FROM npwd_messages_conversations - INNER JOIN npwd_messages_participants - on npwd_messages_conversations.id = npwd_messages_participants.conversation_id - WHERE conversation_list = ? - AND npwd_messages_participants.participant = ?`; - - const [results] = await DbInterface._rawExec(query, [ - conversationList, - phoneNumber, - ]); - const result = results; - const count = result[0].count; - - return count > 0; - } - - // misc stuff - async getConversationId(conversationList: string): Promise { - const query = `SELECT id - FROM npwd_messages_conversations - WHERE conversation_list = ?`; - ``; - const [results] = await DbInterface._rawExec(query, [conversationList]); - const result = results; - - return result[0].id; - } - - async getMessageFromId(messageId: number): Promise { - const query = `SELECT npwd_messages.id, - npwd_messages.conversation_id, - npwd_messages.author, - npwd_messages.message, - UNIX_TIMESTAMP(npwd_messages.createdAt) as createdAt, - npwd_messages.is_embed, - npwd_messages.embed - FROM npwd_messages - WHERE id = ?`; - - const [results] = await DbInterface._rawExec(query, [messageId]); - const result = results; - return result[0]; - } -} - -export const MessagesDB = new _MessagesDB(); diff --git a/apps/game/server/messages/messages.service.ts b/apps/game/server/messages/messages.service.ts deleted file mode 100644 index 42b702a84..000000000 --- a/apps/game/server/messages/messages.service.ts +++ /dev/null @@ -1,449 +0,0 @@ -import { MessagesDB, _MessagesDB } from "./messages.database"; -import { - createGroupHashID, - getIdentifiersFromParticipants, - messagesLogger, -} from "./messages.utils"; -import { - PromiseEventResp, - PromiseRequest, -} from "../lib/PromiseNetEvents/promise.types"; -import { - DeleteConversationRequest, - EmitMessageExportCtx, - Message, - MessageConversation, - MessageEvents, - MessagesRequest, - PreDBConversation, - PreDBMessage, - Location, -} from "@typings/messages"; -import PlayerService from "../players/player.service"; -import { emitNetTyped } from "../utils/miscUtils"; - -class _MessagesService { - private readonly messagesDB: _MessagesDB; - - constructor() { - this.messagesDB = MessagesDB; - messagesLogger.debug("Messages service started"); - } - - async handleFetchMessageConversations( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); - - try { - const conversations = await MessagesDB.getConversations(phoneNumber); - - resp({ status: "ok", data: conversations }); - } catch (err) { - resp({ status: "error", errorMsg: err.message }); - } - } - - async handleCreateMessageConversation( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const playerPhoneNumber = PlayerService.getPlayer( - reqObj.source, - ).getPhoneNumber(); - const conversation = reqObj.data; - const participants = conversation.participants; - - const conversationList = createGroupHashID(participants); - - const doesExist = - await this.messagesDB.doesConversationExist(conversationList); - - if (doesExist) { - const playerHasConversation = - await this.messagesDB.doesConversationExistForPlayer( - conversationList, - playerPhoneNumber, - ); - - if (playerHasConversation) { - return resp({ - status: "error", - errorMsg: "MESSAGES.FEEDBACK.MESSAGE_CONVERSATION_DUPLICATE", - }); - } else { - const conversationId = - await this.messagesDB.addParticipantToConversation( - conversationList, - playerPhoneNumber, - ); - - const respData = { - id: conversationId, - label: conversation.conversationLabel, - conversationList, - isGroupChat: conversation.isGroupChat, - }; - - return resp({ - status: "ok", - data: { ...respData, participant: playerPhoneNumber }, - }); - } - } - - try { - const conversationId = await MessagesDB.createConversation( - participants, - conversationList, - conversation.conversationLabel, - conversation.isGroupChat, - ); - - // Return data - const respData = { - id: conversationId, - label: conversation.conversationLabel, - conversationList, - isGroupChat: conversation.isGroupChat, - }; - - resp({ - status: "ok", - data: { ...respData, participant: playerPhoneNumber }, - }); - - for (const participant of participants) { - if (participant !== playerPhoneNumber) { - const participantIdentifier = - await PlayerService.getIdentifierByPhoneNumber(participant); - const participantPlayer = PlayerService.getPlayerFromIdentifier( - participantIdentifier, - ); - - if (participantPlayer) { - emitNetTyped( - MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, - { - ...respData, - participant: participantPlayer.getPhoneNumber(), - }, - participantPlayer.source, - ); - } - } - } - } catch (err) { - messagesLogger.error( - `Error occurred on creating message conversation (${reqObj.source}), Error: ${err.message}`, - ); - resp({ status: "error", errorMsg: err.message }); - } - } - - async handleFetchMessages( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - try { - const messages = await MessagesDB.getMessages(reqObj.data); - - // its just 20 elements, won't do that much harm - const sortedMessages = messages.sort((a, b) => a.id - b.id); - - resp({ status: "ok", data: sortedMessages }); - } catch (err) { - resp({ status: "error", errorMsg: err.message }); - } - } - - async handleSendMessage( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - try { - const player = PlayerService.getPlayer(reqObj.source); - const authorPhoneNumber = player.getPhoneNumber(); - const messageData = reqObj.data; - const participants = getIdentifiersFromParticipants( - messageData.conversationList, - ); - const userIdentifier = player.getIdentifier(); - - const conversationDetails = await this.messagesDB.getConversation( - messageData.conversationId, - ); - - const message = await this.messagesDB.createMessage({ - userIdentifier, - authorPhoneNumber, - conversationId: messageData.conversationId, - message: messageData.message, - is_embed: messageData.is_embed, - embed: messageData.embed, - }); - - resp({ - status: "ok", - data: { - ...message, - message: message.message, - conversation_id: messageData.conversationId, - author: authorPhoneNumber, - is_embed: messageData.is_embed, - }, - }); - - const conversationData = { - id: messageData.conversationId, - label: conversationDetails.label, - conversationList: conversationDetails.conversationList, - isGroupChat: conversationDetails.isGroupChat, - }; - - // participantId is the participants phone number - for (const participantId of participants) { - if (participantId !== authorPhoneNumber) { - try { - const playerHasConversation = - await this.messagesDB.doesConversationExistForPlayer( - messageData.conversationList, - participantId, - ); - - // We need to create a conversation for the participant before broadcasting - if (!playerHasConversation) { - const conversationId = - await this.messagesDB.addParticipantToConversation( - conversationDetails.conversationList, - participantId, - ); - } - - const participantIdentifier = - await PlayerService.getIdentifierByPhoneNumber( - participantId, - true, - ); - - const participantNumber = - await PlayerService.getPhoneNumberFromIdentifier( - participantIdentifier, - ); - - const participantPlayer = PlayerService.getPlayerFromIdentifier( - participantIdentifier, - ); - - await this.messagesDB.setMessageUnread( - messageData.conversationId, - participantNumber, - ); - - if (participantPlayer) { - if (!playerHasConversation) { - emitNetTyped( - MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, - { - ...conversationData, - participant: authorPhoneNumber, - }, - participantPlayer.source, - ); - } - - emitNet( - MessageEvents.SEND_MESSAGE_SUCCESS, - participantPlayer.source, - { - ...message, - conversation_id: messageData.conversationId, - author: authorPhoneNumber, - }, - ); - emitNet( - MessageEvents.CREATE_MESSAGE_BROADCAST, - participantPlayer.source, - { - conversationName: authorPhoneNumber, - conversation_id: messageData.conversationId, - message: messageData.message, - is_embed: messageData.is_embed, - embed: messageData.embed, - }, - ); - } - } catch (err) { - messagesLogger.warn( - `Failed to broadcast message. Player is not online.`, - ); - } - } - } - } catch (err) { - resp({ status: "error", errorMsg: err.message }); - } - } - - async handleSetMessageRead( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); - - try { - await this.messagesDB.setMessageRead(reqObj.data, phoneNumber); - - resp({ status: "ok" }); - } catch (err) { - messagesLogger.error(`Failed to read message. Error: ${err.message}`); - resp({ status: "error" }); - } - } - - async handleDeleteMessage( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - try { - await this.messagesDB.deleteMessage(reqObj.data); - - resp({ status: "ok" }); - } catch (err) { - resp({ status: "error", errorMsg: err.message }); - } - } - - async handleDeleteConversation( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); - const conversationsId = reqObj.data.conversationsId; - - try { - for (const id of conversationsId) { - await this.messagesDB.deleteConversation(id, phoneNumber); - } - resp({ status: "ok" }); - } catch (err) { - resp({ status: "error", errorMsg: err.message }); - } - } - - // Exports - async handleEmitMessage(dto: EmitMessageExportCtx) { - const { senderNumber, targetNumber, message, embed } = dto; - - try { - // this will post an error message if the number doesn't exist but emitMessage will so go through from roleplay number - const senderPlayer = await PlayerService.getIdentifierFromPhoneNumber( - senderNumber, - true, - ); - - const participantIdentifier = - await PlayerService.getIdentifierFromPhoneNumber(targetNumber); - const participantPlayer = PlayerService.getPlayerFromIdentifier( - participantIdentifier, - ); - - // Create our groupId hash - const conversationList = createGroupHashID([senderNumber, targetNumber]); - - const doesConversationExist = - await this.messagesDB.doesConversationExist(conversationList); - let conversationId: number; - - // Generate conversation id or assign from existing conversation - // If we generate the conversation we add the player and update their front-end if they're online - if (!doesConversationExist) { - conversationId = await this.messagesDB.createConversation( - [senderNumber, targetNumber], - conversationList, - "", - false, - ); - - if (participantPlayer) { - emitNetTyped( - MessageEvents.CREATE_MESSAGE_CONVERSATION_SUCCESS, - { - id: conversationId, - conversationList, - label: "", - isGroupChat: false, - participant: targetNumber, - }, - participantPlayer.source, - ); - } - } else { - conversationId = - await this.messagesDB.getConversationId(conversationList); - } - - const msg = await this.messagesDB.createMessage({ - message, - embed: embed, - is_embed: !!embed, - conversationId, - userIdentifier: senderPlayer || senderNumber, - authorPhoneNumber: senderNumber, - }); - - // Create respondObj - const messageData = { - ...msg, - conversationList, - conversation_id: conversationId, - author: senderNumber, - }; - - if (participantPlayer) { - emitNet(MessageEvents.SEND_MESSAGE_SUCCESS, participantPlayer.source, { - ...messageData, - conversation_id: conversationId, - author: senderNumber, - }); - emitNet( - MessageEvents.CREATE_MESSAGE_BROADCAST, - participantPlayer.source, - { - ...messageData, - conversationName: senderNumber, - conversation_id: conversationId, - message: messageData.message, - is_embed: messageData.is_embed, - embed: messageData.embed, - }, - ); - } - - await this.messagesDB.setMessageUnread(conversationId, targetNumber); - } catch (err) { - console.log(`Failed to emit message. Error: ${err.message}`); - } - } - - async handleGetLocation( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const phoneNumber = PlayerService.getPlayer(reqObj.source).getPhoneNumber(); - const playerPed = GetPlayerPed(reqObj.source.toString()); - - resp({ - status: "ok", - data: { - phoneNumber, - coords: GetEntityCoords(playerPed), - }, - }); - } -} - -const MessagesService = new _MessagesService(); - -export default MessagesService; diff --git a/apps/game/server/messages/messages.utils.ts b/apps/game/server/messages/messages.utils.ts deleted file mode 100644 index ddb8d7294..000000000 --- a/apps/game/server/messages/messages.utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const messagesLogger = mainLogger.child({ module: 'messages' }); - -export function createGroupHashID(participants: string[]) { - // make sure we are always in a consistent order. It is very important - // that this not change! Changing this order can result in the ability - // of duplicate message groups being created. - participants.sort(); - return participants.join('+'); - // we don't need this to be secure. Its purpose is to create a unique - // string derived from the identifiers. In this way we can check - // that this groupId isn't used before. If it has then it means - // we are trying to create a duplicate message group! -} - -export function getIdentifiersFromParticipants(conversationId: string) { - return conversationId.split('+'); -} diff --git a/apps/game/server/messages/middleware/emitMessage.ts b/apps/game/server/messages/middleware/emitMessage.ts deleted file mode 100644 index 5dc5b369e..000000000 --- a/apps/game/server/messages/middleware/emitMessage.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { EmitMessageExportCtx } from '@typings/messages'; -import PlayerService from '../../players/player.service'; -import MessagesService from '../messages.service'; -import { messagesLogger } from '../messages.utils'; -import { OnMessageExportMap } from './onMessage'; - -const exp = global.exports; - -// FIXME: Fix this, oh wait, no one contributes anymore -exp('emitMessage', async ({ senderNumber, targetNumber, message, embed }: EmitMessageExportCtx) => { - await MessagesService.handleEmitMessage({ - senderNumber, - targetNumber, - message, - embed: embed && JSON.stringify(embed), - }).then(async () => { - const funcRef = OnMessageExportMap.get(targetNumber); - - const senderIdentifier = await PlayerService.getIdentifierByPhoneNumber(senderNumber, false); - const senderPlayer = PlayerService.getPlayerFromIdentifier(senderIdentifier); - - if (funcRef) { - try { - await funcRef({ - data: { embed, message, sourcePhoneNumber: senderNumber, tgtPhoneNumber: targetNumber }, - source: senderPlayer.source, - }); - } catch (e) { - messagesLogger.error( - `Failed to find a callback reference for onMessage. Probably because the resource(s) using the export was stopped or restarted. Please restart said resource(s). Error: ${e.message}`, - ); - } - } - }); -}); diff --git a/apps/game/server/messages/middleware/onMessage.ts b/apps/game/server/messages/middleware/onMessage.ts deleted file mode 100644 index 9f017addf..000000000 --- a/apps/game/server/messages/middleware/onMessage.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { OnMessageExportCtx } from '@typings/messages'; - -const exp = global.exports; - -export const OnMessageExportMap = new Map(); - -exp('onMessage', (phoneNumber: string, cb: (messageCtx: OnMessageExportCtx) => void) => { - OnMessageExportMap.set(phoneNumber, cb); -}); diff --git a/apps/game/server/misc/discord.ts b/apps/game/server/misc/discord.ts deleted file mode 100644 index 94e91631c..000000000 --- a/apps/game/server/misc/discord.ts +++ /dev/null @@ -1,133 +0,0 @@ -import axios, { AxiosResponse } from 'axios'; - -import { Tweet, Profile } from '../../../typings/twitter'; - -import { mainLogger } from '../sv_logger'; -import { MarketplaceListing } from '../../../typings/marketplace'; - -const IMAGE_DELIMITER = '||!||'; -const discordLogger = mainLogger.child({ module: 'discord' }); - -const DISCORD_WEBHOOK = GetConvar('NPWD_DISCORD_TOKEN', ''); -/** - * https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure - */ -interface DiscordEmbedField { - name: string; - value: string; - inline?: boolean; -} - -interface DiscordEmbed { - title: string; - color: number; - description: string; - timestamp: string; - fields: DiscordEmbedField[]; -} - -interface DiscordMessage { - username: string; - embeds: DiscordEmbed[]; -} - -const postToWebhook = async (content: DiscordMessage): Promise => { - // If convar isnt set - if (!DISCORD_WEBHOOK) { - discordLogger.warn( - 'Got a request to report a listing but discord is not configures. See README on how to configure discord endpoints.', - ); - return; - } - const resp = await axios.post(DISCORD_WEBHOOK, { ...content }); - // If we get a bad request throw error - if (resp.status < 200 && resp.status >= 300) - throw new Error(`Discord Error: ${resp.status} Error - ${resp.statusText}`); -}; - -const createDiscordMsgObj = (type: string, message: string, fields: DiscordEmbedField[]) => { - // Get ISO 8601 as its required by Discord API - const curTime = new Date().toISOString(); - - return { - username: 'NPWD Report', - embeds: [ - { - title: `${type} REPORT`, - color: 0xe74c3c, - description: message, - timestamp: curTime, - fields, - }, - ], - }; -}; - -export async function reportTweetToDiscord(tweet: Tweet, reportingProfile: Profile): Promise { - const guaranteedFields = [ - { - name: 'Reported By:', - value: `\`\`\`Profile Name: ${reportingProfile.profile_name}\nProfile ID: ${reportingProfile.id}\nUser Identifier: ${reportingProfile.identifier}\`\`\``, - }, - { - name: 'Reported User Data:', - value: `\`\`\`Profile Name: ${tweet.profile_name}\nProfile ID: ${tweet.profile_id}\nUser Identifier: ${tweet.identifier}\`\`\``, - }, - { - name: 'Tweet Message:', - value: `\`\`\`Message: ${tweet.message}\`\`\``, - }, - ]; - - const finalFields = tweet.images - ? guaranteedFields.concat({ - name: 'Reported Image:', - value: tweet.images.split(IMAGE_DELIMITER).join('\n'), - }) - : guaranteedFields; - - const msgObj = createDiscordMsgObj('TWITTER', `Received a report for a tweet`, finalFields); - try { - await postToWebhook(msgObj); - } catch (e) { - discordLogger.error(e.message); - } -} - -export async function reportListingToDiscord( - listing: MarketplaceListing, - reportingProfile: string, -): Promise { - const guaranteedFields = [ - { - name: 'Reported By', - value: `\`\`\`Profile Name: ${reportingProfile}\`\`\``, - }, - { - name: 'Reported User Data', - value: `\`\`\`Profile Name: ${listing.username}\nProfile ID: ${listing.id}\nUser Identifier: ${listing.identifier}\`\`\``, - }, - { - name: 'Reported Listing Title', - value: `\`\`\`Title: ${listing.name}\`\`\``, - }, - { - name: 'Reported Listing Desc.', - value: `\`\`\`Description: ${listing.description}\`\`\``, - }, - ]; - - const finalFields = listing.url - ? guaranteedFields.concat({ - name: 'Reported Image:', - value: listing.url.split(IMAGE_DELIMITER).join('\n'), - }) - : guaranteedFields; - - const msgObj = createDiscordMsgObj('MARKETPLACE', `Received a report for a listing`, finalFields); - try { - await postToWebhook(msgObj); - } catch (e) { - discordLogger.error(e.message); - } -} diff --git a/apps/game/server/misc/functions.ts b/apps/game/server/misc/functions.ts deleted file mode 100644 index 11258b979..000000000 --- a/apps/game/server/misc/functions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { config } from '../server'; -import { DbInterface } from '@npwd/database'; -import { generateUniquePhoneNumber } from './generateUniquePhoneNumber'; -import { playerLogger } from '../players/player.utils'; -import { ResultSetHeader } from 'mysql2'; - -export async function findOrGeneratePhoneNumber(identifier: string): Promise { - const query = `SELECT ${config.database.phoneNumberColumn} FROM ${config.database.playerTable} WHERE ${config.database.identifierColumn} = ? LIMIT 1`; - const [res] = await DbInterface._rawExec(query, [identifier]); - - playerLogger.debug('Find user for number generation data >'); - playerLogger.debug(res); - - const castRes = res as Record[]; - - if (castRes && castRes[0] && castRes[0][config.database.phoneNumberColumn] !== null) { - return castRes[0][config.database.phoneNumberColumn] as string; - } - - playerLogger.debug('Phone number was returned as null, generating new number'); - const gennedNumber = await generateUniquePhoneNumber(); - - playerLogger.debug(`Phone number generated > ${gennedNumber}`); - - const updateQuery = `UPDATE ${config.database.playerTable} SET ${config.database.phoneNumberColumn} = ? WHERE ${config.database.identifierColumn} = ?`; - // Update profile with new generated number - const result = await DbInterface._rawExec(updateQuery, [gennedNumber, identifier]); - - // Temporary bad typing, need to update dbInterface - if (!result || !result[0] || !(result[0] as ResultSetHeader).affectedRows) { - playerLogger.error(`Failed to store phone number in database`); - playerLogger.error( - `UPDATE ${config.database.playerTable} SET ${config.database.phoneNumberColumn} = ${gennedNumber} WHERE ${config.database.identifierColumn} = ${identifier}`, - ); - } - - return gennedNumber; -} diff --git a/apps/game/server/misc/generateUniquePhoneNumber.ts b/apps/game/server/misc/generateUniquePhoneNumber.ts deleted file mode 100644 index 888f0b0e4..000000000 --- a/apps/game/server/misc/generateUniquePhoneNumber.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { config } from '../server'; -import { DbInterface } from '@npwd/database'; -import { playerLogger } from '../players/player.utils'; -import {mainLogger} from "../sv_logger"; - -const exp = global.exports; - -const genNumber = (length: number): string => { - const addAmount = 1; - let localMax = 11; - - if (length > localMax) { - return genNumber(localMax) + genNumber(length - localMax); - } - - localMax = Math.pow(10, length + addAmount); - const min = localMax / 10; - const number = Math.floor(Math.random() * (localMax - min + 1)) + min; - - const strNumber = '' + number; - - return strNumber.substr(addAmount); -}; - -/* -const generateUsNumber = (): string => { - const rawNumber = genNumber(10); - return rawNumber.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3'); - -}; -*/ - -function generatePhoneNumber(pattern: string): string { - // Extract digit groups from the pattern - const digitGroups = pattern.match(/\d+/g); - - if (!digitGroups) { - mainLogger.error('Invalid phone number format'); - throw new Error('Invalid phone number format'); - } - - // Generate random numbers for each group - const randomNumberParts = digitGroups.map(group => { - const length = parseInt(group); - let randomNumber = ''; - for (let i = 0; i < length; i++) { - randomNumber += Math.floor(Math.random() * 10).toString(); - } - return randomNumber; - }); - - // Join the parts with a dash if more than one group, otherwise return as is - return randomNumberParts.join(randomNumberParts.length > 1 ? '-' : ''); -} - -/**/ -export async function generateUniquePhoneNumber(): Promise { - if (config.customPhoneNumber.enabled) { - try { - const { exportResource, exportFunction } = config.customPhoneNumber; - return await exp[exportResource][exportFunction](); - } catch (e) { - playerLogger.error(e.message); - playerLogger.error('Please check your config for custom number generation'); - } - } - - const query = `SELECT EXISTS(SELECT * FROM ${config.database.playerTable} WHERE ${config.database.phoneNumberColumn} = ?)`; - - const dashNumber = generatePhoneNumber(config.general.phoneNumberFormat); - - const [results] = await DbInterface._rawExec(query, [dashNumber]); - - if (!results) return generateUniquePhoneNumber(); - - return dashNumber; -} diff --git a/apps/game/server/notes/notes.controller.ts b/apps/game/server/notes/notes.controller.ts deleted file mode 100644 index 3e979f92e..000000000 --- a/apps/game/server/notes/notes.controller.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { BeforeDBNote, DeleteNoteDTO, NoteItem, NotesEvents } from '@typings/notes'; -import NotesService from './notes.service'; -import { notesLogger } from './notes.utils'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; - -onNetPromise(NotesEvents.ADD_NOTE, (reqObj, resp) => { - NotesService.handleAddNote(reqObj, resp).catch((e) => { - notesLogger.error(`Error occured in add note event (${reqObj.source}), Error: ${e.message}`); - resp({ status: 'error', errorMsg: 'UNKNOWN_ERROR' }); - }); -}); - -onNetPromise(NotesEvents.FETCH_ALL_NOTES, (reqObj, resp) => { - NotesService.handleFetchNotes(reqObj, resp).catch((e) => { - notesLogger.error( - `Error occurred in fetch note event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'UNKNOWN_ERROR' }); - }); -}); - -onNetPromise(NotesEvents.DELETE_NOTE, async (reqObj, resp) => { - NotesService.handleDeleteNote(reqObj, resp).catch((e) => { - notesLogger.error( - `Error occured in delete note event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'UNKNOWN_ERROR' }); - }); -}); - -onNetPromise(NotesEvents.UPDATE_NOTE, async (reqObj, resp) => { - NotesService.handleUpdateNote(reqObj, resp).catch((e) => { - notesLogger.error(`Error occured in fetch note event (${reqObj.source}), Error: ${e.message}`); - resp({ status: 'error', errorMsg: 'UNKNOWN_ERROR' }); - }); -}); diff --git a/apps/game/server/notes/notes.database.ts b/apps/game/server/notes/notes.database.ts deleted file mode 100644 index 3aef19d89..000000000 --- a/apps/game/server/notes/notes.database.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { BeforeDBNote, NoteItem } from '@typings/notes'; -import { ResultSetHeader } from 'mysql2'; -import { DbInterface } from '@npwd/database'; - -export class _NotesDB { - async addNote(identifier: string, note: BeforeDBNote): Promise { - const query = 'INSERT INTO npwd_notes (identifier, title, content) VALUES (?, ?, ?)'; - const [result] = await DbInterface._rawExec(query, [identifier, note.title, note.content]); - return (result).insertId; - } - - async fetchNotes(identifier: string): Promise { - const query = 'SELECT * FROM npwd_notes WHERE identifier = ? ORDER BY id DESC'; - const [result] = await DbInterface._rawExec(query, [identifier]); - return result; - } - - async deleteNote(noteId: number, identifier: string): Promise { - const query = 'DELETE FROM npwd_notes WHERE id = ? AND identifier = ?'; - await DbInterface._rawExec(query, [noteId, identifier]); - } - - async updateNote(note: NoteItem, identifier: string): Promise { - const query = 'UPDATE npwd_notes SET title = ?, content = ? WHERE id = ? AND identifier = ?'; - await DbInterface._rawExec(query, [note.title, note.content, note.id, identifier]); - } -} - -export const NotesDB = new _NotesDB(); diff --git a/apps/game/server/notes/notes.service.ts b/apps/game/server/notes/notes.service.ts deleted file mode 100644 index 21796c23a..000000000 --- a/apps/game/server/notes/notes.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { BeforeDBNote, DeleteNoteDTO, NoteItem } from '@typings/notes'; -import PlayerService from '../players/player.service'; -import { NotesDB, _NotesDB } from './notes.database'; -import { notesLogger } from './notes.utils'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; - -class _NotesService { - private readonly notesDB: _NotesDB; - - constructor() { - this.notesDB = NotesDB; - notesLogger.debug('Notes service started'); - } - - async handleAddNote( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - notesLogger.debug('Handling add note, note:'); - notesLogger.debug(reqObj.data); - - const identifer = PlayerService.getIdentifier(reqObj.source); - - try { - const noteId = await this.notesDB.addNote(identifer, reqObj.data); - resp({ - status: 'ok', - data: { id: noteId, content: reqObj.data.content, title: reqObj.data.title }, - }); - } catch (e) { - notesLogger.error(`Error in handleAddNote, ${e.message}`); - - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleFetchNotes(reqObj: PromiseRequest, resp: PromiseEventResp) { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - const notes = await this.notesDB.fetchNotes(identifier); - resp({ status: 'ok', data: notes }); - } catch (e) { - notesLogger.error(`Error in handleFetchNote, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleUpdateNote(reqObj: PromiseRequest, resp: PromiseEventResp) { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - await this.notesDB.updateNote(reqObj.data, identifier); - resp({ status: 'ok' }); - } catch (e) { - notesLogger.error(`Error in handleUpdateNote, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - // async handleShareNote(src: number) { - // // To implement someday - // } - - async handleDeleteNote( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const identifier = PlayerService.getIdentifier(reqObj.source); - try { - await this.notesDB.deleteNote(reqObj.data.id, identifier); - resp({ status: 'ok', data: reqObj.data }); - } catch (e) { - notesLogger.error(`Error in handleDeleteNote, ${e.message}`); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } -} - -const NotesService = new _NotesService(); -export default NotesService; diff --git a/apps/game/server/notes/notes.utils.ts b/apps/game/server/notes/notes.utils.ts deleted file mode 100644 index db427b68b..000000000 --- a/apps/game/server/notes/notes.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const notesLogger = mainLogger.child({ module: 'notes' }); diff --git a/apps/game/server/photo/photo.controller.ts b/apps/game/server/photo/photo.controller.ts deleted file mode 100644 index aa7927405..000000000 --- a/apps/game/server/photo/photo.controller.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { GalleryPhoto, PhotoEvents } from '@typings/photo'; -import PhotoService from './photo.service'; -import { photoLogger } from './photo.utils'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; - -onNetPromise(PhotoEvents.UPLOAD_PHOTO, (reqObj, resp) => { - PhotoService.handleUploadPhoto(reqObj, resp).catch((e) => { - photoLogger.error( - `Error occurred in upload photo event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(PhotoEvents.FETCH_PHOTOS, (reqObj, resp) => { - PhotoService.handleFetchPhotos(reqObj, resp).catch((e) => { - photoLogger.error( - `Error occurred in upload photo event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(PhotoEvents.DELETE_PHOTO, (reqObj, resp) => { - PhotoService.handleDeletePhoto(reqObj, resp).catch((e) => { - photoLogger.error( - `Error occurred in delete photo event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(PhotoEvents.DELETE_MULTIPLE_PHOTOS, (reqObj, resp) => { - PhotoService.handleDeleteMultiplePhotos(reqObj, resp).catch((e) => { - photoLogger.error( - `Error occurred in delete multiple photos event (${reqObj.source}), Error: ${e.message}`, - ); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise<{ url: string }, GalleryPhoto>(PhotoEvents.SAVE_IMAGE, (reqObj, resp) => { - PhotoService.handleSaveImage(reqObj, resp).catch((e) => { - photoLogger.error(`Error occurred in save image event (${reqObj.source}), Error: ${e.message}`); - resp({ status: 'error', errorMsg: 'INTERNAL_ERROR' }); - }); -}); - -onNetPromise(PhotoEvents.GET_AUTHORISATION_TOKEN, (reqObj, resp) => { - resp({ - status: 'ok', - data: GetConvar('SCREENSHOT_BASIC_TOKEN', 'none'), - }); -}); diff --git a/apps/game/server/photo/photo.database.ts b/apps/game/server/photo/photo.database.ts deleted file mode 100644 index 6d830aeb7..000000000 --- a/apps/game/server/photo/photo.database.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { GalleryPhoto } from '@typings/photo'; -import { DbInterface } from '@npwd/database'; - -export class _PhotoDB { - async uploadPhoto(identifier: string, image: string): Promise { - const query = 'INSERT INTO npwd_phone_gallery (identifier, image) VALUES (?, ?)'; - const [results] = (await DbInterface._rawExec(query, [identifier, image])) as any; - return { id: results.insertId, image }; - } - - async getPhotosByIdentifier(identifier: string): Promise { - const query = 'SELECT id, image FROM npwd_phone_gallery WHERE identifier = ? ORDER BY id DESC'; - const [results] = await DbInterface._rawExec(query, [identifier]); - return results; - } - - async deletePhoto(photo: GalleryPhoto, identifier: string) { - const query = 'DELETE FROM npwd_phone_gallery WHERE image = ? AND identifier = ?'; - await DbInterface._rawExec(query, [photo.image, identifier]); - } - - async deletePhotoById(photo: number, identifier: string) { - const query = 'DELETE FROM npwd_phone_gallery WHERE id = ? AND identifier = ?'; - await DbInterface._rawExec(query, [photo, identifier]); - } -} - -export const PhotoDB = new _PhotoDB(); diff --git a/apps/game/server/photo/photo.service.ts b/apps/game/server/photo/photo.service.ts deleted file mode 100644 index e0134fcc9..000000000 --- a/apps/game/server/photo/photo.service.ts +++ /dev/null @@ -1,192 +0,0 @@ -import PlayerService from '../players/player.service'; -import { GalleryPhoto } from '@typings/photo'; -import { PhotoDB, _PhotoDB } from './photo.database'; -import { photoLogger } from './photo.utils'; -import { PromiseEventResp, PromiseRequest } from '../lib/PromiseNetEvents/promise.types'; -import { config } from '@npwd/config/server'; -import { v4 as uuidv4 } from 'uuid'; -import { FormData, fileFromSync } from 'node-fetch'; -import * as fs from 'fs'; -import { apiPhotoUpload, webhookPhotoUpload } from '../lib/http-service'; - -const exp = global.exports; -const path = GetResourcePath('npwd') + '/uploads'; - -class _PhotoService { - private readonly photoDB: _PhotoDB; - private readonly TOKEN: string; - - constructor() { - this.photoDB = PhotoDB; - this.TOKEN = GetConvar('SCREENSHOT_BASIC_TOKEN', ''); - photoLogger.debug('Photo service started'); - } - - async handleUploadPhoto( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - try { - exp['screenshot-basic'].requestClientScreenshot( - reqObj.source, - { - encoding: config.images.imageEncoding, - quality: 0.85, - }, - async (err: string | boolean, data: string) => { - if (!fs.existsSync(path)) fs.mkdirSync(path); - - let type: string; - switch (config.images.imageEncoding) { - case 'jpg': - type = 'image/jpeg'; - break; - case 'png': - type = 'image/png'; - break; - case 'webp': - type = 'image/webp'; - break; - } - - const filePath = `${path}/screenshot-${uuidv4()}.${config.images.imageEncoding}`; - - const _data = Buffer.from(data.replace(`data:${type};base64`, ''), 'base64'); - fs.writeFileSync(filePath, _data); - - let body: string | FormData = new FormData(); - const blob = fileFromSync(filePath, type); - - if (config.images.type === 'imgur') { - body = data.replace(/^data:image\/[a-z]+;base64,/, '').trim(); - } else if (config.images.type === 'upload.io') { - body = blob; - } else { - body.append(config.images.type, blob); - } - - if (config.images.useWebhook) { - try { - const player = PlayerService.getPlayer(reqObj.source); - const res = await webhookPhotoUpload(this.TOKEN, filePath, blob, player); - - const identifier = PlayerService.getIdentifier(reqObj.source); - const photo = await this.photoDB.uploadPhoto(identifier, res); - - // File is uploaded, so its safe to remove - fs.rmSync(filePath); - - return resp({ status: 'ok', data: photo }); - } catch (err) { - photoLogger.error(`Failed to upload photo: Error: ${err}. Message: ${err.message}`, { - source: reqObj.source, - }); - return resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - let returnData; - await apiPhotoUpload(body, this.TOKEN) - .then((result) => { - returnData = result; - }) - .catch((err) => { - photoLogger.error( - `Failed to upload photo, status code: ${err.statusCode}: ${err.errorText}`, - { - source: reqObj.source, - }, - ); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - }); - - fs.rmSync(filePath); - if (!returnData) return; // Already caught - - for (const index of config.images.returnedDataIndexes) returnData = returnData[index]; - - const identifier = PlayerService.getIdentifier(reqObj.source); - const photo = await this.photoDB.uploadPhoto(identifier, returnData); - - resp({ status: 'ok', data: photo }); - }, - ); - } catch (e) { - photoLogger.error(`Failed to upload photo, ${e.message}`, { - source: reqObj.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleFetchPhotos(reqObj: PromiseRequest, resp: PromiseEventResp) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const photos = await this.photoDB.getPhotosByIdentifier(identifier); - - resp({ status: 'ok', data: photos }); - } catch (e) { - photoLogger.error(`Failed to fetch photos, ${e.message}`, { - source: reqObj.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleDeletePhoto( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - await this.photoDB.deletePhoto(reqObj.data, identifier); - - resp({ status: 'ok' }); - } catch (e) { - photoLogger.error(`Failed to delete photo, ${e.message}`, { - source: reqObj.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleDeleteMultiplePhotos( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - const identifier = PlayerService.getIdentifier(reqObj.source); - const photoIds = reqObj.data; - try { - for (const photo of photoIds) { - await this.photoDB.deletePhotoById(photo, identifier); - } - resp({ status: 'ok' }); - } catch (e) { - photoLogger.error(`Failed to delete photos, ${e.message}`, { - source: reqObj.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } - - async handleSaveImage( - reqObj: PromiseRequest<{ url: string }>, - resp: PromiseEventResp, - ): Promise { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const photo = await this.photoDB.uploadPhoto(identifier, reqObj.data.url); - - resp({ status: 'ok', data: photo }); - } catch (e) { - photoLogger.error(`Failed to upload photo, ${e.message}`, { - source: reqObj.source, - }); - resp({ status: 'error', errorMsg: 'GENERIC_DB_ERROR' }); - } - } -} - -const PhotoService = new _PhotoService(); - -export default PhotoService; diff --git a/apps/game/server/photo/photo.utils.ts b/apps/game/server/photo/photo.utils.ts deleted file mode 100644 index e0f5584cf..000000000 --- a/apps/game/server/photo/photo.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const photoLogger = mainLogger.child({ module: 'photo' }); diff --git a/apps/game/server/players/player.class.ts b/apps/game/server/players/player.class.ts deleted file mode 100644 index 6f839f28a..000000000 --- a/apps/game/server/players/player.class.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { CreatePlayerInstance } from './player.interfaces'; - -export class Player { - public readonly source: number; - public readonly username: string; - private _phoneNumber: string; - private _identifier: string; - private _firstname: string | null; - private _lastname: string | null; - - constructor({ source, identifier, phoneNumber, username }: CreatePlayerInstance) { - this.source = source; - this._identifier = identifier; - this._phoneNumber = phoneNumber; - this.username = username; - } - - public getIdentifier(): string { - return this._identifier; - } - - /** - * Set the identifier for a player, useful in systems with multicharacter - * players - * @param identifier {string} - Set the identifier for a player - */ - public setIdentifier(identifier: string): Player { - this._identifier = identifier; - return this; - } - - /** - * Returns the stored firstname for a user - */ - public getFirstName(): string | null { - return this._firstname; - } - - /** - * Set the first name for a user - * @param firstname {string} The first name to set for the user - **/ - public setFirstName(firstname: string): Player { - this._firstname = firstname; - return this; - } - - /** - * Returns the stored lastname for a user - **/ - public getLastName(): string | null { - return this._lastname; - } - - /** - * Set the last name for a user - * @param lastname {string} The last name to set for the user - **/ - public setLastName(lastname: string): Player { - this._lastname = lastname; - return this; - } - - /** - * Get the full name of the user - **/ - public getName(): string | null { - if (!this._firstname || !this._lastname) return null; - return `${this._firstname} ${this._lastname}`; - } - - /** - * Get the stored phone number for a user - **/ - public getPhoneNumber(): string { - return this._phoneNumber; - } - - /** - * Set the stored phone number for a user - **/ - public setPhoneNumber(number: string): Player { - this._phoneNumber = number; - return this; - } -} diff --git a/apps/game/server/players/player.controller.ts b/apps/game/server/players/player.controller.ts deleted file mode 100644 index e975f5a88..000000000 --- a/apps/game/server/players/player.controller.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { getSource } from '../utils/miscUtils'; -import PlayerService from './player.service'; -import { config } from '@npwd/config/server'; -import { playerLogger } from './player.utils'; -import { PhoneEvents } from '@typings/phone'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; - -onNet(PhoneEvents.FETCH_CREDENTIALS, () => { - const src = getSource(); - const player = PlayerService.getPlayer(src); - const phoneNumber = player.getPhoneNumber(); - const playerIdentifier = player.getIdentifier(); - - emitNet(PhoneEvents.SEND_CREDENTIALS, src, phoneNumber, src, playerIdentifier); -}); - -onNetPromise(PhoneEvents.GET_PHONE_NUMBER, async (reqObj, resp) => { - const src = reqObj.source; - - const phoneNumber = PlayerService.getPlayer(src).getPhoneNumber(); - - resp({ status: 'ok', data: phoneNumber }); -}); - -/** - * Essentially this whole file acts as a controller layer - * for player related actions. Everything here is registered, - * within the global scope like routes in a web server. - */ - -// If multicharacter mode is disabled, we instantiate a new -// NPWD player by ourselves without waiting for an export - -if (!config.general.useResourceIntegration) { - on('playerJoining', async () => { - const src = getSource(); - await PlayerService.handleNewPlayerJoined(src); - }); -} - -// Handle removing from player maps when player disconnects -on('playerDropped', async () => { - const src = getSource(); - // Get identifier for player to remove - try { - await PlayerService.handleUnloadPlayerEvent(src); - } catch (e) { - playerLogger.debug(`${src} failed to unload, likely was never loaded in the first place.`); - } -}); - -// Can use this to debug the player table if needed. Disabled by default -// RegisterCommand( -// 'getPlayers', -// () => { -// playerLogger.debug(Players); -// }, -// false, -// ); - -// Used for debug purposes, if the resource is restarted and the multicharacter option is false -// we will handle all the currently online players -if (!config.general.useResourceIntegration) { - on('onServerResourceStart', async (resource: string) => { - if (resource === GetCurrentResourceName()) { - const onlinePlayers = getPlayers(); - for (const player of onlinePlayers) { - await PlayerService.handleNewPlayerJoined(parseInt(player)); - } - } - }); -} diff --git a/apps/game/server/players/player.database.ts b/apps/game/server/players/player.database.ts deleted file mode 100644 index 8d446c61c..000000000 --- a/apps/game/server/players/player.database.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { config } from '@npwd/config/server'; -import { DbInterface } from '@npwd/database'; - -export class _PlayerRepo { - async fetchIdentifierFromPhoneNumber(phoneNumber: string): Promise { - const query = `SELECT ${config.database.identifierColumn} FROM ${config.database.playerTable} WHERE ${config.database.phoneNumberColumn} = ?`; - const [results] = await DbInterface._rawExec(query, [phoneNumber]); - // Get identifier from results - return (results as any[])[0]?.[config.database.identifierColumn] || null; - } - - async fetchPhoneNumberFromIdentifier(identifier: string) { - const query = `SELECT ${config.database.phoneNumberColumn} FROM ${config.database.playerTable} WHERE ${config.database.identifierColumn} = ?`; - - const [results] = await DbInterface._rawExec(query, [identifier]); - return (results as any[])[0][config.database.phoneNumberColumn] || null; - } -} - -export const PlayerRepo = new _PlayerRepo(); diff --git a/apps/game/server/players/player.interfaces.ts b/apps/game/server/players/player.interfaces.ts deleted file mode 100644 index 888e8d229..000000000 --- a/apps/game/server/players/player.interfaces.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface PlayerAddData { - source: number; - identifier: string; - phoneNumber?: string; - firstname?: string; - lastname?: string; -} - -export interface CreatePlayerInstance { - source: number; - username: string; - identifier: string; - phoneNumber: string; -} - -export interface ExportedPlayerData { - phoneNumber: string; - firstName: string; - lastName: string; - name: string; - identifier: string; - source: number; -} diff --git a/apps/game/server/players/player.service.ts b/apps/game/server/players/player.service.ts deleted file mode 100644 index 614ce02e4..000000000 --- a/apps/game/server/players/player.service.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { findOrGeneratePhoneNumber } from '../misc/functions'; -import { PhoneEvents } from '@typings/phone'; -import { Player } from './player.class'; -import { PlayerAddData } from './player.interfaces'; -import Collection from '@discordjs/collection'; -import { getPlayerGameLicense } from '../utils/getPlayerGameLicense'; -import { playerLogger } from './player.utils'; -import MarketplaceService from '../marketplace/marketplace.service'; -import CallsService from '../calls/calls.service'; -import { Delay } from '../../utils/fivem'; -import { config } from '@npwd/config/server'; -import { _PlayerRepo, PlayerRepo } from './player.database'; - -class _PlayerService { - private readonly playersBySource: Collection; - private readonly playersByIdentifier: Collection; - private readonly playerDB: _PlayerRepo; - private readonly callService: typeof CallsService; - - constructor() { - this.playersBySource = new Collection(); - this.playersByIdentifier = new Collection(); - this.playerDB = PlayerRepo; - this.callService = CallsService; - playerLogger.debug('Player Service started'); - } - - /** - * Adds a Player instance to the source & identifier maps - * @param source - The player's source - * @param player - The player instance to use as a value - */ - - addPlayerToMaps(source: number, player: Player) { - this.playersBySource.set(source, player); - this.playersByIdentifier.set(player.getIdentifier(), player); - } - - /** - * Deletes a player from both map by source & identifier - * @param source - The player's source - */ - deletePlayerFromMaps(source: number) { - const identifier = this.playersBySource.get(source).getIdentifier(); - this.playersByIdentifier.delete(identifier); - this.playersBySource.delete(source); - } - - /** - * Returns the player instance for a given source - * Will return null if no player is found online with that source - **/ - getPlayer(source: number): Player | null { - const player = this.playersBySource.get(source); - if (!player) return null; - return player; - } - - /** - * Returns the player identifier for a given source - * Will return null if no player is found online with that source - **/ - getIdentifier(source: number): string { - return this.getPlayer(source).getIdentifier(); - } - - /** - * Returns the player phoneNumber for a passed identifier - * @param identifier The players phone number - */ - getPlayerFromIdentifier(identifier: string): Player | null { - const player = this.playersByIdentifier.get(identifier); - if (!player) { - return null; - } - return player; - } - - isBusy(source: number): boolean { - return this.callService.isPlayerInCall(source); - } - - /** - * Will return the given identifier from a phone number - * @param phoneNumber - The phone number of the player - * @param fetch - Whether to fetch for the identifier if they are offline - **/ - async getIdentifierFromPhoneNumber(phoneNumber: string, fetch?: boolean): Promise { - const onlinePlayer = this.playersBySource.find( - (player: Player) => player.getPhoneNumber() === phoneNumber, - ); - // Return the player if they are online - if (onlinePlayer) return onlinePlayer.getIdentifier(); - if (fetch) { - const fetchResult: string | null = await this.playerDB - .fetchIdentifierFromPhoneNumber(phoneNumber) - .catch((e) => { - playerLogger.error( - `Failed to fetch identifier from phone number for ${phoneNumber}, error: ${e.message}`, - ); - return null; - }); - - return fetchResult; - } - // Return null if all else doesn't return - return null; - } - - /** - * We call this function on the event `playerJoined`, - * only if the multicharacter option is set to false in the config. - * @param pSource - The source of the player to handle - */ - async handleNewPlayerJoined(pSource: number) { - const playerIdentifier = getPlayerGameLicense(pSource); - - if (!playerIdentifier) { - throw new Error(`License identifier could not be found for source (${pSource})`); - } - - const username = GetPlayerName(pSource.toString()); - playerLogger.info(`Started loading for ${username} (${pSource})`); - // Ensure phone number exists or generate - - const phone_number = await findOrGeneratePhoneNumber(playerIdentifier); - - const newPlayer = new Player({ - identifier: playerIdentifier, - source: pSource, - username, - phoneNumber: phone_number, - }); - - this.addPlayerToMaps(pSource, newPlayer); - - playerLogger.info('NPWD Player Loaded!'); - playerLogger.debug(newPlayer); - - // This is a stupid hack for development reloading - // Client handlers are experiencing a race condition where the event - // is sent out before the client handlers can load. - if (process.env.NODE_ENV === 'development') { - await Delay(100); - } - - emitNet(PhoneEvents.SET_PLAYER_LOADED, pSource, true); - } - - /** - * Instantiate a basic Player instance from the constructor - * @param src - The player source - * @param identifier - The primary identifier - * - */ - - async createNewPlayer({ - src, - identifier, - phoneNumber, - }: { - src: number; - identifier: string; - phoneNumber: string; - }): Promise { - const username = GetPlayerName(src.toString()); - - if (!phoneNumber) { - phoneNumber = await findOrGeneratePhoneNumber(identifier); - if (!phoneNumber) return null; - } - - return new Player({ - source: src, - identifier, - phoneNumber, - username, - }); - } - - /** - * We call this function whenever we receive a `npwd:newPlayer` - * event while multichar is enabled. - * @param NewPlayerDTO - A DTO with all the new info required to instantiate a new player - * - */ - async handleNewPlayerEvent({ - source: src, - identifier, - phoneNumber, - firstname, - lastname, - }: PlayerAddData) { - const player = await this.createNewPlayer({ - src, - identifier: identifier.toString(), - phoneNumber, - }); - - if (firstname) player.setFirstName(firstname); - if (lastname) player.setLastName(lastname); - - this.addPlayerToMaps(src, player); - - playerLogger.info(`New NPWD Player added through event (${src}) (${identifier})`); - playerLogger.debug(player); - - emitNet(PhoneEvents.SET_PLAYER_LOADED, src, true); - } - - /** - * Return the attached identifier for a given phone number - * @param phoneNumber The phone number to return identifier for - * @param fetch Whether or not to query the database if a given player is offline - **/ - async getIdentifierByPhoneNumber(phoneNumber: string, fetch?: boolean): Promise { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const [_, player] of this.playersBySource) { - if (player.getPhoneNumber() === phoneNumber) return player.getIdentifier(); - } - // Whether we fetch from database if not found in online players - if (fetch) { - return await this.playerDB.fetchIdentifierFromPhoneNumber(phoneNumber); - } - } - - async getPhoneNumberFromIdentifier(identifier: string) { - return await this.playerDB.fetchPhoneNumberFromIdentifier(identifier); - } - - /** - * Clear all data from the database we don't want to stored after the player as disconnected. - */ - async clearPlayerData(src: number) { - const identifier = this.getIdentifier(src); - if (!config.marketplace.persistListings) { - try { - await MarketplaceService.handleDeleteListingsOnDrop(identifier); - } catch (e) { - playerLogger.error(`Failed to clear player data when dropped, Error: ${e.message}`); - } - } - } - - /** - * Unload event handler - * @param src - Source of player being unloaded - **/ - async handleUnloadPlayerEvent(src: number) { - await this.clearPlayerData(src); - - this.deletePlayerFromMaps(src); - emitNet(PhoneEvents.SET_PLAYER_LOADED, src, false); - playerLogger.info(`Unloaded NPWD Player, source: (${src})`); - } -} - -const PlayerService = new _PlayerService(); - -export default PlayerService; diff --git a/apps/game/server/players/player.utils.ts b/apps/game/server/players/player.utils.ts deleted file mode 100644 index 6ec3e18bd..000000000 --- a/apps/game/server/players/player.utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { mainLogger } from '../sv_logger'; -import PlayerService from './player.service'; - -export const playerLogger = mainLogger.child({ - module: 'player', -}); - -export async function getDefaultProfileNames(source: number): Promise { - const defaultProfileNames: string[] = []; - const player = PlayerService.getPlayer(source); - - if (player.getFirstName() && player.getLastName()) { - defaultProfileNames.push(`${player.getFirstName()}_${player.getLastName()}`); - } else if (player.getFirstName()) { - defaultProfileNames.push(player.getFirstName()); - } else if (player.getLastName()) { - defaultProfileNames.push(player.getLastName()); - } - - if (player.getPhoneNumber()) { - defaultProfileNames.push(player.getPhoneNumber()); - } - return defaultProfileNames; -} diff --git a/apps/game/server/rcon/exports.ts b/apps/game/server/rcon/exports.ts deleted file mode 100644 index 1ba674c06..000000000 --- a/apps/game/server/rcon/exports.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Message, MessageEvents } from '@typings/messages'; - -global.exports('emitRconMessage', (src: number, data: Message) => { - emitNet(MessageEvents.SEND_MESSAGE_SUCCESS, src, data); -}); diff --git a/apps/game/server/router-server.ts b/apps/game/server/router-server.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/game/server/server.ts b/apps/game/server/server.ts deleted file mode 100644 index d042cc8a1..000000000 --- a/apps/game/server/server.ts +++ /dev/null @@ -1,57 +0,0 @@ -import './config'; - -import { RewriteFrames } from '@sentry/integrations'; -import { config as resourceConfig } from '@npwd/config/server'; - -export const config = resourceConfig; -import { registerCommands } from './commands/registerCommands'; - -// This is ugly as fuck and we need to import things in a order that makes sense -// Setup controllers -// -//import './db/pool'; -import '@npwd/database/src/db/pool'; -import './bridge/framework-strategy'; - -import './boot/boot.controller'; -import './players/player.controller'; -import './calls/calls.controller'; -import './notes/notes.controller'; -import './contacts/contacts.controller'; -import './photo/photo.controller'; -import './messages/messages.controller'; -import './marketplace/marketplace.controller'; -import './twitter/twitter.controller'; -import './match/match.controller'; -import './darkchat/darkchat.controller'; -import './audio/audio.controller'; - -// setup exports -import './bridge/sv_exports'; -import './messages/middleware/emitMessage'; -import './rcon/exports'; - -import { mainLogger } from './sv_logger'; -import * as Sentry from '@sentry/node'; - -// register commands -registerCommands(); - -// Setup sentry tracing -if (config.debug.sentryEnabled && process.env.NODE_ENV === 'production') { - Sentry.init({ - dsn: 'https://5c5da180a57e4db1acb617ef2c6cb59f@sentry.projecterror.dev/3', - integrations: [new RewriteFrames()], - release: process.env.SENTRY_RELEASE || '0.0.0', - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, - }); -} - -on('onServerResourceStart', (resource: string) => { - if (resource === GetCurrentResourceName()) { - mainLogger.info('Successfully started'); - } -}); diff --git a/apps/game/server/sv_logger.ts b/apps/game/server/sv_logger.ts deleted file mode 100644 index 6caf790c7..000000000 --- a/apps/game/server/sv_logger.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { config } from '@npwd/config/server'; -import path from 'path'; -import { createLogger, transports, format } from 'winston'; - -// Needed to manually apply a color to componenent property of log -const manualColorize = (strToColor: string): string => `[\x1b[35m${strToColor}\x1b[0m]`; - -// Format handler passed to winston -const formatLogs = (log: any): string => { - if (log.module) - return `${log.label} ${manualColorize(log.module)} [${log.level}]: ${log.message}`; - - return `${log.label} [${log.level}]: ${log.message}`; -}; - -const findLogPath = () => `${path.join(GetResourcePath(GetCurrentResourceName()), 'sv_npwd.log')}`; -// Initiate the main logger for NPWD - -export const mainLogger = createLogger({ - silent: !config.debug.enabled ?? false, - transports: [ - new transports.File({ - filename: findLogPath(), - level: 'silly', - format: format.combine(format.errors({ stack: true }), format.timestamp(), format.json()), - }), - new transports.Console({ - level: config.debug.level ?? 'info', - format: format.combine( - format.label({ label: '[NPWD]' }), - format.colorize({ all: true }), - format.printf(formatLogs), - ), - }), - ], -}); diff --git a/apps/game/server/tsconfig.json b/apps/game/server/tsconfig.json deleted file mode 100644 index 27dd70149..000000000 --- a/apps/game/server/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "*": ["./types/*"], - "@shared/*": ["../../../shared/*"], - "@typings/*": ["../../../typings/*"] - }, - "outDir": "./", - "noImplicitAny": true, - "module": "commonjs", - "target": "ES2018", - "allowJs": false, - "lib": ["es2018"], - "types": ["@citizenfx/server", "@types/node", "jest"], - "moduleResolution": "node", - "sourceMap": true, - "resolveJsonModule": true, - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true - }, - "include": ["./**/*", "../../shared/*", "../../bridge/*"], - "exclude": ["**/node_modules", "**/__tests__/*"] -} diff --git a/apps/game/server/twitter/twitter.controller.ts b/apps/game/server/twitter/twitter.controller.ts deleted file mode 100644 index f758e27d0..000000000 --- a/apps/game/server/twitter/twitter.controller.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { config } from '@npwd/config/server'; -import { twitterLogger } from './twitter.utils'; -import { TwitterProfile as Profile, Tweet, TwitterEvents } from '@typings/twitter'; -import { getSource } from '../utils/miscUtils'; -import TwitterService from './twitter.service'; -import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; - -onNetPromise( - TwitterEvents.GET_OR_CREATE_PROFILE, - async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleGetOrCreateProfile(reqObj, resp).catch((e) => { - twitterLogger.error( - `Error occurred in getOrCreateProfile event (${_source}), Error: ${e.message}`, - ); - }); - }, -); - -onNetPromise(TwitterEvents.CREATE_PROFILE, async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleCreateProfile(reqObj, resp).catch((e) => - twitterLogger.error(`Error occurred in createProfile event (${_source}), Error: ${e.message}`), - ); -}); - -onNetPromise(TwitterEvents.UPDATE_PROFILE, async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleUpdateProfile(reqObj, resp).catch((e) => - twitterLogger.error(`Error occurred in updateProfile event (${_source}), Error: ${e.message}`), - ); -}); - -onNetPromise<{ searchValue: string }, Tweet[]>( - TwitterEvents.FETCH_TWEETS_FILTERED, - async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleFetchTweetsFiltered(reqObj, resp).catch((e) => - twitterLogger.error( - `Error occurred in fetchTweetsFiltered event (${_source}), Error: ${e.message}`, - ), - ); - }, - { rateLimit: 5000 }, -); - -onNetPromise(TwitterEvents.CREATE_TWEET, async (reqObj, resp) => { - TwitterService.handleCreateTweet(reqObj, resp).catch((e) => { - twitterLogger.error( - `Error occurred in createTweet event (${reqObj.source}), Error: ${e.message}`, - ); - }); -}); - -onNetPromise<{ tweetId: number }, void>(TwitterEvents.DELETE_TWEET, async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleDeleteTweet(reqObj, resp).catch((e) => { - twitterLogger.error(`Error occurred in deleteTweet event (${_source}), Error: ${e.message}`); - }); -}); - -onNetPromise<{ tweetId: number }, void>(TwitterEvents.TOGGLE_LIKE, async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleToggleLike(reqObj, resp).catch((e) => { - twitterLogger.error(`Error occurred in toggleEvent event (${_source}), Error: ${e.message}`); - }); -}); - -onNetPromise<{ tweetId: number }, void>(TwitterEvents.RETWEET, async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleRetweet(reqObj, resp).catch((e) => - twitterLogger.error(`Error occurred in retweet event (${_source}), Error: ${e.message}`), - ); -}); - -onNetPromise<{ tweetId: number }, void>(TwitterEvents.REPORT, async (reqObj, resp) => { - const _source = getSource(); - TwitterService.handleReport(reqObj, resp).catch((e) => - twitterLogger.error(`Error occurred in report event (${_source}), Error: ${e.message}`), - ); -}); - -onNetPromise<{ pageId: number }, Tweet[]>(TwitterEvents.FETCH_TWEETS, (req, res) => { - TwitterService.handleFetchTweets(req.source, req.data.pageId, res).catch((e) => - twitterLogger.error(`Error occurred in fetchTweets event (${req.source}), Error: ${e.message}`), - ); -}); - -if (!config.twitter.allowEditableProfileName && !config.twitter.generateProfileNameFromUsers) { - const warning = - `Both allowEdtiableProfileName and generateProfileNameFromUsers ` + - `are set false - this means users will likely not have profile names ` + - `for the Twitter App and won't be able to use it!`; - twitterLogger.warn(warning); -} diff --git a/apps/game/server/twitter/twitter.database.ts b/apps/game/server/twitter/twitter.database.ts deleted file mode 100644 index 54a4252cd..000000000 --- a/apps/game/server/twitter/twitter.database.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { NewTweet, TwitterProfile as Profile, Tweet } from '@typings/twitter'; -import { DbInterface, pool } from '@npwd/database'; -import { ResultSetHeader } from 'mysql2'; -import { config } from '@npwd/config/server'; -import { generateProfileName } from '../utils/generateProfileName'; -import { twitterLogger } from './twitter.utils'; - -const SELECT_FIELDS = ` - npwd_twitter_tweets.id, - npwd_twitter_tweets.identifier, - npwd_twitter_profiles.id AS profile_id, - npwd_twitter_profiles.profile_name, - npwd_twitter_profiles.avatar_url, - npwd_twitter_tweets.likes, - npwd_twitter_tweets.visible, - IFNULL(COALESCE(retweets.message, npwd_twitter_tweets.message), '') AS message, - IFNULL(COALESCE(retweets.images, npwd_twitter_tweets.images), '') AS images, - npwd_twitter_tweets.retweet IS NOT NULL AS isRetweet, - retweets.id AS retweetId, - retweets_profiles.profile_name AS retweetProfileName, - retweets_profiles.avatar_url AS retweetAvatarUrl, - npwd_twitter_likes.id IS NOT NULL AS isLiked, - npwd_twitter_reports.id IS NOT NULL AS isReported, - npwd_twitter_tweets.createdAt, - npwd_twitter_tweets.updatedAt, - TIME_TO_SEC(TIMEDIFF( NOW(), npwd_twitter_tweets.createdAt)) AS seconds_since_tweet -`; - -const TWEETS_PER_PAGE = config?.twitter?.resultsLimit || 25; - -const formatTweets = - (profileId: number) => - (tweet: Tweet): Tweet => ({ - ...tweet, - isMine: tweet.profile_id === profileId, - isRetweet: tweet.isRetweet === 1, - }); - -export class _TwitterDB { - /** - * Retrieve the latest 50 tweets - * @param profileId - twitter profile id of the player - */ - async fetchAllTweets(profileId: number, currPage: number): Promise { - currPage = typeof currPage === 'number' ? currPage : 1; // avoid sql injection without prepared query - const offset = currPage * TWEETS_PER_PAGE; - const query = ` - SELECT ${SELECT_FIELDS} - FROM npwd_twitter_tweets - LEFT OUTER JOIN npwd_twitter_profiles - ON npwd_twitter_tweets.identifier = npwd_twitter_profiles.identifier - LEFT OUTER JOIN npwd_twitter_likes ON npwd_twitter_tweets.id = npwd_twitter_likes.tweet_id AND - npwd_twitter_likes.profile_id = ? - LEFT OUTER JOIN npwd_twitter_reports ON npwd_twitter_tweets.id = npwd_twitter_reports.tweet_id AND - npwd_twitter_reports.profile_id = ? - LEFT OUTER JOIN npwd_twitter_tweets AS retweets ON retweets.id = npwd_twitter_tweets.retweet - LEFT OUTER JOIN npwd_twitter_profiles AS retweets_profiles - ON retweets.identifier = retweets_profiles.identifier - WHERE npwd_twitter_tweets.visible = 1 - ORDER BY id DESC - LIMIT ? OFFSET ? - `; - const [results] = await DbInterface._rawExec(query, [ - profileId, - profileId, - TWEETS_PER_PAGE.toString(), - offset.toString(), - ]); - const tweets = results; - - return tweets.map(formatTweets(profileId)); - } - - /** - * Fetch tweets by some search value submitted by the user. Intended to let - * player's search for tweets by profile, message or more. - * @param profileId - twitter profile id of the player - * @param searchValue - value to search - */ - async fetchTweetsFiltered(profileId: number, searchValue: string): Promise { - const parameterizedSearchValue = `%${searchValue}%`; - const query = ` - SELECT ${SELECT_FIELDS} - FROM npwd_twitter_tweets - LEFT OUTER JOIN npwd_twitter_profiles - ON npwd_twitter_tweets.identifier = npwd_twitter_profiles.identifier - LEFT OUTER JOIN npwd_twitter_likes ON npwd_twitter_tweets.id = npwd_twitter_likes.tweet_id AND - npwd_twitter_likes.profile_id = ? - LEFT OUTER JOIN npwd_twitter_reports ON npwd_twitter_tweets.id = npwd_twitter_reports.tweet_id AND - npwd_twitter_reports.profile_id = ? - LEFT OUTER JOIN npwd_twitter_tweets AS retweets ON retweets.id = npwd_twitter_tweets.retweet - LEFT OUTER JOIN npwd_twitter_profiles AS retweets_profiles - ON retweets.identifier = retweets_profiles.identifier - WHERE npwd_twitter_tweets.visible = 1 - AND (npwd_twitter_profiles.profile_name LIKE ? OR npwd_twitter_tweets.message LIKE ?) - ORDER BY npwd_twitter_tweets.id DESC - LIMIT 25 - `; - const [results] = await DbInterface._rawExec(query, [ - profileId, - profileId, - parameterizedSearchValue, - parameterizedSearchValue, - ]); - const tweets = results; - return tweets.map(formatTweets(profileId)); - } - - /** - * Retrieve a Tweet by ID - * @param profileId Unique id for the twitter profile (NOT user identifier) - * @param tweetId - primary key of the tweet ID - */ - async getTweet(profileId: number, tweetId: number): Promise { - const query = ` - SELECT ${SELECT_FIELDS} - FROM npwd_twitter_tweets - LEFT OUTER JOIN npwd_twitter_likes ON npwd_twitter_tweets.id = npwd_twitter_likes.tweet_id AND - npwd_twitter_likes.profile_id = ? - LEFT OUTER JOIN npwd_twitter_reports ON npwd_twitter_tweets.id = npwd_twitter_reports.tweet_id AND - npwd_twitter_reports.profile_id = ? - LEFT OUTER JOIN npwd_twitter_tweets AS retweets ON retweets.id = npwd_twitter_tweets.retweet - LEFT OUTER JOIN npwd_twitter_profiles AS retweets_profiles - ON retweets.identifier = retweets_profiles.identifier - LEFT OUTER JOIN npwd_twitter_profiles - ON npwd_twitter_tweets.identifier = npwd_twitter_profiles.identifier - WHERE npwd_twitter_tweets.id = ? - `; - const [results] = await DbInterface._rawExec(query, [profileId, profileId, tweetId]); - const tweets = results; - return tweets.map(formatTweets(profileId))[0]; - } - - /** - * Creates a tweet in the database and then retrieves the tweet - * @param identifier - player identifier - * @param tweet - tweet to be created - */ - async createTweet(identifier: string, tweet: NewTweet): Promise { - const profile = await this.getProfile(identifier); - const query = ` - INSERT INTO npwd_twitter_tweets (identifier, message, images, retweet, profile_id) - VALUES (?, ?, ?, ?, ?) - `; - const [results] = await DbInterface._rawExec(query, [ - identifier, - tweet.message, - tweet.images, - tweet.retweet, - profile.id, - ]); - // This should not be an any type and instead should be - // a Tweet[] according to mysql2 documentation. But instead - // this exec promise returns the row itself, not an array of rows like it states - // Therefore type assertion doesn't work here for correctly typing this out. - const insertData = results; - return await this.getTweet(profile.id, insertData.insertId); - } - - async createTweetReport(tweetId: number, profileId: number): Promise { - const query = ` - INSERT INTO npwd_twitter_reports (tweet_id, profile_id) - VALUES (?, ?) - `; - await pool.execute(query, [tweetId, profileId]); - } - - /** - * Retrieve a player's twitter profile by their identifier - * @param identifier - player identifier - */ - async getProfile(identifier: string): Promise { - const query = ` - SELECT * - FROM npwd_twitter_profiles - WHERE identifier = ? - LIMIT 1 - `; - const [results] = await DbInterface._rawExec(query, [identifier]); - const profiles = results; - return profiles.length > 0 ? profiles[0] : null; - } - - /** - * Create a twitter profile - * @param identifier - player's identifier - * @param profileName - profile name to be created - * @returns - */ - async createProfile(identifier: string, profileName: string): Promise { - const query = ` - INSERT INTO npwd_twitter_profiles (identifier, profile_name) - VALUES (?, ?) - `; - - await pool.execute(query, [identifier, profileName]); - return await this.getProfile(identifier); - } - - /** - * Create a default twitter profile for a player. This is the intial profile - * that all player's will receive and can then customize. - * @param identifier - player's identifier - */ - async createDefaultProfile(identifier: string): Promise { - // case where the server owner wants players to select their own names - if (!config.twitter.generateProfileNameFromUsers) return null; - - const defaultProfileName = await generateProfileName(identifier); - // case where we tried to generate a profile name but failed due to - // some database misconfiguration or error - if (!defaultProfileName) return null; - - twitterLogger.info(`Creating default Twitter profile ${defaultProfileName} for ${identifier}`); - return await this.createProfile(identifier, defaultProfileName); - } - - /** - * Retrieve the player's profile by their identifier if it exists. If - * not, create it and return it. - * @param identifier - player's identifier - */ - async getOrCreateProfile(identifier: string): Promise { - const profile = await this.getProfile(identifier); - return profile || (await this.createDefaultProfile(identifier)); - } - - async updateProfile(identifier: string, profile: Profile): Promise { - const { avatar_url, profile_name } = profile; - const query = ` - UPDATE npwd_twitter_profiles - SET avatar_url = ?, - profile_name = ? - WHERE identifier = ? - `; - await pool.execute(query, [avatar_url, profile_name, identifier]); - - return profile; - } - - /** - * Create a "like" in the database that ties a profile & tweet together - * @param profileId - primary key of the profile conducting the like - * @param tweetId - primary key of the tweet being liked - */ - async createLike(profileId: number, tweetId: number): Promise { - const query = ` - INSERT INTO npwd_twitter_likes (profile_id, tweet_id) - VALUES (?, ?) - `; - await pool.execute(query, [profileId, tweetId]); - await DbInterface._rawExec(`UPDATE npwd_twitter_tweets SET likes = likes + 1 WHERE id = ?`, [ - tweetId, - ]); - } - - /** - * Delete a "like" relationship from the database - * @param profileId - primary key of the profile removing the like - * @param tweetId - primary key of the tweet to remove the like from - */ - async deleteLike(profileId: number, tweetId: number): Promise { - const query = ` - DELETE - FROM npwd_twitter_likes - WHERE profile_id = ? - AND tweet_id = ? - `; - await pool.execute(query, [profileId, tweetId]); - - await DbInterface._rawExec(`UPDATE npwd_twitter_tweets SET likes = likes - 1 WHERE id = ?`, [ - tweetId, - ]); - } - - async getTotalLikesFromTweet(tweetId: number): Promise { - const query = `SELECT COUNT(*) as count FROM npwd_twitter_likes WHERE tweet_id = ?`; - - const [results] = await DbInterface._rawExec(query, [tweetId]); - const result = <{ count: number }[]>results; - - return result[0].count; - } - - /** - * Delete a tweet from the database - * @param identifier - identifier of the source user - * @param tweetId - primary key of the tweet to remove the like from - */ - async deleteTweet(identifier: string, tweetId: number): Promise { - if (!config.twitter.allowDeleteTweets) return; - - const query = ` - DELETE - FROM npwd_twitter_tweets - WHERE identifier = ? - AND id = ? - `; - await pool.execute(query, [identifier, tweetId]); - } - - /** - * Check if a "like" relationship between a profile and a tweet exists - * @param profileId - primary key of the profile to check - * @param tweetId - primary key of the tweet to check - */ - async doesLikeExist(profileId: number, tweetId: number): Promise { - const query = ` - SELECT * - FROM npwd_twitter_likes - WHERE profile_id = ? - AND tweet_id = ? - LIMIT 1 - `; - const [results] = await DbInterface._rawExec(query, [profileId, tweetId]); - const likes = results; - return likes.length > 0; - } - - /** - * Check if a tweet has been reported by this profile already - * @param tweetId - primary key of the tweet to check - * @param profileId - primary key of the profile to check - */ - async doesReportExist(tweetId: number, profileId: number): Promise { - const query = ` - SELECT * - FROM npwd_twitter_reports - WHERE tweet_id = ? - AND profile_id = ? - LIMIT 1 - `; - const [results] = await DbInterface._rawExec(query, [tweetId, profileId]); - const reports = results; - return reports.length > 0; - } - - /** - * Check to see if this player has already retweeted this tweet or - * is the original poster of a retweeted tweet - * @param tweetId - ID of the tweet in question - * @param identifier - player identifier of the person performing the retweet - */ - async doesRetweetExist(tweetId: number, identifier: string): Promise { - const query = ` - SELECT COUNT(id) as count - FROM npwd_twitter_tweets - WHERE (id = ? OR retweet = ?) - AND identifier = ? - `; - const [results] = await DbInterface._rawExec(query, [tweetId, tweetId, identifier]); - const counts = results; - return counts[0].count > 0; - } -} - -export const TwitterDB = new _TwitterDB(); diff --git a/apps/game/server/twitter/twitter.service.ts b/apps/game/server/twitter/twitter.service.ts deleted file mode 100644 index b9f88ad5b..000000000 --- a/apps/game/server/twitter/twitter.service.ts +++ /dev/null @@ -1,378 +0,0 @@ -import PlayerService from "../players/player.service"; -import { _TwitterDB, TwitterDB } from "./twitter.database"; -import { - NewTweet, - Tweet, - TwitterEvents, - TwitterProfile, -} from "@typings/twitter"; -import { twitterLogger } from "./twitter.utils"; -import { reportTweetToDiscord } from "../misc/discord"; -import { - PromiseEventResp, - PromiseRequest, -} from "../lib/PromiseNetEvents/promise.types"; -import { getDefaultProfileNames } from "../players/player.utils"; -import { checkAndFilterImage } from "./../utils/imageFiltering"; -import { tweetDiscordPost } from "../lib/http-service"; - -class _TwitterService { - private readonly twitterDB: _TwitterDB; - private readonly DISCORD_WEBHOOK: string; - - constructor() { - this.twitterDB = TwitterDB; - this.DISCORD_WEBHOOK = GetConvar("TWITTER_WEBHOOK", ""); - twitterLogger.debug("Twitter service started"); - } - - async handleGetOrCreateProfile( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - const identifier = PlayerService.getIdentifier(reqObj.source); - - try { - if (!identifier) return; - - const profile = await this.twitterDB.getOrCreateProfile(identifier); - - // if we got null from getOrCreateProfile it means it doesn't exist and - // we failed to create it. In this case we pass the UI some default - // profile names it can choose from. - - // We should be able to comment this out as profile **should** be guranteed, - // as we create a default profile in that process. - - if (!profile) { - const defaultProfileNames = await getDefaultProfileNames(reqObj.source); - if (!defaultProfileNames) return; - } else { - resp({ status: "ok", data: profile }); - } - } catch (e) { - resp({ status: "error", errorMsg: e.message }); - twitterLogger.error(`Failed to get or create profile, ${e.message}`, { - source: reqObj.source, - }); - } - } - - async handleCreateProfile( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const profile = await this.twitterDB.createProfile( - identifier, - reqObj.data.profile_name, - ); - - resp({ status: "ok", data: profile }); - } catch (e) { - twitterLogger.error(`Failed to create twitter profile: ${e.message}`, { - source: reqObj.source, - }); - resp({ status: "error", errorMsg: e.message }); - } - } - - // we get both the profile and a profile name - async handleUpdateProfile( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const imageUrl = checkAndFilterImage(reqObj.data.avatar_url); - if (imageUrl == null) { - return resp({ - status: "error", - errorMsg: "GENERIC_INVALID_IMAGE_HOST", - }); - } - reqObj.data.avatar_url = imageUrl; - const profile = await this.twitterDB.updateProfile( - identifier, - reqObj.data, - ); - - resp({ status: "ok", data: profile }); - } catch (e) { - twitterLogger.error(`Failed to update twitter profile: ${e.message}`, { - source: reqObj.source, - }); - resp({ - status: "error", - errorMsg: "TWITTER.FEEDBACK.EDIT_PROFILE_FAILURE", - }); - } - } - - async handleFetchTweets( - src: number, - pageIdx: number, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(src); - const profile = await this.twitterDB.getProfile(identifier); - - if (!profile) { - twitterLogger.warn( - `Aborted fetching tweets for user ${identifier} because they do not have a profile.`, - ); - return resp({ status: "ok", data: [] }); - } - - const tweets = await this.twitterDB.fetchAllTweets(profile.id, pageIdx); - - resp({ data: tweets, status: "ok" }); - } catch (e) { - twitterLogger.error(`Fetching tweets failed, ${e.message}`, { - source: src, - }); - resp({ status: "error", errorMsg: e.message }); - } - } - - async handleFetchTweetsFiltered( - reqObj: PromiseRequest<{ searchValue: string }>, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const profile = await this.twitterDB.getProfile(identifier); - const tweets = await this.twitterDB.fetchTweetsFiltered( - profile.id, - reqObj.data.searchValue, - ); - - resp({ status: "ok", data: tweets }); - } catch (e) { - twitterLogger.error(`Fetch filtered tweets failed, ${e.message}`, { - source: reqObj.source, - }); - resp({ - status: "error", - errorMsg: "TWITTER.FEEDBACK.FILTERED_FETCH_FAILED", - }); - } - } - - async postDiscord( - identifier: string, - tweet: NewTweet, - images: string[], - ): Promise { - try { - if (this.DISCORD_WEBHOOK && this.DISCORD_WEBHOOK != "") { - const profile = await this.twitterDB.getProfile(identifier); - const data = [ - { - color: "1942002", - title: "New Tweet", - description: tweet.message, - image: { - url: images[0], - }, - footer: { - text: profile.profile_name + " " + profile.id + ":" + identifier, - icon_url: profile.avatar_url, - }, - }, - ]; - - await tweetDiscordPost( - this.DISCORD_WEBHOOK, - JSON.stringify({ username: "Twitter", embeds: data }), - ); - } - } catch (e) { - twitterLogger.error(`Discord post failed, ${e.message}`, { - source: identifier, - }); - } - - return; - } - - async handleCreateTweet( - reqObj: PromiseRequest, - resp: PromiseEventResp, - ): Promise { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - - let newImageString = ""; - - const images = reqObj.data.images.split("||!||"); - - for (let i = 0; i < images.length; i++) { - const img = images[i]; - const imageUrl = checkAndFilterImage(img); - if (imageUrl == null) { - return resp({ - status: "error", - errorMsg: "GENERIC_INVALID_IMAGE_HOST", - }); - } - newImageString += `${imageUrl}${i != images.length - 1 ? "||!||" : ""}`; - } - reqObj.data.images = newImageString; - - const createdTweet = await this.twitterDB.createTweet( - identifier, - reqObj.data, - ); - emitNet(TwitterEvents.CREATE_TWEET_BROADCAST, -1, createdTweet); - resp({ status: "ok" }); - this.postDiscord(identifier, reqObj.data, images); - } catch (e) { - twitterLogger.error(`Create tweet failed, ${e.message}`, { - source: reqObj.source, - }); - resp({ status: "error", errorMsg: e.message }); - } - } - - async handleDeleteTweet( - reqObj: PromiseRequest<{ tweetId: number }>, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - await this.twitterDB.deleteTweet(identifier, reqObj.data.tweetId); - - resp({ status: "ok" }); - emitNet(TwitterEvents.DELETE_TWEET_BROADCAST, -1, reqObj.data.tweetId); - } catch (e) { - twitterLogger.error(`Delete tweet failed, ${e.message}`, { - source: reqObj.source, - }); - - resp({ status: "error", errorMsg: e.message }); - } - } - - async handleToggleLike( - reqObj: PromiseRequest<{ tweetId: number }>, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const profile = await this.twitterDB.getOrCreateProfile(identifier); - const likeExists = await this.twitterDB.doesLikeExist( - profile.id, - reqObj.data.tweetId, - ); - const likedByProfileName = profile.profile_name; - - if (likeExists) { - await this.twitterDB.deleteLike(profile.id, reqObj.data.tweetId); - } else { - await this.twitterDB.createLike(profile.id, reqObj.data.tweetId); - } - - resp({ status: "ok" }); - emitNet( - TwitterEvents.TWEET_LIKED_BROADCAST, - -1, - reqObj.data.tweetId, - !likeExists, - likedByProfileName, - ); - } catch (e) { - twitterLogger.error(`Like failed, ${e.message}`, { - source: reqObj.source, - }); - - resp({ status: "error", errorMsg: e.message }); - } - } - - async handleRetweet( - reqObj: PromiseRequest<{ tweetId: number }>, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - - // alert the player that they have already retweeted - // this post (or that they are the original poster) - if ( - await this.twitterDB.doesRetweetExist(reqObj.data.tweetId, identifier) - ) { - return resp({ - status: "error", - errorMsg: "TWITTER.FEEDBACK.RETWEET_EXISTS", - }); - } - - // our message for this row is blank because when we - // query for this tweet it will join off of the retweet - // column and fetch the message from the related tweet - const retweet: NewTweet = { - message: "", - images: "", - retweet: reqObj.data.tweetId, - }; - const createdTweet = await this.twitterDB.createTweet( - identifier, - retweet, - ); - - const profile = await this.twitterDB.getProfile(identifier); - const tweet = await this.twitterDB.getTweet(profile.id, createdTweet.id); - - resp({ status: "ok" }); - emitNet(TwitterEvents.CREATE_TWEET_BROADCAST, -1, tweet); - } catch (e) { - twitterLogger.error(`Retweet failed, ${e.message}`, { - source: reqObj.source, - }); - - resp({ status: "error", errorMsg: e.message }); - } - } - - async handleReport( - reqObj: PromiseRequest<{ tweetId: number }>, - resp: PromiseEventResp, - ) { - try { - const identifier = PlayerService.getIdentifier(reqObj.source); - const profile = await this.twitterDB.getProfile(identifier); - const tweet = await this.twitterDB.getTweet( - profile.id, - reqObj.data.tweetId, - ); - - const reportExists = await this.twitterDB.doesReportExist( - tweet.id, - profile.id, - ); - - if (reportExists) { - return twitterLogger.warn( - "This profile has already reported this tweet", - ); - } - - await this.twitterDB.createTweetReport(tweet.id, profile.id); - await reportTweetToDiscord(tweet, profile); - - resp({ status: "ok" }); - } catch (e) { - resp({ status: "error", errorMsg: e.message }); - twitterLogger.error(`Twitter report failed, ${e.message}`, { - source: reqObj.source, - }); - } - } -} - -const TwitterService = new _TwitterService(); - -export default TwitterService; diff --git a/apps/game/server/twitter/twitter.utils.ts b/apps/game/server/twitter/twitter.utils.ts deleted file mode 100644 index 94b3b5e65..000000000 --- a/apps/game/server/twitter/twitter.utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -export const twitterLogger = mainLogger.child({ module: 'twitter' }); diff --git a/apps/game/server/utils/ServerConstants.ts b/apps/game/server/utils/ServerConstants.ts deleted file mode 100644 index ecd989fcc..000000000 --- a/apps/game/server/utils/ServerConstants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum FetchDefaultLimits { - NOTES_FETCH_LIMIT = 20, - CALLS_FETCH_LIMIT = 50, - CONTACTS_FETCH_LIMIT = 20, -} diff --git a/apps/game/server/utils/config.ts b/apps/game/server/utils/config.ts deleted file mode 100644 index ae31126be..000000000 --- a/apps/game/server/utils/config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ResourceConfig } from '@typings/config'; -import defaultConfig from '../../../../config.default.json'; -import { deepMergeObjects } from '@shared/deepMergeObjects'; - -export const getConfig = (): ResourceConfig => { - const resourceName = GetCurrentResourceName(); - const config: ResourceConfig = JSON.parse(LoadResourceFile(resourceName, 'config.json')); - - return deepMergeObjects({}, defaultConfig, config); -}; diff --git a/apps/game/server/utils/generateProfileName.ts b/apps/game/server/utils/generateProfileName.ts deleted file mode 100644 index 7e6125fc3..000000000 --- a/apps/game/server/utils/generateProfileName.ts +++ /dev/null @@ -1,28 +0,0 @@ -import PlayerService from '../players/player.service'; -import { clean } from './miscUtils'; -/** - * Generate a profile name by the player's name and/or phone number - * @param identifier - player's identifier - * @param delimiter - What we split the profile name by, defaults to - - */ -export async function generateProfileName( - identifier: string, - delimiter = '_', -): Promise { - const player = PlayerService.getPlayerFromIdentifier(identifier); - - const firstname = clean(player.getFirstName()); - const lastname = clean(player.getLastName()); - const phone_number = clean(player.getPhoneNumber()); - - if (firstname && lastname) { - return `${firstname}${delimiter}${lastname}`; - } else if (firstname) { - return firstname; - } else if (lastname) { - return lastname; - } else if (phone_number) { - return phone_number; - } - return null; -} diff --git a/apps/game/server/utils/getPlayerGameLicense.ts b/apps/game/server/utils/getPlayerGameLicense.ts deleted file mode 100644 index 564ce0d34..000000000 --- a/apps/game/server/utils/getPlayerGameLicense.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { config } from '../server'; - -export const getPlayerGameLicense = (src: number): null | string => { - // Parse specifically for license identifier as its - // guaranteed - const playerIdentifiers = getPlayerIdentifiers(src.toString()); - - let playerIdentifier; - for (const identifier of playerIdentifiers) { - if (identifier.includes(config.database.identifierType + ':')) { - if (config.database.useIdentifierPrefix) playerIdentifier = identifier; - else playerIdentifier = identifier.split(':')[1]; - } - } - - if (!playerIdentifier) return null; - return playerIdentifier; -}; diff --git a/apps/game/server/utils/imageFiltering.ts b/apps/game/server/utils/imageFiltering.ts deleted file mode 100644 index 57aba0b4a..000000000 --- a/apps/game/server/utils/imageFiltering.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { config } from '@npwd/config/server'; - -export const checkAndFilterImage = (imageUrl: string): string | null => { - const image = imageUrl.trim(); - if (image == '') { - return image; - } - - // If we don't care about filtering images just return the image - if (!config.imageSafety.filterUnsafeImageUrls) return image; - - const hostname = new URL(imageUrl).hostname; - // We have an allowed image url! - if (config.imageSafety.safeImageUrls.includes(hostname)) return image; - - // Embed unsafe urls so we don't accidentally expose our users IP address - if (config.imageSafety.embedUnsafeImages) return `${config.imageSafety.embedUrl}?url=${imageUrl}`; - - return null; -}; diff --git a/apps/game/server/utils/miscUtils.ts b/apps/game/server/utils/miscUtils.ts deleted file mode 100644 index b14e3ae9d..000000000 --- a/apps/game/server/utils/miscUtils.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Will apply any type to source -export const getSource = (): number => global.source; - -// Will clean string of any character that isn't 0-9, or a-z -export const clean = (input: string): string => (input ? input.replace(/[^0-9a-z]/gi, '') : input); - -type onNetTypedCB = (data: T) => void; - -// Simple generic wrapper around onNet for type safety -export const onNetTyped = (eventName: string, cb: onNetTypedCB) => onNet(eventName, cb); - -// Simple generic wrapper around emitNet, except that we switch src and first arg if on server. -// source is defined using the third arg -export const emitNetTyped = (eventName: string, data: T, src?: number) => { - if (src) { - return emitNet(eventName, src, data); - } - - emitNet(eventName, data); -}; - -export const distanceBetweenCoords = (coords1: number[], coords2: number[]): number => { - const [x1,y1] = coords1 - const [x2, y2] = coords2 - - return Math.sqrt( - Math.pow((x2-x1), 2) + - Math.pow((y2-y1), 2) - ) -} diff --git a/apps/game/server/utils/withProfile.ts b/apps/game/server/utils/withProfile.ts deleted file mode 100644 index 376e7053f..000000000 --- a/apps/game/server/utils/withProfile.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { mainLogger } from '../sv_logger'; - -const profilerLog = mainLogger.child({ module: 'profiler' }); - -const RESOURCE_NAME = GetCurrentResourceName(); - -// Simple higher order function profiler -// currently targeted towards ms as unit and db queries -// but can be altered to be generic in future -export const withProfile = async (fn: Function, ...args: any[]) => { - const startTime = process.hrtime.bigint(); - - // https://forum.cfx.re/t/node-mysql2-question-about-performance-process-nexttick/4550064/2?u=taso - ScheduleResourceTick(RESOURCE_NAME); - const res = await fn(...args); - - const endTime = process.hrtime.bigint(); - - const timeMs = Number(endTime - startTime) / 1e6; - - profilerLog.info(`Executed '${fn.name} (${[...args].join(', ')}) in ${timeMs}ms'`); - - return res; -}; diff --git a/apps/game/utils/apps.ts b/apps/game/utils/apps.ts deleted file mode 100644 index 98c2b271f..000000000 --- a/apps/game/utils/apps.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default { - TWITTER: 'TWITTER', - MATCH: 'MATCH', - MESSAGES: 'MESSAGES', - NOTES: 'NOTES', - MARKETPLACE: 'MARKETPLACE', - CONTACTS: 'CONTACTS', - CAMERA: 'CAMERA', - PHONE: 'PHONE', -}; diff --git a/apps/game/utils/fivem.ts b/apps/game/utils/fivem.ts deleted file mode 100644 index 684571dd2..000000000 --- a/apps/game/utils/fivem.ts +++ /dev/null @@ -1,28 +0,0 @@ -// https://forum.cfx.re/t/typescript-vs-lua-questions/612483/11 -export const Delay = (ms: number): Promise => new Promise((res) => setTimeout(res, ms)); - -// Credits to d0p3t -// https://github.com/d0p3t/fivem-js/blob/master/src/utils/UUIDV4.ts -export const uuidv4 = (): string => { - let uuid = ''; - for (let ii = 0; ii < 32; ii += 1) { - switch (ii) { - case 8: - case 20: - uuid += '-'; - uuid += ((Math.random() * 16) | 0).toString(16); - break; - case 12: - uuid += '-'; - uuid += '4'; - break; - case 16: - uuid += '-'; - uuid += ((Math.random() * 4) | 8).toString(16); - break; - default: - uuid += ((Math.random() * 16) | 0).toString(16); - } - } - return uuid; -}; diff --git a/apps/game/utils/messages.ts b/apps/game/utils/messages.ts deleted file mode 100644 index b284211e3..000000000 --- a/apps/game/utils/messages.ts +++ /dev/null @@ -1,43 +0,0 @@ -import apps from './apps'; - -export function sendMessage(app: string, method: string, data: any): void { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return SendNUIMessage({ - app, - method, - data, - }); -} - -export function sendTwitterMessage(method: string, data: any = {}): void { - return sendMessage(apps.TWITTER, method, data); -} - -export function sendMessageEvent(method: string, data: any = {}): void { - return sendMessage(apps.MESSAGES, method, data); -} - -export function sendNotesEvent(method: string, data: any = {}): void { - return sendMessage(apps.NOTES, method, data); -} - -export function sendMarketplaceEvent(method: string, data: any = {}): void { - sendMessage(apps.MARKETPLACE, method, data); -} - -export function sendContactsEvent(method: string, data: any = {}): void { - sendMessage(apps.CONTACTS, method, data); -} - -export function sendCameraEvent(method: string, data: any = {}): void { - sendMessage(apps.CAMERA, method, data); -} - -export function sendMatchEvent(method: string, data: any = {}): void { - return sendMessage(apps.MATCH, method, data); -} - -export function sendPhoneEvent(method: string, data: any = {}): void { - return sendMessage(apps.PHONE, method, data); -} diff --git a/apps/game/utils/misc.ts b/apps/game/utils/misc.ts deleted file mode 100644 index 4b1407a80..000000000 --- a/apps/game/utils/misc.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-function -export const noop = () => {}; diff --git a/apps/game/webpack.config.js b/apps/game/webpack.config.js deleted file mode 100644 index 40769a6d4..000000000 --- a/apps/game/webpack.config.js +++ /dev/null @@ -1,117 +0,0 @@ -const webpack = require('webpack'); -const path = require('path'); -const RemovePlugin = require('remove-files-webpack-plugin'); -const SentryCliPlugin = require('@sentry/webpack-plugin'); -const { RewriteFrames } = require('@sentry/integrations'); -const buildPath = path.resolve(__dirname, 'dist'); - -const alias = { - '@shared': path.resolve(__dirname, '../shared'), - '@typings': path.resolve(__dirname, '../typings'), -}; - -const server = () => { - const plugins = [ - new RemovePlugin({ - before: { - include: [path.resolve(buildPath, 'server')], - }, - watch: { - include: [path.resolve(buildPath, 'server')], - }, - }), - // Ignore cardinal as its optional - new webpack.IgnorePlugin(/^cardinal$/, /./), - ]; - - if (process.env.IS_SENTRY) { - plugins.push( - new SentryCliPlugin({ - url: 'https://sentry.projecterror.dev', - release: process.env.npm_package_version, - authToken: process.env.SENTRY_AUTH_TOKEN, - org: 'project-error', - project: 'npwd-s', - include: ['dist/server'], - ignore: ['node_modules'], - validate: true, - integrations: [ - new RewriteFrames({ - // root: rootDir, - // iteratee: (frame) => { - // if (!frame.filename || frame.filename === '') return frame; - // - // frame.filename = frame.filename = frame.filename.replace('@npwd', ''); - // return frame; - // }, - }), - ], - }), - ); - } - - return { - entry: './server/server.ts', - module: { - rules: [ - { - test: /\.tsx?$/, - use: ['ts-loader'], - exclude: /node_modules/, - }, - ], - }, - plugins, - devtool: 'source-map', - optimization: { - minimize: true, - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'], - alias, - }, - output: { - filename: '[contenthash].server.js', - path: path.resolve(buildPath, 'server'), - }, - target: 'node', - }; -}; - -const client = () => { - return { - entry: './client/client.ts', - module: { - rules: [ - { - test: /\.tsx?$/, - use: ['ts-loader'], - exclude: /node_modules/, - }, - ], - }, - plugins: [ - new RemovePlugin({ - before: { - include: [path.resolve(buildPath, 'client')], - }, - watch: { - include: [path.resolve(buildPath, 'client')], - }, - }), - ], - optimization: { - minimize: true, - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'], - alias, - }, - output: { - filename: '[contenthash].client.js', - path: path.resolve(buildPath, 'client'), - }, - }; -}; - -module.exports = [server, client];