From 55ae637fd84b02578ac3fd37a58f7e66586da4ee Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:06:16 -0700 Subject: [PATCH 1/9] Move to TS source --- index.js | 110 ------------------------------------- index.ts | 122 +++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- tsconfig.json | 15 +++++ typings/account.ts | 8 +++ typings/index.d.ts | 48 ---------------- typings/index.ts | 6 ++ typings/node.ts | 3 + typings/nodeElement.ts | 32 +++++++++++ typings/page.ts | 12 ++++ typings/pageList.ts | 5 ++ typings/pageViews.ts | 3 + typings/telegraph.d.ts | 41 -------------- 13 files changed, 209 insertions(+), 202 deletions(-) delete mode 100644 index.js create mode 100644 index.ts create mode 100644 tsconfig.json create mode 100644 typings/account.ts delete mode 100644 typings/index.d.ts create mode 100644 typings/index.ts create mode 100644 typings/node.ts create mode 100644 typings/nodeElement.ts create mode 100644 typings/page.ts create mode 100644 typings/pageList.ts create mode 100644 typings/pageViews.ts delete mode 100644 typings/telegraph.d.ts diff --git a/index.js b/index.js deleted file mode 100644 index 6538b9c..0000000 --- a/index.js +++ /dev/null @@ -1,110 +0,0 @@ -const fetch = require('isomorphic-fetch') - -class Telegraph { - constructor (token, opts) { - this.options = Object.assign({ - token: token, - apiRoot: 'https://api.telegra.ph' - }, opts) - } - - set token (token) { - this.options.token = token - } - - callService (method, payload) { - return fetch(`${this.options.apiRoot}/${method}`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(payload) - }) - .then(unwrap) - } - - createAccount (shortName, name, url) { - return this.callService('createAccount', { - short_name: shortName, - author_name: name, - author_url: url - }) - } - - createPage (title, content, authorName, authorUrl, returnContent) { - return this.callService('createPage', { - access_token: this.options.token, - title: title, - author_name: authorName, - author_url: authorUrl, - content: content, - return_content: returnContent - }) - } - - editAccountInfo (shortName, name, url) { - return this.callService('editAccountInfo', { - access_token: this.options.token, - short_name: shortName, - author_name: name, - author_url: url - }) - } - - editPage (path, title, content, authorName, authorUrl, returnContent) { - return this.callService(`editPage/${path}`, { - access_token: this.options.token, - title: title, - author_name: authorName, - author_url: authorUrl, - content: content, - return_content: returnContent - }) - } - - getPage (path, returnContent) { - return this.callService(`getPage/${path}`, { - access_token: this.options.token, - return_content: returnContent - }) - } - - getViews (path, year, month, day, hour) { - return this.callService(`getViews/${path}`, { - access_token: this.options.token, - year: year, - month: month, - day: day, - hour: hour - }) - } - - getPageList (offset, limit) { - return this.callService('getPageList', { - access_token: this.options.token, - offset: offset, - limit: limit - }) - } - - revokeAccessToken () { - return this.callService('revokeAccessToken', { - access_token: this.options.token - }) - } -} - -function unwrap (res) { - if (!res.ok) { - const err = new Error(res.statusText || 'Error calling telegra.ph') - err.statusCode = res.status - throw err - } - return res.json() - .then((json) => { - if (('ok' in json && !json.ok)) { - throw new Error(json.error || 'Error calling telegra.ph') - } - return json.result - }) -} - -module.exports = Telegraph diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..d78ff3a --- /dev/null +++ b/index.ts @@ -0,0 +1,122 @@ +import type { Account, Node, Page, PageList, PageViews } from "./typings" +import fetch from "isomorphic-fetch" + +export type Options = { + token: string + apiRoot: string +} + +export class Telegraph { + private options: Options + + constructor(token: string, opts: Options) { + this.options = Object.assign( + { + token: token, + apiRoot: "https://api.telegra.ph", + }, + opts + ) + } + + protected callService(method: string, payload: unknown): Promise { + return fetch(`${this.options.apiRoot}/${method}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }).then(unwrap) + } + + public set token(token: string) { + this.options.token = token + } + + public createAccount(shortName: string, name: string, url?: string): Promise { + return this.callService("createAccount", { + short_name: shortName, + author_name: name, + author_url: url, + }) + } + + public createPage(title: string, content: Array, authorName: string, authorUrl?: string, returnContent?: boolean): Promise { + return this.callService("createPage", { + access_token: this.options.token, + title: title, + author_name: authorName, + author_url: authorUrl, + content: content, + return_content: returnContent, + }) + } + + public editAccountInfo(shortName: string | undefined = undefined, name: string | undefined = undefined, url: string | undefined = undefined): Promise { + return this.callService("editAccountInfo", { + access_token: this.options.token, + short_name: shortName, + author_name: name, + author_url: url, + }) + } + + public editPage(path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise { + return this.callService(`editPage/${path}`, { + access_token: this.options.token, + title: title, + author_name: authorName, + author_url: authorUrl, + content: content, + return_content: returnContent, + }) + } + + public getPage(path: string, returnContent?: boolean): Promise { + return this.callService(`getPage/${path}`, { + access_token: this.options.token, + return_content: returnContent, + }) + } + + getViews(path: string): Promise + getViews(path: string, year: number): Promise + getViews(path: string, year: number, month: number): Promise + getViews(path: string, year: number, month: number, day: number): Promise + getViews(path: string, year: number, month: number, day: number, hour: number): Promise + public getViews(path: string, year?: number, month?: number, day?: number, hour?: number): Promise { + return this.callService(`getViews/${path}`, { + access_token: this.options.token, + year: year, + month: month, + day: day, + hour: hour, + }) + } + + public getPageList(offset?: number, limit?: number): Promise { + return this.callService("getPageList", { + access_token: this.options.token, + offset: offset, + limit: limit, + }) + } + + public revokeAccessToken() { + return this.callService("revokeAccessToken", { + access_token: this.options.token, + }) + } +} + +function unwrap(res) { + if (!res.ok) { + const err: Error & { statusCode?: any } = new Error(res.statusText || "Error calling telegra.ph") + err.statusCode = res.status + throw err + } + return res.json().then((json) => { + if ("ok" in json && !json.ok) { + throw new Error(json.error || "Error calling telegra.ph") + } + return json.result + }) +} diff --git a/package.json b/package.json index 31ef5ef..7d9bdf0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "telegra.ph", "version": "1.0.1", "description": "Tiny API helper for Telegra.ph", - "main": "index.js", + "main": "dist/index.js", "scripts": { "test": "eslint . && ava test" }, @@ -25,8 +25,7 @@ }, "homepage": "https://github.com/telegraf/telegra.ph#readme", "files": [ - "index.js", - "typings/*.ts" + "dist" ], "devDependencies": { "ava": "^1.2.0", @@ -39,6 +38,7 @@ "eslint-plugin-standard": "^4.0.0" }, "dependencies": { + "@types/isomorphic-fetch": "^0.0.39", "isomorphic-fetch": "^2.2.1" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6e8f0c3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "declaration": true, + "esModuleInterop": true, + "noImplicitAny": false, + "outDir": "./dist", + "importsNotUsedAsValues": "remove" + }, + "include": ["src/**/*"] +} diff --git a/typings/account.ts b/typings/account.ts new file mode 100644 index 0000000..4ee614e --- /dev/null +++ b/typings/account.ts @@ -0,0 +1,8 @@ +export interface Account { + short_name: string + author_name: string + author_url?: string + access_token?: string + auth_url?: string + page_count?: number +} diff --git a/typings/index.d.ts b/typings/index.d.ts deleted file mode 100644 index 555be0f..0000000 --- a/typings/index.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as tph from './telegraph.d' - -export interface Telegraph { - - token: string - - callService (method: string, payload: object): Promise - - createAccount (shortName: string, name?: string, url?: string): Promise - - createPage (title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise - - editAccountInfo (shortName?: string, name?: string, url?: string): Promise - - editPage (path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise - - getPage (path: string, returnContent?: boolean): Promise - - getViews (path: string, year: number, month: number, day: number, hour?: number): Promise - - getPageList (offset?: number, limit?: number): Promise - - revokeAccessToken (): Promise -} - - -export interface TelegraphConstructor { - /** - * Initialize new Telegraph app. - * @param token Access token - * @param opts options - * @example - * new Telegraph(token, opts) - */ - new (token: string, options?: TelegraphOptions): Telegraph -} - -export interface TelegraphOptions { - - token?: string - - apiRoot?: string - -} - -export const Telegraph: TelegraphConstructor - -export default Telegraph \ No newline at end of file diff --git a/typings/index.ts b/typings/index.ts new file mode 100644 index 0000000..d4ff9e0 --- /dev/null +++ b/typings/index.ts @@ -0,0 +1,6 @@ +export { Account } from "./account" +export { Node } from "./node" +export { NodeElement } from "./nodeElement" +export { Page } from "./page" +export { PageList } from "./pageList" +export { PageViews } from "./pageViews" diff --git a/typings/node.ts b/typings/node.ts new file mode 100644 index 0000000..b69b83e --- /dev/null +++ b/typings/node.ts @@ -0,0 +1,3 @@ +import { NodeElement } from "./nodeElement" + +export type Node = string | NodeElement diff --git a/typings/nodeElement.ts b/typings/nodeElement.ts new file mode 100644 index 0000000..aa1a969 --- /dev/null +++ b/typings/nodeElement.ts @@ -0,0 +1,32 @@ +export interface NodeElement { + tag: + | "a" + | "aside" + | "b" + | "blockquote" + | "br" + | "code" + | "em" + | "figcaption" + | "figure" + | "h3" + | "h4" + | "hr" + | "i" + | "iframe" + | "img" + | "li" + | "ol" + | "p" + | "pre" + | "s" + | "strong" + | "u" + | "ul" + | "video" + attrs?: { + href?: string + src?: string + } + children?: Array | string +} diff --git a/typings/page.ts b/typings/page.ts new file mode 100644 index 0000000..3dd68f1 --- /dev/null +++ b/typings/page.ts @@ -0,0 +1,12 @@ +export interface Page { + path: string + url: string + title: string + description: string + author_name?: string + author_url?: string + image_url?: string + content?: Array + views: number + can_edit?: boolean +} diff --git a/typings/pageList.ts b/typings/pageList.ts new file mode 100644 index 0000000..5f79ddb --- /dev/null +++ b/typings/pageList.ts @@ -0,0 +1,5 @@ +import type { Page } from "./page" +export interface PageList { + total_count: number + pages: Array +} diff --git a/typings/pageViews.ts b/typings/pageViews.ts new file mode 100644 index 0000000..2e5a4e6 --- /dev/null +++ b/typings/pageViews.ts @@ -0,0 +1,3 @@ +export interface PageViews { + views: number +} diff --git a/typings/telegraph.d.ts b/typings/telegraph.d.ts deleted file mode 100644 index 299c6b2..0000000 --- a/typings/telegraph.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -export interface Account { - short_name: string - author_name: string - author_url?: string - access_token?: string - auth_url?: string - page_count?: number -} - -export interface PageList { - total_count: number - pages: Array -} - -export interface Page { - path: string - url: string - title: string - description: string - author_name?: string - author_url?: string - image_url?: string - content?: Array - views: number - can_edit?: boolean -} - -export interface PageViews { - views: number -} - -export type Node = string | NodeElement - -export interface NodeElement { - tag: 'a' | 'aside' | 'b' | 'blockquote' | 'br' | 'code' | 'em' | 'figcaption' | 'figure' | 'h3' | 'h4' | 'hr' | 'i' | 'iframe' | 'img' | 'li' | 'ol' | 'p' | 'pre' | 's' | 'strong' | 'u' | 'ul' | 'video' - attrs?: { - href?: string - src?: string - } - children?: Array | string -} \ No newline at end of file From 0d4ed63e3456bb23cb9eff1c496c7a6d5df89ad9 Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:07:18 -0700 Subject: [PATCH 2/9] Use JSDoc --- index.ts | 109 +++++++++++++++++++++++++++++++++++++++-- typings/account.ts | 22 +++++++++ typings/node.ts | 5 +- typings/nodeElement.ts | 14 ++++++ typings/page.ts | 34 +++++++++++++ typings/pageList.ts | 10 ++++ typings/pageViews.ts | 7 +++ 7 files changed, 197 insertions(+), 4 deletions(-) diff --git a/index.ts b/index.ts index d78ff3a..284cf7f 100644 --- a/index.ts +++ b/index.ts @@ -6,6 +6,13 @@ export type Options = { apiRoot: string } +/** + * Initialize new Telegraph app. + * @param {string} token Access token + * @param {Options} opts options + * @example + * new Telegraph(token, opts) + */ export class Telegraph { private options: Options @@ -18,7 +25,7 @@ export class Telegraph { opts ) } - + protected callService(method: string, payload: unknown): Promise { return fetch(`${this.options.apiRoot}/${method}`, { method: "POST", @@ -31,6 +38,16 @@ export class Telegraph { this.options.token = token } + /** + * Use this method to create a new Telegraph account. Most users only need one account, but this can be useful for channel administrators who would like to keep individual author names and profile links for each of their channels. + * @param {string} shortName **1-32 characters.** **Required.** Account name, helps users with several accounts remember which they are currently using. Displayed to the user above the "Edit/Publish" button on Telegra.ph, other users don't see this name. + * @param {string} name **0-128 characters.** Default author name used when creating new articles. + * @param {string | undefined} url **0-512 characters.** Default profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with the regular fields and an additional `access_token` field. + * @example + * const telegraph = new Telegraph(token) + * const account = await telegraph.createAccount('Sandbox', 'Anonymous') + */ public createAccount(shortName: string, name: string, url?: string): Promise { return this.callService("createAccount", { short_name: shortName, @@ -39,6 +56,20 @@ export class Telegraph { }) } + /** + * Use this method to create a new Telegraph page. + * @see https://telegra.ph/api#createPage + * @param {string} title **1-256 characters.** **Required.** Page title. + * @param {Array} content Content of the page. + * @param {string} authorName **0-128 characters.** Author name, displayed below the article's title. + * @param {string | undefined} authorUrl **0-512 characters.** Profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @param {boolean | undefined} returnContent If `true`, a `content` field will be returned in the Page object (see: [Content format](https://telegra.ph/api#Content-format)). + * @returns {Promise} On success, returns a [`Page`](https://telegra.ph/api#Page) object. + * @example + * const telegraph = new Telegraph(token) + * const content = [ { tag: 'p', children: [ 'Hello, world!' ] } ] + * const page = await telegraph.createPage('Sample Page', content, 'Anonymous') + */ public createPage(title: string, content: Array, authorName: string, authorUrl?: string, returnContent?: boolean): Promise { return this.callService("createPage", { access_token: this.options.token, @@ -50,7 +81,22 @@ export class Telegraph { }) } - public editAccountInfo(shortName: string | undefined = undefined, name: string | undefined = undefined, url: string | undefined = undefined): Promise { + /** + * Use this method to update information about a Telegraph account. Pass only the parameters that you want to edit. + * @see https://telegra.ph/api#editAccountInfo + * @param {string | undefined} shortName New account name. + * @param {string | undefined} name New default author name used when creating new articles. + * @param {string | undefined} url New default profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with the default fields. + * @example + * const telegraph = new Telegraph(token) + * const account = await telegraph.editAccountInfo(, 'Anonymous', 'https://telegra.ph/api') + */ + public editAccountInfo( + shortName: string | undefined = undefined, + name: string | undefined = undefined, + url: string | undefined = undefined + ): Promise { return this.callService("editAccountInfo", { access_token: this.options.token, short_name: shortName, @@ -59,6 +105,25 @@ export class Telegraph { }) } + /** + * Use this method to edit an existing Telegraph page. + * @see https://telegra.ph/api#editPage + * @param {string} path **Required.** Path to the page. + * @param {string} title **1-256 characters.** **Required.** Page title. + * @param {Array} content **up to 64 KB.** **Required.** Content of the page. + * @param {string | undefined} authorName **0-128 characters.** Author name, displayed below the article's title. + * @param {string | undefined} authorUrl **0-512 characters.** Profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + * @param {boolean | undefined} returnContent If `true`, a `content` field will be returned in the Page object (see: [Content format](https://telegra.ph/api#Content-format)). + * @returns {Promise} On success, returns a [`Page`](https://telegra.ph/api#Page) object. + * @example + * const telegraph = new Telegraph(token) + * + * const content = [ { tag: 'p', children: [ 'Hello, world!' ] } ] + * const page = await telegraph.createPage('Sample Page', content, 'Anonymous') + * + * const newContent = [ { tag: 'b', children: [ 'Hello, world!' ] } ] + * const editedPage = await telegraph.editPage(page.path, 'Sample Page', newContent, 'Anonymous') + */ public editPage(path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise { return this.callService(`editPage/${path}`, { access_token: this.options.token, @@ -70,6 +135,16 @@ export class Telegraph { }) } + /** + * Use this method to get a Telegraph page. + * @see https://telegra.ph/api#getPage + * @param {string} path **Required.** Path to the Telegraph page (in the format `Title-12-31`, i.e. everything that comes after `http://telegra.ph/`). + * @param {boolean | undefined} returnContent If `true`, content field will be returned in Page object. + * @returns {Promise} On success, returns a [`Page`](https://telegra.ph/api#Page) object. + * @example + * const telegraph = new Telegraph(token) + * const page = await telegraph.getPage('Sample-Page-12-15', true) + */ public getPage(path: string, returnContent?: boolean): Promise { return this.callService(`getPage/${path}`, { access_token: this.options.token, @@ -77,6 +152,19 @@ export class Telegraph { }) } + /** + * Use this method to get the number of views for a Telegraph article. By default, the total number of page views will be returned + * @see https://telegra.ph/api#getViews + * @param {string} path **Required.** Path to the Telegraph page (in the format `Title-12-31`, where `12` is the month and `31` the day the article was first published). + * @param {number | undefined} year Required if `month` is passed. If passed, the number of page views for the requested year will be returned. + * @param {number | undefined} month Required if `day` is passed. If passed, the number of page views for the requested month will be returned. + * @param {number | undefined} day Required if `hour` is passed. If passed, the number of page views for the requested day will be returned. + * @param {number | undefined} hour If passed, the number of page views for the requested hour will be returned. + * @returns {Promise} On success, returns a [`PageViews`](https://telegra.ph/api#PageViews) object. + * @example + * const telegraph = new Telegraph(token) + * const views = await telegraph.getViews('Sample-Page-12-15', 2016, 12) + */ getViews(path: string): Promise getViews(path: string, year: number): Promise getViews(path: string, year: number, month: number): Promise @@ -92,6 +180,16 @@ export class Telegraph { }) } + /** + * Use this method to get a list of pages belonging to a Telegraph account. + * @see https://telegra.ph/api#getPageList + * @param {number | undefined} offset Sequential number of the first page to be returned. + * @param {number | undefined} limit Limits the number of pages to be retrieved. + * @returns {Promise} On success, returns a [`PageList`](https://telegra.ph/api#PageList) object. + * @example + * const telegraph = new Telegraph(token) + * const pageList = await telegraph.getPageList(undefined, 3) + */ public getPageList(offset?: number, limit?: number): Promise { return this.callService("getPageList", { access_token: this.options.token, @@ -100,7 +198,12 @@ export class Telegraph { }) } - public revokeAccessToken() { + /** + * Use this method to revoke access_token and generate a new one, for example, if the user would like to reset all connected sessions, or you have reasons to believe the token was compromised. + * @see https://telegra.ph/api#revokeAccessToken + * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with new `access_token` and `auth_url` fields. + */ + public revokeAccessToken(): Promise { return this.callService("revokeAccessToken", { access_token: this.options.token, }) diff --git a/typings/account.ts b/typings/account.ts index 4ee614e..7555485 100644 --- a/typings/account.ts +++ b/typings/account.ts @@ -1,8 +1,30 @@ +/** + * This object represents a Telegraph account. + * @see https://telegra.ph/api#Account + */ export interface Account { + /** + * Account name, helps users with several accounts remember which they are currently using. Displayed to the user above the "Edit/Publish" button on Telegra.ph, other users don't see this name. + */ short_name: string + /** + * Default author name used when creating new articles. + */ author_name: string + /** + * Default profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + */ author_url?: string + /** + * Optional. Only returned by the `createAccount` and `revokeAccessToken` method. Access token of the Telegraph account. + */ access_token?: string + /** + * Optional. URL to authorize a browser on telegra.ph and connect it to a Telegraph account. This URL is valid for only one use and for 5 minutes only. + */ auth_url?: string + /** + * Optional. Number of pages belonging to the Telegraph account. + */ page_count?: number } diff --git a/typings/node.ts b/typings/node.ts index b69b83e..8f51f11 100644 --- a/typings/node.ts +++ b/typings/node.ts @@ -1,3 +1,6 @@ import { NodeElement } from "./nodeElement" - +/** + * This abstract object represents a DOM Node. It can be a String which represents a DOM text node or a NodeElement object. + * @see https://telegra.ph/api#Node + */ export type Node = string | NodeElement diff --git a/typings/nodeElement.ts b/typings/nodeElement.ts index aa1a969..5f80103 100644 --- a/typings/nodeElement.ts +++ b/typings/nodeElement.ts @@ -1,4 +1,12 @@ +import { Node } from "./node" +/** + * This object represents a DOM element node. + * @see https://telegra.ph/api#NodeElement + */ export interface NodeElement { + /** + * Name of the DOM element. Available tags: `a`, `aside`, `b`, `blockquote`, `br`, `code`, `em`, `figcaption`, `figure`, `h3`, `h4`, `hr`, `i`, `iframe`, `img`, `li`, `ol`, `p`, `pre`, `s`, `strong`, `u`, `ul`, `video`. + */ tag: | "a" | "aside" @@ -24,9 +32,15 @@ export interface NodeElement { | "u" | "ul" | "video" + /** + * Optional. Attributes of the DOM element. Key of object represents name of attribute, value represents value of attribute. Available attributes: `href`, `src`. + */ attrs?: { href?: string src?: string } + /** + * Optional. List of child nodes for the DOM element. + */ children?: Array | string } diff --git a/typings/page.ts b/typings/page.ts index 3dd68f1..8bc4f1a 100644 --- a/typings/page.ts +++ b/typings/page.ts @@ -1,12 +1,46 @@ +/** + * This object represents a page on Telegraph. + * @see https://telegra.ph/api#Page + */ export interface Page { + /** + * Path to the page. + */ path: string + /** + * URL of the page. + */ url: string + /** + * Title of the page. + */ title: string + /** + * Description of the page. + */ description: string + /** + * Optional. Name of the author, displayed below the title. + */ author_name?: string + /** + * Optional. Profile link, opened when users click on the author's name below the title. Can be any link, not necessarily to a Telegram profile or channel. + */ author_url?: string + /** + * Optional. Image URL of the page. + */ image_url?: string + /** + * Optional. Content of the page. + */ content?: Array + /** + * Number of page views for the page. + */ views: number + /** + * Optional. Only returned if access_token passed. True, if the target Telegraph account can edit the page. + */ can_edit?: boolean } diff --git a/typings/pageList.ts b/typings/pageList.ts index 5f79ddb..50bae84 100644 --- a/typings/pageList.ts +++ b/typings/pageList.ts @@ -1,5 +1,15 @@ import type { Page } from "./page" +/** + * This object represents a list of Telegraph articles belonging to an account. Most recently created articles first. + * @see https://telegra.ph/api#PageList + */ export interface PageList { + /** + * Total number of pages belonging to the target Telegraph account. + */ total_count: number + /** + * Requested pages of the target Telegraph account. + */ pages: Array } diff --git a/typings/pageViews.ts b/typings/pageViews.ts index 2e5a4e6..5a995c2 100644 --- a/typings/pageViews.ts +++ b/typings/pageViews.ts @@ -1,3 +1,10 @@ +/** + * This object represents the number of page views for a Telegraph article. + * @see https://telegra.ph/api#PageViews + */ export interface PageViews { + /** + * Number of page views for the target page. + */ views: number } From a92cb78747cb594ebbde5a5a3e62eaa7fec07176 Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:08:45 -0700 Subject: [PATCH 3/9] Use a `src` directory --- index.ts => src/index.ts | 0 {typings => src/typings}/account.ts | 0 {typings => src/typings}/index.ts | 0 {typings => src/typings}/node.ts | 0 {typings => src/typings}/nodeElement.ts | 0 {typings => src/typings}/page.ts | 0 {typings => src/typings}/pageList.ts | 0 {typings => src/typings}/pageViews.ts | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename index.ts => src/index.ts (100%) rename {typings => src/typings}/account.ts (100%) rename {typings => src/typings}/index.ts (100%) rename {typings => src/typings}/node.ts (100%) rename {typings => src/typings}/nodeElement.ts (100%) rename {typings => src/typings}/page.ts (100%) rename {typings => src/typings}/pageList.ts (100%) rename {typings => src/typings}/pageViews.ts (100%) diff --git a/index.ts b/src/index.ts similarity index 100% rename from index.ts rename to src/index.ts diff --git a/typings/account.ts b/src/typings/account.ts similarity index 100% rename from typings/account.ts rename to src/typings/account.ts diff --git a/typings/index.ts b/src/typings/index.ts similarity index 100% rename from typings/index.ts rename to src/typings/index.ts diff --git a/typings/node.ts b/src/typings/node.ts similarity index 100% rename from typings/node.ts rename to src/typings/node.ts diff --git a/typings/nodeElement.ts b/src/typings/nodeElement.ts similarity index 100% rename from typings/nodeElement.ts rename to src/typings/nodeElement.ts diff --git a/typings/page.ts b/src/typings/page.ts similarity index 100% rename from typings/page.ts rename to src/typings/page.ts diff --git a/typings/pageList.ts b/src/typings/pageList.ts similarity index 100% rename from typings/pageList.ts rename to src/typings/pageList.ts diff --git a/typings/pageViews.ts b/src/typings/pageViews.ts similarity index 100% rename from typings/pageViews.ts rename to src/typings/pageViews.ts From f729fae637f8bfb3acf0d0a88c175a6b0820cfc2 Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:10:18 -0700 Subject: [PATCH 4/9] Add optional dependency injection for http client --- src/clients/fetch.ts | 33 ++++++++++++++++++++++ src/clients/https.ts | 50 +++++++++++++++++++++++++++++++++ src/clients/index.ts | 7 +++++ src/index.ts | 67 +++++++++++++++++--------------------------- 4 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 src/clients/fetch.ts create mode 100644 src/clients/https.ts create mode 100644 src/clients/index.ts diff --git a/src/clients/fetch.ts b/src/clients/fetch.ts new file mode 100644 index 0000000..258b0f4 --- /dev/null +++ b/src/clients/fetch.ts @@ -0,0 +1,33 @@ +import { Client } from "./index" + +export class Fetch implements Client { + constructor(public baseURL: string) {} + + async get(path: string): Promise { + const response = await fetch(`${this.baseURL}${path}`) + const json = await response.json() + if (json.ok === false) { + if (json.error) throw new Error(json.error) + else throw new Error(json) + } + if (json.result) return json.result + else return json + } + + async post(path: string, body: unknown): Promise { + const response = await fetch(`${this.baseURL}${path}`, { + method: "POST", + body: JSON.stringify(body), + headers: { + "Content-Type": "application/json", + }, + }) + const json = await response.json() + if (json.ok === false) { + if (json.error) throw new Error(json.error) + else throw new Error(json) + } + if (json.result) return json.result + else return json + } +} diff --git a/src/clients/https.ts b/src/clients/https.ts new file mode 100644 index 0000000..c462628 --- /dev/null +++ b/src/clients/https.ts @@ -0,0 +1,50 @@ +import type { Client } from "./" +import https from "https" + +export class HTTPS implements Client { + constructor(public baseURL: string) {} + + protected request(method: string, path: string, data: unknown): Promise { + return new Promise((resolve, reject) => { + const req = https.request( + `${this.baseURL}${path}`, + { + method, + headers: { + "Content-Type": "application/json", + }, + }, + (res) => { + let body = "" + res.on("data", (chunk) => { + body += chunk + }) + res.on("end", () => { + try { + const obj = JSON.parse(body) + if (obj.ok === false) { + if (obj.error) reject(new Error(obj.error)) + else reject(new Error(obj)) + } + if (obj.result) resolve(obj.result) + else resolve(obj) + } catch (err) { + reject(err) + } + }) + } + ) + req.on("error", reject) + req.write(JSON.stringify(data)) + req.end() + }) + } + + post(path: string, data: unknown): Promise { + return this.request("POST", path, data) + } + + get(path: string, data: unknown): Promise { + return this.request("GET", path, data) + } +} diff --git a/src/clients/index.ts b/src/clients/index.ts new file mode 100644 index 0000000..76e50e1 --- /dev/null +++ b/src/clients/index.ts @@ -0,0 +1,7 @@ +export interface Client { + post: (path: string, data: unknown) => Promise + get: (path: string, data: unknown) => Promise +} + +export { Fetch } from "./fetch" +export { HTTPS } from "./https" diff --git a/src/index.ts b/src/index.ts index 284cf7f..4a8e6b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import type { Account, Node, Page, PageList, PageViews } from "./typings" -import fetch from "isomorphic-fetch" +import { type Client, HTTPS } from "./clients" export type Options = { - token: string - apiRoot: string + token?: string + apiRoot?: string + client?: new (baseURL: string) => Client } /** @@ -15,8 +16,9 @@ export type Options = { */ export class Telegraph { private options: Options + protected client: Client - constructor(token: string, opts: Options) { + constructor(token: string, opts?: Options) { this.options = Object.assign( { token: token, @@ -24,14 +26,9 @@ export class Telegraph { }, opts ) - } - protected callService(method: string, payload: unknown): Promise { - return fetch(`${this.options.apiRoot}/${method}`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify(payload), - }).then(unwrap) + const client = this.options.client || HTTPS + this.client = new client(this.options.apiRoot!) } public set token(token: string) { @@ -48,8 +45,8 @@ export class Telegraph { * const telegraph = new Telegraph(token) * const account = await telegraph.createAccount('Sandbox', 'Anonymous') */ - public createAccount(shortName: string, name: string, url?: string): Promise { - return this.callService("createAccount", { + public createAccount(shortName: string, name: string, url?: string): Promise { + return this.client.post("createAccount", { short_name: shortName, author_name: name, author_url: url, @@ -70,8 +67,8 @@ export class Telegraph { * const content = [ { tag: 'p', children: [ 'Hello, world!' ] } ] * const page = await telegraph.createPage('Sample Page', content, 'Anonymous') */ - public createPage(title: string, content: Array, authorName: string, authorUrl?: string, returnContent?: boolean): Promise { - return this.callService("createPage", { + public createPage(title: string, content: Array, authorName: string, authorUrl?: string, returnContent?: boolean): Promise { + return this.client.post("createPage", { access_token: this.options.token, title: title, author_name: authorName, @@ -92,12 +89,12 @@ export class Telegraph { * const telegraph = new Telegraph(token) * const account = await telegraph.editAccountInfo(, 'Anonymous', 'https://telegra.ph/api') */ - public editAccountInfo( + public editAccountInfo( shortName: string | undefined = undefined, name: string | undefined = undefined, url: string | undefined = undefined - ): Promise { - return this.callService("editAccountInfo", { + ): Promise { + return this.client.post("editAccountInfo", { access_token: this.options.token, short_name: shortName, author_name: name, @@ -124,8 +121,8 @@ export class Telegraph { * const newContent = [ { tag: 'b', children: [ 'Hello, world!' ] } ] * const editedPage = await telegraph.editPage(page.path, 'Sample Page', newContent, 'Anonymous') */ - public editPage(path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise { - return this.callService(`editPage/${path}`, { + public editPage(path: string, title: string, content: Array, authorName?: string, authorUrl?: string, returnContent?: boolean): Promise { + return this.client.post(`editPage/${path}`, { access_token: this.options.token, title: title, author_name: authorName, @@ -145,8 +142,8 @@ export class Telegraph { * const telegraph = new Telegraph(token) * const page = await telegraph.getPage('Sample-Page-12-15', true) */ - public getPage(path: string, returnContent?: boolean): Promise { - return this.callService(`getPage/${path}`, { + public getPage(path: string, returnContent?: boolean): Promise { + return this.client.post(`getPage/${path}`, { access_token: this.options.token, return_content: returnContent, }) @@ -170,8 +167,8 @@ export class Telegraph { getViews(path: string, year: number, month: number): Promise getViews(path: string, year: number, month: number, day: number): Promise getViews(path: string, year: number, month: number, day: number, hour: number): Promise - public getViews(path: string, year?: number, month?: number, day?: number, hour?: number): Promise { - return this.callService(`getViews/${path}`, { + public getViews(path: string, year?: number, month?: number, day?: number, hour?: number): Promise { + return this.client.post(`getViews/${path}`, { access_token: this.options.token, year: year, month: month, @@ -190,8 +187,8 @@ export class Telegraph { * const telegraph = new Telegraph(token) * const pageList = await telegraph.getPageList(undefined, 3) */ - public getPageList(offset?: number, limit?: number): Promise { - return this.callService("getPageList", { + public getPageList(offset?: number, limit?: number): Promise { + return this.client.post("getPageList", { access_token: this.options.token, offset: offset, limit: limit, @@ -203,23 +200,9 @@ export class Telegraph { * @see https://telegra.ph/api#revokeAccessToken * @returns {Promise} On success, returns an [`Account`](https://telegra.ph/api#Account) object with new `access_token` and `auth_url` fields. */ - public revokeAccessToken(): Promise { - return this.callService("revokeAccessToken", { + public revokeAccessToken(): Promise { + return this.client.post("revokeAccessToken", { access_token: this.options.token, }) } } - -function unwrap(res) { - if (!res.ok) { - const err: Error & { statusCode?: any } = new Error(res.statusText || "Error calling telegra.ph") - err.statusCode = res.status - throw err - } - return res.json().then((json) => { - if ("ok" in json && !json.ok) { - throw new Error(json.error || "Error calling telegra.ph") - } - return json.result - }) -} From dbd5929f8210e307765481083c5136d062543c7a Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:10:35 -0700 Subject: [PATCH 5/9] remove isomorphic-fetch dependency --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 7d9bdf0..aa74245 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,5 @@ "eslint-plugin-node": "^8.0.1", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0" - }, - "dependencies": { - "@types/isomorphic-fetch": "^0.0.39", - "isomorphic-fetch": "^2.2.1" } } From 88a9699416c7f75c87a42ee44f02a38d285e72c7 Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:10:47 -0700 Subject: [PATCH 6/9] Refactor constructor to use ES6 spread for defaults --- src/index.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4a8e6b2..08861b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,21 +14,25 @@ export type Options = { * @example * new Telegraph(token, opts) */ -export class Telegraph { +export default class Telegraph { private options: Options protected client: Client constructor(token: string, opts?: Options) { - this.options = Object.assign( - { - token: token, - apiRoot: "https://api.telegra.ph", - }, - opts - ) + if (opts?.apiRoot) { + opts.apiRoot = opts.apiRoot.endsWith("/") ? opts.apiRoot : opts.apiRoot + "/" + } + this.options = { + apiRoot: "https://api.telegra.ph/", + client: HTTPS, + ...opts, + token, + } - const client = this.options.client || HTTPS - this.client = new client(this.options.apiRoot!) + if (!this.options.apiRoot) throw new Error("apiRoot is required") + if (!this.options.client) throw new Error("client is required") + + this.client = new this.options.client(this.options.apiRoot) } public set token(token: string) { From a18553eb0adb5ac73a3ab5d2198cecc6fa99a8ff Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:13:03 -0700 Subject: [PATCH 7/9] include license and readme with package --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index aa74245..03de1c1 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ }, "homepage": "https://github.com/telegraf/telegra.ph#readme", "files": [ - "dist" + "dist", + "LICENSE", + "readme.md" ], "devDependencies": { "ava": "^1.2.0", From ba6fb9778e02866f3fb697256b63c7e0cca5280c Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:38:48 -0700 Subject: [PATCH 8/9] Update readme to use modern JavaScript syntax --- readme.md | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index 85fa85c..7f030ce 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,5 @@ # 📝 telegra.ph + [![NPM Version](https://img.shields.io/npm/v/telegra.ph.svg?style=flat-square)](https://www.npmjs.com/package/telegra.ph) [![Build Status](https://img.shields.io/travis/telegraf/telegra.ph.svg?branch=master&style=flat-square)](https://travis-ci.org/telegraf/telegra.ph) @@ -10,55 +11,67 @@ Tiny API helper for Telegra.ph. ## Installation -```js +```bash $ npm install telegra.ph --save ``` ## Example +### Create an account + ```js -const Telegraph = require('telegra.ph') +import Telegraph from "telegra.ph" const client = new Telegraph() -client.createAccount().then((account) => { - client.token = account.access_token - return client.getPageList() -}).then((pages) => console.log(pages)) +const account = client.createAccount() +client.token = account.access_token +const pages = await client.getPageList() +console.log(pages) ``` +### Use existing account + ```js -const Telegraph = require('telegra.ph') +import Telegraph from "telegra.ph" const client = new Telegraph(process.env.TOKEN) -client.getPageList().then((pages) => console.log(pages)) +const pages = await client.getPageList() +console.log(pages) ``` ## API documentation - #### [`createAccount`](http://telegra.ph/api#createAccount) + `.createAccount(shortName, name, url)` #### [`createPage`](http://telegra.ph/api#createPage) + `.createPage(title, content, authorName, authorUrl, returnContent)` #### [`editAccountInfo`](http://telegra.ph/api#editAccountInfo) + `.editAccountInfo(shortName, name, url)` #### [`editPage`](http://telegra.ph/api#editPage) + `.editPage(path, title, content, authorName, authorUrl, returnContent)` #### [`getPage`](http://telegra.ph/api#getPage) + `.getPage(path, returnContent)` #### [`getViews`](http://telegra.ph/api#getViews) + `.getViews(path, year, month, day, hour)` #### [`getPageList`](http://telegra.ph/api#getPageList) + `.getPageList(path, offset, limit)` #### [`revokeAccessToken`](http://telegra.ph/api#revokeAccessToken) + `.revokeAccessToken()` From 505ce3c3fa83ac3562e4f49e594f02fb4b1931c2 Mon Sep 17 00:00:00 2001 From: GodBleak Date: Mon, 13 Nov 2023 09:39:28 -0700 Subject: [PATCH 9/9] Document HTTP client dependency injection --- readme.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/readme.md b/readme.md index 7f030ce..1b46c5e 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,18 @@ const pages = await client.getPageList() console.log(pages) ``` +### Use arbitrary HTTP Client + +```js +import Telegraph from "telegra.ph" +import { Fetch } from "telegra.ph/clients" + +const client = new Telegraph(process.env.TOKEN, { client: Fetch }) + +const pages = await client.getPageList() +console.log(pages) +``` + ## API documentation #### [`createAccount`](http://telegra.ph/api#createAccount)