@re621/zestyapi
Version:
Comprehensive JS wrapper for e621.net API
220 lines (185 loc) • 8.31 kB
text/typescript
import Logger from "./components/Logger";
import RequestQueue from "./components/RequestQueue";
import Util from "./components/Util";
import { PrimitiveType } from "./components/UtilType";
import BlipsEndpoint from "./endpoints/Blips";
import CommentsEndpoint from "./endpoints/Comments";
import Favorites from "./endpoints/Favorites";
import ForumPostsEndpoint from "./endpoints/ForumPosts";
import ForumTopicsEndpoint from "./endpoints/ForumTopics";
import IQDBQueriesEndpoint from "./endpoints/IQDBQueries";
import NotesEndpoint from "./endpoints/Notes";
import PoolsEndpoint from "./endpoints/Pools";
import PostEventsEndpoint from "./endpoints/PostEvents";
import PostsEndpoint from "./endpoints/Posts";
import PostSets from "./endpoints/PostSets";
import PostVotes from "./endpoints/PostVotes";
import TagAliasesEndpoint from "./endpoints/TagAliases";
import TagImplicationsEndpoint from "./endpoints/TagImplications";
import TagsEndpoint from "./endpoints/Tags";
import UserFeedbacksEndpoint from "./endpoints/UserFeedbacks";
import UsersEndpoint from "./endpoints/Users";
import UtilityEndpoint from "./endpoints/Utility";
import WikiPagesEndpoint from "./endpoints/WikiPages";
import InitializationError from "./error/InitializationError";
export default class ZestyAPI {
private static instance: ZestyAPI;
private userAgent: string;
private rateLimit: number;
private domain: string;
private authToken: AuthToken;
private authLogin: AuthLogin;
// Endpoint declarations
public Blips = new BlipsEndpoint(this);
public Comments = new CommentsEndpoint(this);
public Favorites = new Favorites(this);
public ForumPosts = new ForumPostsEndpoint(this);
public ForumTopics = new ForumTopicsEndpoint(this);
public IQDBQueries = new IQDBQueriesEndpoint(this);
public Notes = new NotesEndpoint(this);
public Pools = new PoolsEndpoint(this);
public Posts = new PostsEndpoint(this);
public PostEvents = new PostEventsEndpoint(this);
public PostSets = new PostSets(this);
public PostVotes = new PostVotes(this);
public Tags = new TagsEndpoint(this);
public TagAliases = new TagAliasesEndpoint(this);
public TagImplications = new TagImplicationsEndpoint(this);
public Users = new UsersEndpoint(this);
public UserFeedbacks = new UserFeedbacksEndpoint(this);
public Utility = new UtilityEndpoint(this)
public WikiPages = new WikiPagesEndpoint(this);
private constructor(config: APIConfig = {}) {
// User Agent
if (!config.userAgent || typeof config.userAgent !== "string" || config.userAgent.length > 250)
throw InitializationError.UserAgent();
else this.userAgent = config.userAgent;
// Rate Limit
if (!config.rateLimit || typeof config.rateLimit !== "number" || config.rateLimit < 500)
this.rateLimit = 500;
else this.rateLimit = config.rateLimit;
// Domain
if (!config.domain) config.domain = "https://e621.net";
else if (typeof config.domain !== "string")
throw InitializationError.Domain();
try { this.domain = new URL(config.domain).href; }
catch { throw InitializationError.Domain(); }
// Authentication
if (config.authToken) this.login(config.authToken);
else if (config.authLogin) this.login(config.authLogin);
// Debug
if (config.debug) Logger.debug = true;
}
/**
* Get an instance of a E621 object, with access to various endpoints
* @param {APIConfig} config Configuration object. Not necessary if `connect()` was called before.
* @returns {ZestyAPI} E621 object
*/
public static connect(config?: APIConfig): ZestyAPI {
if (!this.instance) this.instance = new ZestyAPI(config);
return this.instance;
}
public login(auth: AuthToken | AuthLogin): void {
this.logout();
if (typeof auth == "string") {
if (auth.length > 250) throw InitializationError.Auth();
else this.authToken = auth;
} else {
if (!auth.username || typeof auth.username !== "string" || auth.username.length > 250
|| !auth.apiKey || typeof auth.apiKey !== "string" || auth.apiKey.length > 250)
throw InitializationError.Auth();
else this.authLogin = auth;
}
}
public logout(): void {
this.authToken = undefined;
this.authLogin = undefined;
}
public getAuthToken(): AuthToken { return this.authToken; }
public getAuthLogin(): AuthLogin { return this.authLogin; }
public get isAuthSet(): boolean {
return typeof this.authToken !== "undefined" || typeof this.authLogin !== "undefined";
}
/**
* Method used to make requests to E621's API.
* It is strongly recommended not to use it directly, and to instead rely on endpoint methods.
* @param {string} endpoint Endpoint address
* @param {RequestConfig} config Request parameters
* @returns {Promise<any>} Promise that is fulfilled when the request receives a response
*/
public makeRequest(endpoint: string, config?: RequestConfig): Promise<any> {
const requestInfo = {};
requestInfo["headers"] = {};
/* Validating the request config */
if (!config) config = {};
// Request method
if (!config.method) config.method = "GET";
requestInfo["method"] = config.method;
// Query parameters and headers
if (!config.query) config.query = {};
if (Util.isBrowser) config.query["_client"] = encodeURIComponent(this.userAgent);
else {
requestInfo["headers"]["User-Agent"] = this.userAgent;
requestInfo["headers"]["X-User-Agent"] = this.userAgent;
}
// Request body
if (!config.body) config.body = {};
if (config.method !== "GET") {
if (this.authToken) config.body["authenticity_token"] = encodeURIComponent(this.authToken);
// const bodyParams = APIQuery.flatten(config.body);
if (Object.keys(config.body).length > 0) {
const data = new FormData();
for (const [key, value] of Object.entries(config.body))
data.append(key, value + "");
requestInfo["body"] = data;
}
}
// Timeout
if (!config.rateLimit) config.rateLimit = this.rateLimit;
else if (config.rateLimit < 500) config.rateLimit = 500;
// Authentication
if (this.authLogin) {
// TODO Check if there is a difference in auth between browser and node
requestInfo["headers"]["Authorization"] = `Basic ${Util.btoa(this.authLogin.username + ":" + this.authLogin.apiKey)}`;
}
/* Compiling the data and adding it to the queue */
let url = this.domain + endpoint;
const queryParams = APIQuery.flatten(config.query);
if (queryParams.length > 0) url += "?" + queryParams.join("&");
return RequestQueue.add(url, requestInfo, config.rateLimit);
}
}
if (typeof process === "undefined")
(window as any).ZestyAPI = ZestyAPI;
interface APIConfig {
userAgent?: string,
rateLimit?: 500 | number,
domain?: "https://e621.net" | "https://e926.net" | string,
authToken?: AuthToken;
authLogin?: AuthLogin
debug?: boolean;
}
type AuthToken = string;
interface AuthLogin {
username: string;
apiKey: string;
}
interface RequestConfig {
method?: "GET" | "POST" | "HEAD" | "PATCH" | "DELETE",
query?: APIQuery,
body?: APIQuery,
rateLimit?: number,
}
export interface APIQuery {
[prop: string]: PrimitiveType;
}
namespace APIQuery {
export function flatten(input: APIQuery): string[] {
const result = [];
for (const [key, value] of Object.entries(input)) {
if (value === null || typeof value == "undefined") continue;
result.push(key + "=" + value); // TODO URLEncode???
}
return result;
}
}