A typescript npm package for interacting with data from ddnet.org
I was bored, and also I needed a decent package to use for interacting with ddnet.org programatically. It is also my first package and I tried my best to make it as good as possible.
Using your node package manager of choice, for example pnpm
:
$ pnpm add ddnet
Import the Player
class from the package and create a new instance of it.
import { Player } from 'ddnet';
const me = await Player.new('Sans3108');
console.log(me);
/*
Player {
name: 'Sans3108',
url: 'https://ddnet.org/players/Sans3108',
globalLeaderboard: GlobalLeaderboard {
completionist: { placement: 8462, points: 2740 },
team: null,
rank: null,
completionistLastMonth: { placement: 3003, points: 224 },
completionistLastWeek: { placement: 1570, points: 89 }
},
totalCompletionistPoints: 32136,
favoriteServer: 'GER',
finishes: Finishes {
first: Finish {
timestamp: 1628418102000,
mapName: 'Multeasymap',
timeSeconds: 2484.68,
timeString: '41:24',
rank: [Object],
region: 'UNK',
players: [Array]
},
recent: [
[RecentFinish],
...
]
},
... (lots of other data)
}
*/
You probably wouldn't want to log everything, just what you need. Examples below show how to log out the points of the player, the first finished map's name and the map with fastest finish time out of all the novice maps:
// Points
import { Player } from 'ddnet';
const player = await Player.new('Sans3108');
console.log(player.globalLeaderboard.completionist?.points); // 2740
// First finish
import { Player } from 'ddnet';
const player = await Player.new('Sans3108');
console.log(player.finishes.first.mapName); // "Multeasymap"
// Fastest finish time of all novice maps
import { Player, CompletedMapStats } from 'ddnet';
const player = await Player.new('Sans3108');
const completed = player.serverTypes.novice.maps.filter(map => map.finishCount > 0) as CompletedMapStats[];
const fastestTime = completed.sort((a, b) => a.bestTimeSeconds - b.bestTimeSeconds)[0];
console.log(`${fastestTime.mapName} ${fastestTime.bestTimeString}`); // "Tangerine 00:42"
Let's say you're not interested in player data that much, and you want to check out on some maps, to do that:
import { Map } from 'ddnet';
const map = await Map.new('Kobra 4');
console.log(map);
/*
Map {
name: 'Kobra 4',
url: 'https://ddnet.org/maps/Kobra-32-4',
thumbnailUrl: 'https://ddnet.org/ranks/maps/Kobra_4.png',
webPreviewUrl: 'https://ddnet.org/mappreview/?map=Kobra+4',
type: 'Novice',
points: 4,
difficulty: 4,
mappers: [
Author {
name: 'Zerodin',
mapShowcaseUrl: 'https://ddnet.org/mappers/Zerodin',
playerUrl: 'https://ddnet.org/players/Zerodin'
}
],
releasedTimestamp: 1438538340000,
... (lots of other data)
}
*/
Like before, this may be a bit much, so let's see some examples. Here are some showcasing how to get a map's author, the number of points it awards upon completion, and the current time record:
// Author
import { Map } from 'ddnet';
const map = await Map.new('Grandma');
console.log(map.mappers[0].name); // "Fňokurka oo7"
// If the map has multiple authors, then:
const map = await Map.new('Nagi');
console.log(map.mappers.map(a => a.name).join(' & ')); // "Cøke & Arrow"
// Points
import { Map } from 'ddnet';
const map = await Map.new('EasyRight');
console.log(map.points); // 4
// Current record
import { Map } from 'ddnet';
const map = await Map.new('Baby Aim 1.0');
console.log(`${map.finishes[0].players[0].name} ${map.finishes[0].timeString}`); // "Cireme 06:25"
I tried my hand at making a renderer based on TeeAssembler-2.0 by AlexIsTheGuy, and I think I nailed it.
Example usage:
import { TeeSkin6 } from 'ddnet';
const mySkin = new TeeSkin6({ skinResource: 'CrystalCat' });
const rendered = await mySkin.render({ eyeVariant: 'eye-happy' });
// Do something with the rendered skin buffer, like sending it in a message on discord as an attachment
// Or optionally, save it to a file by providing a file path, like this:
await mySkin.render({ eyeVariant: 'eye-happy', saveFilePath: 'my-skin.png' }); // Still returns a buffer
my-skin.png
I also tried creating a TW 0.7 skin renderer, which is similar to the 0.6 renderer but with a few changes.
Example usage:
import { TeeSkin7 } from 'ddnet';
const skin = new TeeSkin7({
body: 'fox',
marking: 'fox',
eyes: 'colorable'
});
const rendered = await skin.render({
customColors: {
bodyTWcode: 1102443,
markingTWcode: -485425166,
feetTWcode: 1102450,
eyesTWcode: 1441632
},
eyeVariant: 'eye-evil'
});
// Do something with the rendered skin buffer, like sending it in a message on discord as an attachment
// Or optionally, save it to a file by providing a file path, like this:
await skin.render({
customColors: {
bodyTWcode: 1102443,
markingTWcode: -485425166,
feetTWcode: 1102450,
eyesTWcode: 1441632
},
saveFilePath: 'fox.png',
eyeVariant: 'eye-evil'
}); // Still returns a buffer
fox.png
Most classes cache results, this is done in using the keyv and @keyv/sqlite packages. Cache data is stored in the ddnet_cache.sqlite
file.
It's not really wise to mess with the cache, but there's a couple things you can do, mainly changing the TTL (Time-To-Live) of the cached items* the class is responsible for, and clearing different cache pools.
* Applies to newly added items
For building this package yourself you will need at least Node v18.20.2, and some knowledge of typescript. Package manager choice should not matter, but for smooth operations I recommend pnpm
.
First, clone the repository and navigate to it:
$ git clone https://github.com/Sans3108/DDNet.git
$ cd DDNet
Install the dependencies with:
$ pnpm install
After that, you're ready to open up the project in your code editor of choice, or, if you don't wish to change anything, simply run the following command to build the project:
$ pnpm build
Additionaly you may re-build the typedoc
documentation website with:
$ pnpm typedoc
And after that everything in the /docs
directory should be up to date with your changes.
Help is always appreciated, if you are able to contribute and have the know-how, please do! I will look over every PR and potentially we can integrate your changes!
This readme may not showcase everything, but that's why the documentation website exists! Please check it out, explore and find what you need there if it wasn't shown here.
If something is missing or you would like to suggest something please submit an issue about it!
If you've made it this far and you consider this package useful, please consider starring this repository so more people can see it!
Any bugs can be reported here or on discord by adding me: Sans#0001
.