npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

codeforces-sdk

v0.0.4

Published

SDK for codeforces.com

Downloads

13

Readme

codeforces-sdk

Typescript sdk for codeforces.

Is it official?

Yes, it makes queries to official codeforces API.

Is it unofficial?

Yes, because it's implemented by contestant, not codeforces team.

Installation

npm i codeforces-sdk

Documentation

You can use official codeforces API documentation, but it's easier just use IDE's autocomplete.

From documentation:

API may be requested at most 1 time per two seconds

Sdk uses this limitation and rate limits requests to API, you don't need to manually sleep after each request.

Usage

You can use sdk with es-modules:

import { Types, API, init } from 'codeforces-sdk'

or with commonjs require:

const codeforces = require('codeforces-sdk')

API object has five members: blogEntry, contest, problemset, recentActions and user.

For example, if you want to execute contest.standings method, use API.contest.standings function.

This function has one object argument of type API.contest.StandingsOptions which is alias to

{
    contestId: ContestId
    asManager?: boolean,
    from?: number
    count?: number
    handles?: Handle | Array<Handle>
    room?: number
    showUnofficial?: boolean
}

All methods return classes which are one-to-one relation to codeforces objects:

  • BlogEntry
  • Comment
  • Contest
  • Hack
  • Member
  • Party
  • Problem
  • ProblemResult
  • ProblemStatistics
  • RanklistRow
  • RatingChange
  • RecentAction
  • Submission
  • User

And some more objects:

  • Problemset
  • Standings

Some objects are extended with extra fields for convenience: I've added contest: Contest field in RanklistRow and ProblemResult.

Class methods

Majority of classes (where applicable) has getLink(title) and toString() functions:

>>> user.getLink()
<a  href="https://codeforces.com/profile/Igorjan94">Igorjan94</a>

>>> user.getStyledLink()
<a class="rated-user user-violet" href="https://codeforces.com/profile/Igorjan94">Igorjan94</a>

>>> contest.getLink('Contest')
<a href="https://codeforces.com/contest/1777">Contest</a>

Method arguments

Every API method has exactly one object argument with the same name as method, but CamelCased and appended Options: e.g. contest.list has ContestListOptions argument.

You'll find illogical that, for example, contest.list method accepts object ({gym: boolean}) instead of one boolean argument (gym: boolean), but I believe that:

  • it's consistent with other methods;
  • it's more convenient to explicitly name arguments on caller's side (compare: list(false) and list({gym: false}). What does false mean in first case?);
  • if codeforces will add some arguments (e.g. limit: number, from: number) then sdk will be backward compatible: old code won't break on sdk update.

If you want to proxy function arguments, you can use MethodNameOptions types. It's available for every method. For example:

const multipleStandings = async (options: Omit<API.contest.StandingsOptions, 'contestId'>, ...contestIds: Array<number>) => {
    for (const contestId of contestIds) {
        const s = await API.contest.standings({
            ...options,
            contestId: contestId as Types.ContestId,
        })
        console.log(s.contest.toString())
    }
}

Type aliases

I use aliases SomethingId instead of number and Handle instead of string to avoid messing up function arguments.

For example:

const contestId = 1777 as Types.ContestId
const contest = await API.contest.standings({contestId, from: 1, showUnofficial: true})
const handle = contest.rows[0].party.members[0].name
//                                             ^-- by mistake you used `name` instead of `handle`
const users = await API.user.info({handles: [handle]})
//                                           ^-- Type 'string | undefined' is not assignable to type 'Handle'

This snippet won't compile. But if we use string instead of Handle it'll successfully compile and make request to codeforces API.

Authorization

All methods except user.friends allow anauthorized access, but if you want, you can use authorization:

import {Types, API, init} from 'codeforces-sdk'

const friendList = async () => {
    // Setup from object
    init.setDefaultOptions({
        key: 'key',
        secret: 'secret',
        lang: 'en',
    })

    // Or read from file (file should contain json like options object above)
    init.readDefaultOptionsFromFile('<path>')

    // Or use CODEFORCES_KEY, CODEFORCES_SECRET and CODEFORCES_LANG env options, for example
    // CODEFORCES_LANG=en node out/cjs/src/index.js

    const handles = await API.user.friends()
    console.log(handles)
    
    // Reset authorization
    init.setDefaultOptions({
        key: null,
        secret: null,
    })
}

Example

This example prints users who took part in ALL contestsIds and solved at least one problem in each contest ordered by sum of taken places:

// Import Types and API from sdk
import { Types, API } from 'codeforces-sdk'

// Declare function with one array argument of ContestId --- alias to number
const mergeContestStandings = async (contestIds: Array<Types.ContestId>) => {

    // Initialize empty map from user's handle to some stat object
    const contestants: Record<Types.Handle, {off: number, unoff: number, both: number, place: number}> = {}

    // Range-based for like in c++
    for (const contestId of contestIds) {

        // Query API method 'contest.standing' with query params
        const contest = await API.contest.standings({contestId, from: 1, showUnofficial: true})

        contest.rows
            // Filter participants who didn't solve any problem
            .filter(row => row.points > 0)
            // And for each other participant
            .forEach(row => {
                // Take participant's handle
                // Codeforces returns participants in array of 1..3 objects
                // Typescript allows you to access 0-th element, because we are sure,
                //   that there is at least one participant, so handle's type will be deduced as `Handle`
                //   Participant [1] and [2] are optional, so row.party.members[1].handle won't compile,
                //   because type of row.party.members[1] is `Member | undefined`
                //   If you are sure, that you some contest always had at least 2 participants,
                //   use `!`: `row.party.members[1]!.handle`
                const handle = row.party.members[0].handle

                // If our map doesn't have handle as key, initialize it with empty value.
                // Unlike c++ empty value in map is `undefined`
                if (!(handle in contestants))
                    contestants[handle] = {
                        both: 0,
                        off: 0,
                        place: 0,
                        // I like trailing comma to minimize git diff and simplify adding new elements
                        unoff: 0,
                    }

                // Now we are sure, than this object exists
                // C++: auto& contestant = contestants[handle]
                //          ^------ note that contestant is reference!
                const contestant = contestants[handle]

                // ParticipantType is enum with string values
                const isOff = row.party.participantType == Types.ParticipantType.CONTESTANT
                const isUnoff = row.party.participantType == Types.ParticipantType.OUT_OF_COMPETITION

                // Update stat
                if (isOff) ++contestant.off
                if (isUnoff) ++contestant.unoff
                if (isOff || isUnoff) {
                    ++contestant.both
                    contestant.place += row.rank
                }
            })
    }

    // Object entries is Array<[key, value]>
    const items = Object.entries(contestants)
        // Filter participants which took part in all contests
        .filter(([_, v]) => v.both == contestIds.length)
        // Extend stat with user's handle
        .map(([k, v]) => ({
            ...v,
            handle: k,
        }))

    // Sort them by place
    // Note that typescript comparator returns negative number when first one is greater, positive when less and zero if equal
    items.sort((a, b) => a.place - b.place)

    // Header line
    console.log('Handle place off unoff')
    // Just output all items
    // `${valiable}` is python's f-string: f'{variable}`
    console.log(items.map(v => `${v.handle} ${v.place} ${v.off} ${v.unoff}`).join('\n'))
}

You can find more examples here.

Exceptions

Every method can throw error. It throws CodeforcesError instance if sdk got request from codeforces and Error otherwise (e.g. network timeout).

Extra

If you want to output colorfull standings and handles in console, you can install cli-color and then use toString({withColor: true}) with Party, ProblemResult or User.

Example.

 Image

Tests

Now sdk has only compile tests: you can verify that all methods return proper type from documentation.

Bugs

If you find any bugs (I believe they exist!) feel free to create issue on github or notify anywhere you find me by my nickname Igorjan94.

Future plans

  • Implement unofficial methods like
    • Get submission text
    • Get problem samples
    • Get problem statement
    • ...
  • Add support to overload used colors
  • Runtime tests
  • More examples
  • ...

Contributors

Contribution

Fill free to PR or fork this repository!

LICENSE

MIT