From 724a928c737ef789c2c697f420b57f1d3ab8f886 Mon Sep 17 00:00:00 2001 From: Sundar Guntnur Date: Tue, 23 Mar 2021 00:23:43 +0530 Subject: [PATCH] feature: Add typescript integration. --- package-lock.json | 27 +++ package.json | 8 +- src/api.js | 105 ++++++----- src/api.ts | 63 +++++++ src/events_wrapper.js | 148 ++++++++------- src/events_wrapper.ts | 76 ++++++++ src/helper.js | 7 - src/index.js | 131 ++++++------- src/index.ts | 92 +++++++++ src/resources/accounts.js | 46 +++-- src/resources/accounts.ts | 31 ++++ src/resources/emojis.js | 26 ++- src/resources/emojis.ts | 44 +++++ src/resources/events.js | 21 ++- src/resources/events.ts | 58 ++++++ src/resources/filters.js | 22 ++- src/resources/filters.ts | 24 +++ src/resources/messages.js | 146 ++++++++------- src/resources/messages.ts | 371 +++++++++++++++++++++++++++++++++++++ src/resources/queues.js | 37 ++-- src/resources/queues.ts | 102 ++++++++++ src/resources/reactions.js | 33 ++-- src/resources/reactions.ts | 62 +++++++ src/resources/server.js | 21 ++- src/resources/server.ts | 89 +++++++++ src/resources/streams.js | 73 ++++---- src/resources/streams.ts | 145 +++++++++++++++ src/resources/typing.js | 29 +-- src/resources/typing.ts | 37 ++++ src/resources/users.js | 85 ++++----- src/resources/users.ts | 229 +++++++++++++++++++++++ src/types.js | 10 + src/types.ts | 63 +++++++ src/zuliprc.js | 42 +++-- src/zuliprc.ts | 27 +++ tsconfig.json | 10 + 36 files changed, 2088 insertions(+), 452 deletions(-) create mode 100644 src/api.ts create mode 100644 src/events_wrapper.ts delete mode 100644 src/helper.js create mode 100644 src/index.ts create mode 100644 src/resources/accounts.ts create mode 100644 src/resources/emojis.ts create mode 100644 src/resources/events.ts create mode 100644 src/resources/filters.ts create mode 100644 src/resources/messages.ts create mode 100644 src/resources/queues.ts create mode 100644 src/resources/reactions.ts create mode 100644 src/resources/server.ts create mode 100644 src/resources/streams.ts create mode 100644 src/resources/typing.ts create mode 100644 src/resources/users.ts create mode 100644 src/types.js create mode 100644 src/types.ts create mode 100644 src/zuliprc.ts create mode 100644 tsconfig.json diff --git a/package-lock.json b/package-lock.json index b8af9c8..ee2fdde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1178,12 +1178,33 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/isomorphic-fetch": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.35.tgz", + "integrity": "sha512-DaZNUvLDCAnCTjgwxgiL1eQdxIKEpNLOlTNtAgnZc50bG2copGhRrFN9/PxPBuJe+tZVLCbQ7ls0xveXVRPkvw==", + "dev": true + }, + "@types/isomorphic-form-data": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz", + "integrity": "sha512-A8DxJ9mtMuWuKIQImQkz+hewSFLbvUPtSVl32rHAdL+22Xn0CWPvk2zjuWSFOM8+p/YYoB5MmQThcJshp3no7A==", + "dev": true, + "requires": { + "form-data": "^2.5.1" + } + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/node": { + "version": "14.14.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz", + "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==", + "dev": true + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -4883,6 +4904,12 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typescript": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", + "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/package.json b/package.json index 76a2292..67f1fc6 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,9 @@ "@babel/core": "^7.12.10", "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.10", + "@types/isomorphic-fetch": "0.0.35", + "@types/isomorphic-form-data": "^2.0.0", + "@types/node": "^14.14.35", "chai": "^4.2.0", "eslint": "^7.15.0", "eslint-config-airbnb": "^18.2.1", @@ -26,10 +29,11 @@ "eslint-plugin-react": "^7.21.5", "mocha": "^8.2.1", "prettier": "^2.2.1", - "sinon": "^9.2.1" + "sinon": "^9.2.1", + "typescript": "^4.2.3" }, "scripts": { - "build": "babel --delete-dir-on-start -d lib/ src/", + "build": "tsc && babel --delete-dir-on-start -d lib/ src/", "prepare": "npm run build", "lint": "eslint . && prettier --loglevel=warn --check .", "lint:fix": "eslint --fix . && prettier --loglevel=warn --write .", diff --git a/src/api.js b/src/api.js index dbd2cbf..ec868af 100644 --- a/src/api.js +++ b/src/api.js @@ -1,48 +1,63 @@ -const helper = require('./helper'); - -async function api(baseUrl, config, method, params) { - const url = new URL(baseUrl); - const auth = Buffer.from(`${config.username}:${config.apiKey}`).toString( - 'base64' - ); - const authHeader = `Basic ${auth}`; - const options = { method, headers: { Authorization: authHeader } }; - if (method === 'POST') { - options.body = new helper.FormData(); - Object.keys(params).forEach((key) => { - let data = params[key]; - if (Array.isArray(data)) { - data = JSON.stringify(data); - } - options.body.append(key, data); +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); }); - } else if (params) { - Object.entries(params).forEach(([key, value]) => { - url.searchParams.append(key, value); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.api = void 0; +const types_1 = require("./types"); +const fetch = require('isomorphic-fetch'); +const FormData = require('isomorphic-form-data'); +function api(baseUrl, config, method, params) { + return __awaiter(this, void 0, void 0, function* () { + const url = new URL(baseUrl); + const auth = Buffer.from(`${config.username}:${config.apiKey}`).toString('base64'); + const authHeader = `Basic ${auth}`; + const options = { + method, + headers: { Authorization: authHeader }, + }; + if (method === 'POST') { + options.body = new FormData(); + Object.keys(params).forEach((key) => { + let data = params[key]; + if (Array.isArray(data)) { + data = JSON.stringify(data); + } + options.body.append(key, data); + }); + } + else if (params) { + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + } + const response = yield fetch(url.href, options); + try { + return response.json(); + } + catch (e) { + if (e instanceof SyntaxError) { + // We probably got a non-JSON response from the server. + // We should inform the user of the same. + let message = 'Server Returned a non-JSON response.'; + if (response.status === 404) { + message += ` Maybe endpoint: ${method} ${response.url.replace(config.apiURL, '')} doesn't exist.`; + } + else { + message += ' Please check the API documentation.'; + } + const error = new types_1.ZulipErrorResponse(message); + error.res = response; + throw error; + } + throw e; + } }); - } - const response = await helper.fetch(url.href, options); - try { - return response.json(); - } catch (e) { - if (e instanceof SyntaxError) { - // We probably got a non-JSON response from the server. - // We should inform the user of the same. - let message = 'Server Returned a non-JSON response.'; - if (response.status === 404) { - message += ` Maybe endpoint: ${method} ${response.url.replace( - config.apiURL, - '' - )} doesn't exist.`; - } else { - message += ' Please check the API documentation.'; - } - const error = new Error(message); - error.res = response; - throw error; - } - throw e; - } } - -module.exports = api; +exports.api = api; diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..4394be6 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,63 @@ +import { Config } from './zuliprc'; +import { ZulipErrorResponse } from './types'; +const fetch = require('isomorphic-fetch'); +const FormData = require('isomorphic-form-data'); + +export type HttpMethod = + | "GET" + | "HEAD" + | "POST" + | "PUT" + | "DELETE" + | "TRACE" + | "OPTIONS" + | "CONNECT" + | "PATCH"; + +export async function api(baseUrl: string, config: Config, method: HttpMethod, params: any): Promise { + const url: URL = new URL(baseUrl); + const auth: string = Buffer.from(`${config.username}:${config.apiKey}`).toString( + 'base64' + ); + const authHeader: string = `Basic ${auth}`; + const options: any = { + method, + headers: { Authorization: authHeader }, + }; + if (method === 'POST') { + options.body = new FormData(); + Object.keys(params).forEach((key) => { + let data: string = params[key]; + if (Array.isArray(data)) { + data = JSON.stringify(data); + } + options.body.append(key, data); + }); + } else if (params) { + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value as string); + }); + } + const response: Response = await fetch(url.href, options); + try { + return response.json(); + } catch (e) { + if (e instanceof SyntaxError) { + // We probably got a non-JSON response from the server. + // We should inform the user of the same. + let message: string = 'Server Returned a non-JSON response.'; + if (response.status === 404) { + message += ` Maybe endpoint: ${method} ${response.url.replace( + config.apiURL, + '' + )} doesn't exist.`; + } else { + message += ' Please check the API documentation.'; + } + const error: ZulipErrorResponse = new ZulipErrorResponse(message); + error.res = response; + throw error; + } + throw e; + } +} \ No newline at end of file diff --git a/src/events_wrapper.js b/src/events_wrapper.js index 7f54f5f..250bc79 100644 --- a/src/events_wrapper.js +++ b/src/events_wrapper.js @@ -1,75 +1,87 @@ -const queues = require('./resources/queues'); -const events = require('./resources/events'); - +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.eventsWrapper = void 0; +const queues_1 = require("./resources/queues"); +const events_1 = require("./resources/events"); function sleep(ms) { - // TODO add jitter. - return new Promise((resolve) => setTimeout(resolve, ms)); + // TODO add jitter. + return new Promise((resolve) => setTimeout(resolve, ms)); } - function eventsWrapper(config) { - const z = { - queues: queues(config), - events: events(config), - }; - - function logError(error) { - console.log('zulip-js: Error while communicating with server:', error); // eslint-disable-line no-console - } - - async function registerQueue(eventTypes = null) { - let res; - // eslint-disable-next-line no-constant-condition - while (true) { - try { - const params = { eventTypes }; - res = await z.queues.register(params); // eslint-disable-line no-await-in-loop - if (res.result === 'error') { - logError(res.msg); - await sleep(1000); // eslint-disable-line no-await-in-loop - } else { - return { - queueId: res.queue_id, - lastEventId: res.last_event_id, - }; - } - } catch (e) { - logError(e); - } - } - } - - async function callOnEachEvent(callback, eventTypes = null) { - let queueId = null; - let lastEventId = -1; - const handleEvent = (event) => { - lastEventId = Math.max(lastEventId, event.id); - callback(event); + const z = { + queues: queues_1.queues(config), + events: events_1.events(config), }; - // eslint-disable-next-line no-constant-condition - while (true) { - if (!queueId) { - const queueData = await registerQueue(eventTypes); // eslint-disable-line no-await-in-loop - queueId = queueData.queueId; - lastEventId = queueData.lastEventId; - } - try { - // eslint-disable-next-line no-await-in-loop - const res = await z.events.retrieve({ - queue_id: queueId, - last_event_id: lastEventId, - dont_block: false, + function logError(error) { + console.log('zulip-js: Error while communicating with server:', error); // eslint-disable-line no-console + } + function registerQueue(eventTypes) { + return __awaiter(this, void 0, void 0, function* () { + let res; + // eslint-disable-next-line no-constant-condition + while (true) { + try { + const params = { event_types: eventTypes }; + res = yield z.queues.register(params); // eslint-disable-line no-await-in-loop + if (res.result === 'error') { + logError(res.msg); + yield sleep(1000); // eslint-disable-line no-await-in-loop + } + else { + return { + queueId: res.queue_id, + lastEventId: res.last_event_id, + }; + } + } + catch (e) { + logError(e); + } + } + }); + } + function callOnEachEvent(callback, eventTypes) { + return __awaiter(this, void 0, void 0, function* () { + let queueId = null; + let lastEventId = -1; + const handleEvent = (event) => { + lastEventId = Math.max(lastEventId, event.id); + callback(event); + }; + // eslint-disable-next-line no-constant-condition + while (true) { + if (!queueId) { + const queueData = yield registerQueue(eventTypes); // eslint-disable-line no-await-in-loop + queueId = queueData.queueId; + lastEventId = queueData.lastEventId; + } + try { + // eslint-disable-next-line no-await-in-loop + const res = yield z.events.retrieve({ + queue_id: queueId, + last_event_id: lastEventId, + dont_block: false, + }); + if (res && res.events) { + res.events.forEach(handleEvent); + } + } + catch (e) { + logError(e); + } + yield sleep(1000); // eslint-disable-line no-await-in-loop + } }); - if (res.events) { - res.events.forEach(handleEvent); - } - } catch (e) { - logError(e); - } - await sleep(1000); // eslint-disable-line no-await-in-loop } - } - - return callOnEachEvent; + return callOnEachEvent; } - -module.exports = eventsWrapper; +exports.eventsWrapper = eventsWrapper; diff --git a/src/events_wrapper.ts b/src/events_wrapper.ts new file mode 100644 index 0000000..52c4904 --- /dev/null +++ b/src/events_wrapper.ts @@ -0,0 +1,76 @@ +import { Config } from "./zuliprc"; + +import { queues } from './resources/queues'; +import { events } from './resources/events'; +import { RegisterEventQueueParams } from './resources/queues'; + +function sleep(ms: number): Promise { + // TODO add jitter. + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function eventsWrapper(config: Config) { + const z = { + queues: queues(config), + events: events(config), + }; + + function logError(error: string) { + console.log('zulip-js: Error while communicating with server:', error); // eslint-disable-line no-console + } + + async function registerQueue(eventTypes: string[]|string) { + let res; + // eslint-disable-next-line no-constant-condition + while (true) { + try { + const params = { event_types: eventTypes }; + res = await z.queues.register(params as RegisterEventQueueParams); // eslint-disable-line no-await-in-loop + if (res.result === 'error') { + logError(res.msg); + await sleep(1000); // eslint-disable-line no-await-in-loop + } else { + return { + queueId: res.queue_id, + lastEventId: res.last_event_id, + }; + } + } catch (e) { + logError(e); + } + } + } + + async function callOnEachEvent(callback: any, eventTypes?: string[]|string): Promise { + let queueId = null; + let lastEventId = -1; + const handleEvent = (event: any) => { + lastEventId = Math.max(lastEventId, event.id); + callback(event); + }; + // eslint-disable-next-line no-constant-condition + while (true) { + if (!queueId) { + const queueData = await registerQueue(eventTypes); // eslint-disable-line no-await-in-loop + queueId = queueData.queueId; + lastEventId = queueData.lastEventId; + } + try { + // eslint-disable-next-line no-await-in-loop + const res: any = await z.events.retrieve({ + queue_id: queueId, + last_event_id: lastEventId, + dont_block: false, + }); + if (res && res.events) { + res.events.forEach(handleEvent); + } + } catch (e) { + logError(e); + } + await sleep(1000); // eslint-disable-line no-await-in-loop + } + } + + return callOnEachEvent; +} \ No newline at end of file diff --git a/src/helper.js b/src/helper.js deleted file mode 100644 index 45935fb..0000000 --- a/src/helper.js +++ /dev/null @@ -1,7 +0,0 @@ -const fetch = require('isomorphic-fetch'); -const FormData = require('isomorphic-form-data'); - -module.exports = { - fetch, - FormData, -}; diff --git a/src/index.js b/src/index.js index 82cea08..7c974d0 100644 --- a/src/index.js +++ b/src/index.js @@ -1,67 +1,74 @@ -import parseConfigFile from './zuliprc'; - -const api = require('./api'); - -const accounts = require('./resources/accounts'); -const streams = require('./resources/streams'); -const messages = require('./resources/messages'); -const queues = require('./resources/queues'); -const events = require('./resources/events'); -const users = require('./resources/users'); -const emojis = require('./resources/emojis'); -const typing = require('./resources/typing'); -const reactions = require('./resources/reactions'); -const server = require('./resources/server'); -const filters = require('./resources/filters'); -const eventsWapper = require('./events_wrapper'); - +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const zuliprc_1 = require("./zuliprc"); +const api_1 = require("./api"); +const accounts_1 = require("./resources/accounts"); +const streams_1 = require("./resources/streams"); +const messages_1 = require("./resources/messages"); +const queues_1 = require("./resources/queues"); +const events_1 = require("./resources/events"); +const users_1 = require("./resources/users"); +const emojis_1 = require("./resources/emojis"); +const typing_1 = require("./resources/typing"); +const reactions_1 = require("./resources/reactions"); +const server_1 = require("./resources/server"); +const filters_1 = require("./resources/filters"); +const events_wrapper_1 = require("./events_wrapper"); function getCallEndpoint(config) { - return function callEndpoint(endpoint, method = 'GET', params) { - const myConfig = { ...config }; - let finalendpoint = endpoint; - if (!endpoint.startsWith('/')) { - finalendpoint = `/${endpoint}`; - } - const url = myConfig.apiURL + finalendpoint; - return api(url, myConfig, method, params); - }; + return function callEndpoint(endpoint, method = 'GET', params) { + const myConfig = Object.assign({}, config); + let finalendpoint = endpoint; + if (!endpoint.startsWith('/')) { + finalendpoint = `/${endpoint}`; + } + const url = myConfig.apiURL + finalendpoint; + return api_1.api(url, myConfig, method, params); + }; } - function resources(config) { - return { - config, - callEndpoint: getCallEndpoint(config), - accounts: accounts(config), - streams: streams(config), - messages: messages(config), - queues: queues(config), - events: events(config), - users: users(config), - emojis: emojis(config), - typing: typing(config), - reactions: reactions(config), - server: server(config), - filters: filters(config), - callOnEachEvent: eventsWapper(config), - }; + return { + config, + callEndpoint: getCallEndpoint(config), + accounts: accounts_1.accounts(config), + streams: streams_1.streams(config), + messages: messages_1.messages(config), + queues: queues_1.queues(config), + events: events_1.events(config), + users: users_1.users(config), + emojis: emojis_1.emojis(config), + typing: typing_1.typing(config), + reactions: reactions_1.reactions(config), + server: server_1.server(config), + filters: filters_1.filters(config), + callOnEachEvent: events_wrapper_1.eventsWrapper(config) + }; } - -async function zulip(initialConfig) { - if (initialConfig.zuliprc) { - return resources(await parseConfigFile(initialConfig.zuliprc)); - } - const config = initialConfig; - if (config.realm.endsWith('/api')) { - config.apiURL = `${config.realm}/v1`; - } else { - config.apiURL = `${config.realm}/api/v1`; - } - - if (!config.apiKey) { - const res = await accounts(config).retrieve(); - config.apiKey = res.api_key; - } - return resources(config); +function zulip(initialConfig) { + return __awaiter(this, void 0, void 0, function* () { + if (initialConfig && initialConfig.zuliprc) { + return resources(yield zuliprc_1.default(initialConfig.zuliprc)); + } + const config = initialConfig; + if (config && config.realm && config.realm.endsWith('/api')) { + config.apiURL = `${config.realm}/v1`; + } + else { + config.apiURL = `${config.realm}/api/v1`; + } + if (!config.apiKey) { + const res = yield accounts_1.accounts(config).retrieve(undefined); + config.apiKey = res.api_key; + } + return resources(config); + }); } - -module.exports = zulip; +exports.default = zulip; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4e9981e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,92 @@ +import parseConfigFile, { Config } from './zuliprc'; +import { ZulipSuccessResponse, ZulipErrorResponse } from './types'; + +import { api, HttpMethod } from './api'; + +import { accounts, AccountsClient } from './resources/accounts'; +import { streams, StreamsClient } from './resources/streams'; +import { messages, MessagesClient } from './resources/messages'; +import { queues, QueuesClient } from './resources/queues'; +import { events, EventsClient } from './resources/events'; +import { users, UsersClient } from './resources/users'; +import { emojis, EmojisClient } from './resources/emojis'; +import { typing, TypingClient } from './resources/typing'; +import { reactions, ReactionsClient } from './resources/reactions'; +import { server, ServerClient } from './resources/server'; +import { filters, FiltersClient } from './resources/filters'; +import { eventsWrapper } from './events_wrapper'; + +interface ZulipClient { + config: Config; + callEndpoint: (endpoint: string, method: HttpMethod, params: any) => Promise, + accounts: AccountsClient; + streams: StreamsClient; + messages: MessagesClient; + queues: QueuesClient; + events: EventsClient; + users: UsersClient; + emojis: EmojisClient; + typing: TypingClient; + reactions: ReactionsClient; + server: ServerClient; + filters: FiltersClient; + callOnEachEvent: (callback: any, eventTypes?: string[]|string) => Promise +} + +function getCallEndpoint(config: Config) { + return function callEndpoint(endpoint: string, method: HttpMethod = 'GET', params: any): Promise { + const myConfig: Config = { ...config }; + let finalendpoint: string = endpoint; + if (!endpoint.startsWith('/')) { + finalendpoint = `/${endpoint}`; + } + const url:string = myConfig.apiURL + finalendpoint; + return api(url, myConfig, method, params); + }; +} + +function resources(config: Config): ZulipClient { + return { + config, + callEndpoint: getCallEndpoint(config), + accounts: accounts(config), + streams: streams(config), + messages: messages(config), + queues: queues(config), + events: events(config), + users: users(config), + emojis: emojis(config), + typing: typing(config), + reactions: reactions(config), + server: server(config), + filters: filters(config), + callOnEachEvent: eventsWrapper(config) + }; +} + +interface InitialConfig { + username?: string, + apiKey?: string, + realm?: string, + password?: string, + zuliprc?: string, +} + +export default async function zulip(initialConfig: InitialConfig): Promise { + if (initialConfig && initialConfig.zuliprc) { + return resources(await parseConfigFile(initialConfig.zuliprc)); + } + const config: Config = initialConfig as Config; + if (config && config.realm && config.realm.endsWith('/api')) { + config.apiURL = `${config.realm}/v1`; + } else { + config.apiURL = `${config.realm}/api/v1`; + } + + if (!config.apiKey) { + const res: any = await accounts(config).retrieve(undefined); + config.apiKey = res.api_key; + } + return resources(config); +} + diff --git a/src/resources/accounts.js b/src/resources/accounts.js index 26c6220..5cbff4e 100644 --- a/src/resources/accounts.js +++ b/src/resources/accounts.js @@ -1,19 +1,31 @@ -const helper = require('../helper'); - +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.accounts = void 0; +const fetch = require('isomorphic-fetch'); +const FormData = require("isomorphic-form-data"); function accounts(config) { - return { - retrieve: async () => { - const url = `${config.apiURL}/fetch_api_key`; - const form = new helper.FormData(); - form.append('username', config.username); - form.append('password', config.password); - const res = await helper.fetch(url, { - method: 'POST', - body: form, - }); - return res.json(); - }, - }; + return { + retrieve: () => __awaiter(this, void 0, void 0, function* () { + const url = `${config.apiURL}/fetch_api_key`; + const form = new FormData(); + form.append('username', config.username); + form.append('password', config.password); + const res = yield fetch(url, { + method: 'POST', + body: form + }); + return res.json(); + }), + }; } - -module.exports = accounts; +exports.accounts = accounts; +// module.exports = accounts; diff --git a/src/resources/accounts.ts b/src/resources/accounts.ts new file mode 100644 index 0000000..2cce0b6 --- /dev/null +++ b/src/resources/accounts.ts @@ -0,0 +1,31 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +const fetch = require('isomorphic-fetch'); +const FormData = require("isomorphic-form-data"); + + +export type GetAccountsResponse = GetAccountsSuccess | GetAccountsError; +export interface GetAccountsSuccess extends ZulipSuccessResponse { + api_key: string; + email: string; +} +export interface GetAccountsError extends ZulipErrorResponse { } + +export function accounts(config: Config): AccountsClient { + return { + retrieve: async (): Promise => { + const url: string = `${config.apiURL}/fetch_api_key`; + const form: FormData = new FormData(); + form.append('username', config.username); + form.append('password', config.password as string); + const res: any = await fetch(url, { + method: 'POST', + body: form + }); + return res.json(); + }, + }; +} + +export type AccountsClient = RetrieverClient; +// module.exports = accounts; diff --git a/src/resources/emojis.js b/src/resources/emojis.js index aa35d96..49e0fde 100644 --- a/src/resources/emojis.js +++ b/src/resources/emojis.js @@ -1,12 +1,18 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.emojis = void 0; +const api_1 = require("../api"); +// // SECTION UPLOAD EMOJIS +// export interface UploadEmojisParams { } +// export type UploadEmojisResponse = UploadEmojisSuccess | UploadEmojisError; +// export interface UploadEmojisSuccess extends ZulipSuccessResponse { } +// export interface UploadEmojisError extends ZulipErrorResponse { } function emojis(config) { - return { - retrieve: (params) => { - const url = `${config.apiURL}/realm/emoji`; - return api(url, config, 'GET', params); - }, - }; + return { + retrieve: (params) => { + const url = `${config.apiURL}/realm/emoji`; + return api_1.api(url, config, 'GET', params); + }, + }; } - -module.exports = emojis; +exports.emojis = emojis; diff --git a/src/resources/emojis.ts b/src/resources/emojis.ts new file mode 100644 index 0000000..f15497e --- /dev/null +++ b/src/resources/emojis.ts @@ -0,0 +1,44 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +import { api } from '../api'; + +export type EmojisClient = RetrieverClient; + +// SECTION GET EMOJIS +export interface GetEmojisParams { } + +export type GetEmojisResponse = GetEmojisSuccess | GetEmojisError; + +export interface GetEmojisSuccess extends ZulipSuccessResponse { + emoji: { + [key: number]: Emoji; + } +} + +export interface Emoji { + author_id: number, + deactivated: boolean; + id: string; + name: string; + source_url: string; +} + +export interface GetEmojisError extends ZulipErrorResponse { } + +// // SECTION UPLOAD EMOJIS +// export interface UploadEmojisParams { } + +// export type UploadEmojisResponse = UploadEmojisSuccess | UploadEmojisError; + +// export interface UploadEmojisSuccess extends ZulipSuccessResponse { } + +// export interface UploadEmojisError extends ZulipErrorResponse { } + +export function emojis(config: Config): EmojisClient { + return { + retrieve: (params?: GetEmojisParams): Promise => { + const url = `${config.apiURL}/realm/emoji`; + return api(url, config, 'GET', params); + }, + }; +} diff --git a/src/resources/events.js b/src/resources/events.js index c95c571..8729893 100644 --- a/src/resources/events.js +++ b/src/resources/events.js @@ -1,12 +1,13 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.events = void 0; +const api_1 = require("../api"); function events(config) { - return { - retrieve: (params) => { - const url = `${config.apiURL}/events`; - return api(url, config, 'GET', params); - }, - }; + return { + retrieve: (params) => { + const url = `${config.apiURL}/events`; + return api_1.api(url, config, 'GET', params); + }, + }; } - -module.exports = events; +exports.events = events; diff --git a/src/resources/events.ts b/src/resources/events.ts new file mode 100644 index 0000000..9b54546 --- /dev/null +++ b/src/resources/events.ts @@ -0,0 +1,58 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +import { api } from '../api'; + +export type EventsClient = RetrieverClient; + +// SECTION GET EVENTS +export interface GetEventsParams { + /** "1375801870:2942" No The ID of a queue that you registered via POST /api/v1/register (see Register a queue). */ + queue_id: string; + /** -1 No The highest event ID in this queue that you've received and wish to acknowledge. See the code for call_on_each_event in the zulip Python module for an example implementation of correctly processing each event exactly once. */ + last_event_id?: number; + /** true No Set to true if the client is requesting a nonblocking reply. If not specified, the request will block until either a new event is available or a few minutes have passed, in which case the server will send the client a heartbeat event. Defaults to false. */ + dont_block?: boolean; +} + +export type GetEventsResponse = GetEventsSuccess | GetEventsError; + +export interface GetEventsSuccess extends ZulipSuccessResponse { + events: Event[]; + queue_id: string; +} + +export type Event = MessageEvent | any; + +export interface MessageEvent { + avatar_url: string; + client: string; + content: string; + content_type: string; + display_recipient: string; + id: number; + recipient_id: number; + sender_email: string; + sender_full_name: string; + sender_id: number; + sender_realm_str: string; + sender_short_name: string; + subject: string; + subject_links: string[], + timestamp: number; + type: string; +} + +export interface GetEventsError extends ZulipErrorResponse { + queue_id: string; + last_event_id: number; + dont_block: boolean; +} + +export function events(config: Config): EventsClient { + return { + retrieve: (params: GetEventsParams): Promise => { + const url: string = `${config.apiURL}/events`; + return api(url, config, 'GET', params); + }, + }; +} diff --git a/src/resources/filters.js b/src/resources/filters.js index 8ecd8b4..390d9c5 100644 --- a/src/resources/filters.js +++ b/src/resources/filters.js @@ -1,12 +1,14 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.filters = void 0; +const api_1 = require("../api"); +; function filters(config) { - return { - retrieve: (params) => { - const url = `${config.apiURL}/realm/filters`; - return api(url, config, 'GET', params); - }, - }; + return { + retrieve: (params) => { + const url = `${config.apiURL}/realm/filters`; + return api_1.api(url, config, 'GET', params); + }, + }; } - -module.exports = filters; +exports.filters = filters; diff --git a/src/resources/filters.ts b/src/resources/filters.ts new file mode 100644 index 0000000..f92614e --- /dev/null +++ b/src/resources/filters.ts @@ -0,0 +1,24 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +import { api } from '../api'; + +export type FiltersClient = RetrieverClient; + +export interface GetFiltersParams {}; +export type GetFiltersResult = GetFiltersSuccessResponse | GetFiltersErrorResponse; + +export interface GetFiltersSuccessResponse extends ZulipSuccessResponse { + filters: []; +} + +export interface GetFiltersErrorResponse extends ZulipErrorResponse { +} + +export function filters(config: Config): FiltersClient { + return { + retrieve: (params: GetFiltersParams): Promise => { + const url: string = `${config.apiURL}/realm/filters`; + return api(url, config, 'GET', params); + }, + }; +} diff --git a/src/resources/messages.js b/src/resources/messages.js index 75bbf23..b38d72e 100644 --- a/src/resources/messages.js +++ b/src/resources/messages.js @@ -1,74 +1,76 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.messages = void 0; +const api_1 = require("../api"); function messages(config) { - const baseURL = `${config.apiURL}/messages`; - const flagsURL = `${baseURL}/flags`; - return { - retrieve: (initialParams) => { - const url = `${config.apiURL}/messages`; - const params = { ...initialParams }; - if (params.narrow) { - params.narrow = JSON.stringify(params.narrow); - } - return api(url, config, 'GET', params); - }, - send: (params) => { - const url = `${config.apiURL}/messages`; - return api(url, config, 'POST', params); - }, - render: (initialParams) => { - const url = `${config.apiURL}/messages/render`; - let params = { ...initialParams }; - if (typeof initialParams === 'string') { - params = { - content: initialParams, - }; - } - return api(url, config, 'POST', params); - }, - update: (params) => { - const url = `${config.apiURL}/messages/${params.message_id}`; - return api(url, config, 'PATCH', params); - }, - flags: { - add: (initialParams) => { - // params.flag can be one of 'read', 'starred', 'mentioned', - // 'wildcard_mentioned', 'has_alert_word', 'historical', - const params = { ...initialParams }; - params.op = 'add'; - if (params.messages) { - params.messages = JSON.stringify(params.messages); - } - return api(flagsURL, config, 'POST', params); - }, - remove: (initialParams) => { - // params.flag can be one of 'read', 'starred', 'mentioned', - // 'wildcard_mentioned', 'has_alert_word', 'historical', - const params = { ...initialParams }; - params.op = 'remove'; - if (params.messages) { - params.messages = JSON.stringify(params.messages); - } - return api(flagsURL, config, 'POST', params); - }, - }, - getById: (params) => { - const url = `${config.apiURL}/messages/${params.message_id}`; - return api(url, config, 'GET', params); - }, - getHistoryById: (params) => { - const url = `${config.apiURL}/messages/${params.message_id}/history`; - return api(url, config, 'GET', params); - }, - deleteReactionById: (params) => { - const url = `${config.apiURL}/messages/${params.message_id}/reactions`; - return api(url, config, 'DELETE', params); - }, - deleteById: (params) => { - const url = `${config.apiURL}/messages/${params.message_id}`; - return api(url, config, 'DELETE', params); - }, - }; + const baseURL = `${config.apiURL}/messages`; + const flagsURL = `${baseURL}/flags`; + return { + retrieve: (initialParams) => { + const url = `${config.apiURL}/messages`; + const params = Object.assign({}, initialParams); + if (params.narrow) { + params.narrow = JSON.stringify(params.narrow); + } + return api_1.api(url, config, 'GET', params); + }, + send: (params) => { + const url = `${config.apiURL}/messages`; + return api_1.api(url, config, 'POST', params); + }, + render: (initialParams) => { + const url = `${config.apiURL}/messages/render`; + let params; + if (typeof initialParams === 'string') { + params = { + content: initialParams, + }; + } + else { + params = Object.assign({}, initialParams); + } + return api_1.api(url, config, 'POST', params); + }, + update: (params) => { + const url = `${config.apiURL}/messages/${params.message_id}`; + return api_1.api(url, config, 'PATCH', params); + }, + flags: { + add: (initialParams) => { + // params.flag can be one of 'read', 'starred', 'mentioned', + // 'wildcard_mentioned', 'has_alert_word', 'historical', + const params = Object.assign(Object.assign({}, initialParams), { op: 'add' }); + if (params.messages) { + params.messages = JSON.stringify(params.messages); + } + return api_1.api(flagsURL, config, 'POST', params); + }, + remove: (initialParams) => { + // params.flag can be one of 'read', 'starred', 'mentioned', + // 'wildcard_mentioned', 'has_alert_word', 'historical', + const params = Object.assign(Object.assign({}, initialParams), { op: 'remove' }); + if (params.messages) { + params.messages = JSON.stringify(params.messages); + } + return api_1.api(flagsURL, config, 'POST', params); + }, + }, + getById: (params) => { + const url = `${config.apiURL}/messages/${params.message_id}`; + return api_1.api(url, config, 'GET', params); + }, + getHistoryById: (params) => { + const url = `${config.apiURL}/messages/${params.message_id}/history`; + return api_1.api(url, config, 'GET', params); + }, + deleteReactionById: (params) => { + const url = `${config.apiURL}/messages/${params.message_id}/reactions`; + return api_1.api(url, config, 'DELETE', params); + }, + deleteById: (params) => { + const url = `${config.apiURL}/messages/${params.message_id}`; + return api_1.api(url, config, 'DELETE', params); + }, + }; } - -module.exports = messages; +exports.messages = messages; diff --git a/src/resources/messages.ts b/src/resources/messages.ts new file mode 100644 index 0000000..b29e5b1 --- /dev/null +++ b/src/resources/messages.ts @@ -0,0 +1,371 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, Caller, ZulipSuccessResponse, ZulipErrorResponse, Narrow } from "../types"; +import { api } from '../api'; + +// SECTION MESSAGE CLIENT +export interface MessagesClient extends RetrieverClient { + send: Caller; + render: Caller; + update: Caller; + getById: Caller; + getHistoryById: Caller; + deleteReactionById: Caller; + deleteById: Caller; + flags: MessagesFlagsClient; +} + +export interface MessagesFlagsClient { + add: Caller; + remove: Caller; +} + +export interface GetMessageByIdParams { + message_id: number; +} + +// SECTION GET MESSAGE +export interface GetMessageParams { + /** The message ID to fetch messages near. Required unless use_first_unread_anchor is set to true. */ + anchor?: number; + /** Whether to use the (computed by the server) first unread message matching the narrow as the anchor. Mutually exclusive with anchor. Defaults to false. */ + use_first_unread_anchor?: boolean; + /** The number of messages with IDs less than the anchor to retrieve. */ + num_before: number; + /** The number of messages with IDs greater than the anchor to retrieve. */ + num_after: number; + /** The narrow where you want to fetch the messages from. See how to construct a narrow. Defaults to []. */ + narrow?: Array | string; + /** Whether the client supports computing gravatars URLs. If enabled, avatar_url will be included in the response only if there is a Zulip avatar, and will be null for users who are using gravatar as their avatar. This option significantly reduces the compressed size of user data, since gravatar URLs are long, random strings and thus do not compress well. Defaults to false. */ + client_gravatar?: boolean; + /** If true, message content is returned in the rendered HTML format. If false, message content is returned in the raw markdown-format text that user entered. Defaults to true. */ + apply_markdown?: boolean; +} + +export type GetMessageResponse = GetMessageSuccess | GetMessageError; + +export interface GetMessageSuccess extends ZulipSuccessResponse { + anchor: number; + found_anchor: boolean; + found_oldest: boolean; + found_newest: boolean; + history_limited: boolean; + messages: Message[]; +} + +export interface Message { + avatar_url: string; + client: string; + content: string; + content_type: MessageContentType; + display_recipient: string | []; + flags: MessageFlag[]; + id: number; + is_me_message: boolean; + reactions: Reactions[]; + recipient_id: number; + sender_email: string; + sender_full_name: string; + sender_id: number; + sender_realm_str: string; + sender_short_name?: string; // Didn't find in docs + stream_id: number; + subject: string; + subject_links: []; + submessages: []; + timestamp: number; + type: MessageType, + match_content?: string, + match_subject?: string +} + +export interface Reactions { + emoji_code: string; + emoji_name: string; + reaction_type: ReactionType; + user_id: number; + user: any; +} + +export type ReactionType = "unicode_emoji" | "realm_emoji" | "zulip_extra_emoji"; + +export type MessageContentType = "text/html" | "text/x-markdown"; +export type MessageFlag = + | "read" + | "starred" + | "collapsed" + | "mentioned" + | "wildcard_mentioned" + | "has_alert_word" + | "historical"; + +export interface GetMessageError extends ZulipErrorResponse {} + +// SECTION SEND MESSAGE +export interface SendMessageParams { + /** The type of message to be sent. private for a private message and stream for a stream message. Must be one of: private, stream. */ + type: MessageType; + /** The destination stream, or a CSV/JSON-encoded list containing the usernames (emails) of the recipients. */ + to: string; + /** The topic of the message. Only required if type is stream, ignored otherwise. Maximum length of 60 characters. */ + topic?: string; + /** The content of the message. Maximum message size of 10000 bytes. */ + content: string; + /** For clients supporting local echo, the event queue ID for the client. */ + queue_id: string; + /** For clients supporting local echo, a unique string-format identifier chosen freely by the client; the server will pass it back to the client without inspecting it, as described in the queue_id description. */ + local_id: string; +} + +export type SendMessageResponse = SendMessageSuccess | SendMessageError; + +export interface SendMessageSuccess extends ZulipSuccessResponse { + id: number; + deliver_at?: string; +} + +export interface SendMessageError extends ZulipErrorResponse { + code: CRUDMessageErrorCode; + stream: string; +} + +export type CRUDMessageErrorCode = "STREAM_DOES_NOT_EXIST" | "BAD_REQUEST" | string; + +// SECTION RENDER MESSAGE +export interface RenderMessageParams { + /** The content of the message. */ + content: string; +} + +export type RenderMessageResponse = RenderMessageSuccess | RenderMessageError; + +export interface RenderMessageSuccess extends ZulipSuccessResponse { + rendered: string; +} + +export interface RenderMessageError extends ZulipErrorResponse {} + +// SECTION UPDATE MESSAGE +export interface UpdateMessageParams { + /** The ID of the message that you wish to edit/update. */ + message_id: number; + /** The topic of the message. Only required for stream messages. Maximum length of 60 characters. */ + topic?: string; + /** Which message(s) should be edited: just the one indicated in message_id, messages in the same topic that had been sent after this one, or all of them. Must be one of: change_one, change_later, change_all. Defaults to "change_one". */ + propagate_mode?: PropogateMode; + /** Whether to send breadcrumb message to the old thread to notify users where the messages were moved to. */ + send_notification_to_old_thread?: boolean; + /** Whether to send a notification message to the new thread to notify users where the messages came from. */ + send_notification_to_new_thread?: boolean; + /** The content of the message. Maximum message size of 10000 bytes. */ + content?: string; + /** The ID of the stream to access. */ + stream_id?: number; +} + +export type UpdateMessageResponse = UpdateMessageSuccess | UpdateMessageError; + +export interface UpdateMessageSuccess extends ZulipSuccessResponse {} + +export interface UpdateMessageError extends ZulipErrorResponse { + code: CRUDMessageErrorCode; +} + +// SECTION GET RAW MESSAGE +export interface GetRawMessageParams { + /** The ID of the message that you wish to get the raw message of. */ + message_id: number; +} + +export type GetRawMessageResponse = GetRawMessageSuccess | GetRawMessageError; + +export interface GetRawMessageSuccess extends ZulipSuccessResponse { + raw_content: string; +} + +export interface GetRawMessageError extends ZulipErrorResponse { + code: CRUDMessageErrorCode; +} + +// SECTION UPLOAD A FILE +/* // TODO Define types when implemented in client +export interface UploadFileParams {} +export type UploadFileResponse = UploadFileSuccess | UploadFileError; +export interface UploadFileSuccess extends ZulipSuccessResponse {} +export interface UploadFileError extends ZulipErrorResponse {} +*/ + +// SECTION DELETE A MESSAGE +export interface DeleteMessageParams { + /** The ID of the message that you wish to delete. */ + message_id: number; +} + +export type DeleteMessageResponse = DeleteMessageSuccess | DeleteMessageError; + +export interface DeleteMessageSuccess extends ZulipSuccessResponse {} + +export interface DeleteMessageError extends ZulipErrorResponse { + code: CRUDMessageErrorCode; +} + +// SECTION GET MESSAGE EDIT HISTORY +export interface GetMessageEditHistoryParams { + /** The ID of the message that you wish to get the edit history of. */ + message_id: number; +} + +export type GetMessageEditHistoryResponse = GetMessageEditHistorySuccess | GetMessageEditHistoryError; + +export interface GetMessageEditHistorySuccess extends ZulipSuccessResponse { + message_history: [NewestMessage] | (EditedMessage[] & { 0: NewestMessage }); +} + +export interface HistoricalMessage { + content: string; + timestamp: number; + topic: string; + user_id: number; +} + +export interface NewestMessage extends HistoricalMessage { + rendered_content: string; +} + +export interface EditedMessage extends NewestMessage { + content_html_diff: string; + prev_content: string; + prev_rendered_content: string; + prev_topic: string; +} + +export interface GetMessageEditHistoryError extends ZulipErrorResponse { + code: string; +} + +export interface DeleteReactionParams { + message_id: number; + emoji_name?: string; + emoji_code?: string; + reaction_type?: string; +} + +export type DeleteReactionsResponse = DeleteReactionsSuccess | DeleteReactionsError; + +export interface DeleteReactionsSuccess extends ZulipSuccessResponse { +} + +export interface DeleteReactionsError extends ZulipErrorResponse { + code: string; +} + +// SECTION UPDATE MESSAGE FLAGS +export interface InitialUpdateMessageFlagsParams { + /** An array containing the IDs of the target messages. */ + messages: number[]; + /** The flag that should be added/removed. */ + flag: MessageFlag; +} + +export interface UpdateMessageFlagsParams extends Omit { + messages: string | number[]; + /** Whether to add the flag or remove it. Must be one of: add, remove. */ + op: "add" | "remove"; +} + +export type UpdateMessageFlagsResponse = UpdateMessageFlagsSuccess | UpdateMessageFlagsError; + +export interface UpdateMessageFlagsSuccess extends ZulipSuccessResponse { + messages: number[], +} + +export interface UpdateMessageFlagsError extends ZulipErrorResponse {} + +// SECTION MARK MESSAGES READ +export interface MarkMessagesReadParams { + /** The ID of the stream whose messages should be marked as read. */ + stream_id: number; +} + +export type MarkMessagesReadResponse = MarkMessagesReadSuccess | MarkMessagesReadError; + +export interface MarkMessagesReadSuccess extends ZulipSuccessResponse {} + +export interface MarkMessagesReadError extends ZulipErrorResponse {} + +// SECTION GENERAL TYPES +export type PrivateMessage = "private"; +export type StreamMessage = "stream"; +export type MessageType = PrivateMessage | StreamMessage; + +export type PropogateMode = "change_one" | "change_later" | "change_all"; + +export function messages(config: Config): MessagesClient { + const baseURL: string = `${config.apiURL}/messages`; + const flagsURL: string = `${baseURL}/flags`; + return { + retrieve: (initialParams: GetMessageParams): Promise => { + const url: string = `${config.apiURL}/messages`; + const params = { ...initialParams }; + if (params.narrow) { + params.narrow = JSON.stringify(params.narrow); + } + return api(url, config, 'GET', params); + }, + send: (params: SendMessageParams): Promise => { + const url: string = `${config.apiURL}/messages`; + return api(url, config, 'POST', params); + }, + render: (initialParams: RenderMessageParams | string): Promise => { + const url: string = `${config.apiURL}/messages/render`; + let params: RenderMessageParams; + if (typeof initialParams === 'string') { + params = { + content: initialParams, + } + } else { + params = { ...initialParams }; + } + return api(url, config, 'POST', params); + }, + update: (params: UpdateMessageParams): Promise => { + const url: string = `${config.apiURL}/messages/${params.message_id}`; + return api(url, config, 'PATCH', params); + }, + flags: { + add: (initialParams: InitialUpdateMessageFlagsParams): Promise => { + // params.flag can be one of 'read', 'starred', 'mentioned', + // 'wildcard_mentioned', 'has_alert_word', 'historical', + const params: UpdateMessageFlagsParams = { ...initialParams, op: 'add' }; + if (params.messages) { + params.messages = JSON.stringify(params.messages); + } + return api(flagsURL, config, 'POST', params); + }, + remove: (initialParams: InitialUpdateMessageFlagsParams): Promise => { + // params.flag can be one of 'read', 'starred', 'mentioned', + // 'wildcard_mentioned', 'has_alert_word', 'historical', + const params: UpdateMessageFlagsParams = { ...initialParams, op: 'remove' }; + if (params.messages) { + params.messages = JSON.stringify(params.messages); + } + return api(flagsURL, config, 'POST', params); + }, + }, + getById: (params: GetMessageByIdParams): Promise => { + const url = `${config.apiURL}/messages/${params.message_id}`; + return api(url, config, 'GET', params); + }, + getHistoryById: (params: GetMessageByIdParams): Promise => { + const url = `${config.apiURL}/messages/${params.message_id}/history`; + return api(url, config, 'GET', params); + }, + deleteReactionById: (params: DeleteReactionParams): Promise => { + const url: string = `${config.apiURL}/messages/${params.message_id}/reactions`; + return api(url, config, 'DELETE', params); + }, + deleteById: (params: DeleteMessageParams): Promise => { + const url = `${config.apiURL}/messages/${params.message_id}`; + return api(url, config, 'DELETE', params); + }, + }; +} \ No newline at end of file diff --git a/src/resources/queues.js b/src/resources/queues.js index d5c967b..2d9db60 100644 --- a/src/resources/queues.js +++ b/src/resources/queues.js @@ -1,20 +1,21 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.queues = void 0; +const api_1 = require("../api"); function queues(config) { - return { - register: (initialParams) => { - const url = `${config.apiURL}/register`; - const params = { ...initialParams }; - if (params.event_types) { - params.event_types = JSON.stringify(params.event_types); - } - return api(url, config, 'POST', params); - }, - deregister: (params) => { - const url = `${config.apiURL}/events`; - return api(url, config, 'DELETE', params); - }, - }; + return { + register: (initialParams) => { + const url = `${config.apiURL}/register`; + const params = Object.assign({}, initialParams); + if (params.event_types) { + params.event_types = JSON.stringify(params.event_types); + } + return api_1.api(url, config, 'POST', params); + }, + deregister: (params) => { + const url = `${config.apiURL}/events`; + return api_1.api(url, config, 'DELETE', params); + }, + }; } - -module.exports = queues; +exports.queues = queues; diff --git a/src/resources/queues.ts b/src/resources/queues.ts new file mode 100644 index 0000000..33de22d --- /dev/null +++ b/src/resources/queues.ts @@ -0,0 +1,102 @@ +import { Config } from "../zuliprc"; +import { Caller, Narrow, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +import { Message } from "./messages"; +import { Emoji } from "./emojis"; +import { api } from '../api'; + +export interface QueuesClient { + register: Caller; + deregister: Caller; +} + +// SECTION REGISTER EVENT QUEUE +export interface RegisterEventQueueParams { + /** Set to true if you would like the content to be rendered in HTML format (otherwise the API will return the raw text that the user entered) Defaults to false.*/ + apply_markdown?: boolean; + /** + * The client_gravatar field is set to true if clients can compute their own gravatars. + * Defaults to false. + */ + client_gravatar?: boolean; + /** + * A JSON-encoded array indicating which types of events you're interested in. + * Values that you might find useful include:* message (messages), * subscription (changes in your subscriptions), * realm_user (changes in the list of users in your realm)If you do not specify this argument, you will receive all events, and have to filter out the events not relevant to your client in your client code. + * For most applications, one is only interested in messages, so one specifies: event_types=['message'] + */ + event_types?: EventType[] | string ; + /** + * Set to True if you would like to receive events that occur within all public streams. + * Defaults to false. + */ + all_public_streams?: boolean; + /** + * Set to True if you would like to receive events that include the subscribers for each stream. + * Defaults to false. + */ + include_subscribers?: boolean + /** + * Same as the event_types argument except that the values in fetch_event_types are used to fetch initial data. + * If fetch_event_types is not provided, event_types is used and if event_types is not provided, this argument defaults to None. + */ + fetch_event_types?: EventType[]; + /** + * A JSON-encoded array of length 2 indicating the narrow for which you'd like to receive events for. + * For instance, to receive events for the stream Denmark, you would specify narrow=['stream', 'Denmark']. Another example is narrow=['is', 'private'] for private messages. Defaults to "narrow=[]". + */ + narrow?: Narrow[]; +} +export type RegisterEventQueueResponse = RegisterEventQueueSuccess | RegisterEventQueueError; +export type RegisterEventQueueSuccess = ZulipSuccessResponse & EventMap & { + last_event_id: number; + queue_id: string; +}; + +export type EventMap = { [key in EventType]: Event }; + +export interface RegisterEventQueueError extends ZulipErrorResponse { } + +export type EventType = + | "message" + | "subscription" + | "realm_user" + | "realm_emoji"; + +export type Event = +T extends "message" ? { [key: number]: Message } : +T extends "subscription" ? UnknownEvent : +T extends "realm_user" ? UnknownEvent : +T extends "realm_emoji" ? { [key: number]: Emoji } : any; + +export type UnknownEvent = { [key: number]: any }; + +// SECTION UNREGISTER EVENT QUEUE +export interface DeregisterEventQueueParams { + /** The ID of a queue that you registered via POST /api/v1/register(see Register a queue. */ + queue_id: string; +} + +export type DeregisterEventQueueResponse = DeregisterEventQueueSuccess | DeregisterEventQueueError; + +export interface DeregisterEventQueueSuccess extends ZulipSuccessResponse {} + +export interface DeregisterEventQueueError extends ZulipErrorResponse { + code: string; + queue_id?: string; +} + +export function queues(config: Config): QueuesClient { + return { + register: (initialParams: RegisterEventQueueParams): Promise => { + const url = `${config.apiURL}/register`; + const params = { ...initialParams }; + if (params.event_types) { + params.event_types = JSON.stringify(params.event_types); + } + return api(url, config, 'POST', params); + }, + deregister: (params: DeregisterEventQueueParams): Promise => { + const url = `${config.apiURL}/events`; + return api(url, config, 'DELETE', params); + }, + }; +} diff --git a/src/resources/reactions.js b/src/resources/reactions.js index b58f906..85d93c6 100644 --- a/src/resources/reactions.js +++ b/src/resources/reactions.js @@ -1,16 +1,21 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.reactions = void 0; +const api_1 = require("../api"); function reactions(config) { - const url = (messageID) => `${config.apiURL}/messages/${messageID}/reactions`; - const call = (method, initParams) => { - const params = { ...initParams }; - delete params.message_id; - return api(url(initParams.message_id), config, method, params); - }; - return { - add: (params) => call('POST', params), - remove: (params) => call('DELETE', params), - }; + return { + add: (initParams) => { + const url = `${config.apiURL}/messages/${initParams.message_id}/reactions`; + const params = Object.assign({}, initParams); + // delete params.message_id; + return api_1.api(url, config, 'POST', params); + }, + remove: (initParams) => { + const url = `${config.apiURL}/messages/${initParams.message_id}/reactions`; + const params = Object.assign({}, initParams); + // delete params.message_id; + return api_1.api(url, config, 'DELETE', params); + } + }; } - -module.exports = reactions; +exports.reactions = reactions; diff --git a/src/resources/reactions.ts b/src/resources/reactions.ts new file mode 100644 index 0000000..75292bf --- /dev/null +++ b/src/resources/reactions.ts @@ -0,0 +1,62 @@ +import { Config } from "../zuliprc"; +import { HttpMethod } from "../api"; +import { api } from '../api'; +import { ZulipSuccessResponse, ZulipErrorResponse, Caller } from "../types"; + +export interface ReactionsClient { + add: Caller; + remove: Caller; +} + +export type ReactionType = "unicode_emoji" | "realm_emoji" | "zulip_extra_emoji"; + +export interface AddMessageEmojiParams { + message_id: number; + emoji_name: string; + emoji_code?: string; + reaction_type?: ReactionType; +} + +export type AddMessageEmojiResponse = AddMessageEmojiSuccess | AddMessageEmojiError; + +export interface AddMessageEmojiSuccess extends ZulipSuccessResponse {} + +export interface AddMessageEmojiError extends ZulipErrorResponse {} + + +export interface DeleteMessageEmojiParams { + message_id: number; + emoji_name?: string; + emoji_code?: string; + reaction_type?: ReactionType; +} + +export type DeleteMessageEmojiResponse = DeleteMessageEmojiSuccess | DeleteMessageEmojiError; + +export interface DeleteMessageEmojiSuccess extends ZulipSuccessResponse {} + +export interface DeleteMessageEmojiError extends ZulipErrorResponse {} + +export interface MessageEmojiParams { + message_id?: number; + emoji_name?: string; + emoji_code?: string; + reaction_type?: ReactionType; +} + +export function reactions(config: Config): ReactionsClient { + return { + add: (initParams: AddMessageEmojiParams): Promise => { + const url: string = `${config.apiURL}/messages/${initParams.message_id}/reactions`; + const params = { ...initParams }; + // delete params.message_id; + return api(url, config, 'POST', params); + }, + remove: (initParams: DeleteMessageEmojiParams): Promise => { + const url: string = `${config.apiURL}/messages/${initParams.message_id}/reactions`; + const params = { ...initParams }; + // delete params.message_id; + return api(url, config, 'DELETE', params); + } + }; +} diff --git a/src/resources/server.js b/src/resources/server.js index 83d5f95..98ad4f5 100644 --- a/src/resources/server.js +++ b/src/resources/server.js @@ -1,12 +1,13 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.server = void 0; +const api_1 = require("../api"); function server(config) { - return { - settings: (params) => { - const url = `${config.apiURL}/server_settings`; - return api(url, config, 'GET', params); - }, - }; + return { + settings: (params) => { + const url = `${config.apiURL}/server_settings`; + return api_1.api(url, config, 'GET', params); + }, + }; } - -module.exports = server; +exports.server = server; diff --git a/src/resources/server.ts b/src/resources/server.ts new file mode 100644 index 0000000..ce647ae --- /dev/null +++ b/src/resources/server.ts @@ -0,0 +1,89 @@ +import { Config } from "../zuliprc"; +import { Caller, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +import { api } from '../api'; + +export interface ServerClient { + settings: Caller; +} + +export interface GetServerSettingsParams {} + +export type GetServerSettingsResponse = GetServerSettingsSuccess | GetServerSettingsError; + +export interface GetServerSettingsSuccess extends ZulipSuccessResponse { + /** object in which each key-value pair in the object indicates whether the authentication method is enabled on this server. */ + authentication_methods: { [key in AuthenticationMethod]: boolean }; + /** the version of Zulip running in the server. */ + zulip_version: string; + /** whether mobile/push notifications are enabled. */ + push_notifications_enabled: boolean; + /** whether the Zulip client that has sent a request to this endpoint is deemed incompatible with the server. */ + is_incompatible: boolean; + /** setting for allowing users authenticate with an email-password combination. */ + email_auth_enabled: boolean; + /** whether usernames should have an email address format. This is important for clients to know whether the validate email address format in a login prompt; this value will be false if the server has LDAP authentication enabled with a username and password combination. */ + require_email_format_usernames: string; + /** the organization's canonical URI. */ + realm_uri: string; + /** the organization's name (for display purposes). */ + realm_name: string; + /** the URI of the organization's logo as a square image, used for identifying the organization in small locations in the mobile and desktop apps. */ + realm_icon: string; + /** HTML description of the organization, as configured by the organization profile. */ + realm_description: string; + /** list of dictionaries describing the available external authentication methods (such as google/github/SAML) enabled for this organization. Each dictionary specifies the name and icon that should be displayed on the login buttons (display_name and display_icon, where display_icon can be null, if no icon is to be displayed), the URLs that should be accessed to initiate login/signup using the method (login_url and signup_url) and name, which is a unique, stable, machine-readable name for the authentication method. The list is sorted in the order in which these authentication methods should be displayed. */ + external_authentication_methods: ExternalAuthenticationMethod[]; +} + +export interface GetServerSettingsError extends ZulipErrorResponse { } + +// SECTION GENERAL TYPES +export type AuthenticationMethod = + | "azuread" + | "dev" + | "email" + | "github" + | "google" + | "ldap" + | "password" + | "remoteuser" + | "saml"; + + +export type ExternalAuthenticationMethod = + | SAMLAuthenticationMethod + | GoogleAuthenticationMethod + | GitHubAuthenticationMethod; + +export interface SAMLAuthenticationMethod { + display_icon: null, + display_name: "SAML", + login_url: string; + name: "saml:idp_name", + signup_url: string; +} + +export interface GoogleAuthenticationMethod { + display_icon: string; + display_name: "Google"; + login_url: string; + name: "google"; + signup_url: string; +} + +export interface GitHubAuthenticationMethod { + display_icon: string; + display_name: "GitHub"; + login_url: string; + name: "github"; + signup_url: string; +} + +export function server(config: Config): ServerClient { + return { + settings: (params: GetServerSettingsParams): Promise => { + const url: string = `${config.apiURL}/server_settings`; + return api(url, config, 'GET', params); + }, + }; +} \ No newline at end of file diff --git a/src/resources/streams.js b/src/resources/streams.js index d632169..c613311 100644 --- a/src/resources/streams.js +++ b/src/resources/streams.js @@ -1,38 +1,39 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.streams = void 0; +const api_1 = require("../api"); function streams(config) { - return { - retrieve: (params) => { - const url = `${config.apiURL}/streams`; - return api(url, config, 'GET', params); - }, - getStreamId: (initialParams) => { - const url = `${config.apiURL}/get_stream_id`; - let params = { ...initialParams }; - if (typeof initialParams === 'string') { - params = { - stream: initialParams, - }; - } - return api(url, config, 'GET', params); - }, - subscriptions: { - retrieve: (params) => { - const url = `${config.apiURL}/users/me/subscriptions`; - return api(url, config, 'GET', params); - }, - }, - topics: { - retrieve: (params) => { - const url = `${config.apiURL}/users/me/${params.stream_id}/topics`; - return api(url, config, 'GET'); - }, - }, - deleteById: (params) => { - const url = `${config.apiURL}/streams/${params.stream_id}`; - return api(url, config, 'DELETE', params); - }, - }; + return { + retrieve: (params) => { + const url = `${config.apiURL}/streams`; + return api_1.api(url, config, 'GET', params); + }, + getStreamId: (initialParams) => { + const url = `${config.apiURL}/get_stream_id`; + let params = Object.assign({}, initialParams); + if (typeof initialParams === 'string') { + params = { + stream: initialParams, + }; + } + return api_1.api(url, config, 'GET', params); + }, + subscriptions: { + retrieve: (params) => { + const url = `${config.apiURL}/users/me/subscriptions`; + return api_1.api(url, config, 'GET', params); + }, + }, + topics: { + retrieve: (params) => { + const url = `${config.apiURL}/users/me/${params.stream_id}/topics`; + return api_1.api(url, config, 'GET', {}); + }, + }, + deleteById: (params) => { + const url = `${config.apiURL}/streams/${params.stream_id}`; + return api_1.api(url, config, 'DELETE', params); + }, + }; } - -module.exports = streams; +exports.streams = streams; diff --git a/src/resources/streams.ts b/src/resources/streams.ts new file mode 100644 index 0000000..4d23359 --- /dev/null +++ b/src/resources/streams.ts @@ -0,0 +1,145 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, Caller, ZulipSuccessResponse, ZulipErrorResponse, Subscription, Topic } from "../types"; +import { api } from '../api'; + +export interface StreamsClient extends RetrieverClient { + getStreamId: Caller; + subscriptions: StreamSubscriptions; + topics: StreamTopics; + deleteById: Caller +} + +export interface DeleteStreamParams { + stream_id: number; +} + +export type DeleteStreamResponse = DeleteStreamSuccess | DeleteStreamError; + +export interface DeleteStreamSuccess extends ZulipSuccessResponse { +} + +export interface DeleteStreamError extends ZulipErrorResponse { + code: string; +} + +export type StreamSubscriptions = RetrieverClient; +export type StreamTopics = RetrieverClient; + +// SECTION GET SUBSCRIPTIONS +export interface GetStreamsParams { + /** Include all public streams. Defaults to true. */ + include_public?: boolean; + /** Include all streams that the user is subscribed to. Defaults to true. */ + include_subscribed?: boolean; + /** Include all active streams. The user must have administrative privileges to use this parameter. Defaults to false. */ + include_all_active?: boolean; + /** Include all default streams for the user's realm. Defaults to false. */ + include_default?: boolean; + /** If the user is a bot, include all streams that the bot's owner is subscribed to. Defaults to false. */ + include_owner_subscribed?: boolean; + /** Include all web public streams. */ + include_web_public?: boolean; +} + +export type GetStreamsResponse = GetStreamsSuccess | GetStreamsError; + +export interface GetStreamsSuccess extends ZulipSuccessResponse { + streams: Stream[]; +} + +export interface GetStreamsError extends ZulipErrorResponse { + code: string; +} + +// SECTION GET STREAM BY ID +export interface GetStreamParams { + /** The name of the stream to retrieve the ID for. */ + stream: string; +} + +export type GetStreamResponse = GetStreamSuccess | GetStreamError; + +export interface GetStreamSuccess extends ZulipSuccessResponse { + stream_id: number; +} + +export interface GetStreamError extends ZulipErrorResponse { + code: string; +} + +// SECTION STREAM SUBSCRIPTIONS +export interface GetStreamSubscriptionParams { + /** Set to true if you would like each stream's info to include a list of current subscribers to that stream. (This may be significantly slower in organizations with thousands of users.) Defaults to false. */ + include_subscribers?: boolean; +} + +export type GetStreamSubscriptionResponse = GetStreamSubscriptionSuccess | GetStreamSubscriptionError; + +export interface GetStreamSubscriptionSuccess extends ZulipSuccessResponse { + subscriptions: Subscription[]; +} + +export interface GetStreamSubscriptionError extends ZulipErrorResponse {} + +// SECTION STREAM TOPICS +export interface GetStreamTopicsParams { + stream_id: number; +} + +export type GetStreamTopicsResponse = GetStreamTopicsSuccess | GetStreamTopicsError; + +export interface GetStreamTopicsSuccess extends ZulipSuccessResponse { + topics: Topic[]; +} + +export interface GetStreamTopicsError extends ZulipErrorResponse { + code: string; +} + +// SECTION GENERAL TYPES +export interface Stream { + /** The unique ID of a stream. */ + stream_id: number; + /** The name of a stream. */ + name: string; + /** A short description of a stream. */ + description: string; + /** Specifies whether a stream is private or not. Only people who have been invited can access a private stream. */ + invite_only: boolean; +} + +export function streams(config: Config) { + return { + retrieve: (params: GetStreamsParams): Promise => { + const url: string = `${config.apiURL}/streams`; + return api(url, config, 'GET', params); + }, + getStreamId: (initialParams: GetStreamParams): Promise => { + const url: string = `${config.apiURL}/get_stream_id`; + let params = { ...initialParams }; + if (typeof initialParams === 'string') { + params = { + stream: initialParams, + }; + } + return api(url, config, 'GET', params); + }, + subscriptions: { + retrieve: (params?: GetStreamSubscriptionParams): Promise => { + const url: string = `${config.apiURL}/users/me/subscriptions`; + return api(url, config, 'GET', params); + }, + }, + topics: { + retrieve: (params?: GetStreamTopicsParams): Promise => { + const url: string = `${config.apiURL}/users/me/${params.stream_id}/topics`; + return api(url, config, 'GET', {}); + }, + }, + deleteById: (params: DeleteStreamParams): Promise => { + const url: string = `${config.apiURL}/streams/${params.stream_id}`; + return api(url, config, 'DELETE', params); + }, + }; +} + diff --git a/src/resources/typing.js b/src/resources/typing.js index 0f9fbd4..f28b354 100644 --- a/src/resources/typing.js +++ b/src/resources/typing.js @@ -1,16 +1,17 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.typing = void 0; +const api_1 = require("../api"); function typing(config) { - return { - send: (initialParams) => { - const url = `${config.apiURL}/typing`; - const params = { ...initialParams }; - if (params.to.length > 1) { - params.to = JSON.stringify(params.to); - } - return api(url, config, 'POST', params); - }, - }; + return { + send: (initialParams) => { + const url = `${config.apiURL}/typing`; + const params = Object.assign({}, initialParams); + if (params.to.length > 1) { + params.to = JSON.stringify(params.to); + } + return api_1.api(url, config, 'POST', params); + }, + }; } - -module.exports = typing; +exports.typing = typing; diff --git a/src/resources/typing.ts b/src/resources/typing.ts new file mode 100644 index 0000000..ead0b5d --- /dev/null +++ b/src/resources/typing.ts @@ -0,0 +1,37 @@ +import { Config } from "../zuliprc"; +import { Caller, ZulipSuccessResponse, ZulipErrorResponse } from "../types"; +import { api } from '../api'; + +export interface TypingClient { + send: Caller; +} + +// SECTION SEND TYPING +export interface SendTypingParams { + /** Whether the user has started (start) or stopped (stop) to type. Must be one of: start, stop. */ + op: TypingOperation; + /** The recipients of the message being typed, in the same format used by the send_message API. Typing notifications are only supported for private messages, so this should be a JSON-encoded list of email addresses of the message's recipients. */ + to: string[] | string; +} + +export type SendTypingResponse = SendTypingSuccess | SendTypingError; + +export interface SendTypingSuccess extends ZulipSuccessResponse {} + +export interface SendTypingError extends ZulipErrorResponse {} + +// SECTION GENERAL TYPES +export type TypingOperation = "start" | "stop"; + +export function typing(config: Config) { + return { + send: (initialParams: SendTypingParams): Promise => { + const url: string = `${config.apiURL}/typing`; + const params: SendTypingParams = { ...initialParams }; + if (params.to.length > 1) { + params.to = JSON.stringify(params.to); + } + return api(url, config, 'POST', params); + }, + }; +} diff --git a/src/resources/users.js b/src/resources/users.js index 624faa8..f1aaca4 100644 --- a/src/resources/users.js +++ b/src/resources/users.js @@ -1,48 +1,49 @@ -const api = require('../api'); - +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.users = void 0; +const api_1 = require("../api"); function users(config) { - return { - retrieve: (params) => { - const url = `${config.apiURL}/users`; - return api(url, config, 'GET', params); - }, - create: (params) => { - const url = `${config.apiURL}/users`; - return api(url, config, 'POST', params); - }, - me: { - pointer: { + return { retrieve: (params) => { - const url = `${config.apiURL}/users/me/pointer`; - return api(url, config, 'GET', params); + const url = `${config.apiURL}/users`; + return api_1.api(url, config, 'GET', params); }, - update: (id) => { - const url = `${config.apiURL}/users/me/pointer`; - return api(url, config, 'POST', { pointer: id }); + create: (params) => { + const url = `${config.apiURL}/users`; + return api_1.api(url, config, 'POST', params); }, - }, - getProfile: () => { - const url = `${config.apiURL}/users/me`; - return api(url, config, 'GET'); - }, - subscriptions: { - add: (params) => { - const url = `${config.apiURL}/users/me/subscriptions`; - return api(url, config, 'POST', params); + me: { + pointer: { + retrieve: (params) => { + const url = `${config.apiURL}/users/me/pointer`; + return api_1.api(url, config, 'GET', params); + }, + update: (id) => { + const url = `${config.apiURL}/users/me/pointer`; + return api_1.api(url, config, 'POST', { pointer: id }); + }, + }, + getProfile: () => { + const url = `${config.apiURL}/users/me`; + return api_1.api(url, config, 'GET', {}); + }, + subscriptions: { + add: (params) => { + const url = `${config.apiURL}/users/me/subscriptions`; + return api_1.api(url, config, 'POST', params); + }, + remove: (params) => { + const url = `${config.apiURL}/users/me/subscriptions`; + return api_1.api(url, config, 'DELETE', params); + }, + }, + alertWords: { + retrieve: (params) => { + const url = `${config.apiURL}/users/me/alert_words`; + return api_1.api(url, config, 'GET', params); + }, + }, }, - remove: (params) => { - const url = `${config.apiURL}/users/me/subscriptions`; - return api(url, config, 'DELETE', params); - }, - }, - alertWords: { - retrieve: (params) => { - const url = `${config.apiURL}/users/me/alert_words`; - return api(url, config, 'GET', params); - }, - }, - }, - }; + }; } - -module.exports = users; +exports.users = users; diff --git a/src/resources/users.ts b/src/resources/users.ts new file mode 100644 index 0000000..c44680c --- /dev/null +++ b/src/resources/users.ts @@ -0,0 +1,229 @@ +import { Config } from "../zuliprc"; +import { RetrieverClient, ZulipSuccessResponse, ZulipErrorResponse, Caller, Subscription } from "../types"; +import { api } from '../api'; + +export interface UsersClient extends RetrieverClient { + create: Caller; + me: UsersMeClient; +} + +export interface UsersMeClient { + pointer: UsersMePointerClient; + getProfile: Caller; + subscriptions: SubscriptionsClient; + alertWords: AlertWordsClient +} + +export interface UsersMePointerClient { + retrieve: Caller; + update: Caller; +} + +export interface SubscriptionsClient { + add: Caller; + remove: Caller; +} + +export type AlertWordsClient = RetrieverClient; + +// SECTION CREATE USER +export interface CreateUserParams { + /** The email address of the new user. */ + email: string; + /** The password of the new user. */ + password: string; + /** The full name of the new user. */ + full_name: string; + /** The short name of the new user. Not user-visible. */ + short_name: string; +} + +export type CreateUserResponse = CreateUserSuccess | CreateUserError; + +export interface CreateUserSuccess extends ZulipSuccessResponse {} + +export interface CreateUserError extends ZulipErrorResponse { } + +// SECTION GET MY INFO +export interface GetMyInfoParams {} + +export type GetMyInfoResponse = GetMyInfoSuccess | GetMyInfoError; + +export interface GetMyInfoSuccess extends ZulipSuccessResponse { + avatar_url: string; + client_id: string; + email: string; + full_name: string; + is_admin: boolean; + is_bot: boolean; + max_message_id: number; + pointer: number; + profile_data: { [key: string]: ProfileData }; + short_name: string; + user_id: number; +} + +export interface GetMyInfoError extends ZulipErrorResponse { } + +// SECTION GET USER INFO POINTER +// TODO Find documentation on Get User Info Pointer +export interface GetUserInfoPointerParams { } + +export type GetUserInfoPointerResponse = GetUserInfoPointerSuccess | GetUserInfoPointerError; + +export interface GetUserInfoPointerSuccess extends ZulipSuccessResponse { } + +export interface GetUserInfoPointerError extends ZulipErrorResponse { } + +// SECTION UPDATE USER INFO POINTER +// TODO Find documentation on Update User Info Pointer +export type UpdateUserInfoPointerParams = number; + +export type UpdateUserInfoPointerResponse = UpdateUserInfoPointerSuccess | UpdateUserInfoPointerError; + +export interface UpdateUserInfoPointerSuccess extends ZulipSuccessResponse { } + +export interface UpdateUserInfoPointerError extends ZulipErrorResponse { } + +// SECTION GET PROFILE +// TODO Find documentation on +export interface GetProfileParams { } + +export type GetProfileResponse = GetProfileSuccess | GetProfileError; + +export interface GetProfileSuccess extends ZulipSuccessResponse { } + +export interface GetProfileError extends ZulipErrorResponse { } + +// SECTION ADD USER SUBSCRIPTIONS +export interface AddUserSubscriptionsParams { + /** + * A list of dictionaries containing the the key name and value specifying the name of the stream to subscribe. + * If the stream does not exist a new stream is created. The description of the stream created can be specified by + * setting the dictionary key description with an appropriate value.Note: This argument is called streams and not + * subscriptions in our Python API. + */ + subscriptions: Subscription[]; + /** A boolean specifying whether the streams specified in subscriptions are invite-only or not. Defaults to false. */ + invite_only?: boolean; + /** + * A list of email addresses of the users that will be subscribed to the streams specified in the subscriptions argument. + * If not provided, then the requesting user/bot is subscribed. Defaults to []. + */ + principals?: string[]; + /** + * A boolean specifying whether authorization errors (such as when the requesting user is not authorized to access a private stream) + * should be considered fatal or not. When True, an authorization error is reported as such. When set to False, the returned JSON + * payload indicates that there was an authorization error, but the response is still considered a successful one. Defaults to true. + */ + authorization_errors_fatal?: boolean; + /** A boolean indicating if the history should be available to newly subscribed members. Defaults to "None". */ + history_public_to_subscribers?: boolean; + /** + * A boolean indicating if the stream is an announcements only stream. Only organization admins can post to announcements only streams. + * Defaults to false. + */ + is_announcement_only?: boolean; + /** + * If announce is True and one of the streams specified in subscriptions has to be created (i.e. doesn't exist to begin with), + * an announcement will be made notifying that a new stream was created. + * */ + announce?: boolean; +} + +export type AddUserSubscriptionsResponse = AddUserSubscriptionsSuccess | AddUserSubscriptionsError; + +export interface AddUserSubscriptionsSuccess extends ZulipSuccessResponse { + already_subscribed?: UserSubscriptionStatuses; + subscribed?: UserSubscriptionStatuses; + unauthorized?: string[]; +} + +export interface AddUserSubscriptionsError extends ZulipErrorResponse { } + +// SECTION REMOVE USER SUBSCRIPTIONS +export interface RemoveUserSubscriptionsParams { + /** A list of stream names to unsubscribe from. This argument is called streams in our Python API. */ + subscriptions: string[] & { 0: string }; + /** + * A list of email addresses of the users that will be unsubscribed from the streams specified in the subscriptions argument. + * If not provided, then the requesting user/bot is unsubscribed. */ + principals?: string[]; +} + +export type RemoveUserSubscriptionsResponse = RemoveUserSubscriptionsSuccess | RemoveUserSubscriptionsError; + +export interface RemoveUserSubscriptionsSuccess extends ZulipSuccessResponse { + not_removed: string[]; + removed: string; +} + +export interface RemoveUserSubscriptionsError extends ZulipErrorResponse { + code: string; + stream: string; +} + +// SECTION GET ALERT WORDS +// TODO Find documentation on Get Alert Words +export interface GetAlertWordsParams { } + +export type GetAlertWordsResponse = GetAlertWordsSuccess | GetAlertWordsError; + +export interface GetAlertWordsSuccess extends ZulipSuccessResponse { } + +export interface GetAlertWordsError extends ZulipErrorResponse { } + +// SECTION GENERAL TYPES +export interface ProfileData { + value: string; + rendered_value?: string; +} + +export interface UserSubscriptionStatuses { + [key: string]: string[] +} + +export function users(config: Config): UsersClient { + return { + retrieve: (params: any): any => { + const url = `${config.apiURL}/users`; + return api(url, config, 'GET', params); + }, + create: (params: CreateUserParams): Promise => { + const url = `${config.apiURL}/users`; + return api(url, config, 'POST', params); + }, + me: { + pointer: { + retrieve: (params: GetUserInfoPointerParams): Promise => { + const url = `${config.apiURL}/users/me/pointer`; + return api(url, config, 'GET', params); + }, + update: (id: number): Promise => { + const url = `${config.apiURL}/users/me/pointer`; + return api(url, config, 'POST', { pointer: id }); + }, + }, + getProfile: (): Promise => { + const url = `${config.apiURL}/users/me`; + return api(url, config, 'GET', {}); + }, + subscriptions: { + add: (params: AddUserSubscriptionsParams): Promise => { + const url = `${config.apiURL}/users/me/subscriptions`; + return api(url, config, 'POST', params); + }, + remove: (params: RemoveUserSubscriptionsParams): Promise => { + const url = `${config.apiURL}/users/me/subscriptions`; + return api(url, config, 'DELETE', params); + }, + }, + alertWords: { + retrieve: (params: GetAlertWordsParams): Promise => { + const url = `${config.apiURL}/users/me/alert_words`; + return api(url, config, 'GET', params); + }, + }, + }, + }; +} diff --git a/src/types.js b/src/types.js new file mode 100644 index 0000000..d3b6df2 --- /dev/null +++ b/src/types.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ZulipErrorResponse = void 0; +class ZulipErrorResponse extends Error { + constructor(res) { + super(); + this.res = res; + } +} +exports.ZulipErrorResponse = ZulipErrorResponse; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..62f5de1 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,63 @@ +export type Caller

= (params: P) => Promise; + +export interface RetrieverClient

{ + retrieve: Caller; +} + +export interface ZulipApiResponse { + msg: string; +} + +export interface ZulipSuccessResponse extends ZulipApiResponse { + result: "success"; +} + +export class ZulipErrorResponse extends Error { + res: any; + constructor(res: any) { + super(); + this.res = res; + } +} + +// TODO Expand the possible types for Narrow operations +export interface Narrow { + operator: string; + operand: string | string[]; + negated?: boolean; +} + + +export interface Subscription { + /** The unique ID of a stream. */ + stream_id: number; + /** The name of a stream. */ + name: string; + /** A short description of a stream. */ + description: string; + /** Specifies whether a stream is private or not. Only people who have been invited can access a private stream. */ + invite_only: boolean; + /** A list of email addresses of users who are also subscribed to a given stream. Included only if include_subscribers is true. */ + subscribers: string[]; + /** A boolean specifiying whether desktop notifications are enabled for the given stream. */ + desktop_notifications: boolean; + /** A boolean specifiying whether push notifications are enabled for the given stream. */ + push_notifications: boolean; + /** A boolean specifiying whether audible notifications are enabled for the given stream. */ + audible_notifications: boolean; + /** A boolean specifying whether the given stream has been pinned to the top. */ + pin_to_top: boolean; + /** Email address of the given stream. */ + email_address: string; + /** Whether the given stream is muted or not. Muted streams do not count towards your total unread count and thus, do not show up in All messages view (previously known as Home view). */ + in_home_view: boolean; + /** Stream color. (e.g. "#6f6c6f") */ + color: string; +} + +export interface Topic { + /** The name of the topic. */ + name: string; + /** The message ID of the last message sent to this topic. */ + max_id: number; +} diff --git a/src/zuliprc.js b/src/zuliprc.js index 6fb7f4b..1bfe2b0 100644 --- a/src/zuliprc.js +++ b/src/zuliprc.js @@ -1,16 +1,28 @@ -import { promises as fsPromises } from 'fs'; -import { parse } from 'ini'; - -async function parseConfigFile(filename) { - const data = await fsPromises.readFile(filename, 'utf8'); - const parsedConfig = parse(data); - const config = { - realm: parsedConfig.api.site, - username: parsedConfig.api.email, - apiKey: parsedConfig.api.key, - }; - config.apiURL = `${parsedConfig.api.site}/api/v1`; - return config; +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const fs_1 = require("fs"); +const ini_1 = require("ini"); +function parseConfigFile(filename) { + return __awaiter(this, void 0, void 0, function* () { + const data = yield fs_1.promises.readFile(filename, 'utf8'); + const parsedConfig = ini_1.parse(data); + const config = { + realm: parsedConfig.api.site, + username: parsedConfig.api.email, + apiKey: parsedConfig.api.key, + apiURL: `${parsedConfig.api.site}/api/v1` + }; + return config; + }); } - -export default parseConfigFile; +exports.default = parseConfigFile; +// export function parseConfigFile(filename: string): Promise; diff --git a/src/zuliprc.ts b/src/zuliprc.ts new file mode 100644 index 0000000..01ac226 --- /dev/null +++ b/src/zuliprc.ts @@ -0,0 +1,27 @@ +import { promises as fsPromises } from 'fs'; +import { parse } from 'ini'; + +export interface Config { + realm: string; + username: string; + password?: string; + apiURL: string; + apiKey: string; +} + +async function parseConfigFile(filename: string): Promise { + const data: string = await fsPromises.readFile(filename, 'utf8'); + const parsedConfig: any = parse(data); + const config: Config = { + realm: parsedConfig.api.site, + username: parsedConfig.api.email, + apiKey: parsedConfig.api.key, + apiURL: `${parsedConfig.api.site}/api/v1` + }; + return config; +} + +export default parseConfigFile; + + +// export function parseConfigFile(filename: string): Promise; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7cbdd37 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + }, + "exclude": [ + "node_modules", + "test" + ] +} \ No newline at end of file