├── .gitignore ├── README.md ├── endpoints.json ├── index.js ├── package.json └── tools ├── endpoints-to-markdown.js └── extract-endpoints.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Trakt.tv API v2 2 | 3 | Node.js client for the [Trakt.tv API](http://docs.trakt.apiary.io/). 4 | 5 | Work in progress! 6 | 7 | ### Installation 8 | 9 | ``` 10 | $ npm i trakt-api 11 | ``` 12 | 13 | ### Quick example 14 | 15 | ```javascript 16 | var Trakt = require('trakt-api'); 17 | var trakt = Trakt(API_KEY[, OPTIONS]); 18 | 19 | // Promises... 20 | trakt.show('manhattan', { extended : 'full' }).then(function(show) { 21 | console.log('%j', show); 22 | }).catch(function(err) { 23 | console.warn('oh noes', err); 24 | }); 25 | 26 | // ...or regular callbacks 27 | trakt.show('manhattan', { extended : 'full' }, function(err, show) { 28 | if (err) return console.warn('oh noes', err); 29 | console.log('%j', show); 30 | }); 31 | ``` 32 | 33 | ### Constructor 34 | 35 | ```javascript 36 | var trakt = Trakt(API_KEY : String [, OPTIONS : Object]); 37 | ``` 38 | 39 | * `API_KEY` is the _Client ID_ value listed on your [application's page](https://trakt.tv/oauth/applications). You need to [create a new application](https://trakt.tv/oauth/applications/new) before you can use this API. 40 | * `OPTIONS` is an optional object that may contain the following properties: 41 | 42 | ``` 43 | extended : String // default "extended" level (default: 'min') 44 | logLevel : String // log level (default: 'info') 45 | poolSize : Number // HTTP request pool size (default: 5) 46 | timeout : Number // HTTP timeout in ms (default: 30000) 47 | ``` 48 | 49 | ### API methods 50 | 51 | Most API methods are generated from `endpoints.json`, although not all are implemented yet (see below). API methods generally have required and optional arguments. Required arguments are enforced and will cause an error to be thrown if not defined. 52 | 53 | Most API methods also accept an optional `OPTIONS` object. There's one valid option for now: 54 | 55 | * `extended`: use an [extended information level](http://docs.trakt.apiary.io/#introduction/extended-info) 56 | 57 | However, this option doesn't apply to all API methods. Refer to the Trakt API documentation. 58 | 59 | ##### Promises versus callbacks 60 | 61 | If you don't like promises, or just prefer old school callbacks, pass a function as last argument. Otherwise, all methods return a promise. See the example above. 62 | 63 | ### Methods currently implemented 64 | 65 | Please refer to the [API documentation](http://docs.trakt.apiary.io/) for more information on each method. 66 | 67 | ```javascript 68 | trakt.search(QUERY:String[, TYPE:String][, YEAR:Number][, CALLBACK]) 69 | 70 | trakt.searchAll(QUERY:String[, YEAR:Number][, CALLBACK]) 71 | 72 | trakt.searchShow(QUERY:String[, YEAR:Number][, CALLBACK]) 73 | 74 | trakt.searchMovie(QUERY:String[, YEAR:Number][, CALLBACK]) 75 | 76 | trakt.searchEpisode(QUERY:String[, YEAR:Number][, CALLBACK]) 77 | 78 | trakt.searchPerson(QUERY:String[, YEAR:Number][, CALLBACK]) 79 | 80 | trakt.calendarMyShowsNew([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 81 | 82 | trakt.calendarMyShowsPremieres([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 83 | 84 | trakt.calendarMyMovies([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 85 | 86 | trakt.calendarAllShows([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 87 | 88 | trakt.calendarAllShowsNews([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 89 | 90 | trakt.calendarAllShowsPremieres([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 91 | 92 | trakt.calendarAllMovies([START_DATE][, DAYS][, OPTIONS][, CALLBACK]) 93 | 94 | trakt.moviePopular([OPTIONS][, CALLBACK]) 95 | 96 | trakt.movieTrending([OPTIONS][, CALLBACK]) 97 | 98 | trakt.movieUpdates([START_DATE][, OPTIONS][, CALLBACK]) 99 | 100 | trakt.movie(ID[, OPTIONS][, CALLBACK]) 101 | 102 | trakt.movieAliases(ID[, OPTIONS][, CALLBACK]) 103 | 104 | trakt.movieReleases(ID[, COUNTRY][, OPTIONS][, CALLBACK]) 105 | 106 | trakt.movieTranslations(ID[, LANGUAGE][, OPTIONS][, CALLBACK]) 107 | 108 | trakt.movieComments(ID[, OPTIONS][, CALLBACK]) 109 | 110 | trakt.moviePeople(ID[, OPTIONS][, CALLBACK]) 111 | 112 | trakt.movieRatings(ID[, OPTIONS][, CALLBACK]) 113 | 114 | trakt.movieRelated(ID[, OPTIONS][, CALLBACK]) 115 | 116 | trakt.movieStats(ID[, OPTIONS][, CALLBACK]) 117 | 118 | trakt.movieWatching(ID[, OPTIONS][, CALLBACK]) 119 | 120 | trakt.showPopular([OPTIONS][, CALLBACK]) 121 | 122 | trakt.showTrending([OPTIONS][, CALLBACK]) 123 | 124 | trakt.showUpdates([START_DATE][, OPTIONS][, CALLBACK]) 125 | 126 | trakt.show(ID[, OPTIONS][, CALLBACK]) 127 | 128 | trakt.showAliases(ID[, OPTIONS][, CALLBACK]) 129 | 130 | trakt.showTranslations(ID[, LANGUAGE][, OPTIONS][, CALLBACK]) 131 | 132 | trakt.showComments(ID[, OPTIONS][, CALLBACK]) 133 | 134 | trakt.showProgressCollection(ID[, OPTIONS][, CALLBACK]) 135 | 136 | trakt.showProgressWatched(ID[, OPTIONS][, CALLBACK]) 137 | 138 | trakt.showPeople(ID[, OPTIONS][, CALLBACK]) 139 | 140 | trakt.showRatings(ID[, OPTIONS][, CALLBACK]) 141 | 142 | trakt.showRelated(ID[, OPTIONS][, CALLBACK]) 143 | 144 | trakt.showStats(ID[, OPTIONS][, CALLBACK]) 145 | 146 | trakt.showWatching(ID[, OPTIONS][, CALLBACK]) 147 | 148 | trakt.showSeasons(ID[, OPTIONS][, CALLBACK]) 149 | 150 | trakt.season(ID, SEASON[, OPTIONS][, CALLBACK]) 151 | 152 | trakt.seasonComments(ID, SEASON[, OPTIONS][, CALLBACK]) 153 | 154 | trakt.seasonRatings(ID, SEASON[, OPTIONS][, CALLBACK]) 155 | 156 | trakt.seasonStats(ID, SEASON[, OPTIONS][, CALLBACK]) 157 | 158 | trakt.seasonWatching(ID, SEASON[, OPTIONS][, CALLBACK]) 159 | 160 | trakt.episode(ID, SEASON, EPISODE[, OPTIONS][, CALLBACK]) 161 | 162 | trakt.episodeComments(ID, SEASON, EPISODE[, OPTIONS][, CALLBACK]) 163 | 164 | trakt.episodeRatings(ID, SEASON, EPISODE[, OPTIONS][, CALLBACK]) 165 | 166 | trakt.episodeStats(ID, SEASON, EPISODE[, OPTIONS][, CALLBACK]) 167 | 168 | trakt.episodeWatching(ID, SEASON, EPISODE[, OPTIONS][, CALLBACK]) 169 | 170 | trakt.userHistory(USERNAME[, TYPE][, ID][, CALLBACK]) 171 | 172 | trakt.userWatching(USERNAME[, CALLBACK]) 173 | 174 | ``` 175 | -------------------------------------------------------------------------------- /endpoints.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "", 4 | "method": "GET", 5 | "endpoint": "/oauth/authorize{?response_type,client_id,redirect_uri,state}", 6 | "params": { 7 | "response_type": { 8 | "required": true, 9 | "optional": false 10 | }, 11 | "client_id": { 12 | "required": true, 13 | "optional": false 14 | }, 15 | "redirect_uri": { 16 | "required": true, 17 | "optional": false 18 | }, 19 | "state": { 20 | "required": false, 21 | "optional": true 22 | } 23 | } 24 | }, 25 | { 26 | "name": "", 27 | "method": "POST", 28 | "endpoint": "/oauth/token", 29 | "params": { 30 | "Request": { 31 | "required": false, 32 | "optional": false 33 | }, 34 | "Response 200": { 35 | "required": false, 36 | "optional": false 37 | }, 38 | "Response 401": { 39 | "required": false, 40 | "optional": false 41 | }, 42 | "start_date": { 43 | "required": false, 44 | "optional": true 45 | }, 46 | "days": { 47 | "required": false, 48 | "optional": true 49 | } 50 | } 51 | }, 52 | { 53 | "name": "calendarMyShowsNew", 54 | "method": "GET", 55 | "endpoint": "/calendars/my/shows/new/{start_date}/{days}", 56 | "params": { 57 | "start_date": { 58 | "required": false, 59 | "optional": true 60 | }, 61 | "days": { 62 | "required": false, 63 | "optional": true 64 | } 65 | } 66 | }, 67 | { 68 | "name": "calendarMyShowsPremieres", 69 | "method": "GET", 70 | "endpoint": "/calendars/my/shows/premieres/{start_date}/{days}", 71 | "params": { 72 | "start_date": { 73 | "required": false, 74 | "optional": true 75 | }, 76 | "days": { 77 | "required": false, 78 | "optional": true 79 | } 80 | } 81 | }, 82 | { 83 | "name": "calendarMyMovies", 84 | "method": "GET", 85 | "endpoint": "/calendars/my/movies/{start_date}/{days}", 86 | "params": { 87 | "start_date": { 88 | "required": false, 89 | "optional": true 90 | }, 91 | "days": { 92 | "required": false, 93 | "optional": true 94 | } 95 | } 96 | }, 97 | { 98 | "name": "calendarAllShows", 99 | "method": "GET", 100 | "endpoint": "/calendars/all/shows/{start_date}/{days}", 101 | "params": { 102 | "start_date": { 103 | "required": false, 104 | "optional": true 105 | }, 106 | "days": { 107 | "required": false, 108 | "optional": true 109 | } 110 | } 111 | }, 112 | { 113 | "name": "calendarAllShowsNews", 114 | "method": "GET", 115 | "endpoint": "/calendars/all/shows/new/{start_date}/{days}", 116 | "params": { 117 | "start_date": { 118 | "required": false, 119 | "optional": true 120 | }, 121 | "days": { 122 | "required": false, 123 | "optional": true 124 | } 125 | } 126 | }, 127 | { 128 | "name": "calendarAllShowsPremieres", 129 | "method": "GET", 130 | "endpoint": "/calendars/all/shows/premieres/{start_date}/{days}", 131 | "params": { 132 | "start_date": { 133 | "required": false, 134 | "optional": true 135 | }, 136 | "days": { 137 | "required": false, 138 | "optional": true 139 | } 140 | } 141 | }, 142 | { 143 | "name": "calendarAllMovies", 144 | "method": "GET", 145 | "endpoint": "/calendars/all/movies/{start_date}/{days}", 146 | "params": { 147 | "start_date": { 148 | "required": false, 149 | "optional": true 150 | }, 151 | "days": { 152 | "required": false, 153 | "optional": true 154 | } 155 | } 156 | }, 157 | { 158 | "name": "", 159 | "method": "POST", 160 | "endpoint": "/checkin", 161 | "params": { 162 | "Request": { 163 | "required": false, 164 | "optional": false 165 | }, 166 | "Response 201": { 167 | "required": false, 168 | "optional": false 169 | }, 170 | "Response 409": { 171 | "required": false, 172 | "optional": false 173 | }, 174 | "id": { 175 | "required": true, 176 | "optional": false 177 | } 178 | } 179 | }, 180 | { 181 | "name": "", 182 | "method": "GET", 183 | "endpoint": "/comments/{id}/replies", 184 | "params": { 185 | "id": { 186 | "required": true, 187 | "optional": false 188 | } 189 | } 190 | }, 191 | { 192 | "name": "", 193 | "method": "POST", 194 | "endpoint": "/comments/{id}/like", 195 | "params": { 196 | "id": { 197 | "required": true, 198 | "optional": false 199 | }, 200 | "Request": { 201 | "required": false, 202 | "optional": false 203 | }, 204 | "Response 204": { 205 | "required": false, 206 | "optional": false 207 | }, 208 | "type": { 209 | "required": true, 210 | "optional": false 211 | } 212 | } 213 | }, 214 | { 215 | "name": "moviePopular", 216 | "method": "GET", 217 | "endpoint": "/movies/popular", 218 | "params": {} 219 | }, 220 | { 221 | "name": "movieTrending", 222 | "method": "GET", 223 | "endpoint": "/movies/trending", 224 | "params": {} 225 | }, 226 | { 227 | "name": "movieUpdates", 228 | "method": "GET", 229 | "endpoint": "/movies/updates/{start_date}", 230 | "params": { 231 | "start_date": { 232 | "required": false, 233 | "optional": true 234 | } 235 | } 236 | }, 237 | { 238 | "name": "movie", 239 | "method": "GET", 240 | "endpoint": "/movies/{id}", 241 | "params": { 242 | "id": { 243 | "required": true, 244 | "optional": false 245 | } 246 | } 247 | }, 248 | { 249 | "name": "movieAliases", 250 | "method": "GET", 251 | "endpoint": "/movies/{id}/aliases", 252 | "params": { 253 | "id": { 254 | "required": true, 255 | "optional": false 256 | } 257 | } 258 | }, 259 | { 260 | "name": "movieReleases", 261 | "method": "GET", 262 | "endpoint": "/movies/{id}/releases/{country}", 263 | "params": { 264 | "id": { 265 | "required": true, 266 | "optional": false 267 | }, 268 | "country": { 269 | "required": false, 270 | "optional": true 271 | } 272 | } 273 | }, 274 | { 275 | "name": "movieTranslations", 276 | "method": "GET", 277 | "endpoint": "/movies/{id}/translations/{language}", 278 | "params": { 279 | "id": { 280 | "required": true, 281 | "optional": false 282 | }, 283 | "language": { 284 | "required": false, 285 | "optional": true 286 | } 287 | } 288 | }, 289 | { 290 | "name": "movieComments", 291 | "method": "GET", 292 | "endpoint": "/movies/{id}/comments", 293 | "params": { 294 | "id": { 295 | "required": true, 296 | "optional": false 297 | } 298 | } 299 | }, 300 | { 301 | "name": "moviePeople", 302 | "method": "GET", 303 | "endpoint": "/movies/{id}/people", 304 | "params": { 305 | "id": { 306 | "required": true, 307 | "optional": false 308 | } 309 | } 310 | }, 311 | { 312 | "name": "movieRatings", 313 | "method": "GET", 314 | "endpoint": "/movies/{id}/ratings", 315 | "params": { 316 | "id": { 317 | "required": true, 318 | "optional": false 319 | } 320 | } 321 | }, 322 | { 323 | "name": "movieRelated", 324 | "method": "GET", 325 | "endpoint": "/movies/{id}/related", 326 | "params": { 327 | "id": { 328 | "required": true, 329 | "optional": false 330 | } 331 | } 332 | }, 333 | { 334 | "name": "movieStats", 335 | "method": "GET", 336 | "endpoint": "/movies/{id}/stats", 337 | "params": { 338 | "id": { 339 | "required": true, 340 | "optional": false 341 | } 342 | } 343 | }, 344 | { 345 | "name": "movieWatching", 346 | "method": "GET", 347 | "endpoint": "/movies/{id}/watching", 348 | "params": { 349 | "id": { 350 | "required": true, 351 | "optional": false 352 | } 353 | } 354 | }, 355 | { 356 | "name": "", 357 | "method": "GET", 358 | "endpoint": "/people/{id}", 359 | "params": { 360 | "id": { 361 | "required": true, 362 | "optional": false 363 | } 364 | } 365 | }, 366 | { 367 | "name": "", 368 | "method": "GET", 369 | "endpoint": "/people/{id}/movies", 370 | "params": { 371 | "id": { 372 | "required": true, 373 | "optional": false 374 | } 375 | } 376 | }, 377 | { 378 | "name": "", 379 | "method": "GET", 380 | "endpoint": "/people/{id}/shows", 381 | "params": { 382 | "id": { 383 | "required": true, 384 | "optional": false 385 | } 386 | } 387 | }, 388 | { 389 | "name": "", 390 | "method": "GET", 391 | "endpoint": "/recommendations/movies", 392 | "params": {} 393 | }, 394 | { 395 | "name": "", 396 | "method": "DELETE", 397 | "endpoint": "/recommendations/movies/{id}", 398 | "params": { 399 | "id": { 400 | "required": true, 401 | "optional": false 402 | }, 403 | "Request": { 404 | "required": false, 405 | "optional": false 406 | } 407 | } 408 | }, 409 | { 410 | "name": "", 411 | "method": "DELETE", 412 | "endpoint": "/recommendations/shows/{id}", 413 | "params": { 414 | "id": { 415 | "required": true, 416 | "optional": false 417 | }, 418 | "Request": { 419 | "required": false, 420 | "optional": false 421 | }, 422 | "Response 201": { 423 | "required": false, 424 | "optional": false 425 | }, 426 | "query": { 427 | "required": true, 428 | "optional": false 429 | }, 430 | "type": { 431 | "required": false, 432 | "optional": true 433 | }, 434 | "year": { 435 | "required": false, 436 | "optional": true 437 | } 438 | } 439 | }, 440 | { 441 | "name": "", 442 | "method": "GET", 443 | "endpoint": "/search{?id_type,id}", 444 | "params": { 445 | "id_type": { 446 | "required": true, 447 | "optional": false 448 | }, 449 | "id": { 450 | "required": true, 451 | "optional": false 452 | } 453 | } 454 | }, 455 | { 456 | "name": "showPopular", 457 | "method": "GET", 458 | "endpoint": "/shows/popular", 459 | "params": {} 460 | }, 461 | { 462 | "name": "showTrending", 463 | "method": "GET", 464 | "endpoint": "/shows/trending", 465 | "params": {} 466 | }, 467 | { 468 | "name": "showUpdates", 469 | "method": "GET", 470 | "endpoint": "/shows/updates/{start_date}", 471 | "params": { 472 | "start_date": { 473 | "required": false, 474 | "optional": true 475 | } 476 | } 477 | }, 478 | { 479 | "name": "show", 480 | "method": "GET", 481 | "endpoint": "/shows/{id}", 482 | "params": { 483 | "id": { 484 | "required": true, 485 | "optional": false 486 | } 487 | } 488 | }, 489 | { 490 | "name": "showAliases", 491 | "method": "GET", 492 | "endpoint": "/shows/{id}/aliases", 493 | "params": { 494 | "id": { 495 | "required": true, 496 | "optional": false 497 | } 498 | } 499 | }, 500 | { 501 | "name": "showTranslations", 502 | "method": "GET", 503 | "endpoint": "/shows/{id}/translations/{language}", 504 | "params": { 505 | "id": { 506 | "required": true, 507 | "optional": false 508 | }, 509 | "language": { 510 | "required": false, 511 | "optional": true 512 | } 513 | } 514 | }, 515 | { 516 | "name": "showComments", 517 | "method": "GET", 518 | "endpoint": "/shows/{id}/comments", 519 | "params": { 520 | "id": { 521 | "required": true, 522 | "optional": false 523 | } 524 | } 525 | }, 526 | { 527 | "name": "showProgressCollection", 528 | "method": "GET", 529 | "endpoint": "/shows/{id}/progress/collection", 530 | "params": { 531 | "id": { 532 | "required": true, 533 | "optional": false 534 | } 535 | } 536 | }, 537 | { 538 | "name": "showProgressWatched", 539 | "method": "GET", 540 | "endpoint": "/shows/{id}/progress/watched", 541 | "params": { 542 | "id": { 543 | "required": true, 544 | "optional": false 545 | } 546 | } 547 | }, 548 | { 549 | "name": "showPeople", 550 | "method": "GET", 551 | "endpoint": "/shows/{id}/people", 552 | "params": { 553 | "id": { 554 | "required": true, 555 | "optional": false 556 | } 557 | } 558 | }, 559 | { 560 | "name": "showRatings", 561 | "method": "GET", 562 | "endpoint": "/shows/{id}/ratings", 563 | "params": { 564 | "id": { 565 | "required": true, 566 | "optional": false 567 | } 568 | } 569 | }, 570 | { 571 | "name": "showRelated", 572 | "method": "GET", 573 | "endpoint": "/shows/{id}/related", 574 | "params": { 575 | "id": { 576 | "required": true, 577 | "optional": false 578 | } 579 | } 580 | }, 581 | { 582 | "name": "showStats", 583 | "method": "GET", 584 | "endpoint": "/shows/{id}/stats", 585 | "params": { 586 | "id": { 587 | "required": true, 588 | "optional": false 589 | } 590 | } 591 | }, 592 | { 593 | "name": "showWatching", 594 | "method": "GET", 595 | "endpoint": "/shows/{id}/watching", 596 | "params": { 597 | "id": { 598 | "required": true, 599 | "optional": false 600 | } 601 | } 602 | }, 603 | { 604 | "name": "showSeasons", 605 | "method": "GET", 606 | "endpoint": "/shows/{id}/seasons", 607 | "params": { 608 | "id": { 609 | "required": true, 610 | "optional": false 611 | } 612 | } 613 | }, 614 | { 615 | "name": "season", 616 | "method": "GET", 617 | "endpoint": "/shows/{id}/seasons/{season}", 618 | "params": { 619 | "id": { 620 | "required": true, 621 | "optional": false 622 | }, 623 | "season": { 624 | "required": true, 625 | "optional": false 626 | } 627 | } 628 | }, 629 | { 630 | "name": "seasonComments", 631 | "method": "GET", 632 | "endpoint": "/shows/{id}/seasons/{season}/comments", 633 | "params": { 634 | "id": { 635 | "required": true, 636 | "optional": false 637 | }, 638 | "season": { 639 | "required": true, 640 | "optional": false 641 | } 642 | } 643 | }, 644 | { 645 | "name": "seasonRatings", 646 | "method": "GET", 647 | "endpoint": "/shows/{id}/seasons/{season}/ratings", 648 | "params": { 649 | "id": { 650 | "required": true, 651 | "optional": false 652 | }, 653 | "season": { 654 | "required": true, 655 | "optional": false 656 | } 657 | } 658 | }, 659 | { 660 | "name": "seasonStats", 661 | "method": "GET", 662 | "endpoint": "/shows/{id}/seasons/{season}/stats", 663 | "params": { 664 | "id": { 665 | "required": true, 666 | "optional": false 667 | }, 668 | "season": { 669 | "required": true, 670 | "optional": false 671 | } 672 | } 673 | }, 674 | { 675 | "name": "seasonWatching", 676 | "method": "GET", 677 | "endpoint": "/shows/{id}/seasons/{season}/watching", 678 | "params": { 679 | "id": { 680 | "required": true, 681 | "optional": false 682 | }, 683 | "season": { 684 | "required": true, 685 | "optional": false 686 | } 687 | } 688 | }, 689 | { 690 | "name": "episode", 691 | "method": "GET", 692 | "endpoint": "/shows/{id}/seasons/{season}/episodes/{episode}", 693 | "params": { 694 | "id": { 695 | "required": true, 696 | "optional": false 697 | }, 698 | "season": { 699 | "required": true, 700 | "optional": false 701 | }, 702 | "episode": { 703 | "required": true, 704 | "optional": false 705 | } 706 | } 707 | }, 708 | { 709 | "name": "episodeComments", 710 | "method": "GET", 711 | "endpoint": "/shows/{id}/seasons/{season}/episodes/{episode}/comments", 712 | "params": { 713 | "id": { 714 | "required": true, 715 | "optional": false 716 | }, 717 | "season": { 718 | "required": true, 719 | "optional": false 720 | }, 721 | "episode": { 722 | "required": true, 723 | "optional": false 724 | } 725 | } 726 | }, 727 | { 728 | "name": "episodeRatings", 729 | "method": "GET", 730 | "endpoint": "/shows/{id}/seasons/{season}/episodes/{episode}/ratings", 731 | "params": { 732 | "id": { 733 | "required": true, 734 | "optional": false 735 | }, 736 | "season": { 737 | "required": true, 738 | "optional": false 739 | }, 740 | "episode": { 741 | "required": true, 742 | "optional": false 743 | } 744 | } 745 | }, 746 | { 747 | "name": "episodeStats", 748 | "method": "GET", 749 | "endpoint": "/shows/{id}/seasons/{season}/episodes/{episode}/stats", 750 | "params": { 751 | "id": { 752 | "required": true, 753 | "optional": false 754 | }, 755 | "season": { 756 | "required": true, 757 | "optional": false 758 | }, 759 | "episode": { 760 | "required": true, 761 | "optional": false 762 | } 763 | } 764 | }, 765 | { 766 | "name": "episodeWatching", 767 | "method": "GET", 768 | "endpoint": "/shows/{id}/seasons/{season}/episodes/{episode}/watching", 769 | "params": { 770 | "id": { 771 | "required": true, 772 | "optional": false 773 | }, 774 | "season": { 775 | "required": true, 776 | "optional": false 777 | }, 778 | "episode": { 779 | "required": true, 780 | "optional": false 781 | } 782 | } 783 | }, 784 | { 785 | "name": "", 786 | "method": "GET", 787 | "endpoint": "/sync/last_activities", 788 | "params": {} 789 | }, 790 | { 791 | "name": "", 792 | "method": "GET", 793 | "endpoint": "/sync/playback/{type}", 794 | "params": { 795 | "type": { 796 | "required": false, 797 | "optional": true 798 | } 799 | } 800 | }, 801 | { 802 | "name": "", 803 | "method": "GET", 804 | "endpoint": "/sync/collection/{type}", 805 | "params": { 806 | "type": { 807 | "required": true, 808 | "optional": false 809 | } 810 | } 811 | }, 812 | { 813 | "name": "", 814 | "method": "POST", 815 | "endpoint": "/sync/collection", 816 | "params": { 817 | "Request": { 818 | "required": false, 819 | "optional": false 820 | }, 821 | "Response 201": { 822 | "required": false, 823 | "optional": false 824 | }, 825 | "Response 200": { 826 | "required": false, 827 | "optional": false 828 | }, 829 | "type": { 830 | "required": true, 831 | "optional": false 832 | } 833 | } 834 | }, 835 | { 836 | "name": "", 837 | "method": "POST", 838 | "endpoint": "/sync/history", 839 | "params": { 840 | "Request": { 841 | "required": false, 842 | "optional": false 843 | }, 844 | "Response 201": { 845 | "required": false, 846 | "optional": false 847 | }, 848 | "Response 200": { 849 | "required": false, 850 | "optional": false 851 | }, 852 | "type": { 853 | "required": true, 854 | "optional": false 855 | }, 856 | "rating": { 857 | "required": false, 858 | "optional": true 859 | } 860 | } 861 | }, 862 | { 863 | "name": "", 864 | "method": "POST", 865 | "endpoint": "/sync/ratings", 866 | "params": { 867 | "Request": { 868 | "required": false, 869 | "optional": false 870 | }, 871 | "Response 201": { 872 | "required": false, 873 | "optional": false 874 | }, 875 | "Response 200": { 876 | "required": false, 877 | "optional": false 878 | }, 879 | "type": { 880 | "required": true, 881 | "optional": false 882 | } 883 | } 884 | }, 885 | { 886 | "name": "", 887 | "method": "POST", 888 | "endpoint": "/sync/watchlist", 889 | "params": { 890 | "Request": { 891 | "required": false, 892 | "optional": false 893 | }, 894 | "Response 201": { 895 | "required": false, 896 | "optional": false 897 | }, 898 | "Response 200": { 899 | "required": false, 900 | "optional": false 901 | } 902 | } 903 | }, 904 | { 905 | "name": "", 906 | "method": "GET", 907 | "endpoint": "/users/requests", 908 | "params": {} 909 | }, 910 | { 911 | "name": "", 912 | "method": "POST", 913 | "endpoint": "/users/requests/{id}", 914 | "params": { 915 | "id": { 916 | "required": true, 917 | "optional": false 918 | }, 919 | "Request": { 920 | "required": false, 921 | "optional": false 922 | }, 923 | "Response 200": { 924 | "required": false, 925 | "optional": false 926 | }, 927 | "Response 204": { 928 | "required": false, 929 | "optional": false 930 | }, 931 | "section": { 932 | "required": true, 933 | "optional": false 934 | }, 935 | "type": { 936 | "required": false, 937 | "optional": true 938 | } 939 | } 940 | }, 941 | { 942 | "name": "", 943 | "method": "GET", 944 | "endpoint": "/users/{username}", 945 | "params": { 946 | "username": { 947 | "required": true, 948 | "optional": false 949 | } 950 | } 951 | }, 952 | { 953 | "name": "", 954 | "method": "GET", 955 | "endpoint": "/users/{username}/collection/{type}", 956 | "params": { 957 | "username": { 958 | "required": true, 959 | "optional": false 960 | }, 961 | "type": { 962 | "required": true, 963 | "optional": false 964 | } 965 | } 966 | }, 967 | { 968 | "name": "", 969 | "method": "GET", 970 | "endpoint": "/users/{username}/lists", 971 | "params": { 972 | "username": { 973 | "required": true, 974 | "optional": false 975 | } 976 | } 977 | }, 978 | { 979 | "name": "", 980 | "method": "GET", 981 | "endpoint": "/users/{username}/lists/{id}", 982 | "params": { 983 | "username": { 984 | "required": true, 985 | "optional": false 986 | }, 987 | "id": { 988 | "required": true, 989 | "optional": false 990 | } 991 | } 992 | }, 993 | { 994 | "name": "", 995 | "method": "POST", 996 | "endpoint": "/users/{username}/lists/{id}/like", 997 | "params": { 998 | "username": { 999 | "required": true, 1000 | "optional": false 1001 | }, 1002 | "id": { 1003 | "required": true, 1004 | "optional": false 1005 | }, 1006 | "Request": { 1007 | "required": false, 1008 | "optional": false 1009 | }, 1010 | "Response 204": { 1011 | "required": false, 1012 | "optional": false 1013 | } 1014 | } 1015 | }, 1016 | { 1017 | "name": "", 1018 | "method": "POST", 1019 | "endpoint": "/users/{username}/lists/{id}/items/remove", 1020 | "params": { 1021 | "username": { 1022 | "required": true, 1023 | "optional": false 1024 | }, 1025 | "id": { 1026 | "required": true, 1027 | "optional": false 1028 | }, 1029 | "Request": { 1030 | "required": false, 1031 | "optional": false 1032 | }, 1033 | "Response 200": { 1034 | "required": false, 1035 | "optional": false 1036 | }, 1037 | "Response 201": { 1038 | "required": false, 1039 | "optional": false 1040 | }, 1041 | "Response 204": { 1042 | "required": false, 1043 | "optional": false 1044 | } 1045 | } 1046 | }, 1047 | { 1048 | "name": "", 1049 | "method": "GET", 1050 | "endpoint": "/users/{username}/following", 1051 | "params": { 1052 | "username": { 1053 | "required": true, 1054 | "optional": false 1055 | } 1056 | } 1057 | }, 1058 | { 1059 | "name": "", 1060 | "method": "GET", 1061 | "endpoint": "/users/{username}/friends", 1062 | "params": { 1063 | "username": { 1064 | "required": true, 1065 | "optional": false 1066 | } 1067 | } 1068 | }, 1069 | { 1070 | "name": "userHistory", 1071 | "method": "GET", 1072 | "endpoint": "/users/{username}/history/{type}/{id}", 1073 | "params": { 1074 | "username": { 1075 | "required": true, 1076 | "optional": false 1077 | }, 1078 | "type": { 1079 | "required": false, 1080 | "optional": true 1081 | }, 1082 | "id": { 1083 | "required": false, 1084 | "optional": true 1085 | } 1086 | } 1087 | }, 1088 | { 1089 | "name": "", 1090 | "method": "GET", 1091 | "endpoint": "/users/{username}/ratings/{type}/{rating}", 1092 | "params": { 1093 | "username": { 1094 | "required": true, 1095 | "optional": false 1096 | }, 1097 | "type": { 1098 | "required": true, 1099 | "optional": false 1100 | }, 1101 | "rating": { 1102 | "required": false, 1103 | "optional": true 1104 | } 1105 | } 1106 | }, 1107 | { 1108 | "name": "", 1109 | "method": "GET", 1110 | "endpoint": "/users/{username}/watchlist/{type}", 1111 | "params": { 1112 | "username": { 1113 | "required": true, 1114 | "optional": false 1115 | }, 1116 | "type": { 1117 | "required": true, 1118 | "optional": false 1119 | } 1120 | } 1121 | }, 1122 | { 1123 | "name": "userWatching", 1124 | "method": "GET", 1125 | "endpoint": "/users/{username}/watching", 1126 | "params": { 1127 | "username": { 1128 | "required": true, 1129 | "optional": false 1130 | } 1131 | } 1132 | }, 1133 | { 1134 | "name": "", 1135 | "method": "GET", 1136 | "endpoint": "/users/{username}/watched/{type}", 1137 | "params": { 1138 | "username": { 1139 | "required": true, 1140 | "optional": false 1141 | }, 1142 | "type": { 1143 | "required": true, 1144 | "optional": false 1145 | } 1146 | } 1147 | }, 1148 | { 1149 | "name": "", 1150 | "method": "GET", 1151 | "endpoint": "/users/{username}/stats", 1152 | "params": { 1153 | "username": { 1154 | "required": true, 1155 | "optional": false 1156 | } 1157 | } 1158 | } 1159 | ] 1160 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var norma = require('norma'); 3 | var lodash = require('lodash'); 4 | var request = require('request'); 5 | var Promise = require('bluebird').Promise; 6 | var logger = require('winston'); 7 | 8 | var DEFAULTS = { 9 | apiUrl : 'https://api-v2launch.trakt.tv', 10 | extended : 'min', 11 | logLevel : 'info', 12 | poolSize : 5, 13 | timeout : 30000 14 | }; 15 | 16 | var Trakt = module.exports = function Trakt(apiKey, opts) { 17 | if (! (this instanceof Trakt)) { 18 | return new Trakt(apiKey, opts); 19 | } 20 | assert(apiKey, 'missing API key'); 21 | 22 | // Merge options with defaults 23 | this.opts = lodash.merge({}, DEFAULTS, opts); 24 | 25 | // Create a request instance with the proper defaults. 26 | this.req = request.defaults({ 27 | pool : { maxSockets : this.opts.poolSize }, 28 | timeout : this.opts.timeout, 29 | headers : { 30 | 'trakt-api-key' : apiKey, 31 | 'trakt-api-version' : '2', 32 | 'content-type' : 'application/json', 33 | } 34 | }); 35 | 36 | // Set log level 37 | logger.level = this.opts.logLevel; 38 | logger.debug('initialized'); 39 | }; 40 | 41 | Trakt.prototype.request = function() { 42 | var args = norma('method:s endpoint:s endpointParams:o? qsParams:o? callback:f?', arguments); 43 | 44 | // Additional request parameters 45 | var params = args.qsParams || {}; 46 | if (! params.extended && this.opts.extended) { 47 | params.extended = this.opts.extended; 48 | } 49 | 50 | // Perform API request. 51 | var url = this.opts.apiUrl + this.expand(args.endpoint, args.endpointParams || {}); 52 | var req = this.req.bind(this.req); 53 | 54 | var _this = this; 55 | return new Promise(function(resolve, reject) { 56 | logger.debug('making API request', { url : url, method : args.method, qs : JSON.stringify(params) }); 57 | 58 | req({ 59 | method : args.method, 60 | url : url, 61 | qs : params, 62 | }, function(err, message, body) { 63 | 64 | // Reject errors outright. 65 | if (err) return reject(err); 66 | 67 | // Reject error status codes 68 | if (message.statusCode >= 400) { 69 | err = new Error('unexpected API response'); 70 | err.statusCode = message.statusCode; 71 | return reject(err); 72 | } 73 | 74 | // Handle "no content" status code. 75 | if (message.statusCode === 204) { 76 | return resolve(); 77 | } 78 | 79 | // Parse response. 80 | try { 81 | return resolve(JSON.parse(body)); 82 | } catch(e) { 83 | return reject(e); 84 | } 85 | 86 | }); 87 | }).nodeify(args.callback); 88 | }; 89 | 90 | // Dynamically generate API methods. 91 | require('./endpoints.json').forEach(function(endpoint) { 92 | if (! endpoint.name) return; 93 | 94 | // Compile arguments handler. 95 | var argspec = norma.compile(lodash.map(endpoint.params, function(flags, param) { 96 | return lodash.camelCase(param) + ':s|i' + (flags.optional ? '?' : ''); 97 | }).concat('opts:o?', 'callback:f?').join(' ')); 98 | 99 | // Create the API method. 100 | Trakt.prototype[endpoint.name] = function() { 101 | // Parse arguments. This throws if the method isn't 102 | // called with the proper (required) arguments. 103 | var args = argspec(arguments); 104 | 105 | // Collect parameters. 106 | var params = lodash(endpoint.params).keys().map(function(param) { 107 | return [ param, args[lodash.camelCase(param)] ]; 108 | }).zipObject().value(); 109 | 110 | // Perform the request. 111 | return this.request(endpoint.method, endpoint.endpoint, params, args.opts, args.callback); 112 | }; 113 | }); 114 | 115 | // Search methods. 116 | Trakt.prototype.search = Trakt.prototype.searchAll = function() { 117 | var args = norma('query:s type:s? year:i? callback:f?', arguments); 118 | var params = { 119 | query : args.query, 120 | type : args.type, 121 | year : args.year, 122 | }; 123 | return this.request('GET', '/search', {}, params, args.callback); 124 | }; 125 | 126 | [ 'Show', 'Movie', 'Episode', 'Person' ].forEach(function(type) { 127 | Trakt.prototype['search' + type] = function() { 128 | var args = [].slice.call(arguments); 129 | args.splice(1, 0, type.toLowerCase()); 130 | return this.search.apply(this, args); 131 | }; 132 | }); 133 | 134 | // Helper methods. 135 | Trakt.prototype.expand = function(template, params) { 136 | return template.replace(/{(.*?)}/g, function(m, b) { return params[b] || ''; }); 137 | }; 138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trakt-api", 3 | "version": "0.4.1", 4 | "description": "Trakt API v2 client", 5 | "main": "index.js", 6 | "author": "Robert Klep ", 7 | "license": "MIT", 8 | "repository": "robertklep/node-trakt-api", 9 | "dependencies": { 10 | "bluebird": "^2.9.25", 11 | "lodash": "^3.7.0", 12 | "norma": "^0.3.0", 13 | "request": "^2.55.0", 14 | "winston": "^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tools/endpoints-to-markdown.js: -------------------------------------------------------------------------------- 1 | var endpoints = require('../endpoints.json'); 2 | 3 | endpoints.forEach(function(endpoint) { 4 | if (! endpoint.name) return; 5 | endpoint.params.OPTIONS = { optional : true }; 6 | var params = Object.keys(endpoint.params || {}).map(function(param, i) { 7 | var arg = (i !== 0 ? ', ' : '') + param.toUpperCase(); 8 | if (endpoint.params[param].optional) { 9 | arg = '[' + arg + ']'; 10 | } 11 | return arg; 12 | }).join(''); 13 | console.log('trakt.%s(%s[, CALLBACK])\n', endpoint.name, params); 14 | }); 15 | -------------------------------------------------------------------------------- /tools/extract-endpoints.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var apidoc = fs.readFileSync('trakt.apib').toString(); 3 | var endpoints = apidoc.match(/^##\s(?:.|[\r\n])*?\[GET\]\s*$/gm); 4 | 5 | console.log('%j', endpoints.map(function(ep) { 6 | var endpoint = ep.match(/\[(.*?)\]/)[1]; 7 | var method = ep.match(/\[([A-Z]+)\]/m)[1]; 8 | 9 | // parameter handling 10 | var regexp = /^\s+\+\s+(.*?)\s+\((.*?)\)/gm; 11 | var matches, params = {}; 12 | while ((matches = regexp.exec(ep)) !== null) { 13 | var name = matches[1]; 14 | params[name] = { 15 | required : matches[2].indexOf('required') !== -1, 16 | optional : matches[2].indexOf('optional') !== -1, 17 | }; 18 | } 19 | return { 20 | name : '', 21 | method : method, 22 | endpoint : endpoint, 23 | params : params, 24 | }; 25 | })); 26 | --------------------------------------------------------------------------------