// ==UserScript== // @name Trakt.tv | Trakt API Wrapper // @description Exposes an authenticated Trakt API Wrapper. Intended to run alongside other userscripts which require (authenticated) access to the Trakt API. See README for details. // @version 1.0.4 // @namespace https://github.com/Fenn3c401 // @author Fenn3c401 // @license GPL-3.0-or-later // @homepageURL https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection#readme // @supportURL https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection/issues // @updateURL https://update.greasyfork.org/scripts/564750.meta.js // @downloadURL https://raw.githubusercontent.com/Fenn3c401/Trakt.tv-Userscript-Collection/main/userscripts/dist/f785bub0.min.user.js // @icon https://trakt.tv/assets/logos/logomark.square.gradient-b644b16c38ff775861b4b1f58c1230f6a097a2466ab33ae00445a505c33fcb91.svg // @match https://trakt.tv/* // @match https://classic.trakt.tv/* // @run-at document-start // @grant unsafeWindow // @grant GM_info // @grant GM_getValue // @grant GM_setValue // @grant GM.xmlHttpRequest // @connect trakt.tv // ==/UserScript== /* README > Based on iDavide94/iFelix18's [Trakt API wrapper for userscripts](https://github.com/iDavide94) which in turn was based on > vankasteelj's [Trakt.tv API wrapper for Node.js](https://github.com/vankasteelj/trakt.tv). ### General - If you're not interested in the technical details, then the rest of the documentation here will probably be of little interest to you. This script will mostly speed up some of my other userscripts which would otherwise scrape the respective data instead of pulling it from the api (the api responses are cached for 8 hours). - This wrapper is entirely self sufficient and requires no prior configuration or user input. It creates its own api application, fetches the respective client credentials and authenticates the user on the fly when a method is called. You can change the name and description of that api application to whatever you like, however please keep in mind that the name (in addition to the id) is used for checking for an existing application before creating a new one (useful after script reinstall). If you delete it a new one will be created in its place when the wrapper is used again. - Only works if a user is logged in. - The methods were last updated in Jan. 2026. ### Usage - The wrapper is exposed through `window.userscriptTraktApiWrapper` and can be used like:
`const { data, meta } = await userscriptTraktApiWrapper.search.id({ id_type: 'trakt', id: 1234, type: 'episode', extended: 'full', _auth: true, _meta: true, _revalidate: true });`
There are two types of props you can pass to a method, parameters corresponding to those listed in the Trakt API docs and options (denoted by a leading `_`) for the wrapper itself. - ***Parameters***
There are three categories: path parameters, search parameters, and the props for the request's body. First the mandatory and optional parameters for the path are filled in, then for `GET`/`DELETE` requests the remaining parameters are appended as search parameters, whereas for `POST`/`PUT` requests they are added to the body. - ***Options***
- `_method` - The type of HTTP request to make, normally supplied by the method config, but as with all options you can override it. - `_path` - The path template to use, normally supplied by the method config. Override it in case it's outdated. - `_auth` - Whether to authenticate the request. Normally supplied by the method config for methods with mandatory authentication. Override it to enable optional authentication (which also has a separate rate limit). - `_meta` - Whether to wrap the response's data in an object with the supplied trakt header metadata like: `{ data: returned_data, meta: { pagination_page: 3 } }`. `meta` includes all the trakt `x-`-prefixed headers and a couple select others in a normalized form to allow for dot-syntax access. Type coercion is done for numbers and booleans. It's also included as `parsedTraktHeaders` with the raw response object which get's thrown in case of a failed request. - `_revalidate` - Whether to revalidate the data instead of directly pulling it from the disk cache when possible. - `_retry` - Configuration for a basic exponential backoff based retry mechanism. By default only activated for authed `POST`/`PUT`/`DELETE` requests. Doesn't use the `ratelimit` and `retry_after` trakt headers. Takes a config object like `{ limit: 5, req_delay: 1000, resp_delay: 0 }`, with each retry `limit` gets decremented (= 5 retries) and `req_delay` doubled. If you want to turn it off you can just override it with `_retry: null`. - In the userscript storage tab you can change the `apiUrl` to `https://api-staging.trakt.tv` for a sandbox environment and you can activate console logging of all api requests with `logApiRequests`. - There's built-in rate-limiting for authed `POST`/`PUT`/`DELETE` requests (1/sec), which is complemented by the default `_retry` config, so you can just make a bunch of requests at once and they'll be queued up and executed one by one in the same order in which you made them (retries will block the queue). */ "use strict";const logger={_defaults:{title:(typeof moduleName<"u"?moduleName:GM_info.script.name).replace("Trakt.tv","Userscript"),toast:!0,toastrOpt:{positionClass:"toast-top-right",timeOut:1e4,progressBar:!0},toastrStyles:"#toast-container#toast-container a { color: #fff !important; border-bottom: dotted 1px #fff; }"},_print(e,o,t="",r={}){const{title:a=this._defaults.title,toast:s=this._defaults.toast,toastrOpt:h,toastrStyles:i="",consoleStyles:d="",data:_}=r,p=`${t}${_!==void 0?" See console for details.":""}`;console[e](`%c${a}: ${t}`,d,..._!==void 0?[_]:[]),s&&unsafeWindow.toastr?.[o](p,a,{...this._defaults.toastrOpt,...h})},info(e,o){this._print("info","info",e,o)},success(e,o){this._print("info","success",e,{consoleStyles:"color:#00c853;",...o})},warning(e,o){this._print("warn","warning",e,o)},error(e,o){this._print("error","error",e,o)}},gmStorage={logApiRequests:!1,apiUrl:"https://api.trakt.tv",app:{},auth:{},...GM_getValue("traktApiWrapper")};GM_setValue("traktApiWrapper",gmStorage);const userslug=document.cookie.match(/(?:^|; )trakt_userslug=([^;]*)/)?.[1];let authedNonGetReqChain=Promise.resolve(),activeFetchApp=Promise.resolve(),activeFetchAuth=Promise.resolve(),methods;userslug&&(unsafeWindow.userscriptTraktApiWrapper=methods=buildMethods(getRawMethods()));async function callMethod(e){const o=Object.groupBy(Object.entries(e),([s])=>s.startsWith("_")?"opts":"params"),t=Object.fromEntries(o.opts??[]),r=Object.fromEntries(o.params??[]);Object.assign(gmStorage,GM_getValue("traktApiWrapper"));const a={method:t._method,...t._revalidate!=null&&{revalidate:!!t._revalidate},responseType:"json",headers:{"content-type":"application/json","user-agent":"TraktApiWrapper/v1","trakt-api-version":"2"}};return a.url=gmStorage.apiUrl+t._path.replaceAll(/:(\?)?(\w+)/g,(s,h,i)=>{if(r[i]!=null)return delete r[i],e[i];if(h)return"";throw new Error(`Missing mandatory path parameter: ${i}`)}),/GET|DELETE/.test(t._method)?a.url=a.url+(Object.keys(r).length?`?${new URLSearchParams(r)}`:""):/POST|PUT/.test(t._method)&&(a.data=JSON.stringify(r)),await activeFetchApp,(!gmStorage.app.clientId||!gmStorage.app.clientSecret||!gmStorage.app.redirectUri)&&(activeFetchApp=fetchAppClientCreds(),await activeFetchApp),a.headers["trakt-api-key"]=gmStorage.app.clientId,t._auth&&(await activeFetchAuth,(!gmStorage.auth.accessToken||!gmStorage.auth.expiresAt||gmStorage.auth.expiresAt{authedNonGetReqChain=authedNonGetReqChain.then(()=>{const i=sendApiRequest(a,{_retry:{limit:5,req_delay:1100,resp_delay:1100},...t});return h(i),new Promise(d=>{setTimeout(()=>i.finally(d),1100)})})}):sendApiRequest(a,t)}function sendApiRequest(e,o){return GM.xmlHttpRequest(e).then(t=>{if(gmStorage.logApiRequests&&logger.info(`${e.method}: ${e.url}`,{toast:!1,data:{req:e,resp:t}}),t.parsedTraktHeaders=parseTraktHeaders(t.responseHeaders),t.status>=200&&t.status<300)return o._meta?{data:t.response,meta:t.parsedTraktHeaders}:t.response;if(t.status===429&&o._retry?.limit){const r={...o,_retry:{limit:o._retry.limit-1,req_delay:o._retry.req_delay*2}};return new Promise(a=>setTimeout(()=>a(sendApiRequest(e,r)),o._retry.req_delay)).then(a=>new Promise(s=>setTimeout(()=>s(a),o._retry.resp_delay)))}throw t.status===401&&!t.parsedTraktHeaders.private_user&&(logger.warning("Auth tokens might be invalid and have been cleared.",{data:gmStorage.auth}),gmStorage.auth={},GM_setValue("traktApiWrapper",gmStorage)),t.status===403&&(logger.warning("Client credentials might be invalid and have been cleared.",{data:gmStorage}),gmStorage.app={id:gmStorage.app.id},gmStorage.auth={},GM_setValue("traktApiWrapper",gmStorage)),t})}function parseTraktHeaders(e){const o=e.split(/\r?\n/).map(r=>r.split(":")).map(([r,a])=>[r.trim().toLowerCase(),a?.trim()]),t=o.find(([r])=>r==="access-control-expose-headers")[1].toLowerCase().split(",");return Object.fromEntries(o.filter(([r])=>t.includes(r)).map(([r,a])=>[(r.startsWith("x-")?r.slice(2):r).replaceAll("-","_"),a==="true"?!0:a==="false"?!1:/^-?\d*\.?\d+$/.test(a)?+a:a]))}async function fetchAppClientCreds(){try{logger.info("No valid client credentials found. Checking for matching application..");const e="Trakt API Wrapper for Userscripts";let o;const t=await fetch("/oauth/applications").then(a=>a.text()).then(a=>new DOMParser().parseFromString(a,"text/html")),r=[...t.querySelectorAll('#authorized-applications > .row:has(.label-success) h4 a[href^="/oauth/applications/"]')].find(a=>{const s=gmStorage.app.id?a.getAttribute("href").endsWith(gmStorage.app.id):!1,h=a.textContent.trim().toLowerCase()===e.toLowerCase();return s||h});if(r&&(o=await fetch(r.getAttribute("href")).then(a=>a.text()).then(a=>new DOMParser().parseFromString(a,"text/html"))),!o){const a=new FormData;[["authenticity_token",t.querySelector('head meta[name="csrf-token"]').content],["doorkeeper_application[name]",e],["doorkeeper_application[description]","A userscript which provides authenticated Trakt API access to other userscripts. https://github.com/Fenn3c401/Trakt.tv-Userscript-Collection"],["doorkeeper_application[redirect_uri]","https://trakt.tv/dashboard"],["doorkeeper_application[origins]","https://trakt.tv"],["doorkeeper_application[icon]",new Blob([],{type:"application/octet-stream"}),""],["doorkeeper_application[checkin]","0"],["doorkeeper_application[checkin]","1"],["doorkeeper_application[scrobble]","0"],["doorkeeper_application[scrobble]","1"],["commit","Save App"]].forEach(h=>a.append(...h));const s=await GM.xmlHttpRequest({url:"/oauth/applications",method:"POST",data:a,headers:{referer:"https://trakt.tv/oauth/applications/new"}});s.status>=200&&s.status<300&&(o=new DOMParser().parseFromString(s.responseText,"text/html"),logger.info(`No matching application found. New Trakt API application has been created. ${s.finalUrl}`))}GM_setValue("traktApiWrapper",Object.assign(gmStorage,{app:{clientId:o.querySelector("#authorized-applications .credentials:nth-child(1 of .credentials) code").textContent,clientSecret:o.querySelector("#authorized-applications .credentials:nth-child(2 of .credentials) code").textContent,redirectUri:o.querySelector(".redirect-uris code").textContent,id:+o.querySelector('.btn.update[href$="edit"]').getAttribute("href").match(/\d+/)[0]}})),logger.success("Successfully fetched client credentials!",{data:gmStorage.app})}catch(e){throw logger.error("Failed to fetch client credentials!"),e}}async function fetchAuthTokens(){try{const e=!gmStorage.auth.refreshToken||gmStorage.auth.userslug!==userslug;let o;if(e){logger.info("No valid refresh token found. Authorizing application..");const r=new URLSearchParams({response_type:"code",client_id:gmStorage.app.clientId,redirect_uri:gmStorage.app.redirectUri}),a=`${gmStorage.apiUrl.replace(/api[.-]/,"")}/oauth/authorize?${r}`,s=await fetch(a).then(i=>i.text()).then(i=>new DOMParser().parseFromString(i,"text/html")),h=await GM.xmlHttpRequest({method:"POST",url:"/oauth/authorize",headers:{referer:a},data:new URLSearchParams([["authenticity_token",s.querySelector('head meta[name="csrf-token"]').content],["client_id",gmStorage.app.clientId],["redirect_uri",gmStorage.app.redirectUri],["state",""],["response_type","code"],["response_mode","query"],["scope","public"],["nonce",""],["code_challenge",""],["code_challenge_method",""],["commit","Yes"]])});h.status>=200&&h.status<300?(o=new URL(h.finalUrl).searchParams.get("code"),logger.success("Application successfully authorized!",{data:{code:o}})):(gmStorage.app={id:gmStorage.app.id},GM_setValue("traktApiWrapper",gmStorage),logger.warning("Client credentials might be invalid and have been cleared."))}const t=await methods.oauth.get({client_id:gmStorage.app.clientId,client_secret:gmStorage.app.clientSecret,redirect_uri:gmStorage.app.redirectUri,grant_type:e?"authorization_code":"refresh_token",...e?{code:o}:{refresh_token:gmStorage.auth.refreshToken}});GM_setValue("traktApiWrapper",Object.assign(gmStorage,{auth:{accessToken:t.access_token,expiresAt:(t.created_at+t.expires_in)*1e3,refreshToken:t.refresh_token,userslug}}))}catch(e){throw logger.error("Failed to fetch authentication tokens!"),e}}function buildMethods(e,o){if(e._path=(o??"")+(e._path??""),e._method)return t=>callMethod({...e,...t});for(const t in e)t.startsWith("_")||(e[t]=buildMethods(e[t],e._path));return delete e._path,e}function getRawMethods(){return{oauth:{_path:"/oauth",get:{_method:"POST",_path:"/token"},revoke:{_method:"POST",_path:"/revoke"}},calendars:{_path:"/calendars",my:{_path:"/my",shows:{_method:"GET",_path:"/shows/:?start_date/:?days",_auth:!0},new_shows:{_method:"GET",_path:"/shows/new/:?start_date/:?days",_auth:!0},season_premieres:{_method:"GET",_path:"/shows/premieres/:?start_date/:?days",_auth:!0},finales:{_method:"GET",_path:"/shows/finales/:?start_date/:?days",_auth:!0},movies:{_method:"GET",_path:"/movies/:?start_date/:?days",_auth:!0},streaming:{_method:"GET",_path:"/streaming/:?start_date/:?days",_auth:!0},dvd:{_method:"GET",_path:"/dvd/:?start_date/:?days",_auth:!0}},all:{_path:"/all",shows:{_method:"GET",_path:"/shows/:?start_date/:?days"},new_shows:{_method:"GET",_path:"/shows/new/:?start_date/:?days"},season_premieres:{_method:"GET",_path:"/shows/premieres/:?start_date/:?days"},finales:{_method:"GET",_path:"/shows/finales/:?start_date/:?days"},movies:{_method:"GET",_path:"/movies/:?start_date/:?days"},streaming:{_method:"GET",_path:"/streaming/:?start_date/:?days"},dvd:{_method:"GET",_path:"/dvd/:?start_date/:?days"}}},checkin:{_path:"/checkin",add:{_method:"POST",_auth:!0},remove:{_method:"DELETE",_auth:!0}},certifications:{_method:"GET",_path:"/certifications/:type"},comments:{_path:"/comments",comment:{add:{_method:"POST",_auth:!0},get:{_method:"GET",_path:"/:id"},update:{_method:"PUT",_path:"/:id",_auth:!0},remove:{_method:"DELETE",_path:"/:id",_auth:!0}},replies:{_path:"/:id/replies",get:{_method:"GET"},add:{_method:"POST",_auth:!0}},item:{_method:"GET",_path:"/:id/item"},likes:{_path:"/:id",get:{_method:"GET",_path:"/likes"},add:{_method:"POST",_path:"/like",_auth:!0},remove:{_method:"DELETE",_path:"/like",_auth:!0}},trending:{_method:"GET",_path:"/trending/:?comment_type/:?type"},recent:{_method:"GET",_path:"/recent/:?comment_type/:?type"},updates:{_method:"GET",_path:"/updates/:?comment_type/:?type"}},countries:{_method:"GET",_path:"/countries/:type"},genres:{_method:"GET",_path:"/genres/:type"},languages:{_method:"GET",_path:"/languages/:type"},lists:{_path:"/lists",trending:{_method:"GET",_path:"/trending/:?type"},popular:{_method:"GET",_path:"/popular/:?type"},list:{_path:"/:id",get:{_method:"GET"},items:{_method:"GET",_path:"/items/:?type/:?sort_by/:?sort_how"},comments:{_method:"GET",_path:"/comments/:?sort"},likes:{get:{_method:"GET",_path:"/likes"},add:{_method:"POST",_path:"/like",_auth:!0},remove:{_method:"DELETE",_path:"/like",_auth:!0}}}},movies:{_path:"/movies",trending:{_method:"GET",_path:"/trending"},popular:{_method:"GET",_path:"/popular"},favorited:{_method:"GET",_path:"/favorited/:?period"},played:{_method:"GET",_path:"/played/:?period"},watched:{_method:"GET",_path:"/watched/:?period"},collected:{_method:"GET",_path:"/collected/:?period"},anticipated:{_method:"GET",_path:"/anticipated"},boxoffice:{_method:"GET",_path:"/boxoffice"},updates:{_method:"GET",_path:"/updates/:?start_date"},updated_ids:{_method:"GET",_path:"/updates/id/:?start_date"},summary:{_method:"GET",_path:"/:id"},aliases:{_method:"GET",_path:"/:id/aliases"},releases:{_method:"GET",_path:"/:id/releases/:?country"},translations:{_method:"GET",_path:"/:id/translations/:?language"},comments:{_method:"GET",_path:"/:id/comments/:?sort"},lists:{_method:"GET",_path:"/:id/lists/:?type/:?sort"},people:{_method:"GET",_path:"/:id/people"},ratings:{_method:"GET",_path:"/:id/ratings"},related:{_method:"GET",_path:"/:id/related"},stats:{_method:"GET",_path:"/:id/stats"},studios:{_method:"GET",_path:"/:id/studios"},watching:{_method:"GET",_path:"/:id/watching"},videos:{_method:"GET",_path:"/:id/videos"},refresh:{_method:"POST",_path:"/:id/refresh",_auth:!0}},networks:{_method:"GET",_path:"/networks"},notes:{_path:"/notes",add:{_method:"POST",_auth:!0},get:{_method:"GET",_path:"/:id",_auth:!0},update:{_method:"PUT",_path:"/:id",_auth:!0},remove:{_method:"DELETE",_path:"/:id",_auth:!0},item:{_method:"GET",_path:"/:id/item"}},people:{_path:"/people",updates:{_method:"GET",_path:"/updates/:?start_date"},updated_ids:{_method:"GET",_path:"/updates/id/:?start_date"},summary:{_method:"GET",_path:"/:id"},movies:{_method:"GET",_path:"/:id/movies"},shows:{_method:"GET",_path:"/:id/shows"},lists:{_method:"GET",_path:"/:id/lists/:?type/:?sort"},refresh:{_method:"POST",_path:"/:id/refresh",_auth:!0}},recommendations:{_path:"/recommendations",movies:{_path:"/movies",get:{_method:"GET",_auth:!0},hide:{_method:"DELETE",_path:"/:id",_auth:!0}},shows:{_path:"/shows",get:{_method:"GET",_auth:!0},hide:{_method:"DELETE",_path:"/:id",_auth:!0}}},scrobble:{_path:"/scrobble",start:{_method:"POST",_path:"/start",_auth:!0},stop:{_method:"POST",_path:"/stop",_auth:!0}},search:{_path:"/search",text:{_method:"GET",_path:"/:type"},id:{_method:"GET",_path:"/:id_type/:id"}},shows:{_path:"/shows",trending:{_method:"GET",_path:"/trending"},popular:{_method:"GET",_path:"/popular"},favorited:{_method:"GET",_path:"/favorited/:?period"},played:{_method:"GET",_path:"/played/:?period"},watched:{_method:"GET",_path:"/watched/:?period"},collected:{_method:"GET",_path:"/collected/:?period"},anticipated:{_method:"GET",_path:"/anticipated"},updates:{_method:"GET",_path:"/updates/:?start_date"},updated_ids:{_method:"GET",_path:"/updates/id/:?start_date"},summary:{_method:"GET",_path:"/:id"},aliases:{_method:"GET",_path:"/:id/aliases"},certifications:{_method:"GET",_path:"/:id/certifications"},translations:{_method:"GET",_path:"/:id/translations/:?language"},comments:{_method:"GET",_path:"/:id/comments/:?sort"},lists:{_method:"GET",_path:"/:id/lists/:?type/:?sort"},progress:{_path:"/:id/progress",collection:{_method:"GET",_path:"/collection",_auth:!0},watched:{_method:"GET",_path:"/watched",_auth:!0},reset:{_method:"POST",_path:"/watched/reset",_auth:!0},undo_reset:{_method:"DELETE",_path:"/watched/reset",_auth:!0}},people:{_method:"GET",_path:"/:id/people"},ratings:{_method:"GET",_path:"/:id/ratings"},related:{_method:"GET",_path:"/:id/related"},stats:{_method:"GET",_path:"/:id/stats"},studios:{_method:"GET",_path:"/:id/studios"},watching:{_method:"GET",_path:"/:id/watching"},next_episode:{_method:"GET",_path:"/:id/next_episode"},last_episode:{_method:"GET",_path:"/:id/last_episode"},videos:{_method:"GET",_path:"/:id/videos"},refresh:{_method:"POST",_path:"/:id/refresh",_auth:!0}},seasons:{_path:"/shows/:id/seasons",summary:{_method:"GET"},season:{_method:"GET",_path:"/:season/info"},episodes:{_method:"GET",_path:"/:season"},translations:{_method:"GET",_path:"/:season/translations/:?language"},comments:{_method:"GET",_path:"/:season/comments/:?sort"},lists:{_method:"GET",_path:"/:season/lists/:?type/:?sort"},people:{_method:"GET",_path:"/:season/people"},ratings:{_method:"GET",_path:"/:season/ratings"},stats:{_method:"GET",_path:"/:season/stats"},watching:{_method:"GET",_path:"/:season/watching"},videos:{_method:"GET",_path:"/:season/videos"}},episodes:{_path:"/shows/:id/seasons/:season/episodes/:episode",summary:{_method:"GET"},translations:{_method:"GET",_path:"/translations/:?language"},comments:{_method:"GET",_path:"/comments/:?sort"},lists:{_method:"GET",_path:"/lists/:?type/:?sort"},people:{_method:"GET",_path:"/people"},ratings:{_method:"GET",_path:"/ratings"},stats:{_method:"GET",_path:"/stats"},watching:{_method:"GET",_path:"/watching"},videos:{_method:"GET",_path:"/videos"}},sync:{_path:"/sync",last_activities:{_method:"GET",_path:"/last_activities",_auth:!0},playback:{_path:"/playback",get:{_method:"GET",_path:"/:?type",_auth:!0},remove:{_method:"DELETE",_path:"/:id",_auth:!0}},collection:{_path:"/collection",get:{_method:"GET",_path:"/:type",_auth:!0},add:{_method:"POST",_auth:!0},remove:{_method:"POST",_path:"/remove",_auth:!0}},watched:{_method:"GET",_path:"/watched/:type",_auth:!0},history:{_path:"/history",get:{_method:"GET",_path:"/:?type/:?id",_auth:!0},add:{_method:"POST",_auth:!0},remove:{_method:"POST",_path:"/remove",_auth:!0}},ratings:{_path:"/ratings",get:{_method:"GET",_path:"/:?type/:?rating",_auth:!0},add:{_method:"POST",_auth:!0},remove:{_method:"POST",_path:"/remove",_auth:!0}},watchlist:{_path:"/watchlist",get:{_method:"GET",_path:"/:?type/:?sort_by/:?sort_how",_auth:!0},update:{_method:"PUT",_auth:!0},add:{_method:"POST",_auth:!0},remove:{_method:"POST",_path:"/remove",_auth:!0},reorder:{_method:"POST",_path:"/reorder",_auth:!0},update_item:{_method:"PUT",_path:"/:list_item_id",_auth:!0}},favorites:{_path:"/favorites",get:{_method:"GET",_path:"/:?type/:?sort_by/:?sort_how",_auth:!0},update:{_method:"PUT",_auth:!0},add:{_method:"POST",_auth:!0},remove:{_method:"POST",_path:"/remove",_auth:!0},reorder:{_method:"POST",_path:"/reorder",_auth:!0},update_item:{_method:"PUT",_path:"/:list_item_id",_auth:!0}}},users:{_path:"/users",settings:{_method:"GET",_path:"/settings",_auth:!0},requests:{_path:"/requests",following:{_method:"GET",_path:"/following",_auth:!0},get:{_method:"GET",_auth:!0},approve:{_method:"POST",_path:"/:id",_auth:!0},deny:{_method:"DELETE",_path:"/:id",_auth:!0}},saved_filters:{_method:"GET",_path:"/saved_filters/:?section",_auth:!0},hidden:{_path:"/hidden",get:{_method:"GET",_path:"/:section",_auth:!0},add:{_method:"POST",_path:"/:section",_auth:!0},remove:{_method:"POST",_path:"/:section/remove",_auth:!0}},profile:{_method:"GET",_path:"/:id"},likes:{_method:"GET",_path:"/:id/likes/:?type"},collection:{_method:"GET",_path:"/:id/collection/:type"},comments:{_method:"GET",_path:"/:id/comments/:?comment_type/:?type"},notes:{_method:"GET",_path:"/:id/notes/:?type"},lists:{_path:"/:id/lists",get:{_method:"GET"},create:{_method:"POST",_auth:!0},reorder:{_method:"POST",_path:"/reorder",_auth:!0},collaborations:{_method:"GET",_path:"/collaborations"}},list:{_path:"/:id/lists/:list_id",get:{_method:"GET"},update:{_method:"PUT",_auth:!0},delete:{_method:"DELETE",_auth:!0},likes:{get:{_method:"GET",_path:"/likes"},add:{_method:"POST",_path:"/like",_auth:!0},remove:{_method:"DELETE",_path:"/like",_auth:!0}},items:{_path:"/items",get:{_method:"GET",_path:"/:?type/:?sort_by/:?sort_how"},add:{_method:"POST",_auth:!0},remove:{_method:"POST",_path:"/remove",_auth:!0},reorder:{_method:"POST",_path:"/reorder",_auth:!0},update:{_method:"PUT",_path:"/:list_item_id",_auth:!0}},comments:{_method:"GET",_path:"/comments/:?sort"}},follow:{_path:"/:id/follow",add:{_method:"POST",_auth:!0},remove:{_method:"DELETE",_auth:!0}},followers:{_method:"GET",_path:"/:id/followers"},following:{_method:"GET",_path:"/:id/following"},friends:{_method:"GET",_path:"/:id/friends"},history:{_method:"GET",_path:"/:id/history/:?type/:?item_id"},ratings:{_method:"GET",_path:"/:id/ratings/:?type/:?rating"},watchlist:{_method:"GET",_path:"/:id/watchlist/:?type/:?sort_by/:?sort_how"},watchlist_comments:{_method:"GET",_path:"/:id/watchlist/comments/:?sort"},favorites:{_method:"GET",_path:"/:id/favorites/:?type/:?sort_by/:?sort_how",_auth:!0},favorites_comments:{_method:"GET",_path:"/:id/favorites/comments/:?sort"},watching:{_method:"GET",_path:"/:id/watching"},watched:{_method:"GET",_path:"/:id/watched/:type"},stats:{_method:"GET",_path:"/:id/stats"}}}}