Skip to content

Commit

Permalink
remove dependency moment, fix es6, update doc
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmed-dinar committed Oct 6, 2016
1 parent a78ae65 commit 2f85a6d
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 44 deletions.
34 changes: 27 additions & 7 deletions README.md
@@ -1,16 +1,17 @@
# NodeJS Client Library for [Codeforces API](http://codeforces.com/api/help)

[![Build Status](https://travis-ci.org/ahmed-dinar/codeforces-api-node.svg?branch=master)](https://travis-ci.org/ahmed-dinar/codeforces-api-node)
[![Build Status](https://travis-ci.org/ahmed-dinar/codeforces-api-node.svg?branch=master)](https://travis-ci.org/ahmed-dinar/codeforces-api-node)
[![Coverage Status](https://coveralls.io/repos/github/ahmed-dinar/codeforces-api-node/badge.svg?branch=master)](https://coveralls.io/github/ahmed-dinar/codeforces-api-node?branch=master)
[![npm version](https://badge.fury.io/js/codeforces-api.svg)](https://badge.fury.io/js/codeforces-api)
[![Dependency Status](https://david-dm.org/ahmed-dinar/codeforces-api-node.svg)](https://david-dm.org/ahmed-dinar/codeforces-api-node)

codeforces-api-node is a simple NodeJS library for Codeforces Api with streaming support.

## Install
```
$ npm install codeforces-api
```

## Usage

codeforces-api-node supports both ES5 and ES6.
Expand Down Expand Up @@ -88,7 +89,7 @@ Codeforces.user.rating({ handle: 'user_handle' } , function (err, data) {

## Authorization

To access data, API and SECRET key must be needed.To generate API and SECRET KEY visit: [API Settings](http://codeforces.com/settings/api)
Although most of the method of the API supports anonymously request, ```codeforces-api-node``` does not allow anonymous request yet.To access API data, must set API and SECRET key before calling methods.To generate API and SECRET KEY visit: [API Settings](http://codeforces.com/settings/api)


## Return Data
Expand All @@ -107,10 +108,12 @@ Codeforces.user.rating({ handle: 'user_handle' } , function (err, data) {
You can stream responses to a file stream.When json data is huge, you may need this feature.

```javascript
Codeforces.user.ratedList( parameters, callback ).pipe( fs.createWriteStream('./rateedList.json') );
Codeforces.user.ratedList( parameters, callback )
.pipe( fs.createWriteStream('./rateedList.json') );

//version >= 1.0.2 (with or without callback)
Codeforces.user.ratedList( parameters ).pipe( fs.createWriteStream('./ratedList.json') );
Codeforces.user.ratedList( parameters )
.pipe( fs.createWriteStream('./ratedList.json') );
```

Also emits response events.
Expand All @@ -119,7 +122,7 @@ Also emits response events.
Codeforces.user.ratedList( parameters, function(err, data){

if(err){ //request error }

//data also available here

}).on('data', function(data) {
Expand All @@ -133,10 +136,27 @@ Codeforces.user.ratedList( parameters, function(err, data){
// compressed data as it is received
console.log('received ' + data.length + ' bytes of compressed data')
});

}).pipe( fs.createWriteStream('./ratedList.json') );
```
## Contributing
Everyone wellcome!
* Create an issue > [Fork](https://github.com/ahmed-dinar/codeforces-api-node/fork) > Create own branch > Commit changes > Push the branch > Creat pull request
## Test
Before running test, must set API and SECRET key in environment variable.Keys are:
```javascript
CFK = API Key
CFS = API Secret
```
After setting keys, simply run
```javascript
npm test
```
## License
##### MIT © [Ahmed Dinar](https://ahmeddinar.com/)
1 change: 0 additions & 1 deletion example/es5/api.js
Expand Up @@ -15,7 +15,6 @@ Codeforces.user.info(parameters, function (err,data) {
return console.log(err);
}
console.log(data);
console.log('huha');
});

/*
Expand Down
3 changes: 1 addition & 2 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "codeforces-api",
"version": "1.0.2",
"version": "1.0.3",
"description": "Codeforces API Client Library for Node.js",
"main": "dist/codeforces.js",
"scripts": {
Expand Down Expand Up @@ -28,7 +28,6 @@
"dependencies": {
"crypto-js": "^3.1.6",
"lodash": "^4.16.2",
"moment": "^2.15.1",
"qs": "^6.2.1",
"randomstring": "^1.1.5",
"request": "^2.75.0"
Expand Down
96 changes: 62 additions & 34 deletions src/codeforces.js
@@ -1,7 +1,6 @@
"use strict";

import * as _ from "lodash";
import moment from 'moment';
import qs from 'qs';
import randomstring from 'randomstring';
import Request from 'request/request';
Expand All @@ -13,20 +12,22 @@ import sha512 from 'crypto-js/sha512';
*/
class CF {

/**
* Class constructor, It will Set default routes and options
*/
constructor () {

//credentials for API call
//
// credentials for API call
//
this.options = {
API_URL: "http://codeforces.com/api",
API_KEY: "",
API_SECRET: "",
DEFAULT_TIMEOUT: 60000 //1 minute
};

//user method

//
// user method
//
this.user = {
blogEntries: callApi.bind(this,"user.blogEntries"),
friends: callApi.bind(this,"user.friends"),
Expand All @@ -36,7 +37,10 @@ class CF {
status: callApi.bind(this,"user.status")
};

//contest method

//
// contest method
//
this.contest = {
hacks: callApi.bind(this,"contest.hacks"),
list: callApi.bind(this,"contest.list"),
Expand All @@ -45,19 +49,28 @@ class CF {
status: callApi.bind(this,"contest.status")
};

//all problemset method

//
// all problemset method
//
this.problemset = {
problems: callApi.bind(this,"problemset.problems"),
recentStatus: callApi.bind(this,"problemset.recentStatus")
};

//blog method

//
// blog method
//
this.blogEntry = {
comments: callApi.bind(this,"blogEntry.comments"),
view: callApi.bind(this,"blogEntry.view")
};

//recent news method

//
// recent news method
//
this.recentActions = callApi.bind(this,"recentActions");
}

Expand All @@ -75,7 +88,7 @@ class CF {


/**
* About method and parameters, see official doc - http://codeforces.com/api/help/
* Send request to api
*
* @param {string} method - method of API request.
* @param {object} parameters - API url parameters
Expand All @@ -92,9 +105,9 @@ function callApi(method, parameters, callback) {
throw new Error('valid parameters object required.');
}

let opts = this.options;
var opts = this.options;

let noCallback = !callback || typeof callback !== 'function';
var noCallback = !callback || typeof callback !== 'function';
let noApiKey = typeof opts.API_KEY !== 'string' || opts.API_KEY.length === 0 || typeof opts.API_SECRET !== 'string' || opts.API_SECRET.length === 0;
if( noApiKey ){
if( noCallback ){
Expand All @@ -105,7 +118,9 @@ function callApi(method, parameters, callback) {

opts.method = method;

//target API url with hashes
//
// final API url with hashes
//
let url = makeApiUrl(opts, parameters);

let reqOptions = {
Expand All @@ -114,12 +129,17 @@ function callApi(method, parameters, callback) {
timeout: process.env.CF_TIMEOUT || opts.DEFAULT_TIMEOUT
};

//callback not exists, just return the request modules Request class instance for event

//
// callback not exists, just return the request modules Request class instance for event
//
if( noCallback ){
return new Request(reqOptions);
}

//callback exists, return Request for streaming and handle callback for error handling and custom formatted data
//
// callback exists, return Request for streaming and handle callback for error handling and custom formatted data
//
return callRequest(reqOptions, handleCallback.bind(null,callback) );
}

Expand All @@ -139,9 +159,11 @@ function handleCallback(callback, err, httpResponse, body) {
return callback(err);
}

//API returns error
//
// API returns error
//
if( body.status !== 'OK' ){
return callback(body.comment);
return callback(new Error(body.comment));
}

return callback(null, body.result);
Expand Down Expand Up @@ -169,39 +191,45 @@ function callRequest(options,callback) {
*/
function makeApiUrl(options,parameters) {

//main query to add in API url request
let query = parameters;
let curTime = moment().unix();
let randomToken = randomstring.generate(6);
var query = parameters;

query.time = curTime;
query.apiKey = options.API_KEY;

//if any parameter given as array, make it string separated by semicolon(;)
for(let key in parameters){
if( _.isArray(parameters[key]) ){
parameters[key] = _.join(parameters[key],';');
//
// If any parameter given in array, make it string separated by semicolon(;)
//
for(let key in query){
if( _.isArray(query[key]) ){
query[key] = _.join(query[key],';');
}
}

let curTime = Math.floor(Date.now() / 1000);
let randomToken = randomstring.generate(6);

query.time = curTime;
query.apiKey = options.API_KEY;

//sort the parameters according to codeforces API rules
//
// Sort parameters according to codeforces API rules
//
query = _
.chain(query)
.map( function(val, key) {
return { key: key, value: val }
.map( (value, key) => {
return { key, value };
})
.orderBy(['key', 'value'], ['desc', 'desc'])
.reverse()
.keyBy('key')
.mapValues('value')
.value();

let apiSig = randomToken + '/' + options.method + '?' + qs.stringify(query,{ encode: false }) + '#' + options.API_SECRET;
let qsFy = qs.stringify(query,{ encode: false });
let apiSig = `${randomToken}/${options.method}?${qsFy}#${options.API_SECRET}`;

apiSig = sha512(apiSig).toString();
query.apiSig = randomToken + apiSig;
qsFy = qs.stringify(query,{ encode: false });

let url = options.API_URL + '/' + options.method + '?' + qs.stringify(query,{ encode: false });
let url = `${options.API_URL}/${options.method}?${qsFy}`;

return url;
}
Expand Down

0 comments on commit 2f85a6d

Please sign in to comment.