forked from OpenWF/SpaceNinjaServer
		
	Compare commits
	
		
			182 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9369e6cc5a | |||
| 49eb14f758 | |||
| 5e455d1e90 | |||
| 4d93dc80dc | |||
| 38326fc452 | |||
| 6e9c787c59 | |||
| 82fe598056 | |||
| bc271216ac | |||
| c5463166a8 | |||
| 8bce83d14c | |||
| cc5682760d | |||
| 00acaed62a | |||
| d794bd94ce | |||
| cecc65197b | |||
| b1c1b56de3 | |||
| 167da9c573 | |||
| 5e6955ae32 | |||
| f2145ed91b | |||
| 20d9a699b4 | |||
| 2b054d1728 | |||
| 5ac73528a0 | |||
| 678ad0c4a1 | |||
| 4bdb759463 | |||
| 71be8a2868 | |||
| f3072e84c9 | |||
| b2749765a3 | |||
| 654652b889 | |||
| e3048ea188 | |||
| bb1d6a98c5 | |||
| c3bf0ae7c7 | |||
| 3a72617a0f | |||
| 3ae535ccbc | |||
| 23abe5de02 | |||
| 0d21c73ab7 | |||
| 482101ccd0 | |||
| 60e87543aa | |||
| c4c17f24d7 | |||
| 43fa1978c0 | |||
| 18fafc38b5 | |||
| 98a46e51de | |||
| 2a7767ef4a | |||
| e867123f89 | |||
| 2322a994c6 | |||
| be8e2feae6 | |||
| 4f8b07322e | |||
| 4b3b1969da | |||
| a0ce110e7e | |||
| 7fe00da2a4 | |||
| bac23a8465 | |||
| db112ee5ed | |||
| 86998b6760 | |||
| be3dd7ab66 | |||
| e6fb675e21 | |||
| fb4c42490e | |||
| 96a15e25df | |||
| ff234c9874 | |||
| 7d3915fe05 | |||
| 4b3e2dfc62 | |||
| 737d013655 | |||
| 1f8d437fad | |||
| 9263b8f179 | |||
| 875f4b9fa4 | |||
| fd7ddd9696 | |||
| 065afc0089 | |||
| c1c14b2068 | |||
| 9e66d22256 | |||
| 02f0935710 | |||
| af4c3a93ce | |||
| 4141970530 | |||
| ca589cb7cf | |||
| d6ed22d1ff | |||
| 610a432e46 | |||
| 2ca895a5f8 | |||
| fd2286c253 | |||
| 5a582daa1a | |||
| 6a571e5e78 | |||
| 0349c4a32c | |||
| e1563bf298 | |||
| af6f422fec | |||
| f5c1b83598 | |||
| 30f380f37e | |||
| 0f7a85db59 | |||
| 43bc12713a | |||
| 6022bf97b5 | |||
| 159e151dc0 | |||
| 56954260c8 | |||
| c535044af8 | |||
| f5146be129 | |||
| d38ec06ed6 | |||
| 060f65900f | |||
| 66d3057d40 | |||
| b14a5925df | |||
| 9da47c406a | |||
| 09065bdb4e | |||
| 8f04fc5fdf | |||
| 230ee5f638 | |||
| 21db6ce265 | |||
| 1ecf53c96b | |||
| e67ef63b77 | |||
| 5772ebe746 | |||
| 0136e4d152 | |||
| 8b3ee4b4f5 | |||
| 6e8800f048 | |||
| d65a667acd | |||
| c6a3e86d2b | |||
| a8e41c95e7 | |||
| 9426359370 | |||
| e5247700df | |||
| 1c3f1e2276 | |||
| 7710e7c13f | |||
| a64c5ea3c1 | |||
| 17e1eb86dd | |||
| de9dfb3d71 | |||
| fc38f818dd | |||
| e76f08db89 | |||
| 7bcb5f21ce | |||
| 3641d63f6f | |||
| 71c4835a69 | |||
| 86a63ace41 | |||
| 32c95b6715 | |||
| 6f8b14fb2d | |||
| 3d8aa60838 | |||
| 87da94658d | |||
| 05fbefa7f4 | |||
| a2abf6db8f | |||
| 64a1c8b276 | |||
| 4fa07a1319 | |||
| a3cc7d9f92 | |||
| c47c60fdcc | |||
| 367455baaa | |||
| 6c2b7a61e2 | |||
| 6a6683fb25 | |||
| e3b6accb5d | |||
| 7e437d75bf | |||
| 62570177b6 | |||
| d2aff211c6 | |||
| 791ae389d8 | |||
| d027e7f26e | |||
| cd6ce61b80 | |||
| a5be29159f | |||
| f099b64ef4 | |||
| c4f348c252 | |||
| 0d388b4b0f | |||
| d64531f4b2 | |||
| 01b8f7acf3 | |||
| 8a7db2cd85 | |||
| 5a9415ae0c | |||
| 39f898cd30 | |||
| 9c55a8a4aa | |||
| 253ae09f24 | |||
| 703e9007b0 | |||
| 3e555b1753 | |||
| 1066b4a983 | |||
| b9a2cea862 | |||
| 0342f52359 | |||
| ea9012bd56 | |||
| 13400b6d83 | |||
| 8d57eda9d2 | |||
| 6b66cb495b | |||
| f4f7ed00d1 | |||
| 18556cb2f5 | |||
| 648af9ae18 | |||
| e16da9da44 | |||
| 4d8dbd99aa | |||
| 0a3f9549a9 | |||
| 2cfb21b98e | |||
| 3fedc701f1 | |||
| ed596aa3f3 | |||
| e2349b361e | |||
| 9221178522 | |||
| 9a5c2ab4a4 | |||
| d4c477769a | |||
| 6de9f0dcdb | |||
| 9662da00de | |||
| 662d824369 | |||
| a0bac12e95 | |||
| e98cb2ec24 | |||
| b5c6c3e485 | |||
| fa65ba3f25 | |||
| 0c54c064eb | |||
| b4e789bf0d | |||
| 9add016d7b | 
@ -31,7 +31,8 @@
 | 
			
		||||
        "no-mixed-spaces-and-tabs": "error",
 | 
			
		||||
        "@typescript-eslint/require-await": "error",
 | 
			
		||||
        "import/no-named-as-default-member": "off",
 | 
			
		||||
        "import/no-cycle": "warn"
 | 
			
		||||
        "import/no-cycle": "warn",
 | 
			
		||||
        "@typescript-eslint/no-deprecated": "warn"
 | 
			
		||||
    },
 | 
			
		||||
    "parser": "@typescript-eslint/parser",
 | 
			
		||||
    "parserOptions": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							@ -4,9 +4,9 @@ on:
 | 
			
		||||
        branches:
 | 
			
		||||
            - main
 | 
			
		||||
jobs:
 | 
			
		||||
    docker-amd64:
 | 
			
		||||
    docker:
 | 
			
		||||
        if: github.repository == 'OpenWF/SpaceNinjaServer'
 | 
			
		||||
        runs-on: amd64
 | 
			
		||||
        runs-on: ubuntu-latest
 | 
			
		||||
        steps:
 | 
			
		||||
            - name: Set up Docker buildx
 | 
			
		||||
              uses: docker/setup-buildx-action@v3
 | 
			
		||||
@ -18,27 +18,10 @@ jobs:
 | 
			
		||||
            - name: Build and push
 | 
			
		||||
              uses: docker/build-push-action@v6
 | 
			
		||||
              with:
 | 
			
		||||
                  platforms: linux/amd64
 | 
			
		||||
                  platforms: linux/arm64,linux/amd64
 | 
			
		||||
                  push: true
 | 
			
		||||
                  tags: |
 | 
			
		||||
                      openwf/spaceninjaserver:latest
 | 
			
		||||
                      openwf/spaceninjaserver:${{ github.sha }}
 | 
			
		||||
    docker-arm64:
 | 
			
		||||
        if: github.repository == 'OpenWF/SpaceNinjaServer'
 | 
			
		||||
        runs-on: arm64
 | 
			
		||||
        steps:
 | 
			
		||||
            - name: Set up Docker buildx
 | 
			
		||||
              uses: docker/setup-buildx-action@v3
 | 
			
		||||
            - name: Log in to container registry
 | 
			
		||||
              uses: docker/login-action@v3
 | 
			
		||||
              with:
 | 
			
		||||
                  username: openwf
 | 
			
		||||
                  password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
            - name: Build and push
 | 
			
		||||
              uses: docker/build-push-action@v6
 | 
			
		||||
              with:
 | 
			
		||||
                  platforms: linux/arm64
 | 
			
		||||
                  push: true
 | 
			
		||||
                  tags: |
 | 
			
		||||
                      openwf/spaceninjaserver:latest-arm64
 | 
			
		||||
                      openwf/spaceninjaserver:${{ github.sha }}
 | 
			
		||||
                      openwf/spaceninjaserver:${{ github.sha }}-arm64
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								AGENTS.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								AGENTS.md
									
									
									
									
									
								
							@ -1,17 +0,0 @@
 | 
			
		||||
## In General
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
 | 
			
		||||
Use `npm i` or `npm ci` to install all dependencies.
 | 
			
		||||
 | 
			
		||||
### Testing
 | 
			
		||||
 | 
			
		||||
Use `npm run verify` to verify that your changes pass TypeScript's checks.
 | 
			
		||||
 | 
			
		||||
### Formatting
 | 
			
		||||
 | 
			
		||||
Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure.
 | 
			
		||||
 | 
			
		||||
## WebUI Specific
 | 
			
		||||
 | 
			
		||||
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context.
 | 
			
		||||
							
								
								
									
										19
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								CONTRIBUTING.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
## In General
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
 | 
			
		||||
Use `npm i` or `npm ci` to install all dependencies, including dev dependencies.
 | 
			
		||||
 | 
			
		||||
## Development Process
 | 
			
		||||
 | 
			
		||||
Auto reloading is supported for server and WebUI development. Simply use `npm run dev` or `npm run dev:bun` to start the server and edit away.
 | 
			
		||||
 | 
			
		||||
### Testing
 | 
			
		||||
 | 
			
		||||
Before submitting a PR:
 | 
			
		||||
- Use `npm run verify` to verify that the code is type-safe.
 | 
			
		||||
- Use `npm run fix` to fix formatting issues as well as be informed of any unfixable issues. Avoid introducing new warnings.
 | 
			
		||||
 | 
			
		||||
## WebUI Specific
 | 
			
		||||
 | 
			
		||||
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT provide translations generated by AI or other automated tools.
 | 
			
		||||
@ -6,18 +6,21 @@ More information for the moment here: [https://discord.gg/PNNZ3asUuY](https://di
 | 
			
		||||
 | 
			
		||||
This project is in active development at <https://onlyg.it/OpenWF/SpaceNinjaServer>.
 | 
			
		||||
 | 
			
		||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues?q=&type=all&state=open&labels=-4%2C-10&milestone=0&assignee=0&poster=). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
 | 
			
		||||
To get an idea of what functionality you can expect to be missing [have a look through the issues](https://onlyg.it/OpenWF/SpaceNinjaServer/issues). However, many things have been implemented and *should* work as expected. Please open an issue for anything where that's not the case and/or the server is reporting errors.
 | 
			
		||||
 | 
			
		||||
## config.json
 | 
			
		||||
 | 
			
		||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
 | 
			
		||||
 | 
			
		||||
- `skipTutorial` affects only newly created accounts, so you may wish to change it before logging in for the first time.
 | 
			
		||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
 | 
			
		||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
 | 
			
		||||
- `ircExecutable` can be provided with a relative path to an EXE which will be ran as a child process of SpaceNinjaServer.
 | 
			
		||||
- `ircAddress`, `hubAddress`, and `nrsAddress` can be provided if these secondary servers are on a different machine.
 | 
			
		||||
- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift.
 | 
			
		||||
- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis.
 | 
			
		||||
- `worldState.duviriOverride` can be set to `joy`, `anger`, `envy`, `sorrow`, or `fear` to lock the Duviri spiral.
 | 
			
		||||
- `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values:
 | 
			
		||||
  - `RadioLegionIntermission14Syndicate` for Nora's Mix: Dreams of the Dead
 | 
			
		||||
  - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9
 | 
			
		||||
  - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8
 | 
			
		||||
  - `RadioLegionIntermission11Syndicate` for Nora's Mix Vol. 7
 | 
			
		||||
@ -34,5 +37,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
 | 
			
		||||
  - `RadioLegion2Syndicate` for The Emissary
 | 
			
		||||
  - `RadioLegionIntermissionSyndicate` for Intermission I
 | 
			
		||||
  - `RadioLegionSyndicate` for The Wolf of Saturn Six
 | 
			
		||||
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
 | 
			
		||||
- `worldState.allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
 | 
			
		||||
- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.
 | 
			
		||||
 | 
			
		||||
@ -14,13 +14,18 @@ if %errorlevel% == 0 (
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	echo Updating dependencies...
 | 
			
		||||
	call npm i --omit=dev
 | 
			
		||||
 | 
			
		||||
	call npm run build
 | 
			
		||||
	node scripts/raw-precheck.js > NUL
 | 
			
		||||
	if %errorlevel% == 0 (
 | 
			
		||||
		call npm run start
 | 
			
		||||
		echo SpaceNinjaServer seems to have crashed.
 | 
			
		||||
		call npm i --omit=dev --omit=optional
 | 
			
		||||
		call npm run raw
 | 
			
		||||
	) else (
 | 
			
		||||
		call npm i --omit=dev
 | 
			
		||||
		call npm run build
 | 
			
		||||
		if %errorlevel% == 0 (
 | 
			
		||||
			call npm run start
 | 
			
		||||
		)
 | 
			
		||||
	)
 | 
			
		||||
	echo SpaceNinjaServer seems to have crashed.
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
:a
 | 
			
		||||
 | 
			
		||||
@ -14,11 +14,16 @@ if [ $? -eq 0 ]; then
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo "Updating dependencies..."
 | 
			
		||||
    npm i --omit=dev
 | 
			
		||||
 | 
			
		||||
    npm run build
 | 
			
		||||
    node scripts/raw-precheck.js > /dev/null
 | 
			
		||||
    if [ $? -eq 0 ]; then
 | 
			
		||||
        npm run start
 | 
			
		||||
        echo "SpaceNinjaServer seems to have crashed."
 | 
			
		||||
        npm i --omit=dev --omit=optional
 | 
			
		||||
        npm run raw
 | 
			
		||||
    else
 | 
			
		||||
        npm i --omit=dev
 | 
			
		||||
        npm run build
 | 
			
		||||
        if [ $? -eq 0 ]; then
 | 
			
		||||
            npm run start
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
    echo "SpaceNinjaServer seems to have crashed."
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
@ -5,31 +5,19 @@
 | 
			
		||||
    "level": "trace"
 | 
			
		||||
  },
 | 
			
		||||
  "myAddress": "localhost",
 | 
			
		||||
  "bindAddress": "0.0.0.0",
 | 
			
		||||
  "httpPort": 80,
 | 
			
		||||
  "httpsPort": 443,
 | 
			
		||||
  "NRS": ["localhost"],
 | 
			
		||||
  "ircExecutable": null,
 | 
			
		||||
  "ircAddress": null,
 | 
			
		||||
  "hubAddress": null,
 | 
			
		||||
  "nrsAddress": null,
 | 
			
		||||
  "administratorNames": [],
 | 
			
		||||
  "autoCreateAccount": true,
 | 
			
		||||
  "skipTutorial": false,
 | 
			
		||||
  "unlockAllScans": false,
 | 
			
		||||
  "unlockAllShipFeatures": false,
 | 
			
		||||
  "unlockAllShipDecorations": false,
 | 
			
		||||
  "unlockAllFlavourItems": false,
 | 
			
		||||
  "unlockAllSkins": false,
 | 
			
		||||
  "unlockAllCapturaScenes": false,
 | 
			
		||||
  "fullyStockedVendors": false,
 | 
			
		||||
  "unlockAllProfitTakerStages": false,
 | 
			
		||||
  "skipClanKeyCrafting": false,
 | 
			
		||||
  "noDojoRoomBuildStage": false,
 | 
			
		||||
  "noDecoBuildStage": false,
 | 
			
		||||
  "fastDojoRoomDestruction": false,
 | 
			
		||||
  "noDojoResearchCosts": false,
 | 
			
		||||
  "noDojoResearchTime": false,
 | 
			
		||||
  "fastClanAscension": false,
 | 
			
		||||
  "unlockAllSimarisResearchEntries": false,
 | 
			
		||||
  "spoofMasteryRank": -1,
 | 
			
		||||
  "relicRewardItemCountMultiplier": 1,
 | 
			
		||||
  "nightwaveStandingMultiplier": 1,
 | 
			
		||||
  "unfaithfulBugFixes": {
 | 
			
		||||
    "ignore1999LastRegionPlayed": false,
 | 
			
		||||
    "fixXtraCheeseTimer": false,
 | 
			
		||||
@ -44,15 +32,21 @@
 | 
			
		||||
    "baroAlwaysAvailable": false,
 | 
			
		||||
    "baroFullyStocked": false,
 | 
			
		||||
    "varziaFullyStocked": false,
 | 
			
		||||
    "wolfHunt": false,
 | 
			
		||||
    "wolfHunt": null,
 | 
			
		||||
    "orphixVenom": false,
 | 
			
		||||
    "longShadow": false,
 | 
			
		||||
    "hallowedFlame": false,
 | 
			
		||||
    "anniversary": null,
 | 
			
		||||
    "hallowedNightmares": false,
 | 
			
		||||
    "hallowedNightmaresRewardsOverride": 0,
 | 
			
		||||
    "naberusNightsOverride": null,
 | 
			
		||||
    "proxyRebellion": false,
 | 
			
		||||
    "proxyRebellionRewardsOverride": 0,
 | 
			
		||||
    "voidCorruption2025Week1": false,
 | 
			
		||||
    "voidCorruption2025Week2": false,
 | 
			
		||||
    "voidCorruption2025Week3": false,
 | 
			
		||||
    "voidCorruption2025Week4": false,
 | 
			
		||||
    "qtccAlerts": false,
 | 
			
		||||
    "galleonOfGhouls": 0,
 | 
			
		||||
    "ghoulEmergenceOverride": null,
 | 
			
		||||
    "plagueStarOverride": null,
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
services:
 | 
			
		||||
    spaceninjaserver:
 | 
			
		||||
        # The image to use. If you have an ARM CPU, replace 'latest' with 'latest-arm64'.
 | 
			
		||||
        image: openwf/spaceninjaserver:latest
 | 
			
		||||
 | 
			
		||||
        volumes:
 | 
			
		||||
@ -19,9 +18,6 @@ services:
 | 
			
		||||
            - mongodb
 | 
			
		||||
    mongodb:
 | 
			
		||||
        image: docker.io/library/mongo:8.0.0-noble
 | 
			
		||||
        environment:
 | 
			
		||||
            MONGO_INITDB_ROOT_USERNAME: openwfagent
 | 
			
		||||
            MONGO_INITDB_ROOT_PASSWORD: spaceninjaserver
 | 
			
		||||
        volumes:
 | 
			
		||||
            - ./docker-data/database:/data/db
 | 
			
		||||
        command: mongod --quiet --logpath /dev/null
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
if [ ! -f conf/config.json ]; then
 | 
			
		||||
	jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
 | 
			
		||||
	jq --arg value "mongodb://mongodb:27017/openWF" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
exec npm run raw -- --configPath conf/config.json
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -9,6 +9,7 @@
 | 
			
		||||
      "version": "0.1.0",
 | 
			
		||||
      "license": "GNU",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "body-parser": "^2.2.0",
 | 
			
		||||
        "chokidar": "^4.0.3",
 | 
			
		||||
        "crc-32": "^1.2.2",
 | 
			
		||||
        "express": "^5",
 | 
			
		||||
@ -17,7 +18,7 @@
 | 
			
		||||
        "morgan": "^1.10.0",
 | 
			
		||||
        "ncp": "^2.0.0",
 | 
			
		||||
        "undici": "^7.10.0",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.83",
 | 
			
		||||
        "warframe-public-export-plus": "^0.5.93",
 | 
			
		||||
        "warframe-riven-info": "^0.1.2",
 | 
			
		||||
        "winston": "^3.17.0",
 | 
			
		||||
        "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
@ -33,7 +34,11 @@
 | 
			
		||||
        "prettier": "^3.5.3",
 | 
			
		||||
        "tree-kill": "^1.2.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=20.18.1"
 | 
			
		||||
      },
 | 
			
		||||
      "optionalDependencies": {
 | 
			
		||||
        "@types/body-parser": "^1.19.6",
 | 
			
		||||
        "@types/express": "^5",
 | 
			
		||||
        "@types/morgan": "^1.9.9",
 | 
			
		||||
        "@types/websocket": "^1.0.10",
 | 
			
		||||
@ -5529,9 +5534,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-public-export-plus": {
 | 
			
		||||
      "version": "0.5.84",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.84.tgz",
 | 
			
		||||
      "integrity": "sha512-ZpI1Y5CgWDmCwM4/oQpv9u0GD6KFvsJ9f1vJVXYhm5VD9DdOJcFzXgXgg98HXJ5JHbO16ZGIj83117qdpd0RQA=="
 | 
			
		||||
      "version": "0.5.93",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.93.tgz",
 | 
			
		||||
      "integrity": "sha512-A8LSFJoyg7sU1n4L0zhLK1g0CREh8Fxvk7eXKoT8nMTroQg6YgEw02gK0MUi9U3rWTnlaGTsXZMp/tgC7HWUKw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/warframe-riven-info": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								package.json
									
									
									
									
									
								
							@ -5,28 +5,28 @@
 | 
			
		||||
  "main": "index.ts",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node --enable-source-maps build/src/index.js",
 | 
			
		||||
    "build": "tsgo --sourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:dev": "tsgo --sourceMap",
 | 
			
		||||
    "build:dev:tsc": "tsc --incremental --sourceMap",
 | 
			
		||||
    "build": "tsgo --inlineSourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:tsc": "tsc --incremental --inlineSourceMap && ncp static/webui build/static/webui",
 | 
			
		||||
    "build:dev": "tsgo --inlineSourceMap",
 | 
			
		||||
    "build:dev:tsc": "tsc --incremental --inlineSourceMap",
 | 
			
		||||
    "build-and-start": "npm run build && npm run start",
 | 
			
		||||
    "build-and-start:bun": "npm run verify && npm run bun-run",
 | 
			
		||||
    "dev": "node scripts/dev.cjs",
 | 
			
		||||
    "dev:bun": "bun scripts/dev.cjs",
 | 
			
		||||
    "verify": "tsgo --noEmit",
 | 
			
		||||
    "verify:tsc": "tsc --noEmit",
 | 
			
		||||
    "raw": "node --experimental-transform-types src/index.ts",
 | 
			
		||||
    "raw": "node scripts/raw-precheck.js && node --experimental-transform-types src/index.ts",
 | 
			
		||||
    "raw:bun": "bun src/index.ts",
 | 
			
		||||
    "lint": "eslint --ext .ts .",
 | 
			
		||||
    "lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
 | 
			
		||||
    "lint:fix": "eslint --fix --ext .ts .",
 | 
			
		||||
    "prettier": "prettier --write .",
 | 
			
		||||
    "update-translations": "cd scripts && node update-translations.cjs",
 | 
			
		||||
    "fix": "npm run update-translations && npm run lint:fix"
 | 
			
		||||
    "fix": "npm run update-translations && npm run prettier"
 | 
			
		||||
  },
 | 
			
		||||
  "license": "GNU",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "body-parser": "^2.2.0",
 | 
			
		||||
    "chokidar": "^4.0.3",
 | 
			
		||||
    "crc-32": "^1.2.2",
 | 
			
		||||
    "express": "^5",
 | 
			
		||||
@ -35,13 +35,14 @@
 | 
			
		||||
    "morgan": "^1.10.0",
 | 
			
		||||
    "ncp": "^2.0.0",
 | 
			
		||||
    "undici": "^7.10.0",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.83",
 | 
			
		||||
    "warframe-public-export-plus": "^0.5.93",
 | 
			
		||||
    "warframe-riven-info": "^0.1.2",
 | 
			
		||||
    "winston": "^3.17.0",
 | 
			
		||||
    "winston-daily-rotate-file": "^5.0.0",
 | 
			
		||||
    "ws": "^8.18.2"
 | 
			
		||||
  },
 | 
			
		||||
  "optionalDependencies": {
 | 
			
		||||
    "@types/body-parser": "^1.19.6",
 | 
			
		||||
    "@types/express": "^5",
 | 
			
		||||
    "@types/morgan": "^1.9.9",
 | 
			
		||||
    "@types/websocket": "^1.0.10",
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,17 @@ args.push("--dev");
 | 
			
		||||
args.push("--secret");
 | 
			
		||||
args.push(secret);
 | 
			
		||||
 | 
			
		||||
const cangoraw = (() => {
 | 
			
		||||
    if (process.versions.bun) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
 | 
			
		||||
    if (major > 22 || (major == 22 && minor >= 7)) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
let buildproc, runproc;
 | 
			
		||||
const spawnopts = { stdio: "inherit", shell: true };
 | 
			
		||||
function run(changedFile) {
 | 
			
		||||
@ -29,7 +40,10 @@ function run(changedFile) {
 | 
			
		||||
        runproc = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
 | 
			
		||||
    const thisbuildproc = spawn(
 | 
			
		||||
        [process.versions.bun ? "bun" : "npm", "run", cangoraw ? "verify" : "build:dev"].join(" "),
 | 
			
		||||
        spawnopts
 | 
			
		||||
    );
 | 
			
		||||
    const thisbuildstart = Date.now();
 | 
			
		||||
    buildproc = thisbuildproc;
 | 
			
		||||
    buildproc.on("exit", code => {
 | 
			
		||||
@ -38,8 +52,17 @@ function run(changedFile) {
 | 
			
		||||
        }
 | 
			
		||||
        buildproc = undefined;
 | 
			
		||||
        if (code === 0) {
 | 
			
		||||
            console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
 | 
			
		||||
            runproc = spawn("npm", ["run", process.versions.bun ? "raw:bun" : "start", "--", ...args], spawnopts);
 | 
			
		||||
            console.log(`${cangoraw ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
 | 
			
		||||
            runproc = spawn(
 | 
			
		||||
                [
 | 
			
		||||
                    process.versions.bun ? "bun" : "npm",
 | 
			
		||||
                    "run",
 | 
			
		||||
                    cangoraw ? (process.versions.bun ? "raw:bun" : "raw") : "start",
 | 
			
		||||
                    "--",
 | 
			
		||||
                    ...args
 | 
			
		||||
                ].join(" "),
 | 
			
		||||
                spawnopts
 | 
			
		||||
            );
 | 
			
		||||
            runproc.on("exit", () => {
 | 
			
		||||
                runproc = undefined;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								scripts/raw-precheck.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								scripts/raw-precheck.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
const [major, minor] = process.versions.node.split(".").map(x => parseInt(x));
 | 
			
		||||
if (major > 22 || (major == 22 && minor >= 7)) {
 | 
			
		||||
    // ok
 | 
			
		||||
} else {
 | 
			
		||||
    console.log("Sorry, your Node version is a bit too old for this. You have 2 options:");
 | 
			
		||||
    console.log("- Update Node.js.");
 | 
			
		||||
    console.log("- Use 'npm run build && npm run start'. Optional libraries must be installed for this.");
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
}
 | 
			
		||||
@ -31,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
 | 
			
		||||
            const strings = extractStrings(line);
 | 
			
		||||
            if (Object.keys(strings).length > 0) {
 | 
			
		||||
                Object.entries(strings).forEach(([key, value]) => {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
 | 
			
		||||
                    if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED]")) {
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`${targetStrings[key]}\`,\n`);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        fs.writeSync(fileHandle, `    ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ const app = express();
 | 
			
		||||
 | 
			
		||||
app.use((req, _res, next) => {
 | 
			
		||||
    // 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
 | 
			
		||||
    // The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
 | 
			
		||||
    // The client patch is expected to decrypt it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
 | 
			
		||||
    if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
 | 
			
		||||
        req.headers["content-encoding"] = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
export const EPOCH = 1734307200_000; // Monday, Dec 16, 2024 @ 00:00 UTC+0; should logically be the start of winter in 1999 iteration 0
 | 
			
		||||
 | 
			
		||||
const millisecondsPerSecond = 1000;
 | 
			
		||||
const secondsPerMinute = 60;
 | 
			
		||||
const minutesPerHour = 60;
 | 
			
		||||
 | 
			
		||||
@ -31,12 +31,13 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
 | 
			
		||||
 | 
			
		||||
    if (request.DecoId) {
 | 
			
		||||
        removeDojoDeco(guild, request.ComponentId, request.DecoId);
 | 
			
		||||
        await guild.save();
 | 
			
		||||
        res.json(await getDojoClient(guild, 0, request.ComponentId));
 | 
			
		||||
    } else {
 | 
			
		||||
        await removeDojoRoom(guild, request.ComponentId);
 | 
			
		||||
        await guild.save();
 | 
			
		||||
        res.json(await getDojoClient(guild, 0));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json(await getDojoClient(guild, 0, request.ComponentId));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IAbortDojoComponentRequest {
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import type { IInventoryClient, IUpgradeClient } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { addMods, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const artifactsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -57,6 +58,7 @@ export const artifactsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.send(itemId);
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IArtifactsRequest {
 | 
			
		||||
 | 
			
		||||
@ -95,10 +95,7 @@ export const confirmGuildInvitationPostController: RequestHandler = async (req,
 | 
			
		||||
        await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
 | 
			
		||||
 | 
			
		||||
        // Update inventory of new member
 | 
			
		||||
        const inventory = await getInventory(
 | 
			
		||||
            guildMember.accountId.toString(),
 | 
			
		||||
            "GuildId LevelKeys Recipes skipClanKeyCrafting"
 | 
			
		||||
        );
 | 
			
		||||
        const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
 | 
			
		||||
        inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
 | 
			
		||||
        giveClanKey(inventory);
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,23 @@ import { Guild, GuildMember } from "../../models/guildModel.ts";
 | 
			
		||||
import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const createGuildController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
    const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
 | 
			
		||||
    if (inventory.GuildId) {
 | 
			
		||||
        const guild = await Guild.findById(inventory.GuildId);
 | 
			
		||||
        if (guild) {
 | 
			
		||||
            res.json({
 | 
			
		||||
                ...(await getGuildClient(guild, account))
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove pending applications for this account
 | 
			
		||||
    await GuildMember.deleteMany({ accountId: account._id, status: 1 });
 | 
			
		||||
 | 
			
		||||
@ -27,7 +39,6 @@ export const createGuildController: RequestHandler = async (req, res) => {
 | 
			
		||||
        rank: 0
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes skipClanKeyCrafting");
 | 
			
		||||
    inventory.GuildId = guild._id;
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = {};
 | 
			
		||||
    giveClanKey(inventory, inventoryChanges);
 | 
			
		||||
@ -37,6 +48,7 @@ export const createGuildController: RequestHandler = async (req, res) => {
 | 
			
		||||
        ...(await getGuildClient(guild, account)),
 | 
			
		||||
        InventoryChanges: inventoryChanges
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ICreateGuildRequest {
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,17 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { deleteSession } from "../../managers/sessionManager.ts";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
 | 
			
		||||
const deleteSessionController: RequestHandler = (_req, res) => {
 | 
			
		||||
const deleteSessionController: RequestHandler = async (_req, res) => {
 | 
			
		||||
    const account = await getAccountForRequest(_req);
 | 
			
		||||
    deleteSession(_req.query.sessionId as string);
 | 
			
		||||
    res.sendStatus(200);
 | 
			
		||||
    if (account.BuildLabel && version_compare(account.BuildLabel, "2016.07.08.16.56") < 0) {
 | 
			
		||||
        // Pre-Specters of the Rail
 | 
			
		||||
        res.send(_req.query.sessionId as string); // Unsure if this is correct, but the client is chill with it
 | 
			
		||||
    } else {
 | 
			
		||||
        res.sendStatus(200);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { deleteSessionController };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										62
									
								
								src/controllers/api/feedPrinceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/controllers/api/feedPrinceController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const feedPrinceController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "MiscItems NokkoColony NodeIntrosCompleted");
 | 
			
		||||
    const payload = getJSONfromString<IFeedPrinceRequest>(String(req.body));
 | 
			
		||||
 | 
			
		||||
    switch (payload.Mode) {
 | 
			
		||||
        case "r": {
 | 
			
		||||
            inventory.NokkoColony ??= {
 | 
			
		||||
                FeedLevel: 0,
 | 
			
		||||
                JournalEntries: []
 | 
			
		||||
            };
 | 
			
		||||
            const InventoryChanges: IInventoryChanges = {};
 | 
			
		||||
            inventory.NokkoColony.FeedLevel += payload.Amount;
 | 
			
		||||
            if (
 | 
			
		||||
                (!inventory.NodeIntrosCompleted.includes("CompletedVision1") && inventory.NokkoColony.FeedLevel > 20) ||
 | 
			
		||||
                (!inventory.NodeIntrosCompleted.includes("CompletedVision2") && inventory.NokkoColony.FeedLevel > 60)
 | 
			
		||||
            ) {
 | 
			
		||||
                res.json({
 | 
			
		||||
                    FeedSucceeded: false,
 | 
			
		||||
                    FeedLevel: inventory.NokkoColony.FeedLevel - payload.Amount,
 | 
			
		||||
                    InventoryChanges
 | 
			
		||||
                } satisfies IFeedPrinceResponse);
 | 
			
		||||
            } else {
 | 
			
		||||
                addMiscItem(
 | 
			
		||||
                    inventory,
 | 
			
		||||
                    "/Lotus/Types/Items/MiscItems/MushroomFood",
 | 
			
		||||
                    payload.Amount * -1,
 | 
			
		||||
                    InventoryChanges
 | 
			
		||||
                );
 | 
			
		||||
                await inventory.save();
 | 
			
		||||
                res.json({
 | 
			
		||||
                    FeedSucceeded: true,
 | 
			
		||||
                    FeedLevel: inventory.NokkoColony.FeedLevel,
 | 
			
		||||
                    InventoryChanges
 | 
			
		||||
                } satisfies IFeedPrinceResponse);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
			
		||||
            throw new Error(`unknown feedPrince mode: ${payload.Mode}`);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IFeedPrinceRequest {
 | 
			
		||||
    Mode: string; // r
 | 
			
		||||
    Amount: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IFeedPrinceResponse {
 | 
			
		||||
    FeedSucceeded: boolean;
 | 
			
		||||
    FeedLevel: number;
 | 
			
		||||
    InventoryChanges: IInventoryChanges;
 | 
			
		||||
}
 | 
			
		||||
@ -1,23 +1,91 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
import { ExportFocusUpgrades } from "warframe-public-export-plus";
 | 
			
		||||
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
 | 
			
		||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
 | 
			
		||||
export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    switch (req.query.op) {
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
 | 
			
		||||
    let op = req.query.op as string;
 | 
			
		||||
    const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0;
 | 
			
		||||
    if (focus2) {
 | 
			
		||||
        // Focus 2.0
 | 
			
		||||
        switch (req.query.op) {
 | 
			
		||||
            case Focus2Operation.InstallLens:
 | 
			
		||||
                op = "InstallLens";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.UnlockWay:
 | 
			
		||||
                op = "UnlockWay";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.UnlockUpgrade:
 | 
			
		||||
                op = "UnlockUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.IncreasePool:
 | 
			
		||||
                op = "IncreasePool";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.LevelUpUpgrade:
 | 
			
		||||
                op = "LevelUpUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.ActivateWay:
 | 
			
		||||
                op = "ActivateWay";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.UpdateUpgrade:
 | 
			
		||||
                op = "UpdateUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.SentTrainingAmplifier:
 | 
			
		||||
                op = "SentTrainingAmplifier";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.UnbindUpgrade:
 | 
			
		||||
                op = "UnbindUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus2Operation.ConvertShard:
 | 
			
		||||
                op = "ConvertShard";
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // Focus 3.0
 | 
			
		||||
        switch (req.query.op) {
 | 
			
		||||
            case Focus3Operation.InstallLens:
 | 
			
		||||
                op = "InstallLens";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.UnlockWay:
 | 
			
		||||
                op = "UnlockWay";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.UnlockUpgrade:
 | 
			
		||||
                op = "UnlockUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.LevelUpUpgrade:
 | 
			
		||||
                op = "LevelUpUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.ActivateWay:
 | 
			
		||||
                op = "ActivateWay";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.SentTrainingAmplifier:
 | 
			
		||||
                op = "SentTrainingAmplifier";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.UnbindUpgrade:
 | 
			
		||||
                op = "UnbindUpgrade";
 | 
			
		||||
                break;
 | 
			
		||||
            case Focus3Operation.ConvertShard:
 | 
			
		||||
                op = "ConvertShard";
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (op) {
 | 
			
		||||
        default:
 | 
			
		||||
            logger.error("Unhandled focus op type: " + String(req.query.op));
 | 
			
		||||
            logger.debug(String(req.body));
 | 
			
		||||
            res.end();
 | 
			
		||||
            break;
 | 
			
		||||
        case FocusOperation.InstallLens: {
 | 
			
		||||
        case "InstallLens": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as ILensInstallRequest;
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString());
 | 
			
		||||
            const item = inventory[request.Category].id(request.WeaponId);
 | 
			
		||||
            if (item) {
 | 
			
		||||
                item.FocusLens = request.LensType;
 | 
			
		||||
@ -35,15 +103,15 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.UnlockWay: {
 | 
			
		||||
        case "UnlockWay": {
 | 
			
		||||
            const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
 | 
			
		||||
            const focusPolarity = focusTypeToPolarity(focusType);
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP");
 | 
			
		||||
            const cost = inventory.FocusAbility ? 50_000 : 0;
 | 
			
		||||
            inventory.FocusAbility ??= focusType;
 | 
			
		||||
            inventory.FocusUpgrades.push({ ItemType: focusType });
 | 
			
		||||
            if (inventory.FocusXP) {
 | 
			
		||||
                inventory.FocusXP[focusPolarity]! -= cost;
 | 
			
		||||
            if (cost) {
 | 
			
		||||
                inventory.FocusXP![focusPolarity]! -= cost;
 | 
			
		||||
            }
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
            res.json({
 | 
			
		||||
@ -52,12 +120,29 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.ActivateWay: {
 | 
			
		||||
        case "IncreasePool": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as IIncreasePoolRequest;
 | 
			
		||||
            const focusPolarity = focusTypeToPolarity(request.FocusType);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity");
 | 
			
		||||
            let cost = 0;
 | 
			
		||||
            for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) {
 | 
			
		||||
                cost += increasePoolCost[capacity - 5];
 | 
			
		||||
            }
 | 
			
		||||
            inventory.FocusXP![focusPolarity]! -= cost;
 | 
			
		||||
            inventory.FocusCapacity = request.NewTotalCapacity;
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
            res.json({
 | 
			
		||||
                TotalCapacity: request.NewTotalCapacity,
 | 
			
		||||
                FocusPointCosts: { [focusPolarity]: cost }
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "ActivateWay": {
 | 
			
		||||
            const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
 | 
			
		||||
 | 
			
		||||
            await Inventory.updateOne(
 | 
			
		||||
                {
 | 
			
		||||
                    accountOwnerId: accountId
 | 
			
		||||
                    accountOwnerId: account._id.toString()
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    FocusAbility: focusType
 | 
			
		||||
@ -69,13 +154,20 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.UnlockUpgrade: {
 | 
			
		||||
        case "UnlockUpgrade": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest;
 | 
			
		||||
            const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString());
 | 
			
		||||
            let cost = 0;
 | 
			
		||||
            for (const focusType of request.FocusTypes) {
 | 
			
		||||
                cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
 | 
			
		||||
                if (focusType in ExportFocusUpgrades) {
 | 
			
		||||
                    cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
 | 
			
		||||
                } else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") {
 | 
			
		||||
                    // Zenurik's Inner Might (Focus 2.0)
 | 
			
		||||
                    cost += 50_000;
 | 
			
		||||
                } else {
 | 
			
		||||
                    logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`);
 | 
			
		||||
                }
 | 
			
		||||
                inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
 | 
			
		||||
            }
 | 
			
		||||
            inventory.FocusXP![focusPolarity]! -= cost;
 | 
			
		||||
@ -86,15 +178,20 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.LevelUpUpgrade: {
 | 
			
		||||
        case "LevelUpUpgrade":
 | 
			
		||||
        case "UpdateUpgrade": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest;
 | 
			
		||||
            const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType);
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString());
 | 
			
		||||
            let cost = 0;
 | 
			
		||||
            for (const focusUpgrade of request.FocusInfos) {
 | 
			
		||||
                cost += focusUpgrade.FocusXpCost;
 | 
			
		||||
                const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
 | 
			
		||||
                focusUpgradeDb.Level = focusUpgrade.Level;
 | 
			
		||||
                if (op == "UpdateUpgrade") {
 | 
			
		||||
                    focusUpgradeDb.IsActive = focusUpgrade.IsActive;
 | 
			
		||||
                } else {
 | 
			
		||||
                    focusUpgradeDb.Level = focusUpgrade.Level;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            inventory.FocusXP![focusPolarity]! -= cost;
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
@ -104,9 +201,9 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.SentTrainingAmplifier: {
 | 
			
		||||
        case "SentTrainingAmplifier": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString());
 | 
			
		||||
            const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
 | 
			
		||||
                ModularParts: [
 | 
			
		||||
                    "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
 | 
			
		||||
@ -119,10 +216,10 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            res.json(inventoryChanges.OperatorAmps![0]);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.UnbindUpgrade: {
 | 
			
		||||
        case "UnbindUpgrade": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
 | 
			
		||||
            const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString());
 | 
			
		||||
            inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
 | 
			
		||||
            addMiscItems(inventory, [
 | 
			
		||||
                {
 | 
			
		||||
@ -149,7 +246,7 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case FocusOperation.ConvertShard: {
 | 
			
		||||
        case "ConvertShard": {
 | 
			
		||||
            const request = JSON.parse(String(req.body)) as IConvertShardRequest;
 | 
			
		||||
            // Tally XP
 | 
			
		||||
            let xp = 0;
 | 
			
		||||
@ -167,7 +264,7 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
            for (const shard of request.Shards) {
 | 
			
		||||
                shard.ItemCount *= -1;
 | 
			
		||||
            }
 | 
			
		||||
            const inventory = await getInventory(accountId);
 | 
			
		||||
            const inventory = await getInventory(account._id.toString());
 | 
			
		||||
            const polarity = request.Polarity;
 | 
			
		||||
            inventory.FocusXP ??= {};
 | 
			
		||||
            inventory.FocusXP[polarity] ??= 0;
 | 
			
		||||
@ -179,7 +276,8 @@ export const focusController: RequestHandler = async (req, res) => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum FocusOperation {
 | 
			
		||||
// Focus 3.0
 | 
			
		||||
enum Focus3Operation {
 | 
			
		||||
    InstallLens = "1",
 | 
			
		||||
    UnlockWay = "2",
 | 
			
		||||
    UnlockUpgrade = "3",
 | 
			
		||||
@ -190,6 +288,20 @@ enum FocusOperation {
 | 
			
		||||
    ConvertShard = "9"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focus 2.0
 | 
			
		||||
enum Focus2Operation {
 | 
			
		||||
    InstallLens = "1",
 | 
			
		||||
    UnlockWay = "2",
 | 
			
		||||
    UnlockUpgrade = "3",
 | 
			
		||||
    IncreasePool = "4",
 | 
			
		||||
    LevelUpUpgrade = "5",
 | 
			
		||||
    ActivateWay = "6",
 | 
			
		||||
    UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest
 | 
			
		||||
    SentTrainingAmplifier = "9",
 | 
			
		||||
    UnbindUpgrade = "10",
 | 
			
		||||
    ConvertShard = "11"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// For UnlockWay & ActivateWay
 | 
			
		||||
interface IWayRequest {
 | 
			
		||||
    FocusType: string;
 | 
			
		||||
@ -199,6 +311,13 @@ interface IUnlockUpgradeRequest {
 | 
			
		||||
    FocusTypes: string[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focus 2.0
 | 
			
		||||
interface IIncreasePoolRequest {
 | 
			
		||||
    FocusType: string;
 | 
			
		||||
    CurrentTotalCapacity: number;
 | 
			
		||||
    NewTotalCapacity: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ILevelUpUpgradeRequest {
 | 
			
		||||
    FocusInfos: {
 | 
			
		||||
        ItemType: string;
 | 
			
		||||
@ -206,6 +325,7 @@ interface ILevelUpUpgradeRequest {
 | 
			
		||||
        IsUniversal: boolean;
 | 
			
		||||
        Level: number;
 | 
			
		||||
        IsActiveAbility: boolean;
 | 
			
		||||
        IsActive?: number; // Focus 2.0
 | 
			
		||||
    }[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -231,7 +351,7 @@ interface ILensInstallRequest {
 | 
			
		||||
 | 
			
		||||
// Works for ways & upgrades
 | 
			
		||||
const focusTypeToPolarity = (type: string): TFocusPolarity => {
 | 
			
		||||
    return ("AP_" + type.substr(1).split("/")[3].toUpperCase()) as TFocusPolarity;
 | 
			
		||||
    return ("AP_" + type.substring(1).split("/")[3].toUpperCase()) as TFocusPolarity;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const shardValues = {
 | 
			
		||||
@ -240,3 +360,19 @@ const shardValues = {
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000,
 | 
			
		||||
    "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0)
 | 
			
		||||
const increasePoolCost = [
 | 
			
		||||
    2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918,
 | 
			
		||||
    13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344,
 | 
			
		||||
    25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775,
 | 
			
		||||
    37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961,
 | 
			
		||||
    50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759,
 | 
			
		||||
    64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078,
 | 
			
		||||
    78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849,
 | 
			
		||||
    93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065,
 | 
			
		||||
    108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624,
 | 
			
		||||
    121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443,
 | 
			
		||||
    135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503,
 | 
			
		||||
    149517
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/controllers/api/forceRemoveItemController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/controllers/api/forceRemoveItemController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const forceRemoveItemController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "MiscItems");
 | 
			
		||||
    const body = getJSONfromString<IForceRemoveItemRequest>(String(req.body));
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = {};
 | 
			
		||||
    for (const item of body.items) {
 | 
			
		||||
        const index = inventory.MiscItems.findIndex(x => x.ItemType == item);
 | 
			
		||||
        if (index != -1) {
 | 
			
		||||
            inventoryChanges.MiscItems ??= [];
 | 
			
		||||
            inventoryChanges.MiscItems.push({ ItemType: item, ItemCount: inventory.MiscItems[index].ItemCount * -1 });
 | 
			
		||||
 | 
			
		||||
            inventory.MiscItems.splice(index, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({ InventoryChanges: inventoryChanges });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IForceRemoveItemRequest {
 | 
			
		||||
    items: string[];
 | 
			
		||||
}
 | 
			
		||||
@ -19,7 +19,7 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
 | 
			
		||||
            _id: new Types.ObjectId(),
 | 
			
		||||
            pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
 | 
			
		||||
            ppf: "",
 | 
			
		||||
            CompletionTime: new Date(Date.now()),
 | 
			
		||||
            CompletionTime: new Date(Date.now() - 1000),
 | 
			
		||||
            DecoCapacity: 600
 | 
			
		||||
        });
 | 
			
		||||
        await guild.save();
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { EPOCH, getSeasonChallengePools, getWorldState, pushWeeklyActs } from "../../services/worldStateService.ts";
 | 
			
		||||
import { unixTimesInMs } from "../../constants/timeConstants.ts";
 | 
			
		||||
import { getSeasonChallengePools, getWorldState, pushWeeklyActs } from "../../services/worldStateService.ts";
 | 
			
		||||
import { EPOCH, unixTimesInMs } from "../../constants/timeConstants.ts";
 | 
			
		||||
import type { ISeasonChallenge } from "../../types/worldStateTypes.ts";
 | 
			
		||||
import { ExportChallenges } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,4 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import allShipFeatures from "../../../static/fixed_responses/allShipFeatures.json" with { type: "json" };
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { createGarden, getPersonalRooms } from "../../services/personalRoomsService.ts";
 | 
			
		||||
import type { IGetShipResponse, IPersonalRoomsClient } from "../../types/personalRoomsTypes.ts";
 | 
			
		||||
@ -31,9 +29,5 @@ export const getShipController: RequestHandler = async (req, res) => {
 | 
			
		||||
        TailorShop: personalRooms.TailorShop
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllShipFeatures) {
 | 
			
		||||
        getShipResponse.Ship.Features = allShipFeatures;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.json(getShipResponse);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,11 @@ export const getVoidProjectionRewardsController: RequestHandler = async (req, re
 | 
			
		||||
 | 
			
		||||
    if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
 | 
			
		||||
        const inventory = await getInventory(accountId);
 | 
			
		||||
        await crackRelic(inventory, data.ParticipantInfo);
 | 
			
		||||
        const reward = await crackRelic(inventory, data.ParticipantInfo);
 | 
			
		||||
        if (!inventory.MissionRelicRewards || inventory.MissionRelicRewards.length >= data.CurrentWave) {
 | 
			
		||||
            inventory.MissionRelicRewards = [];
 | 
			
		||||
        }
 | 
			
		||||
        inventory.MissionRelicRewards.push({ ItemType: reward.type, ItemCount: reward.itemCount });
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { addMiscItems, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
@ -75,5 +75,5 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
 | 
			
		||||
        InventoryChanges: inventoryChanges,
 | 
			
		||||
        AffiliationMods: affiliationMods
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,17 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { parseString } from "../../helpers/general.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { giveKeyChainItem } from "../../services/questService.ts";
 | 
			
		||||
import type { IKeyChainRequest } from "../../types/requestTypes.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
 | 
			
		||||
export const giveKeyChainTriggeredItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo);
 | 
			
		||||
    const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain)!;
 | 
			
		||||
    const inventoryChanges = await giveKeyChainItem(inventory, keyChainInfo, questKey);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    res.send(inventoryChanges);
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,9 @@ export const giveKeyChainTriggeredMessageController: RequestHandler = async (req
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const keyChainInfo = JSON.parse((req.body as Buffer).toString()) as IKeyChainRequest;
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId, "QuestKeys");
 | 
			
		||||
    await giveKeyChainMessage(inventory, accountId, keyChainInfo);
 | 
			
		||||
    const inventory = await getInventory(accountId, "QuestKeys accountOwnerId");
 | 
			
		||||
    const questKey = inventory.QuestKeys.find(qk => qk.ItemType === keyChainInfo.KeyChain)!;
 | 
			
		||||
    await giveKeyChainMessage(inventory, keyChainInfo, questKey);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    res.send(1);
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,6 @@ import {
 | 
			
		||||
import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import type { ITechProjectClient } from "../../types/guildTypes.ts";
 | 
			
		||||
import { GuildPermission } from "../../types/guildTypes.ts";
 | 
			
		||||
import { GuildMember } from "../../models/guildModel.ts";
 | 
			
		||||
@ -83,16 +82,16 @@ export const guildTechController: RequestHandler = async (req, res) => {
 | 
			
		||||
                    guild.TechProjects[
 | 
			
		||||
                        guild.TechProjects.push({
 | 
			
		||||
                            ItemType: data.RecipeType,
 | 
			
		||||
                            ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
			
		||||
                            ReqCredits: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
			
		||||
                            ReqItems: recipe.ingredients.map(x => ({
 | 
			
		||||
                                ItemType: x.ItemType,
 | 
			
		||||
                                ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
			
		||||
                                ItemCount: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
			
		||||
                            })),
 | 
			
		||||
                            State: 0
 | 
			
		||||
                        }) - 1
 | 
			
		||||
                    ];
 | 
			
		||||
                setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
			
		||||
                if (config.noDojoResearchCosts) {
 | 
			
		||||
                if (guild.noDojoResearchCosts) {
 | 
			
		||||
                    processFundedGuildTechProject(guild, techProject, recipe);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,8 @@ const hostSessionController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const session = createNewSession(hostSessionRequest, account._id);
 | 
			
		||||
    logger.debug(`New Session Created`, { session });
 | 
			
		||||
 | 
			
		||||
    if (account.BuildLabel && version_compare(account.BuildLabel, "2015.03.21.08.17") < 0) {
 | 
			
		||||
        // U15 or below
 | 
			
		||||
    if (account.BuildLabel && version_compare(account.BuildLabel, "2016.07.08.16.56") < 0) {
 | 
			
		||||
        // Pre-Specters of the Rail
 | 
			
		||||
        res.send(session.sessionId.toString());
 | 
			
		||||
    } else {
 | 
			
		||||
        res.json({ sessionId: toOid2(session.sessionId, account.BuildLabel), rewardSeed: 99999999 });
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { config, getReflexiveAddress } from "../../services/configService.ts";
 | 
			
		||||
 | 
			
		||||
const hubController: RequestHandler = (_req, res) => {
 | 
			
		||||
    res.json("hub 127.0.0.1:6952");
 | 
			
		||||
export const hubController: RequestHandler = (req, res) => {
 | 
			
		||||
    res.json(`hub ${config.hubAddress ?? getReflexiveAddress(req).myAddress}:6952`);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { hubController };
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,13 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import type { Request, RequestHandler } from "express";
 | 
			
		||||
import { Inbox } from "../../models/inboxModel.ts";
 | 
			
		||||
import {
 | 
			
		||||
    createMessage,
 | 
			
		||||
    createNewEventMessages,
 | 
			
		||||
    deleteAllMessagesRead,
 | 
			
		||||
    deleteAllMessagesReadNonCin,
 | 
			
		||||
    deleteMessageRead,
 | 
			
		||||
    getAllMessagesSorted,
 | 
			
		||||
    getMessage
 | 
			
		||||
    getMessage,
 | 
			
		||||
    type IMessageCreationTemplate
 | 
			
		||||
} from "../../services/inboxService.ts";
 | 
			
		||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "../../services/loginService.ts";
 | 
			
		||||
import {
 | 
			
		||||
@ -21,6 +22,9 @@ import { ExportFlavour } from "warframe-public-export-plus";
 | 
			
		||||
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
 | 
			
		||||
import { fromStoreItem, isStoreItem } from "../../services/itemDataService.ts";
 | 
			
		||||
import type { IOid } from "../../types/commonTypes.ts";
 | 
			
		||||
import { unixTimesInMs } from "../../constants/timeConstants.ts";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
 | 
			
		||||
@ -31,11 +35,11 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
    if (deleteId) {
 | 
			
		||||
        if (deleteId === "DeleteAllRead") {
 | 
			
		||||
            await deleteAllMessagesRead(accountId);
 | 
			
		||||
            res.status(200).end();
 | 
			
		||||
            return;
 | 
			
		||||
        } else if (deleteId === "DeleteAllReadNonCin") {
 | 
			
		||||
            await deleteAllMessagesReadNonCin(accountId);
 | 
			
		||||
        } else {
 | 
			
		||||
            await deleteMessageRead(parseOid(deleteId as string));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await deleteMessageRead(parseOid(deleteId as string));
 | 
			
		||||
        res.status(200).end();
 | 
			
		||||
    } else if (messageId) {
 | 
			
		||||
        const message = await getMessage(parseOid(messageId as string));
 | 
			
		||||
@ -134,6 +138,119 @@ export const inboxController: RequestHandler = async (req, res) => {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createNewEventMessages = async (req: Request): Promise<void> => {
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
    const newEventMessages: IMessageCreationTemplate[] = [];
 | 
			
		||||
 | 
			
		||||
    // Baro
 | 
			
		||||
    const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
 | 
			
		||||
    const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
 | 
			
		||||
    const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
 | 
			
		||||
    if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
 | 
			
		||||
        newEventMessages.push({
 | 
			
		||||
            sndr: "/Lotus/Language/G1Quests/VoidTraderName",
 | 
			
		||||
            sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
 | 
			
		||||
            msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
 | 
			
		||||
            icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
 | 
			
		||||
            startDate: new Date(baroActualStart),
 | 
			
		||||
            endDate: new Date(baroStart + unixTimesInMs.day * 14),
 | 
			
		||||
            CrossPlatform: true,
 | 
			
		||||
            arg: [
 | 
			
		||||
                {
 | 
			
		||||
                    Key: "NODE_NAME",
 | 
			
		||||
                    Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            date: new Date(baroActualStart)
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
 | 
			
		||||
    const promises = [];
 | 
			
		||||
    if (config.worldState?.creditBoost) {
 | 
			
		||||
        promises.push(
 | 
			
		||||
            (async (): Promise<void> => {
 | 
			
		||||
                if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
 | 
			
		||||
                    newEventMessages.push({
 | 
			
		||||
                        globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
 | 
			
		||||
                        sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
 | 
			
		||||
                        sub: "/Lotus/Language/Items/EventDoubleCreditsName",
 | 
			
		||||
                        msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
 | 
			
		||||
                        icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
 | 
			
		||||
                        startDate: new Date(),
 | 
			
		||||
                        CrossPlatform: true
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            })()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if (config.worldState?.affinityBoost) {
 | 
			
		||||
        promises.push(
 | 
			
		||||
            (async (): Promise<void> => {
 | 
			
		||||
                if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
 | 
			
		||||
                    newEventMessages.push({
 | 
			
		||||
                        globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
 | 
			
		||||
                        sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
 | 
			
		||||
                        sub: "/Lotus/Language/Items/EventDoubleAffinityName",
 | 
			
		||||
                        msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
 | 
			
		||||
                        icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
 | 
			
		||||
                        startDate: new Date(),
 | 
			
		||||
                        CrossPlatform: true
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            })()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if (config.worldState?.resourceBoost) {
 | 
			
		||||
        promises.push(
 | 
			
		||||
            (async (): Promise<void> => {
 | 
			
		||||
                if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
 | 
			
		||||
                    newEventMessages.push({
 | 
			
		||||
                        globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
 | 
			
		||||
                        sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
 | 
			
		||||
                        sub: "/Lotus/Language/Items/EventDoubleResourceName",
 | 
			
		||||
                        msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
 | 
			
		||||
                        icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
 | 
			
		||||
                        startDate: new Date(),
 | 
			
		||||
                        CrossPlatform: true
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            })()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if (config.worldState?.galleonOfGhouls) {
 | 
			
		||||
        promises.push(
 | 
			
		||||
            (async (): Promise<void> => {
 | 
			
		||||
                if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
 | 
			
		||||
                    newEventMessages.push({
 | 
			
		||||
                        sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
 | 
			
		||||
                        sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
 | 
			
		||||
                        msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
 | 
			
		||||
                        icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
 | 
			
		||||
                        transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
 | 
			
		||||
                        att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
 | 
			
		||||
                        startDate: new Date(),
 | 
			
		||||
                        goalTag: "GalleonRobbery"
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            })()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
    if (newEventMessages.length === 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await createMessage(account._id, newEventMessages);
 | 
			
		||||
 | 
			
		||||
    const latestEventMessage = newEventMessages.reduce((prev, current) =>
 | 
			
		||||
        prev.startDate! > current.startDate! ? prev : current
 | 
			
		||||
    );
 | 
			
		||||
    account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
 | 
			
		||||
    await account.save();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
 | 
			
		||||
const parseOid = (oid: string): string => {
 | 
			
		||||
    if (oid[0] == "{") {
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import type {
 | 
			
		||||
    IMiscItem
 | 
			
		||||
} from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { ExportMisc } from "warframe-public-export-plus";
 | 
			
		||||
import { ExportResources } from "warframe-public-export-plus";
 | 
			
		||||
import { getRecipe } from "../../services/itemDataService.ts";
 | 
			
		||||
import { toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
@ -20,6 +20,7 @@ import {
 | 
			
		||||
    applyCheatsToInfestedFoundry,
 | 
			
		||||
    handleSubsumeCompletion
 | 
			
		||||
} from "../../services/infestedFoundryService.ts";
 | 
			
		||||
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
@ -145,7 +146,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
			
		||||
            const currentUnixSeconds = Math.trunc(Date.now() / 1000);
 | 
			
		||||
 | 
			
		||||
            for (const contribution of request.ResourceContributions) {
 | 
			
		||||
                const snack = ExportMisc.helminthSnacks[contribution.ItemType];
 | 
			
		||||
                const snack = ExportResources[contribution.ItemType].helminthSnack!;
 | 
			
		||||
 | 
			
		||||
                // tally items for removal
 | 
			
		||||
                const change = miscItemChanges.find(x => x.ItemType == contribution.ItemType);
 | 
			
		||||
@ -363,6 +364,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
 | 
			
		||||
                );
 | 
			
		||||
                addRecipes(inventory, recipeChanges);
 | 
			
		||||
                await inventory.save();
 | 
			
		||||
                sendWsBroadcastToGame(account._id.toString(), { sync_inventory: true });
 | 
			
		||||
            }
 | 
			
		||||
            res.end();
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { IPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
			
		||||
import { ArtifactPolarity } from "../../types/inventoryTypes/commonInventoryTypes.ts";
 | 
			
		||||
import type { ICountedItem } from "warframe-public-export-plus";
 | 
			
		||||
import { eFaction, ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
 | 
			
		||||
import { ExportCustoms } from "warframe-public-export-plus";
 | 
			
		||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "../../services/infestedFoundryService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    addEmailItem,
 | 
			
		||||
@ -177,7 +177,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cleanupInventory(inventory);
 | 
			
		||||
        await cleanupInventory(inventory);
 | 
			
		||||
 | 
			
		||||
        inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
 | 
			
		||||
        //await inventory.save();
 | 
			
		||||
@ -220,7 +220,10 @@ export const inventoryController: RequestHandler = async (request, response) =>
 | 
			
		||||
                    }
 | 
			
		||||
                    await createMessage(account._id, [
 | 
			
		||||
                        {
 | 
			
		||||
                            sndr: eFaction.find(x => x.tag == factionSidedWith)?.name ?? factionSidedWith, // TOVERIFY
 | 
			
		||||
                            sndr:
 | 
			
		||||
                                factionSidedWith == "FC_GRINEER"
 | 
			
		||||
                                    ? "/Lotus/Language/Menu/GrineerInvasionLeader"
 | 
			
		||||
                                    : "/Lotus/Language/Menu/CorpusInvasionLeader",
 | 
			
		||||
                            msg: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageBody`,
 | 
			
		||||
                            sub: `/Lotus/Language/G1Quests/${factionSidedWith}_InvasionThankyouMessageSubject`,
 | 
			
		||||
                            countedAtt: battlePay,
 | 
			
		||||
@ -307,32 +310,19 @@ export const getInventoryResponse = async (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (inventory.skipAllDialogue) {
 | 
			
		||||
        inventoryResponse.TauntHistory = [
 | 
			
		||||
            {
 | 
			
		||||
        inventoryResponse.TauntHistory ??= [];
 | 
			
		||||
        if (!inventoryResponse.TauntHistory.find(x => x.node == "TreasureTutorial")) {
 | 
			
		||||
            inventoryResponse.TauntHistory.push({
 | 
			
		||||
                node: "TreasureTutorial",
 | 
			
		||||
                state: "TS_COMPLETED"
 | 
			
		||||
            }
 | 
			
		||||
        ];
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        for (const str of allDialogue) {
 | 
			
		||||
            addString(inventoryResponse.NodeIntrosCompleted, str);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllShipDecorations) {
 | 
			
		||||
        inventoryResponse.ShipDecorations = [];
 | 
			
		||||
        for (const [uniqueName, item] of Object.entries(ExportResources)) {
 | 
			
		||||
            if (item.productCategory == "ShipDecorations") {
 | 
			
		||||
                inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllFlavourItems) {
 | 
			
		||||
        inventoryResponse.FlavourItems = [];
 | 
			
		||||
        for (const uniqueName in ExportFlavour) {
 | 
			
		||||
            inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
 | 
			
		||||
        }
 | 
			
		||||
    } else if (config.worldState?.baroTennoConRelay) {
 | 
			
		||||
    if (config.worldState?.baroTennoConRelay) {
 | 
			
		||||
        [
 | 
			
		||||
            "/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
 | 
			
		||||
            "/Lotus/Types/Items/Events/TennoConRelay2023EarlyAccess",
 | 
			
		||||
@ -346,39 +336,29 @@ export const getInventoryResponse = async (
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllSkins) {
 | 
			
		||||
        const missingWeaponSkins = new Set(Object.keys(ExportCustoms));
 | 
			
		||||
        inventoryResponse.WeaponSkins.forEach(x => missingWeaponSkins.delete(x.ItemType));
 | 
			
		||||
        for (const uniqueName of missingWeaponSkins) {
 | 
			
		||||
            inventoryResponse.WeaponSkins.push({
 | 
			
		||||
                ItemId: {
 | 
			
		||||
                    $oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
 | 
			
		||||
                },
 | 
			
		||||
                ItemType: uniqueName
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllCapturaScenes) {
 | 
			
		||||
        for (const uniqueName of Object.keys(ExportResources)) {
 | 
			
		||||
            if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
 | 
			
		||||
                inventoryResponse.MiscItems.push({
 | 
			
		||||
                    ItemType: uniqueName,
 | 
			
		||||
                    ItemCount: 1
 | 
			
		||||
        const ownedWeaponSkins = new Set<string>(inventoryResponse.WeaponSkins.map(x => x.ItemType));
 | 
			
		||||
        for (const [uniqueName, meta] of Object.entries(ExportCustoms)) {
 | 
			
		||||
            if (!meta.alwaysAvailable && !ownedWeaponSkins.has(uniqueName)) {
 | 
			
		||||
                inventoryResponse.WeaponSkins.push({
 | 
			
		||||
                    ItemId: {
 | 
			
		||||
                        $oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
 | 
			
		||||
                    },
 | 
			
		||||
                    ItemType: uniqueName
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) {
 | 
			
		||||
        inventoryResponse.PlayerLevel = config.spoofMasteryRank;
 | 
			
		||||
    if (inventory.spoofMasteryRank && inventory.spoofMasteryRank >= 0) {
 | 
			
		||||
        inventoryResponse.PlayerLevel = inventory.spoofMasteryRank;
 | 
			
		||||
        if (!xpBasedLevelCapDisabled) {
 | 
			
		||||
            // This client has not been patched to accept any mastery rank, need to fake the XP.
 | 
			
		||||
            inventoryResponse.XPInfo = [];
 | 
			
		||||
            let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000;
 | 
			
		||||
            let numFrames = getExpRequiredForMr(Math.min(inventory.spoofMasteryRank, 5030)) / (30 * 200);
 | 
			
		||||
            while (numFrames-- > 0) {
 | 
			
		||||
                inventoryResponse.XPInfo.push({
 | 
			
		||||
                    ItemType: "/Lotus/Powersuits/Mag/Mag",
 | 
			
		||||
                    XP: 1_600_000
 | 
			
		||||
                    XP: 900_000 // Enough for rank 30 as per https://wiki.warframe.com/w/Affinity
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -482,67 +462,19 @@ export const getInventoryResponse = async (
 | 
			
		||||
                        toLegacyOid(id);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (inventoryResponse.GuildId) {
 | 
			
		||||
                    toLegacyOid(inventoryResponse.GuildId);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllProfitTakerStages) {
 | 
			
		||||
        inventoryResponse.CompletedJobChains ??= [];
 | 
			
		||||
        const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
 | 
			
		||||
        if (EudicoHeists) {
 | 
			
		||||
            EudicoHeists.Jobs = allEudicoHeistJobs;
 | 
			
		||||
        } else {
 | 
			
		||||
            inventoryResponse.CompletedJobChains.push({
 | 
			
		||||
                LocationTag: "EudicoHeists",
 | 
			
		||||
                Jobs: allEudicoHeistJobs
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.unlockAllSimarisResearchEntries) {
 | 
			
		||||
        inventoryResponse.LibraryPersonalTarget = undefined;
 | 
			
		||||
        inventoryResponse.LibraryPersonalProgress = [
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research1Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research2Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research3Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research4Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research5Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research6Target",
 | 
			
		||||
            "/Lotus/Types/Game/Library/Targets/Research7Target"
 | 
			
		||||
        ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return inventoryResponse;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const allEudicoHeistJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const getExpRequiredForMr = (rank: number): number => {
 | 
			
		||||
    if (rank <= 30) {
 | 
			
		||||
        return 2500 * rank * rank;
 | 
			
		||||
    }
 | 
			
		||||
    return 2_250_000 + 147_500 * (rank - 30);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
 | 
			
		||||
    let parentName = resourceGetParent(resourceName);
 | 
			
		||||
    for (; parentName != undefined; parentName = resourceGetParent(parentName)) {
 | 
			
		||||
        if (parentName == targetName) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resourceGetParent = (resourceName: string): string | undefined => {
 | 
			
		||||
    if (resourceName in ExportResources) {
 | 
			
		||||
        return ExportResources[resourceName].parentName;
 | 
			
		||||
    }
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    return ExportVirtuals[resourceName]?.parentName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import { config, getReflexiveAddress } from "../../services/configService.ts";
 | 
			
		||||
import { buildConfig } from "../../services/buildConfigService.ts";
 | 
			
		||||
 | 
			
		||||
import { Account } from "../../models/loginModel.ts";
 | 
			
		||||
@ -8,7 +8,10 @@ import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } f
 | 
			
		||||
import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../types/loginTypes.ts";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { handleNonceInvalidation } from "../../services/wsService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { createMessage } from "../../services/inboxService.ts";
 | 
			
		||||
import { fromStoreItem } from "../../services/itemDataService.ts";
 | 
			
		||||
 | 
			
		||||
export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
    const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
 | 
			
		||||
@ -20,21 +23,7 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
            ? request.query.buildLabel.split(" ").join("+")
 | 
			
		||||
            : buildConfig.buildLabel;
 | 
			
		||||
 | 
			
		||||
    let myAddress: string;
 | 
			
		||||
    let myUrlBase: string = request.protocol + "://";
 | 
			
		||||
    if (request.host.indexOf("warframe.com") == -1) {
 | 
			
		||||
        // Client request was redirected cleanly, so we know it can reach us how it's reaching us now.
 | 
			
		||||
        myAddress = request.hostname;
 | 
			
		||||
        myUrlBase += request.host;
 | 
			
		||||
    } else {
 | 
			
		||||
        // Don't know how the client reached us, hoping the config does.
 | 
			
		||||
        myAddress = config.myAddress;
 | 
			
		||||
        myUrlBase += myAddress;
 | 
			
		||||
        const port: number = request.protocol == "http" ? config.httpPort || 80 : config.httpsPort || 443;
 | 
			
		||||
        if (port != (request.protocol == "http" ? 80 : 443)) {
 | 
			
		||||
            myUrlBase += ":" + port;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const { myAddress, myUrlBase } = getReflexiveAddress(request);
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
        !account &&
 | 
			
		||||
@ -88,7 +77,25 @@ export const loginController: RequestHandler = async (request, response) => {
 | 
			
		||||
    account.LastLogin = new Date();
 | 
			
		||||
    await account.save();
 | 
			
		||||
 | 
			
		||||
    sendWsBroadcastTo(account._id.toString(), { nonce_updated: true });
 | 
			
		||||
    handleNonceInvalidation(account._id.toString());
 | 
			
		||||
 | 
			
		||||
    // If the client crashed during an endless fissure mission, discharge rewards to an inbox message. (https://www.reddit.com/r/Warframe/comments/5uwwjm/til_if_you_crash_during_a_fissure_you_keep_any/)
 | 
			
		||||
    const inventory = await getInventory(account._id.toString(), "MissionRelicRewards");
 | 
			
		||||
    if (inventory.MissionRelicRewards) {
 | 
			
		||||
        await createMessage(account._id, [
 | 
			
		||||
            {
 | 
			
		||||
                sndr: "/Lotus/Language/Bosses/Ordis",
 | 
			
		||||
                msg: "/Lotus/Language/Menu/VoidProjectionItemsMessage",
 | 
			
		||||
                sub: "/Lotus/Language/Menu/VoidProjectionItemsSubject",
 | 
			
		||||
                icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
 | 
			
		||||
                countedAtt: inventory.MissionRelicRewards.map(x => ({ ...x, ItemType: fromStoreItem(x.ItemType) })),
 | 
			
		||||
                attVisualOnly: true,
 | 
			
		||||
                highPriority: true // TOVERIFY
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
        inventory.MissionRelicRewards = undefined;
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
 | 
			
		||||
};
 | 
			
		||||
@ -102,18 +109,25 @@ const createLoginResponse = (
 | 
			
		||||
    const resp: ILoginResponse = {
 | 
			
		||||
        id: account.id,
 | 
			
		||||
        DisplayName: account.DisplayName,
 | 
			
		||||
        CountryCode: account.CountryCode,
 | 
			
		||||
        AmazonAuthToken: account.AmazonAuthToken,
 | 
			
		||||
        AmazonRefreshToken: account.AmazonRefreshToken,
 | 
			
		||||
        Nonce: account.Nonce,
 | 
			
		||||
        BuildLabel: buildLabel
 | 
			
		||||
    };
 | 
			
		||||
    if (version_compare(buildLabel, "2014.10.24.08.24") >= 0) {
 | 
			
		||||
        // U15 and up
 | 
			
		||||
        resp.CountryCode = account.CountryCode;
 | 
			
		||||
    } else {
 | 
			
		||||
        // U8
 | 
			
		||||
        resp.NatHash = "0";
 | 
			
		||||
        resp.SteamId = "0";
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
 | 
			
		||||
        resp.NRS = config.NRS;
 | 
			
		||||
        resp.NRS = [config.nrsAddress ?? myAddress];
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
 | 
			
		||||
        // U17 and up
 | 
			
		||||
        resp.IRC = config.myIrcAddresses ?? [myAddress];
 | 
			
		||||
        resp.IRC = [config.ircAddress ?? myAddress];
 | 
			
		||||
    }
 | 
			
		||||
    if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
 | 
			
		||||
        // U24 and up
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { Account } from "../../models/loginModel.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { handleNonceInvalidation } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const logoutController: RequestHandler = async (req, res) => {
 | 
			
		||||
    if (!req.query.accountId) {
 | 
			
		||||
@ -21,7 +21,7 @@ export const logoutController: RequestHandler = async (req, res) => {
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
    if (stat.modifiedCount) {
 | 
			
		||||
        sendWsBroadcastTo(req.query.accountId as string, { nonce_updated: true });
 | 
			
		||||
        handleNonceInvalidation(req.query.accountId as string);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.writeHead(200, {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const maturePetController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -19,6 +20,7 @@ export const maturePetController: RequestHandler = async (req, res) => {
 | 
			
		||||
            : [details.DominantTraits.FurPattern, details.DominantTraits.FurPattern, details.DominantTraits.FurPattern],
 | 
			
		||||
        unmature: data.revert
 | 
			
		||||
    });
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IMaturePetRequest {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,11 @@ import type { RequestHandler } from "express";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { IMissionInventoryUpdateRequest } from "../../types/requestTypes.ts";
 | 
			
		||||
import { addMissionInventoryUpdates, addMissionRewards } from "../../services/missionInventoryUpdateService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    addMissionInventoryUpdates,
 | 
			
		||||
    addMissionRewards,
 | 
			
		||||
    handleConservation
 | 
			
		||||
} from "../../services/missionInventoryUpdateService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getInventoryResponse } from "./inventoryController.ts";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
@ -94,6 +98,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
			
		||||
        SyndicateXPItemReward,
 | 
			
		||||
        ConquestCompletedMissionsCount
 | 
			
		||||
    } = await addMissionRewards(account, inventory, missionReport, firstCompletion);
 | 
			
		||||
    handleConservation(inventory, missionReport, AffiliationMods); // Conservation reports have GS_SUCCESS
 | 
			
		||||
 | 
			
		||||
    if (missionReport.EndOfMatchUpload) {
 | 
			
		||||
        inventory.RewardSeed = generateRewardSeed();
 | 
			
		||||
@ -111,19 +116,35 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
 | 
			
		||||
        AffiliationMods,
 | 
			
		||||
        ConquestCompletedMissionsCount
 | 
			
		||||
    };
 | 
			
		||||
    if (missionReport.RJ) {
 | 
			
		||||
        logger.debug(`railjack interstitial request, sending only deltas`, deltas);
 | 
			
		||||
    if (
 | 
			
		||||
        missionReport.BMI ||
 | 
			
		||||
        missionReport.TNT ||
 | 
			
		||||
        missionReport.SSC ||
 | 
			
		||||
        missionReport.RJ ||
 | 
			
		||||
        missionReport.SS ||
 | 
			
		||||
        missionReport.CMI ||
 | 
			
		||||
        missionReport.EJC
 | 
			
		||||
    ) {
 | 
			
		||||
        logger.debug(`interstitial request, sending only deltas`, deltas);
 | 
			
		||||
        res.json(deltas);
 | 
			
		||||
    } else if (missionReport.RewardInfo) {
 | 
			
		||||
        logger.debug(`classic mission completion, sending everything`);
 | 
			
		||||
        const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
 | 
			
		||||
        const inventoryResponse = await getInventoryResponse(
 | 
			
		||||
            inventory,
 | 
			
		||||
            "xpBasedLevelCapDisabled" in req.query,
 | 
			
		||||
            account.BuildLabel
 | 
			
		||||
        );
 | 
			
		||||
        res.json({
 | 
			
		||||
            InventoryJson: JSON.stringify(inventoryResponse),
 | 
			
		||||
            ...deltas
 | 
			
		||||
        } satisfies IMissionInventoryUpdateResponse);
 | 
			
		||||
    } else {
 | 
			
		||||
        logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`);
 | 
			
		||||
        const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
 | 
			
		||||
        const inventoryResponse = await getInventoryResponse(
 | 
			
		||||
            inventory,
 | 
			
		||||
            "xpBasedLevelCapDisabled" in req.query,
 | 
			
		||||
            account.BuildLabel
 | 
			
		||||
        );
 | 
			
		||||
        res.json({
 | 
			
		||||
            InventoryJson: JSON.stringify(inventoryResponse)
 | 
			
		||||
        } satisfies IMissionInventoryUpdateResponseBackToDryDock);
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import {
 | 
			
		||||
    getInventory,
 | 
			
		||||
@ -197,5 +197,5 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
 | 
			
		||||
            MiscItems: miscItemChanges
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
interface INameWeaponRequest {
 | 
			
		||||
    ItemName: string;
 | 
			
		||||
@ -28,5 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
 | 
			
		||||
    res.json({
 | 
			
		||||
        InventoryChanges: currencyChanges
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { fromDbOid, version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import { fromDbOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts";
 | 
			
		||||
import {
 | 
			
		||||
    antivirusMods,
 | 
			
		||||
@ -149,7 +149,10 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                inventory.Nemesis!.HenchmenKilled += antivirusGain;
 | 
			
		||||
                const antivirusGainMultiplier = (
 | 
			
		||||
                    await getInventory(account._id.toString(), "nemesisAntivirusGainMultiplier")
 | 
			
		||||
                ).nemesisAntivirusGainMultiplier;
 | 
			
		||||
                inventory.Nemesis!.HenchmenKilled += antivirusGain * (antivirusGainMultiplier ?? 1);
 | 
			
		||||
                if (inventory.Nemesis!.HenchmenKilled >= 100) {
 | 
			
		||||
                    inventory.Nemesis!.HenchmenKilled = 100;
 | 
			
		||||
 | 
			
		||||
@ -307,6 +310,26 @@ export const nemesisController: RequestHandler = async (req, res) => {
 | 
			
		||||
        res.json({
 | 
			
		||||
            target: inventory.toJSON().Nemesis
 | 
			
		||||
        });
 | 
			
		||||
    } else if ((req.query.mode as string) == "t") {
 | 
			
		||||
        const inventory = await getInventory(account._id.toString(), "LastNemesisAllySpawnTime");
 | 
			
		||||
        //const body = getJSONfromString<IUpdateAllySpawnTimeRequest>(String(req.body));
 | 
			
		||||
        const now = new Date(Math.trunc(Date.now() / 1000) * 1000);
 | 
			
		||||
        inventory.LastNemesisAllySpawnTime = now;
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        res.json({
 | 
			
		||||
            NewTime: toMongoDate(now)
 | 
			
		||||
        } satisfies IUpdateAllySpawnTimeResponse);
 | 
			
		||||
    } else if ((req.query.mode as string) == "d") {
 | 
			
		||||
        const inventory = await getInventory(account._id.toString(), "NemesisHistory");
 | 
			
		||||
        const body = getJSONfromString<IRelinquishAdversariesRequest>(String(req.body));
 | 
			
		||||
        for (const fp of body.nemesisFingerprints) {
 | 
			
		||||
            const index = inventory.NemesisHistory!.findIndex(x => x.fp == fp);
 | 
			
		||||
            if (index != -1) {
 | 
			
		||||
                inventory.NemesisHistory!.splice(index, 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        res.json(body);
 | 
			
		||||
    } else if ((req.query.mode as string) == "w") {
 | 
			
		||||
        const inventory = await getInventory(account._id.toString(), "Nemesis");
 | 
			
		||||
        //const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
 | 
			
		||||
@ -444,3 +467,15 @@ const consumeModCharge = (
 | 
			
		||||
        response.UpgradeNew.push(true);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IRelinquishAdversariesRequest {
 | 
			
		||||
    nemesisFingerprints: (bigint | number)[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// interface IUpdateAllySpawnTimeRequest {
 | 
			
		||||
//     LastSpawnTime: IMongoDate;
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
interface IUpdateAllySpawnTimeResponse {
 | 
			
		||||
    NewTime: IMongoDate;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,6 @@ import { GuildPermission } from "../../types/guildTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
 | 
			
		||||
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -74,7 +73,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
 | 
			
		||||
            if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDojoDecoBuildStage) {
 | 
			
		||||
            if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || guild.noDojoDecoBuildStage) {
 | 
			
		||||
                deco.CompletionTime = new Date();
 | 
			
		||||
                if (meta) {
 | 
			
		||||
                    processDojoBuildMaterialsGathered(guild, meta);
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import {
 | 
			
		||||
    getDojoClient,
 | 
			
		||||
    getGuildForRequestEx,
 | 
			
		||||
@ -21,7 +20,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re
 | 
			
		||||
    const componentId = req.query.componentId as string;
 | 
			
		||||
 | 
			
		||||
    guild.DojoComponents.id(componentId)!.DestructionTime = new Date(
 | 
			
		||||
        (Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
 | 
			
		||||
        (Math.trunc(Date.now() / 1000) + (guild.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    await guild.save();
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const releasePetController: RequestHandler = async (req, res) => {
 | 
			
		||||
@ -20,7 +20,7 @@ export const releasePetController: RequestHandler = async (req, res) => {
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
 | 
			
		||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IReleasePetRequest {
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import {
 | 
			
		||||
import { createMessage } from "../../services/inboxService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { GuildPermission } from "../../types/guildTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
@ -85,6 +86,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
 | 
			
		||||
        ItemToRemove: "/Lotus/Types/Keys/DojoKey",
 | 
			
		||||
        RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint"
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastTo(payload.userId, { update_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IRemoveFromGuildRequest {
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => {
 | 
			
		||||
        ...data,
 | 
			
		||||
        inventoryChanges: inventoryChanges
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IRenamePetRequest {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										117
									
								
								src/controllers/api/researchMushroomController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/controllers/api/researchMushroomController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
import type { IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { IAffiliationMods } from "../../types/purchaseTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const researchMushroomController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "MiscItems NokkoColony Affiliations");
 | 
			
		||||
    const payload = getJSONfromString<IResearchMushroom>(String(req.body));
 | 
			
		||||
    switch (payload.Mode) {
 | 
			
		||||
        case "r": {
 | 
			
		||||
            const InventoryChanges = {};
 | 
			
		||||
            const AffiliationMods: IAffiliationMods[] = [];
 | 
			
		||||
 | 
			
		||||
            addMiscItem(inventory, payload.MushroomItem, payload.Amount * -1, InventoryChanges);
 | 
			
		||||
            if (payload.Convert) {
 | 
			
		||||
                addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount, InventoryChanges);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            inventory.NokkoColony ??= {
 | 
			
		||||
                FeedLevel: 0,
 | 
			
		||||
                JournalEntries: []
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let journalEntry = inventory.NokkoColony.JournalEntries.find(x => x.EntryType == payload.MushroomItem);
 | 
			
		||||
            if (!journalEntry) {
 | 
			
		||||
                journalEntry = { EntryType: payload.MushroomItem, Progress: 0 };
 | 
			
		||||
                inventory.NokkoColony.JournalEntries.push(journalEntry);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let syndicate = inventory.Affiliations.find(x => x.Tag == "NightcapJournalSyndicate");
 | 
			
		||||
            if (!syndicate) {
 | 
			
		||||
                syndicate = { Tag: "NightcapJournalSyndicate", Title: 0, Standing: 0 };
 | 
			
		||||
                inventory.Affiliations.push(syndicate);
 | 
			
		||||
            }
 | 
			
		||||
            const completedBefore = inventory.NokkoColony.JournalEntries.filter(
 | 
			
		||||
                entry => getJournalRank(entry) === 3
 | 
			
		||||
            ).length;
 | 
			
		||||
            const PrevRank = syndicateTitleThresholds.reduce(
 | 
			
		||||
                (rank, threshold, i) => (completedBefore >= threshold ? i : rank),
 | 
			
		||||
                0
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (getJournalRank(journalEntry) < 3) journalEntry.Progress += payload.Amount;
 | 
			
		||||
 | 
			
		||||
            const completedAfter = inventory.NokkoColony.JournalEntries.filter(
 | 
			
		||||
                entry => getJournalRank(entry) === 3
 | 
			
		||||
            ).length;
 | 
			
		||||
            const NewRank = syndicateTitleThresholds.reduce(
 | 
			
		||||
                (rank, threshold, i) => (completedAfter >= threshold ? i : rank),
 | 
			
		||||
                0
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (NewRank > (syndicate.Title ?? 0)) {
 | 
			
		||||
                syndicate.Title = NewRank;
 | 
			
		||||
                AffiliationMods.push({ Tag: "NightcapJournalSyndicate", Title: NewRank });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
            res.json({
 | 
			
		||||
                PrevRank,
 | 
			
		||||
                NewRank,
 | 
			
		||||
                Progress: journalEntry.Progress,
 | 
			
		||||
                InventoryChanges,
 | 
			
		||||
                AffiliationMods
 | 
			
		||||
            });
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
 | 
			
		||||
            throw new Error(`unknown researchMushroom mode: ${payload.Mode}`);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IResearchMushroom {
 | 
			
		||||
    Mode: string; // r
 | 
			
		||||
    MushroomItem: string;
 | 
			
		||||
    Amount: number;
 | 
			
		||||
    Convert: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const journalEntriesRank: Record<string, number> = {
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/PlainMushroomJournalItem": 1,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/GasMushroomJournalItem": 4,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/ToxinMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/ViralMushroomJournalItem": 4,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/MagneticMushroomJournalItem": 4,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/ElectricMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/TauMushroomJournalItem": 5,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/SlashMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/BlastMushroomJournalItem": 4,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/ImpactMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/ColdMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/CorrosiveMushroomJournalItem": 4,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/PunctureMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/HeatMushroomJournalItem": 3,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/RadiationMushroomJournalItem": 4,
 | 
			
		||||
    "/Lotus/Types/Items/MushroomJournal/VoidMushroomJournalItem": 5
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const syndicateTitleThresholds = [0, 1, 2, 6, 12, 16];
 | 
			
		||||
 | 
			
		||||
const getJournalRank = (journalEntry: IJournalEntry): number => {
 | 
			
		||||
    const k = journalEntriesRank[journalEntry.EntryType];
 | 
			
		||||
    if (!k) return 0;
 | 
			
		||||
 | 
			
		||||
    const thresholds = [k * 1, k * 3, k * 6];
 | 
			
		||||
 | 
			
		||||
    if (journalEntry.Progress >= thresholds[2]) return 3;
 | 
			
		||||
    if (journalEntry.Progress >= thresholds[1]) return 2;
 | 
			
		||||
    if (journalEntry.Progress >= thresholds[0]) return 1;
 | 
			
		||||
    return 0;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										17
									
								
								src/controllers/api/reverseQuestProgressController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/controllers/api/reverseQuestProgressController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { resetQuestKeyToStage } from "../../services/questService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import type { IKeyChainRequest } from "../../types/requestTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const reverseQuestProgressController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const keyChainInfo = getJSONfromString<IKeyChainRequest>((req.body as string).toString());
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    resetQuestKeyToStage(inventory, keyChainInfo);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
@ -17,7 +17,7 @@ import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
 | 
			
		||||
import { sendWsBroadcastEx } from "../../services/wsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import { parseFusionTreasure } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
 | 
			
		||||
export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
@ -77,6 +77,9 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
            requiredFields.add("CrewShipSalvagedWeaponSkins");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.Items.WeaponSkins) {
 | 
			
		||||
        requiredFields.add("WeaponSkins");
 | 
			
		||||
    }
 | 
			
		||||
    const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
 | 
			
		||||
 | 
			
		||||
    // Give currency
 | 
			
		||||
@ -302,12 +305,17 @@ export const sellController: RequestHandler = async (req, res) => {
 | 
			
		||||
            addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.Items.WeaponSkins) {
 | 
			
		||||
        payload.Items.WeaponSkins.forEach(sellItem => {
 | 
			
		||||
            inventory.WeaponSkins.pull({ _id: sellItem.String });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
 | 
			
		||||
    });
 | 
			
		||||
    sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ISellRequest {
 | 
			
		||||
@ -335,6 +343,7 @@ interface ISellRequest {
 | 
			
		||||
        CrewShipWeapons?: ISellItem[];
 | 
			
		||||
        CrewShipWeaponSkins?: ISellItem[];
 | 
			
		||||
        FusionTreasures?: ISellItem[];
 | 
			
		||||
        WeaponSkins?: ISellItem[]; // SNS specific field
 | 
			
		||||
    };
 | 
			
		||||
    SellPrice: number;
 | 
			
		||||
    SellCurrency:
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,7 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
 | 
			
		||||
        await guild.save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.03.24.20.24") > 0) {
 | 
			
		||||
    if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.11.04.18.58") > 0) {
 | 
			
		||||
        res.json({ IsLongMOTD, MOTD });
 | 
			
		||||
    } else {
 | 
			
		||||
        res.send(MOTD).end();
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const setSupportedSyndicateController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -15,4 +16,5 @@ export const setSupportedSyndicateController: RequestHandler = async (req, res)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ export const startCollectibleEntryController: RequestHandler = async (req, res)
 | 
			
		||||
        IncentiveStates: request.other
 | 
			
		||||
    });
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.status(200).end();
 | 
			
		||||
    res.send(`target = ${request.target}key = 0key = 1{"Target":"${request.target}"}`);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IStartCollectibleEntryRequest {
 | 
			
		||||
 | 
			
		||||
@ -11,9 +11,9 @@ import {
 | 
			
		||||
} from "../../services/guildService.ts";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { fromOid } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
 | 
			
		||||
interface IStartDojoRecipeRequest {
 | 
			
		||||
    PlacedComponent: IDojoComponentClient;
 | 
			
		||||
@ -51,13 +51,13 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
                _id: componentId,
 | 
			
		||||
                pf: request.PlacedComponent.pf,
 | 
			
		||||
                ppf: request.PlacedComponent.ppf,
 | 
			
		||||
                pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
 | 
			
		||||
                pi: new Types.ObjectId(fromOid(request.PlacedComponent.pi!)),
 | 
			
		||||
                op: request.PlacedComponent.op,
 | 
			
		||||
                pp: request.PlacedComponent.pp,
 | 
			
		||||
                DecoCapacity: room?.decoCapacity
 | 
			
		||||
            }) - 1
 | 
			
		||||
        ];
 | 
			
		||||
    if (config.noDojoRoomBuildStage) {
 | 
			
		||||
    if (guild.noDojoRoomBuildStage) {
 | 
			
		||||
        component.CompletionTime = new Date(Date.now());
 | 
			
		||||
        if (room) {
 | 
			
		||||
            processDojoBuildMaterialsGathered(guild, room);
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,20 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { updateShipFeature } from "../../services/personalRoomsService.ts";
 | 
			
		||||
import type { IUnlockShipFeatureRequest } from "../../types/requestTypes.ts";
 | 
			
		||||
import { parseString } from "../../helpers/general.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { unlockShipFeature } from "../../services/personalRoomsService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockShipFeatureController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
    const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest;
 | 
			
		||||
    await updateShipFeature(accountId, shipFeatureRequest.Feature);
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "MiscItems accountOwnerId");
 | 
			
		||||
    const request = getJSONfromString<IUnlockShipFeatureRequest>(String(req.body));
 | 
			
		||||
 | 
			
		||||
    await unlockShipFeature(inventory, request.Feature);
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.send([]);
 | 
			
		||||
};
 | 
			
		||||
interface IUnlockShipFeatureRequest {
 | 
			
		||||
    Feature: string;
 | 
			
		||||
    KeyChain: string;
 | 
			
		||||
    ChainStage: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
 | 
			
		||||
 | 
			
		||||
    const inventory = await getInventory(
 | 
			
		||||
        account._id.toString(),
 | 
			
		||||
        "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress"
 | 
			
		||||
        "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress nightwaveStandingMultiplier"
 | 
			
		||||
    );
 | 
			
		||||
    let affiliationMods: IAffiliationMods[] = [];
 | 
			
		||||
    if (challenges.ChallengeProgress) {
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import type { IUpdateQuestRequest } from "../../services/questService.ts";
 | 
			
		||||
import { updateQuestKey } from "../../services/questService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const updateQuestController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = parseString(req.query.accountId);
 | 
			
		||||
@ -29,4 +30,5 @@ export const updateQuestController: RequestHandler = async (req, res) => {
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.send(updateQuestResponse);
 | 
			
		||||
    sendWsBroadcastTo(accountId, { update_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -96,10 +96,15 @@ export const upgradesController: RequestHandler = async (req, res) => {
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/WeaponPrimaryArcaneUnlocker":
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/WeaponSecondaryArcaneUnlocker":
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/WeaponMeleeArcaneUnlocker":
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/WeaponAmpArcaneUnlocker": {
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/WeaponAmpArcaneUnlocker":
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/WeaponArchGunArcaneUnlocker": {
 | 
			
		||||
                    const item = inventory[payload.ItemCategory].id(payload.ItemId.$oid)!;
 | 
			
		||||
                    item.Features ??= 0;
 | 
			
		||||
                    item.Features |= EquipmentFeatures.ARCANE_SLOT;
 | 
			
		||||
                    if (operation.OperationType == "UOT_ARCANE_UNLOCK_1") {
 | 
			
		||||
                        item.Features |= EquipmentFeatures.SECOND_ARCANE_SLOT;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        item.Features |= EquipmentFeatures.ARCANE_SLOT;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case "/Lotus/Types/Items/MiscItems/ValenceAdapter": {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
@ -19,6 +20,7 @@ export const abilityOverrideController: RequestHandler = async (req, res) => {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IAbilityOverrideRequest {
 | 
			
		||||
 | 
			
		||||
@ -1,21 +1,42 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { addFusionPoints, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
 | 
			
		||||
import { GuildPermission } from "../../types/guildTypes.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const addCurrencyController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const request = req.body as IAddCurrencyRequest;
 | 
			
		||||
    const inventory = await getInventory(accountId, request.currency);
 | 
			
		||||
    let projection = request.currency as string;
 | 
			
		||||
    if (request.currency.startsWith("Vault")) projection = "GuildId";
 | 
			
		||||
    const inventory = await getInventory(accountId, projection);
 | 
			
		||||
    if (request.currency == "FusionPoints") {
 | 
			
		||||
        addFusionPoints(inventory, request.delta);
 | 
			
		||||
    } else if (request.currency == "VaultRegularCredits" || request.currency == "VaultPremiumCredits") {
 | 
			
		||||
        const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
        if (await hasGuildPermission(guild, accountId, GuildPermission.Treasurer)) {
 | 
			
		||||
            guild[request.currency] ??= 0;
 | 
			
		||||
            guild[request.currency]! += request.delta;
 | 
			
		||||
            await guild.save();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        inventory[request.currency] += request.delta;
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    if (!request.currency.startsWith("Vault")) {
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        broadcastInventoryUpdate(req);
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IAddCurrencyRequest {
 | 
			
		||||
    currency: "RegularCredits" | "PremiumCredits" | "FusionPoints" | "PrimeTokens";
 | 
			
		||||
    currency:
 | 
			
		||||
        | "RegularCredits"
 | 
			
		||||
        | "PremiumCredits"
 | 
			
		||||
        | "FusionPoints"
 | 
			
		||||
        | "PrimeTokens"
 | 
			
		||||
        | "VaultRegularCredits"
 | 
			
		||||
        | "VaultPremiumCredits";
 | 
			
		||||
    delta: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory, addItem } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const addItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -11,6 +12,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IAddItemRequest {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory, addRecipes } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { ExportRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -21,4 +22,5 @@ export const addMissingHelminthBlueprintsController: RequestHandler = async (req
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -41,4 +42,5 @@ export const addMissingMaxRankModsController: RequestHandler = async (req, res)
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								src/controllers/custom/addVaultTypeCountController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/controllers/custom/addVaultTypeCountController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
 | 
			
		||||
import { GuildPermission } from "../../types/guildTypes.ts";
 | 
			
		||||
import type { ITypeCount } from "../../types/commonTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const addVaultTypeCountController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const { vaultType, items } = req.body as {
 | 
			
		||||
        vaultType: keyof typeof vaultConfig;
 | 
			
		||||
        items: ITypeCount[];
 | 
			
		||||
    };
 | 
			
		||||
    const inventory = await getInventory(accountId, "GuildId");
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    if (!(await hasGuildPermission(guild, accountId, vaultConfig[vaultType]))) {
 | 
			
		||||
        res.status(400).send("-1").end();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    guild[vaultType] ??= [];
 | 
			
		||||
    for (const item of items) {
 | 
			
		||||
        const index = guild[vaultType].findIndex(x => x.ItemType === item.ItemType);
 | 
			
		||||
        if (index === -1) {
 | 
			
		||||
            guild[vaultType].push({
 | 
			
		||||
                ItemType: item.ItemType,
 | 
			
		||||
                ItemCount: item.ItemCount
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            guild[vaultType][index].ItemCount += item.ItemCount;
 | 
			
		||||
            if (guild[vaultType][index].ItemCount < 1) {
 | 
			
		||||
                guild[vaultType].splice(index, 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
const vaultConfig = {
 | 
			
		||||
    VaultShipDecorations: GuildPermission.Treasurer,
 | 
			
		||||
    VaultMiscItems: GuildPermission.Treasurer,
 | 
			
		||||
    VaultDecoRecipes: GuildPermission.Architect
 | 
			
		||||
} as const;
 | 
			
		||||
@ -1,10 +1,11 @@
 | 
			
		||||
import { applyClientEquipmentUpdates, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getMaxLevelCap } from "../../services/itemDataService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import type { IOid } from "../../types/commonTypes.ts";
 | 
			
		||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
 | 
			
		||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { ExportMisc } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export const addXpController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -14,7 +15,7 @@ export const addXpController: RequestHandler = async (req, res) => {
 | 
			
		||||
        for (const clientItem of gear) {
 | 
			
		||||
            const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
 | 
			
		||||
            if (dbItem) {
 | 
			
		||||
                if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
 | 
			
		||||
                if (getMaxLevelCap(dbItem.ItemType) > 30) {
 | 
			
		||||
                    if ((dbItem.Polarized ?? 0) < 5) {
 | 
			
		||||
                        dbItem.Polarized = 5;
 | 
			
		||||
                    }
 | 
			
		||||
@ -25,6 +26,7 @@ export const addXpController: RequestHandler = async (req, res) => {
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type IAddXpRequest = {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
@ -20,6 +21,7 @@ export const changeModularPartsController: RequestHandler = async (req, res) =>
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IUpdateFingerPrintRequest {
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,11 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
 | 
			
		||||
    }
 | 
			
		||||
    addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
 | 
			
		||||
    addString(inventory.NodeIntrosCompleted, "CetusSyndicate_IntroJob");
 | 
			
		||||
    let syndicate = inventory.Affiliations.find(x => x.Tag == "CetusSyndicate");
 | 
			
		||||
    if (!syndicate) {
 | 
			
		||||
        syndicate =
 | 
			
		||||
            inventory.Affiliations[inventory.Affiliations.push({ Tag: "CetusSyndicate", Standing: 250, Title: 0 })]; // Non-zero standing avoids Konzu's "prove yourself" text. 250 is identical to newbie bounty + bonus
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { Account, Ignore } from "../../models/loginModel.ts";
 | 
			
		||||
import { Inbox } from "../../models/inboxModel.ts";
 | 
			
		||||
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
 | 
			
		||||
@ -12,33 +12,44 @@ import { Leaderboard } from "../../models/leaderboardModel.ts";
 | 
			
		||||
import { deleteGuild } from "../../services/guildService.ts";
 | 
			
		||||
import { Friendship } from "../../models/friendModel.ts";
 | 
			
		||||
import { sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import { saveConfig } from "../../services/configWriterService.ts";
 | 
			
		||||
 | 
			
		||||
export const deleteAccountController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
 | 
			
		||||
    // If this account is an admin, remove it from administratorNames
 | 
			
		||||
    if (config.administratorNames) {
 | 
			
		||||
        const adminIndex = config.administratorNames.indexOf(account.DisplayName);
 | 
			
		||||
        if (adminIndex != -1) {
 | 
			
		||||
            config.administratorNames.splice(adminIndex, 1);
 | 
			
		||||
            await saveConfig();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If account is the founding warlord of a guild, delete that guild as well.
 | 
			
		||||
    const guildMember = await GuildMember.findOne({ accountId, rank: 0, status: 0 });
 | 
			
		||||
    const guildMember = await GuildMember.findOne({ accountId: account._id, rank: 0, status: 0 });
 | 
			
		||||
    if (guildMember) {
 | 
			
		||||
        await deleteGuild(guildMember.guildId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await Promise.all([
 | 
			
		||||
        Account.deleteOne({ _id: accountId }),
 | 
			
		||||
        Friendship.deleteMany({ owner: accountId }),
 | 
			
		||||
        Friendship.deleteMany({ friend: accountId }),
 | 
			
		||||
        GuildMember.deleteMany({ accountId: accountId }),
 | 
			
		||||
        Ignore.deleteMany({ ignorer: accountId }),
 | 
			
		||||
        Ignore.deleteMany({ ignoree: accountId }),
 | 
			
		||||
        Inbox.deleteMany({ ownerId: accountId }),
 | 
			
		||||
        Inventory.deleteOne({ accountOwnerId: accountId }),
 | 
			
		||||
        Leaderboard.deleteMany({ ownerId: accountId }),
 | 
			
		||||
        Loadout.deleteOne({ loadoutOwnerId: accountId }),
 | 
			
		||||
        PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
 | 
			
		||||
        Ship.deleteMany({ ShipOwnerId: accountId }),
 | 
			
		||||
        Stats.deleteOne({ accountOwnerId: accountId })
 | 
			
		||||
        Account.deleteOne({ _id: account._id }),
 | 
			
		||||
        Friendship.deleteMany({ owner: account._id }),
 | 
			
		||||
        Friendship.deleteMany({ friend: account._id }),
 | 
			
		||||
        GuildMember.deleteMany({ accountId: account._id }),
 | 
			
		||||
        Ignore.deleteMany({ ignorer: account._id }),
 | 
			
		||||
        Ignore.deleteMany({ ignoree: account._id }),
 | 
			
		||||
        Inbox.deleteMany({ ownerId: account._id }),
 | 
			
		||||
        Inventory.deleteOne({ accountOwnerId: account._id }),
 | 
			
		||||
        Leaderboard.deleteMany({ ownerId: account._id }),
 | 
			
		||||
        Loadout.deleteOne({ loadoutOwnerId: account._id }),
 | 
			
		||||
        PersonalRooms.deleteOne({ personalRoomsOwnerId: account._id }),
 | 
			
		||||
        Ship.deleteMany({ ShipOwnerId: account._id }),
 | 
			
		||||
        Stats.deleteOne({ accountOwnerId: account._id })
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    sendWsBroadcastTo(accountId, { logged_out: true });
 | 
			
		||||
    sendWsBroadcastTo(account._id.toString(), { logged_out: true });
 | 
			
		||||
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
 | 
			
		||||
 | 
			
		||||
export const editSuitInvigorationUpgradeController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const { oid, data } = req.body as {
 | 
			
		||||
        oid: string;
 | 
			
		||||
        data?: {
 | 
			
		||||
            DefensiveUpgrade: string;
 | 
			
		||||
            OffensiveUpgrade: string;
 | 
			
		||||
            UpgradesExpiry?: number;
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const suit = inventory.Suits.id(oid)!;
 | 
			
		||||
    if (data) {
 | 
			
		||||
        suit.DefensiveUpgrade = data.DefensiveUpgrade;
 | 
			
		||||
        suit.OffensiveUpgrade = data.OffensiveUpgrade;
 | 
			
		||||
        if (data.UpgradesExpiry) {
 | 
			
		||||
            suit.UpgradesExpiry = new Date(data.UpgradesExpiry);
 | 
			
		||||
        } else {
 | 
			
		||||
            suit.UpgradesExpiry = new Date(Date.now() + DEFAULT_UPGRADE_EXPIRY_MS);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        suit.DefensiveUpgrade = undefined;
 | 
			
		||||
        suit.OffensiveUpgrade = undefined;
 | 
			
		||||
        suit.UpgradesExpiry = undefined;
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										16
									
								
								src/controllers/custom/getAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/controllers/custom/getAllianceController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
import { Alliance, Guild } from "../../models/guildModel.ts";
 | 
			
		||||
import { getAllianceClient } from "../../services/guildService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const getAllianceController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const guildId = req.query.guildId;
 | 
			
		||||
    if (guildId) {
 | 
			
		||||
        const guild = await Guild.findById(guildId, "Name Tier AllianceId");
 | 
			
		||||
        if (guild && guild.AllianceId) {
 | 
			
		||||
            const alliance = (await Alliance.findById(guild.AllianceId))!;
 | 
			
		||||
            res.json(await getAllianceClient(alliance, guild));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										40
									
								
								src/controllers/custom/getGuildController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/controllers/custom/getGuildController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { Guild, GuildMember } from "../../models/guildModel.ts";
 | 
			
		||||
import { toMongoDate, toOid2 } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts";
 | 
			
		||||
import type { IGuildMemberClient } from "../../types/guildTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const getGuildController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const guildId = req.query.guildId;
 | 
			
		||||
    if (guildId) {
 | 
			
		||||
        const guild = await Guild.findById(guildId);
 | 
			
		||||
        if (guild) {
 | 
			
		||||
            const guildMembers = await GuildMember.find({ guildId: guild._id });
 | 
			
		||||
 | 
			
		||||
            const members: IGuildMemberClient[] = [];
 | 
			
		||||
            const dataFillInPromises: Promise<void>[] = [];
 | 
			
		||||
            for (const guildMember of guildMembers) {
 | 
			
		||||
                const member: IGuildMemberClient = {
 | 
			
		||||
                    _id: toOid2(guildMember.accountId, undefined),
 | 
			
		||||
                    Rank: guildMember.rank,
 | 
			
		||||
                    Status: guildMember.status,
 | 
			
		||||
                    Note: guildMember.RequestMsg,
 | 
			
		||||
                    RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined
 | 
			
		||||
                };
 | 
			
		||||
                dataFillInPromises.push(addAccountDataToFriendInfo(member));
 | 
			
		||||
                dataFillInPromises.push(addInventoryDataToFriendInfo(member));
 | 
			
		||||
 | 
			
		||||
                members.push(member);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Promise.all(dataFillInPromises);
 | 
			
		||||
 | 
			
		||||
            res.json({
 | 
			
		||||
                ...guild.toObject(),
 | 
			
		||||
                Members: members
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            res.status(400).end();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@ -7,10 +7,12 @@ import {
 | 
			
		||||
    ExportAvionics,
 | 
			
		||||
    ExportBoosters,
 | 
			
		||||
    ExportCustoms,
 | 
			
		||||
    ExportDojoRecipes,
 | 
			
		||||
    ExportDrones,
 | 
			
		||||
    ExportFactions,
 | 
			
		||||
    ExportFlavour,
 | 
			
		||||
    ExportGear,
 | 
			
		||||
    ExportKeys,
 | 
			
		||||
    ExportMisc,
 | 
			
		||||
    ExportRailjackWeapons,
 | 
			
		||||
    ExportRecipes,
 | 
			
		||||
    ExportRelics,
 | 
			
		||||
@ -34,10 +36,12 @@ interface ListedItem {
 | 
			
		||||
    partType?: string;
 | 
			
		||||
    chainLength?: number;
 | 
			
		||||
    parazon?: boolean;
 | 
			
		||||
    alwaysAvailable?: boolean;
 | 
			
		||||
    maxLevelCap?: number;
 | 
			
		||||
    eligibleForVault?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ItemLists {
 | 
			
		||||
    uniqueLevelCaps: Record<string, number>;
 | 
			
		||||
    Suits: ListedItem[];
 | 
			
		||||
    LongGuns: ListedItem[];
 | 
			
		||||
    Melee: ListedItem[];
 | 
			
		||||
@ -59,24 +63,28 @@ interface ItemLists {
 | 
			
		||||
    Boosters: ListedItem[];
 | 
			
		||||
    VarziaOffers: ListedItem[];
 | 
			
		||||
    Abilities: ListedItem[];
 | 
			
		||||
    TechProjects: ListedItem[];
 | 
			
		||||
    VaultDecoRecipes: ListedItem[];
 | 
			
		||||
    FlavourItems: ListedItem[];
 | 
			
		||||
    ShipDecorations: ListedItem[];
 | 
			
		||||
    WeaponSkins: ListedItem[];
 | 
			
		||||
    //circuitGameModes: ListedItem[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
 | 
			
		||||
    VPQ_BRONZE: "",
 | 
			
		||||
    VPQ_SILVER: " [Exceptional]",
 | 
			
		||||
    VPQ_GOLD: " [Flawless]",
 | 
			
		||||
    VPQ_PLATINUM: " [Radiant]"
 | 
			
		||||
    VPQ_SILVER: "/Lotus/Language/Relics/VoidProjectionQuality_Silver",
 | 
			
		||||
    VPQ_GOLD: "/Lotus/Language/Relics/VoidProjectionQuality_Gold",
 | 
			
		||||
    VPQ_PLATINUM: "/Lotus/Language/Relics/VoidProjectionQuality_Platinum"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*const toTitleCase = (str: string): string => {
 | 
			
		||||
    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
 | 
			
		||||
};*/
 | 
			
		||||
const toTitleCase = (str: string): string => {
 | 
			
		||||
    return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substring(1).toLowerCase());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
    const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
 | 
			
		||||
    const res: ItemLists = {
 | 
			
		||||
        uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
 | 
			
		||||
        Suits: [],
 | 
			
		||||
        LongGuns: [],
 | 
			
		||||
        Melee: [],
 | 
			
		||||
@ -97,7 +105,12 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
        mods: [],
 | 
			
		||||
        Boosters: [],
 | 
			
		||||
        VarziaOffers: [],
 | 
			
		||||
        Abilities: []
 | 
			
		||||
        Abilities: [],
 | 
			
		||||
        TechProjects: [],
 | 
			
		||||
        VaultDecoRecipes: [],
 | 
			
		||||
        FlavourItems: [],
 | 
			
		||||
        ShipDecorations: [],
 | 
			
		||||
        WeaponSkins: []
 | 
			
		||||
        /*circuitGameModes: [
 | 
			
		||||
            {
 | 
			
		||||
                uniqueName: "Survival",
 | 
			
		||||
@ -129,11 +142,18 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
            }
 | 
			
		||||
        ]*/
 | 
			
		||||
    };
 | 
			
		||||
    const eligibleForVault = new Set<string>([
 | 
			
		||||
        ...Object.values(ExportDojoRecipes.research).flatMap(r => r.ingredients.map(i => i.ItemType)),
 | 
			
		||||
        ...Object.values(ExportDojoRecipes.fabrications).flatMap(f => f.ingredients.map(i => i.ItemType)),
 | 
			
		||||
        ...Object.values(ExportDojoRecipes.rooms).flatMap(r => r.ingredients.map(i => i.ItemType)),
 | 
			
		||||
        ...Object.values(ExportDojoRecipes.decos).flatMap(d => d.ingredients.map(i => i.ItemType))
 | 
			
		||||
    ]);
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
 | 
			
		||||
        res[item.productCategory].push({
 | 
			
		||||
            uniqueName,
 | 
			
		||||
            name: getString(item.name, lang),
 | 
			
		||||
            exalted: item.exalted
 | 
			
		||||
            exalted: item.exalted,
 | 
			
		||||
            maxLevelCap: item.maxLevelCap
 | 
			
		||||
        });
 | 
			
		||||
        item.abilities.forEach(ability => {
 | 
			
		||||
            res.Abilities.push({
 | 
			
		||||
@ -181,7 +201,8 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
            ) {
 | 
			
		||||
                res[item.productCategory].push({
 | 
			
		||||
                    uniqueName,
 | 
			
		||||
                    name: getString(item.name, lang)
 | 
			
		||||
                    name: getString(item.name, lang),
 | 
			
		||||
                    maxLevelCap: item.maxLevelCap
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        } else if (!item.excludeFromCodex) {
 | 
			
		||||
@ -219,27 +240,38 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (
 | 
			
		||||
        if (item.productCategory == "ShipDecorations") {
 | 
			
		||||
            res.ShipDecorations.push({
 | 
			
		||||
                uniqueName: uniqueName,
 | 
			
		||||
                name: name
 | 
			
		||||
            });
 | 
			
		||||
        } else if (
 | 
			
		||||
            name &&
 | 
			
		||||
            uniqueName.substr(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
			
		||||
            uniqueName.substring(0, 30) != "/Lotus/Types/Game/Projections/" &&
 | 
			
		||||
            uniqueName != "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle"
 | 
			
		||||
        ) {
 | 
			
		||||
            res.miscitems.push({
 | 
			
		||||
                uniqueName: uniqueName,
 | 
			
		||||
                name: name,
 | 
			
		||||
                subtype: "Resource"
 | 
			
		||||
                subtype: "Resource",
 | 
			
		||||
                ...(eligibleForVault.has(uniqueName) && { eligibleForVault: true })
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportRelics)) {
 | 
			
		||||
        const qualitySuffix =
 | 
			
		||||
            item.quality !== "VPQ_BRONZE"
 | 
			
		||||
                ? ` [${toTitleCase(getString(relicQualitySuffixes[item.quality], lang))}]`
 | 
			
		||||
                : "";
 | 
			
		||||
 | 
			
		||||
        res.miscitems.push({
 | 
			
		||||
            uniqueName: uniqueName,
 | 
			
		||||
            name:
 | 
			
		||||
                getString("/Lotus/Language/Relics/VoidProjectionName", lang)
 | 
			
		||||
                    .split("|ERA|")
 | 
			
		||||
                    .join(item.era)
 | 
			
		||||
                    .join(getString(`/Lotus/Language/Relics/Era_${item.era.toUpperCase()}`, lang))
 | 
			
		||||
                    .split("|CATEGORY|")
 | 
			
		||||
                    .join(item.category) + relicQualitySuffixes[item.quality]
 | 
			
		||||
                    .join(item.category) + qualitySuffix
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportGear)) {
 | 
			
		||||
@ -276,10 +308,18 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
 | 
			
		||||
        res.miscitems.push({
 | 
			
		||||
            uniqueName: uniqueName,
 | 
			
		||||
            name: getString(item.name, lang)
 | 
			
		||||
        });
 | 
			
		||||
        if (
 | 
			
		||||
            item.productCategory == "WeaponSkins" &&
 | 
			
		||||
            !uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items
 | 
			
		||||
            !uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins
 | 
			
		||||
            !uniqueName.endsWith("Coat") // Frost Prime stuff
 | 
			
		||||
        ) {
 | 
			
		||||
            res.WeaponSkins.push({
 | 
			
		||||
                uniqueName: uniqueName,
 | 
			
		||||
                name: getString(item.name, lang),
 | 
			
		||||
                alwaysAvailable: item.alwaysAvailable
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
 | 
			
		||||
@ -313,7 +353,7 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
                uniqueName,
 | 
			
		||||
                name: getString(arcane.name, lang)
 | 
			
		||||
            };
 | 
			
		||||
            if (arcane.isFrivolous) {
 | 
			
		||||
            if (arcane.excludeFromCodex) {
 | 
			
		||||
                mod.badReason = "frivolous";
 | 
			
		||||
            }
 | 
			
		||||
            res.mods.push(mod);
 | 
			
		||||
@ -367,6 +407,74 @@ const getItemListsController: RequestHandler = (req, response) => {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const uniqueName of Object.keys(ExportDojoRecipes.research)) {
 | 
			
		||||
        if (
 | 
			
		||||
            !["Zekti", "Vidar", "Lavan"].some(house => uniqueName.includes(house)) &&
 | 
			
		||||
            !uniqueName.startsWith("/Lotus/Types/Items/ShipFeatureItems/Railjack/")
 | 
			
		||||
        ) {
 | 
			
		||||
            let resultType;
 | 
			
		||||
            if (uniqueName in ExportRecipes) {
 | 
			
		||||
                resultType = ExportRecipes[uniqueName].resultType;
 | 
			
		||||
            } else if (uniqueName in ExportDojoRecipes.fabrications) {
 | 
			
		||||
                resultType = ExportDojoRecipes.fabrications[uniqueName].resultType;
 | 
			
		||||
            } else if (uniqueName.startsWith("/Lotus/Types/Game/")) {
 | 
			
		||||
                resultType = uniqueName.replace("Blueprint", "");
 | 
			
		||||
            } else {
 | 
			
		||||
                resultType = uniqueName;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let name = getString(getItemName(resultType) || resultType, lang);
 | 
			
		||||
 | 
			
		||||
            if (uniqueName in ExportRecipes) {
 | 
			
		||||
                const recipeNum = ExportRecipes[uniqueName].num;
 | 
			
		||||
                if (recipeNum > 1) {
 | 
			
		||||
                    name = `${name} X ${recipeNum}`;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            res.TechProjects.push({
 | 
			
		||||
                uniqueName,
 | 
			
		||||
                name
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const uniqueName of [
 | 
			
		||||
        ...Object.entries(ExportDojoRecipes.decos)
 | 
			
		||||
            .filter(([_, data]) => data.requiredInVault)
 | 
			
		||||
            .map(([uniqueName]) => uniqueName),
 | 
			
		||||
        // not requiredInVault:
 | 
			
		||||
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyBronzeRecipe",
 | 
			
		||||
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyGoldRecipe",
 | 
			
		||||
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophySilverRecipe",
 | 
			
		||||
        "/Lotus/Levels/ClanDojo/ComponentPropRecipes/PlagueStarEventTrophyTerracottaRecipe"
 | 
			
		||||
        // removed in 38.6.0:
 | 
			
		||||
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyBronzeRecipe",
 | 
			
		||||
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyCrystalRecipe",
 | 
			
		||||
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyGoldRecipe",
 | 
			
		||||
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe",
 | 
			
		||||
        // "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe"
 | 
			
		||||
    ]) {
 | 
			
		||||
        let name = getString(getItemName(uniqueName) || uniqueName, lang);
 | 
			
		||||
        if (uniqueName.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemma")) {
 | 
			
		||||
            const factionTag = uniqueName.includes("Corpus") ? "FC_CORPUS" : "FC_GRINEER";
 | 
			
		||||
            const faction = ExportFactions[factionTag].name;
 | 
			
		||||
            name += ` [${getString(faction, lang)}]`;
 | 
			
		||||
        }
 | 
			
		||||
        res.VaultDecoRecipes.push({
 | 
			
		||||
            uniqueName,
 | 
			
		||||
            name
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const [uniqueName, item] of Object.entries(ExportFlavour)) {
 | 
			
		||||
        res.FlavourItems.push({
 | 
			
		||||
            uniqueName,
 | 
			
		||||
            name: getString(item.name, lang),
 | 
			
		||||
            alwaysAvailable: item.alwaysAvailable
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    response.json(res);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getLoadout } from "../../services/loadoutService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getPersonalRooms } from "../../services/personalRoomsService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import type { IInventoryClient } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { IGetShipResponse } from "../../types/personalRoomsTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
@ -32,6 +33,7 @@ export const importController: RequestHandler = async (req, res) => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IImportRequest {
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import {
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { ExportKeys } from "warframe-public-export-plus";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -36,7 +37,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    switch (operation) {
 | 
			
		||||
        case "completeAll": {
 | 
			
		||||
            for (const questKey of inventory.QuestKeys) {
 | 
			
		||||
                await completeQuest(inventory, questKey.ItemType);
 | 
			
		||||
                await completeQuest(inventory, questKey.ItemType, undefined);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
@ -60,6 +61,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            inventory.QuestKeys.pull({ ItemType: questItemType });
 | 
			
		||||
            if (inventory.ActiveQuest == questItemType) inventory.ActiveQuest = "";
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "completeKey": {
 | 
			
		||||
@ -70,7 +72,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await completeQuest(inventory, questItemType);
 | 
			
		||||
                await completeQuest(inventory, questItemType, undefined);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
@ -101,12 +103,20 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
                    questKey.Completed = false;
 | 
			
		||||
                    questKey.CompletionDate = undefined;
 | 
			
		||||
                }
 | 
			
		||||
                questKey.Progress.pop();
 | 
			
		||||
                const stage = questKey.Progress.length - 1;
 | 
			
		||||
 | 
			
		||||
                const run = questKey.Progress[0]?.c ?? 0;
 | 
			
		||||
                const stage = questKey.Progress.map(p => p.c).lastIndexOf(run);
 | 
			
		||||
 | 
			
		||||
                if (run > 0) {
 | 
			
		||||
                    questKey.Progress[stage].c = run - 1;
 | 
			
		||||
                } else {
 | 
			
		||||
                    questKey.Progress.pop();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (stage > 0) {
 | 
			
		||||
                    await giveKeyChainStageTriggered(inventory, {
 | 
			
		||||
                        KeyChain: questKey.ItemType,
 | 
			
		||||
                        ChainStage: stage
 | 
			
		||||
                        ChainStage: stage - 1
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -122,29 +132,33 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
                }
 | 
			
		||||
                if (!questKey.Progress) break;
 | 
			
		||||
 | 
			
		||||
                const currentStage = questKey.Progress.length;
 | 
			
		||||
                const run = questKey.Progress[0]?.c ?? 0;
 | 
			
		||||
                const currentStage = questKey.Progress.map(p => p.c).lastIndexOf(run);
 | 
			
		||||
 | 
			
		||||
                if (currentStage + 1 == questManifest.chainStages?.length) {
 | 
			
		||||
                    logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
 | 
			
		||||
                    await completeQuest(inventory, questKey.ItemType);
 | 
			
		||||
                    await completeQuest(inventory, questKey.ItemType, undefined, true);
 | 
			
		||||
                } else {
 | 
			
		||||
                    const progress = {
 | 
			
		||||
                        c: 0,
 | 
			
		||||
                        i: false,
 | 
			
		||||
                        m: false,
 | 
			
		||||
                        b: []
 | 
			
		||||
                    };
 | 
			
		||||
                    questKey.Progress.push(progress);
 | 
			
		||||
                    if (run > 0) {
 | 
			
		||||
                        questKey.Progress[currentStage + 1].c = run;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        questKey.Progress.push({ c: run, i: false, m: false, b: [] });
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    await giveKeyChainStageTriggered(inventory, {
 | 
			
		||||
                        KeyChain: questKey.ItemType,
 | 
			
		||||
                        ChainStage: currentStage
 | 
			
		||||
                        ChainStage: currentStage + 1
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if (currentStage > 0) {
 | 
			
		||||
                        await giveKeyChainMissionReward(inventory, {
 | 
			
		||||
                            KeyChain: questKey.ItemType,
 | 
			
		||||
                            ChainStage: currentStage - 1
 | 
			
		||||
                        });
 | 
			
		||||
                        await giveKeyChainMissionReward(
 | 
			
		||||
                            inventory,
 | 
			
		||||
                            {
 | 
			
		||||
                                KeyChain: questKey.ItemType,
 | 
			
		||||
                                ChainStage: currentStage
 | 
			
		||||
                            },
 | 
			
		||||
                            undefined
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -157,4 +171,5 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.status(200).end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const popArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -12,6 +13,7 @@ export const popArchonCrystalUpgradeController: RequestHandler = async (req, res
 | 
			
		||||
        );
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        res.end();
 | 
			
		||||
        broadcastInventoryUpdate(req);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    res.status(400).end();
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const pushArchonCrystalUpgradeController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -15,6 +16,7 @@ export const pushArchonCrystalUpgradeController: RequestHandler = async (req, re
 | 
			
		||||
            }
 | 
			
		||||
            await inventory.save();
 | 
			
		||||
            res.end();
 | 
			
		||||
            broadcastInventoryUpdate(req);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								src/controllers/custom/removeCustomizationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/controllers/custom/removeCustomizationController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const removeCustomizationController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const ItemType = req.query.itemType as string;
 | 
			
		||||
    const inventory = await getInventory(accountId, "FlavourItems");
 | 
			
		||||
    inventory.FlavourItems.pull({ ItemType });
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										24
									
								
								src/controllers/custom/removeIsNewController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/controllers/custom/removeIsNewController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const removeIsNewController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const filteredEquipmentKeys = equipmentKeys.filter(k => k !== "CrewShipWeapons" && k !== "CrewShipSalvagedWeapons");
 | 
			
		||||
    const inventory = await getInventory(accountId, [...filteredEquipmentKeys, "WeaponSkins"].join(" "));
 | 
			
		||||
    for (const key of filteredEquipmentKeys) {
 | 
			
		||||
        if (key in inventory) {
 | 
			
		||||
            for (const equipment of inventory[key]) {
 | 
			
		||||
                if (equipment.IsNew) equipment.IsNew = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    for (const equipment of inventory.WeaponSkins) {
 | 
			
		||||
        if (equipment.IsNew) equipment.IsNew = false;
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
@ -1,18 +1,31 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts";
 | 
			
		||||
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { logger } from "../../utils/logger.ts";
 | 
			
		||||
 | 
			
		||||
export const setAccountCheatController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const payload = req.body as ISetAccountCheatRequest;
 | 
			
		||||
    const inventory = await getInventory(accountId, payload.key);
 | 
			
		||||
    inventory[payload.key] = payload.value;
 | 
			
		||||
 | 
			
		||||
    if (payload.value == undefined) {
 | 
			
		||||
        logger.warn(`Aborting setting ${payload.key} as undefined!`);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inventory[payload.key] = payload.value as never;
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
 | 
			
		||||
        sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
 | 
			
		||||
    } else {
 | 
			
		||||
        sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ISetAccountCheatRequest {
 | 
			
		||||
    key: keyof IAccountCheats;
 | 
			
		||||
    value: boolean;
 | 
			
		||||
    value: IAccountCheats[keyof IAccountCheats];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,45 +1,22 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { ExportBoosters } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
const I32_MAX = 0x7fffffff;
 | 
			
		||||
import type { IBooster } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const setBoosterController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const requests = req.body as { ItemType: string; ExpiryDate: number }[];
 | 
			
		||||
    const requests = req.body as IBooster[];
 | 
			
		||||
    const inventory = await getInventory(accountId, "Boosters");
 | 
			
		||||
    const boosters = inventory.Boosters;
 | 
			
		||||
    if (
 | 
			
		||||
        requests.some(request => {
 | 
			
		||||
            if (typeof request.ItemType !== "string") return true;
 | 
			
		||||
            if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
 | 
			
		||||
                return true;
 | 
			
		||||
            if (typeof request.ExpiryDate !== "number") return true;
 | 
			
		||||
            if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
 | 
			
		||||
            return false;
 | 
			
		||||
        })
 | 
			
		||||
    ) {
 | 
			
		||||
        res.status(400).send("Invalid ItemType provided.");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    const now = Math.trunc(Date.now() / 1000);
 | 
			
		||||
    for (const { ItemType, ExpiryDate } of requests) {
 | 
			
		||||
        if (ExpiryDate <= now) {
 | 
			
		||||
            // remove expired boosters
 | 
			
		||||
            const index = boosters.findIndex(item => item.ItemType === ItemType);
 | 
			
		||||
            if (index !== -1) {
 | 
			
		||||
                boosters.splice(index, 1);
 | 
			
		||||
            }
 | 
			
		||||
    for (const request of requests) {
 | 
			
		||||
        const index = inventory.Boosters.findIndex(item => item.ItemType === request.ItemType);
 | 
			
		||||
        if (index !== -1) {
 | 
			
		||||
            inventory.Boosters[index].ExpiryDate = request.ExpiryDate;
 | 
			
		||||
        } else {
 | 
			
		||||
            const boosterItem = boosters.find(item => item.ItemType === ItemType);
 | 
			
		||||
            if (boosterItem) {
 | 
			
		||||
                boosterItem.ExpiryDate = ExpiryDate;
 | 
			
		||||
            } else {
 | 
			
		||||
                boosters.push({ ItemType, ExpiryDate });
 | 
			
		||||
            }
 | 
			
		||||
            inventory.Boosters.push(request);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const setEvolutionProgressController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -25,6 +26,7 @@ export const setEvolutionProgressController: RequestHandler = async (req, res) =
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ISetEvolutionProgressRequest = {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								src/controllers/custom/setGuildCheatController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/controllers/custom/setGuildCheatController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
import { GuildMember } from "../../models/guildModel.ts";
 | 
			
		||||
import { getGuildForRequestEx } from "../../services/guildService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { IGuildCheats } from "../../types/guildTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const setGuildCheatController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const payload = req.body as ISetGuildCheatRequest;
 | 
			
		||||
    const inventory = await getInventory(accountId, `GuildId`);
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
 | 
			
		||||
 | 
			
		||||
    if (member) {
 | 
			
		||||
        if (member.rank > 1) {
 | 
			
		||||
            res.end();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        guild[payload.key] = payload.value;
 | 
			
		||||
        await guild.save();
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ISetGuildCheatRequest {
 | 
			
		||||
    key: keyof IGuildCheats;
 | 
			
		||||
    value: boolean;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/controllers/custom/setInvigorationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/controllers/custom/setInvigorationController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const setInvigorationController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const request = req.body as ISetInvigorationRequest;
 | 
			
		||||
    const inventory = await getInventory(accountId, "Suits");
 | 
			
		||||
    const suit = inventory.Suits.id(request.oid);
 | 
			
		||||
    if (suit) {
 | 
			
		||||
        const hasUpgrades = request.DefensiveUpgrade && request.OffensiveUpgrade && request.UpgradesExpiry;
 | 
			
		||||
        suit.DefensiveUpgrade = hasUpgrades ? request.DefensiveUpgrade : undefined;
 | 
			
		||||
        suit.OffensiveUpgrade = hasUpgrades ? request.OffensiveUpgrade : undefined;
 | 
			
		||||
        suit.UpgradesExpiry = hasUpgrades ? new Date(request.UpgradesExpiry) : undefined;
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
        broadcastInventoryUpdate(req);
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ISetInvigorationRequest {
 | 
			
		||||
    oid: string;
 | 
			
		||||
    DefensiveUpgrade: string;
 | 
			
		||||
    OffensiveUpgrade: string;
 | 
			
		||||
    UpgradesExpiry: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								src/controllers/custom/techProjectController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/controllers/custom/techProjectController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import {
 | 
			
		||||
    getGuildForRequestEx,
 | 
			
		||||
    setGuildTechLogState,
 | 
			
		||||
    processFundedGuildTechProject,
 | 
			
		||||
    scaleRequiredCount,
 | 
			
		||||
    hasGuildPermission,
 | 
			
		||||
    addGuildMemberMiscItemContribution,
 | 
			
		||||
    processGuildTechProjectContributionsUpdate,
 | 
			
		||||
    processCompletedGuildTechProject
 | 
			
		||||
} from "../../services/guildService.ts";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { GuildPermission } from "../../types/guildTypes.ts";
 | 
			
		||||
import { GuildMember } from "../../models/guildModel.ts";
 | 
			
		||||
 | 
			
		||||
export const addTechProjectController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const requests = req.body as ITechProjectRequest[];
 | 
			
		||||
    const inventory = await getInventory(accountId, "GuildId");
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
			
		||||
        res.status(400).send("-1").end();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    guild.TechProjects ??= [];
 | 
			
		||||
    for (const request of requests) {
 | 
			
		||||
        const recipe = ExportDojoRecipes.research[request.ItemType];
 | 
			
		||||
        if (!guild.TechProjects.find(x => x.ItemType == request.ItemType)) {
 | 
			
		||||
            const techProject =
 | 
			
		||||
                guild.TechProjects[
 | 
			
		||||
                    guild.TechProjects.push({
 | 
			
		||||
                        ItemType: request.ItemType,
 | 
			
		||||
                        ReqCredits: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
 | 
			
		||||
                        ReqItems: recipe.ingredients.map(x => ({
 | 
			
		||||
                            ItemType: x.ItemType,
 | 
			
		||||
                            ItemCount: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
 | 
			
		||||
                        })),
 | 
			
		||||
                        State: 0
 | 
			
		||||
                    }) - 1
 | 
			
		||||
                ];
 | 
			
		||||
            setGuildTechLogState(guild, techProject.ItemType, 5);
 | 
			
		||||
            if (guild.noDojoResearchCosts) {
 | 
			
		||||
                processFundedGuildTechProject(guild, techProject, recipe);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeTechProjectController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const requests = req.body as ITechProjectRequest[];
 | 
			
		||||
    const inventory = await getInventory(accountId, "GuildId");
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
			
		||||
        res.status(400).send("-1").end();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    guild.TechProjects ??= [];
 | 
			
		||||
    for (const request of requests) {
 | 
			
		||||
        const index = guild.TechProjects.findIndex(x => x.ItemType === request.ItemType);
 | 
			
		||||
        if (index !== -1) {
 | 
			
		||||
            guild.TechProjects.splice(index, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const fundTechProjectController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const requests = req.body as ITechProjectRequest[];
 | 
			
		||||
    const inventory = await getInventory(accountId, "GuildId");
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    const guildMember = (await GuildMember.findOne(
 | 
			
		||||
        { accountId, guildId: guild._id },
 | 
			
		||||
        "RegularCreditsContributed MiscItemsContributed"
 | 
			
		||||
    ))!;
 | 
			
		||||
    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
			
		||||
        res.status(400).send("-1").end();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    for (const request of requests) {
 | 
			
		||||
        const techProject = guild.TechProjects!.find(x => x.ItemType == request.ItemType)!;
 | 
			
		||||
 | 
			
		||||
        guildMember.RegularCreditsContributed ??= 0;
 | 
			
		||||
        guildMember.RegularCreditsContributed += techProject.ReqCredits;
 | 
			
		||||
        techProject.ReqCredits = 0;
 | 
			
		||||
 | 
			
		||||
        for (const reqItem of techProject.ReqItems) {
 | 
			
		||||
            addGuildMemberMiscItemContribution(guildMember, reqItem);
 | 
			
		||||
            reqItem.ItemCount = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await processGuildTechProjectContributionsUpdate(guild, techProject);
 | 
			
		||||
    }
 | 
			
		||||
    await Promise.all([guild.save(), guildMember.save()]);
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const completeTechProjectsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const requests = req.body as ITechProjectRequest[];
 | 
			
		||||
    const inventory = await getInventory(accountId, "GuildId");
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
 | 
			
		||||
        res.status(400).send("-1").end();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    for (const request of requests) {
 | 
			
		||||
        const techProject = guild.TechProjects!.find(x => x.ItemType == request.ItemType)!;
 | 
			
		||||
        techProject.CompletionDate = new Date();
 | 
			
		||||
 | 
			
		||||
        if (setGuildTechLogState(guild, techProject.ItemType, 4, techProject.CompletionDate)) {
 | 
			
		||||
            processCompletedGuildTechProject(guild, techProject.ItemType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ITechProjectRequest {
 | 
			
		||||
    ItemType: string;
 | 
			
		||||
}
 | 
			
		||||
@ -1,16 +1,9 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import type { ITunables } from "../../types/bootstrapperTypes.ts";
 | 
			
		||||
 | 
			
		||||
// This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
 | 
			
		||||
 | 
			
		||||
interface ITunables {
 | 
			
		||||
    prohibit_skip_mission_start_timer?: boolean;
 | 
			
		||||
    prohibit_fov_override?: boolean;
 | 
			
		||||
    prohibit_freecam?: boolean;
 | 
			
		||||
    prohibit_teleport?: boolean;
 | 
			
		||||
    prohibit_scripts?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const tunablesController: RequestHandler = (_req, res) => {
 | 
			
		||||
export const tunablesController: RequestHandler = (_req, res) => {
 | 
			
		||||
    const tunables: ITunables = {};
 | 
			
		||||
    //tunables.prohibit_skip_mission_start_timer = true;
 | 
			
		||||
    //tunables.prohibit_fov_override = true;
 | 
			
		||||
@ -19,5 +12,3 @@ const tunablesController: RequestHandler = (_req, res) => {
 | 
			
		||||
    //tunables.prohibit_scripts = true;
 | 
			
		||||
    res.json(tunables);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { tunablesController };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										42
									
								
								src/controllers/custom/unlockAllCapturaScenesController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/controllers/custom/unlockAllCapturaScenesController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { ExportResources, ExportVirtuals } from "warframe-public-export-plus";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { addItem, getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockAllCapturaScenesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
 | 
			
		||||
    let needSync = false;
 | 
			
		||||
    for (const uniqueName of Object.keys(ExportResources)) {
 | 
			
		||||
        if (resourceInheritsFrom(uniqueName, "/Lotus/Types/Items/MiscItems/PhotoboothTile")) {
 | 
			
		||||
            await addItem(inventory, uniqueName, 1);
 | 
			
		||||
            needSync = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    if (needSync) {
 | 
			
		||||
        sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resourceInheritsFrom = (resourceName: string, targetName: string): boolean => {
 | 
			
		||||
    let parentName = resourceGetParent(resourceName);
 | 
			
		||||
    for (; parentName != undefined; parentName = resourceGetParent(parentName)) {
 | 
			
		||||
        if (parentName == targetName) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resourceGetParent = (resourceName: string): string | undefined => {
 | 
			
		||||
    if (resourceName in ExportResources) {
 | 
			
		||||
        return ExportResources[resourceName].parentName;
 | 
			
		||||
    }
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
			
		||||
    return ExportVirtuals[resourceName]?.parentName;
 | 
			
		||||
};
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockAllIntrinsicsController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -16,4 +17,5 @@ export const unlockAllIntrinsicsController: RequestHandler = async (req, res) =>
 | 
			
		||||
    inventory.PlayerSkills.LPS_DRIFT_ENDURANCE = 10;
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
const allEudicoHeistJobs = [
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
 | 
			
		||||
    "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const unlockAllProfitTakerStagesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "CompletedJobChains");
 | 
			
		||||
    inventory.CompletedJobChains ??= [];
 | 
			
		||||
    const chain = inventory.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
 | 
			
		||||
    if (chain) {
 | 
			
		||||
        chain.Jobs = allEudicoHeistJobs;
 | 
			
		||||
    } else {
 | 
			
		||||
        inventory.CompletedJobChains.push({ LocationTag: "EudicoHeists", Jobs: allEudicoHeistJobs });
 | 
			
		||||
    }
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										23
									
								
								src/controllers/custom/unlockAllScansController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/controllers/custom/unlockAllScansController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import allScans from "../../../static/fixed_responses/allScans.json" with { type: "json" };
 | 
			
		||||
import { ExportEnemies } from "warframe-public-export-plus";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getStats } from "../../services/statsService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockAllScansController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const stats = await getStats(accountId);
 | 
			
		||||
 | 
			
		||||
    const scanTypes = new Set<string>(allScans);
 | 
			
		||||
    for (const type of Object.keys(ExportEnemies.avatars)) {
 | 
			
		||||
        scanTypes.add(type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stats.Scans = [];
 | 
			
		||||
    for (const type of scanTypes) {
 | 
			
		||||
        stats.Scans.push({ type, scans: 9999 });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await stats.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										19
									
								
								src/controllers/custom/unlockAllShipFeaturesController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/controllers/custom/unlockAllShipFeaturesController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import allShipFeatures from "../../../static/fixed_responses/allShipFeatures.json" with { type: "json" };
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getPersonalRooms } from "../../services/personalRoomsService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockAllShipFeaturesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const personalRooms = await getPersonalRooms(accountId);
 | 
			
		||||
 | 
			
		||||
    const featureSet = new Set(personalRooms.Ship.Features);
 | 
			
		||||
    for (const feature of allShipFeatures) {
 | 
			
		||||
        if (!featureSet.has(feature)) {
 | 
			
		||||
            personalRooms.Ship.Features.push(feature);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await personalRooms.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId, "LibraryPersonalTarget LibraryPersonalProgress");
 | 
			
		||||
    inventory.LibraryPersonalTarget = undefined;
 | 
			
		||||
    inventory.LibraryPersonalProgress = [
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research1Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research2Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research3Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research4Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research5Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research6Target",
 | 
			
		||||
        "/Lotus/Types/Game/Library/Targets/Research7Target"
 | 
			
		||||
    ].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										25
									
								
								src/controllers/custom/unlockLevelCapController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/controllers/custom/unlockLevelCapController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
 | 
			
		||||
export const unlockLevelCapController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const data = req.body as IunlockLevelCapRequest;
 | 
			
		||||
    const inventory = await getInventory(accountId, data.Category);
 | 
			
		||||
    const equipment = inventory[data.Category].id(data.ItemId)!;
 | 
			
		||||
 | 
			
		||||
    equipment.Polarized ??= 0;
 | 
			
		||||
    equipment.Polarized = data.Polarized;
 | 
			
		||||
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.end();
 | 
			
		||||
    broadcastInventoryUpdate(req);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IunlockLevelCapRequest {
 | 
			
		||||
    Category: TEquipmentKey;
 | 
			
		||||
    ItemId: string;
 | 
			
		||||
    Polarized: number;
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@ import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import type { WeaponTypeInternal } from "../../services/itemDataService.ts";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { sendWsBroadcastToGame } from "../../services/wsService.ts";
 | 
			
		||||
 | 
			
		||||
export const updateFingerprintController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -22,6 +23,7 @@ export const updateFingerprintController: RequestHandler = async (req, res) => {
 | 
			
		||||
        await inventory.save();
 | 
			
		||||
    }
 | 
			
		||||
    res.end();
 | 
			
		||||
    sendWsBroadcastToGame(accountId, { sync_inventory: true });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface IUpdateFingerPrintRequest {
 | 
			
		||||
 | 
			
		||||
@ -20,13 +20,14 @@ import type {
 | 
			
		||||
} from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import { LoadoutIndex } from "../../types/inventoryTypes/inventoryTypes.ts";
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { catBreadHash, getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import type { IStatsClient } from "../../types/statTypes.ts";
 | 
			
		||||
import { toStoreItem } from "../../services/itemDataService.ts";
 | 
			
		||||
import type { FlattenMaps } from "mongoose";
 | 
			
		||||
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
 | 
			
		||||
import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts";
 | 
			
		||||
import { skinLookupTable } from "../../helpers/skinLookupTable.ts";
 | 
			
		||||
 | 
			
		||||
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
 | 
			
		||||
    const account = await Account.findById(playerId, "DisplayName");
 | 
			
		||||
@ -261,8 +262,6 @@ interface IXPComponentClient {
 | 
			
		||||
    locTags?: Record<string, string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let skinLookupTable: Record<number, string> | undefined;
 | 
			
		||||
 | 
			
		||||
const resolveAndCollectSkins = (
 | 
			
		||||
    inventory: TInventoryDatabaseDocument,
 | 
			
		||||
    skins: Set<string>,
 | 
			
		||||
@ -274,12 +273,6 @@ const resolveAndCollectSkins = (
 | 
			
		||||
                // Resolve oids to type names
 | 
			
		||||
                if (config.Skins[i].length == 24) {
 | 
			
		||||
                    if (config.Skins[i].substring(0, 16) == "ca70ca70ca70ca70") {
 | 
			
		||||
                        if (!skinLookupTable) {
 | 
			
		||||
                            skinLookupTable = {};
 | 
			
		||||
                            for (const key of Object.keys(ExportCustoms)) {
 | 
			
		||||
                                skinLookupTable[catBreadHash(key)] = key;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        config.Skins[i] = skinLookupTable[parseInt(config.Skins[i].substring(16), 16)];
 | 
			
		||||
                    } else {
 | 
			
		||||
                        const skinItem = inventory.WeaponSkins.id(config.Skins[i]);
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,12 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { version_compare } from "../../helpers/inventoryHelpers.ts";
 | 
			
		||||
 | 
			
		||||
export const getSkuCatalogController: RequestHandler = (_req, res) => {
 | 
			
		||||
    res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" });
 | 
			
		||||
export const getSkuCatalogController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const account = await getAccountForRequest(req);
 | 
			
		||||
    if (!account.BuildLabel || version_compare(account.BuildLabel, "2025.10.14.16.10") >= 0) {
 | 
			
		||||
        res.sendFile("static/fixed_responses/getSkuCatalogU40.json", { root: "./" });
 | 
			
		||||
    } else {
 | 
			
		||||
        res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,5 @@
 | 
			
		||||
import type { RequestHandler } from "express";
 | 
			
		||||
import { getAccountIdForRequest } from "../../services/loginService.ts";
 | 
			
		||||
import { config } from "../../services/configService.ts";
 | 
			
		||||
import allScans from "../../../static/fixed_responses/allScans.json" with { type: "json" };
 | 
			
		||||
import { ExportEnemies } from "warframe-public-export-plus";
 | 
			
		||||
import { getInventory } from "../../services/inventoryService.ts";
 | 
			
		||||
import { getStats } from "../../services/statsService.ts";
 | 
			
		||||
import type { IStatsClient } from "../../types/statTypes.ts";
 | 
			
		||||
@ -12,7 +9,7 @@ const viewController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const inventory = await getInventory(accountId, "XPInfo");
 | 
			
		||||
    const playerStats = await getStats(accountId);
 | 
			
		||||
 | 
			
		||||
    const responseJson = playerStats.toJSON() as IStatsClient;
 | 
			
		||||
    const responseJson = playerStats.toJSON<IStatsClient>();
 | 
			
		||||
    responseJson.Weapons ??= [];
 | 
			
		||||
    for (const item of inventory.XPInfo) {
 | 
			
		||||
        const weaponIndex = responseJson.Weapons.findIndex(element => element.type == item.ItemType);
 | 
			
		||||
@ -22,24 +19,6 @@ const viewController: RequestHandler = async (req, res) => {
 | 
			
		||||
            responseJson.Weapons.push({ type: item.ItemType, xp: item.XP });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (config.unlockAllScans) {
 | 
			
		||||
        const scans = new Set(allScans);
 | 
			
		||||
        for (const type of Object.keys(ExportEnemies.avatars)) {
 | 
			
		||||
            if (!scans.has(type)) scans.add(type);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Take any existing scans and also set them to 9999
 | 
			
		||||
        if (responseJson.Scans) {
 | 
			
		||||
            for (const scan of responseJson.Scans) {
 | 
			
		||||
                scans.add(scan.type);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        responseJson.Scans = [];
 | 
			
		||||
 | 
			
		||||
        for (const type of scans) {
 | 
			
		||||
            responseJson.Scans.push({ type: type, scans: 9999 });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    res.json(responseJson);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -212,12 +212,12 @@ export const getInfNodes = (manifest: INemesisManifest, rank: number): IInfNode[
 | 
			
		||||
            value.systemIndex === systemIndex &&
 | 
			
		||||
            value.nodeType != 3 && // not hub
 | 
			
		||||
            value.nodeType != 7 && // not junction
 | 
			
		||||
            value.missionIndex && // must have a mission type and not assassination
 | 
			
		||||
            value.missionIndex != 28 && // not open world
 | 
			
		||||
            value.missionIndex != 32 && // not railjack
 | 
			
		||||
            value.missionIndex != 41 && // not saya's visions
 | 
			
		||||
            value.missionIndex != 42 && // not face off
 | 
			
		||||
            value.name.indexOf("1999NodeI") == -1 && // not stage defence
 | 
			
		||||
            value.missionType != "MT_ASSASSINATION" &&
 | 
			
		||||
            value.missionType != "MT_LANDSCAPE" &&
 | 
			
		||||
            value.missionType != "MT_RAILJACK" &&
 | 
			
		||||
            value.missionType != "MT_OFFERING" &&
 | 
			
		||||
            value.missionType != "MT_PVPVE" &&
 | 
			
		||||
            value.name.indexOf("1999NodeI") == -1 && // not stage defense
 | 
			
		||||
            value.name.indexOf("1999NodeJ") == -1 && // not lich bounty
 | 
			
		||||
            !isArchwingMission(value)
 | 
			
		||||
        ) {
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user