diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..bf55404 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,52 @@ +name: PR Checks + +on: + pull_request: + branches: + - master + +jobs: + test: + name: Test + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 12 + - name: Install dependencies + run: yarn install + - name: Run tests + run: yarn run test + env: + CI: true + lint: + name: Lint + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 12 + - name: Install dependencies + run: yarn install + - name: Run lint + run: yarn run lint + dtslint: + name: Lint Typings + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 12 + - name: Install dependencies + run: yarn install + - name: Run dtslint + run: yarn run dtslint \ No newline at end of file diff --git a/package.json b/package.json index 0baae07..c766054 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,10 @@ "source": "twitter.js", "main": "dist/twitter.js", "module": "dist/twitter.m.js", - "types": "index.d.ts", + "types": "types/index.d.ts", "files": [ "dist", - "index.d.ts" + "types/index.d.ts" ], "repository": "draftbit/twitter-lite", "homepage": "https://github.com/draftbit/twitter-lite", @@ -36,6 +36,7 @@ "@types/node": "^13.13.4", "bundlesize": "^0.18.0", "dotenv": "^8.2.0", + "dtslint": "^4.0.6", "eslint": "^6.8.0", "eslint-plugin-jest": "^23.8.2", "flow-bin": "^0.123.0", @@ -46,6 +47,7 @@ }, "scripts": { "lint": "eslint --fix ./", + "dtslint": "dtslint types", "prepare": "microbundle {stream,twitter}.js && bundlesize", "test": "eslint --fix . && jest --detectOpenHandles", "release": "npm run -s prepare && npm test && git tag $npm_package_version && git push && git push --tags && npm publish" @@ -56,7 +58,8 @@ } }, "jest": { - "testEnvironment": "node" + "testEnvironment": "node", + "testRegex": "/test/.*\\.test\\.js" }, "bundlesize": [ { @@ -64,7 +67,7 @@ "maxSize": "3 kB" }, { - "path": "index.d.ts", + "path": "types/index.d.ts", "maxSize": "3 kB" } ] diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index bf3e3fc..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "ES6", - "lib": [ - "es6", - "dom" - ], - "noImplicitAny": true, - "noImplicitThis": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noEmit": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.d.ts" - ] -} \ No newline at end of file diff --git a/index.d.ts b/types/index.d.ts similarity index 66% rename from index.d.ts rename to types/index.d.ts index c8c14a6..be8d4e0 100644 --- a/index.d.ts +++ b/types/index.d.ts @@ -1,3 +1,5 @@ +// Minimum TypeScript Version: 3.8 + /** * Typings for twitter-lite * @@ -56,8 +58,8 @@ export default class Twitter { /** * Construct the data and headers for an authenticated HTTP request to the Twitter API - * @param {'GET | 'POST' | 'PUT'} - * @param {string} resource - the API endpoint + * @param method HTTP method to use when calling this endpoint + * @param resource The API endpoint to be called */ private _makeRequest( method: 'GET' | 'POST' | 'PUT', @@ -70,34 +72,34 @@ export default class Twitter { /** * Send a GET request - * @type {T = any} Expected type for the response from this request, generally `object` or `array`. - * @param {string} resource - endpoint, e.g. `followers/ids` - * @param {object} [parameters] - optional parameters - * @returns {Promise} Promise resolving to the response from the Twitter API. + * @type Expected type for the response from this request, generally `object` or `array`. + * @param resource Endpoint, e.g. `followers/ids` + * @param [parameters] Optional parameters + * @returns Promise resolving to the response from the Twitter API. * The `_header` property will be set to the Response headers (useful for checking rate limits) */ - public get(resource: string, parameters?: object): Promise; + get(resource: string, parameters?: object): Promise; /** * Send a POST request - * @type {T = any} Expected type for the response from this request, generally `object` or `array`. - * @param {string} resource - endpoint, e.g. `users/lookup` - * @param {object} body - POST parameters object. + * @type Expected type for the response from this request, generally `object` or `array`. + * @param resource Endpoint, e.g. `users/lookup` + * @param body POST parameters object. * Will be encoded appropriately (JSON or urlencoded) based on the resource - * @returns {Promise} Promise resolving to the response from the Twitter API. + * @returns Promise resolving to the response from the Twitter API. * The `_header` property will be set to the Response headers (useful for checking rate limits) */ - public post(resource: string, body: object): Promise; + post(resource: string, body: object): Promise; /** * Send a PUT request - * @type {T = any} Expected type for the response from this request, generally `object` or `array`. - * @param {string} resource - endpoint e.g. `direct_messages/welcome_messages/update` - * @param {object} parameters - required or optional query parameters - * @param {object} body - PUT request body - * @returns {Promise} Promise resolving to the response from the Twitter API. + * @type Expected type for the response from this request, generally `object` or `array`. + * @param resource Endpoint e.g. `direct_messages/welcome_messages/update` + * @param parameters Required or optional query parameters + * @param body PUT request body + * @returns Promise resolving to the response from the Twitter API. */ - public put( + put( resource: string, parameters: object, body: object @@ -106,18 +108,18 @@ export default class Twitter { /** * Open a stream to a specified endpoint * - * @param {string} resource - endpoint, e.g. `statuses/filter` - * @param {object} parameters - * @returns {Stream} + * @param resource Endpoint, e.g. `statuses/filter` + * @param parameters Parameters for this endpoint + * @returns Resulting stream */ - public stream(resource: string, parameters: object): Stream; + stream(resource: string, parameters: object): Stream; } /* In reality snowflakes are BigInts. Once BigInt is supported by browsers and Node per default, we could adjust this type. Currently Twitter themselves convert it to strings for the API though, so this change will come some time in the far future. */ -type snowflake = string; +export type snowflake = string; -interface TwitterOptions { +export interface TwitterOptions { /** "api" is the default (change for other subdomains) */ subdomain?: string; /** version "1.1" is the default (change for other subdomains) */ @@ -136,28 +138,30 @@ interface TwitterOptions { bearer_token?: string; } -type OauthToken = string; -type OauthTokenSecret = string; -type AuthType = 'App' | 'User'; +export type OauthToken = string; +export type OauthTokenSecret = string; +export type AuthType = 'App' | 'User'; -interface KeySecret { +export interface KeySecret { key: string; secret: string; } -interface AccessTokenOptions { - /** If using the OAuth web-flow, set these parameters to the values returned in the callback URL. If you are using out-of-band OAuth, set the value of oauth_verifier to the pin-code. - * The oauth_token here must be the same as the oauth_token returned in the request_token step.*/ +export interface AccessTokenOptions { + /** + * If using the OAuth web-flow, set these parameters to the values returned in the callback URL. If you are using out-of-band OAuth, set the value of oauth_verifier to the pin-code. + * The oauth_token here must be the same as the oauth_token returned in the request_token step. + */ oauth_verifier: string | number; oauth_token: string; } -interface BearerResponse { +export interface BearerResponse { token_type: 'bearer'; access_token: string; } -type TokenResponse = +export type TokenResponse = | { oauth_token: OauthToken; oauth_token_secret: OauthTokenSecret; @@ -165,14 +169,14 @@ type TokenResponse = } | { oauth_callback_confirmed: 'false' }; -interface AccessTokenResponse { +export interface AccessTokenResponse { oauth_token: string; oauth_token_secret: string; user_id: snowflake; screen_name: string; } -declare class Stream extends EventEmitter { +export class Stream extends EventEmitter { constructor(); parse(buffer: Buffer): void; diff --git a/types/test.ts b/types/test.ts new file mode 100644 index 0000000..c9ba3b0 --- /dev/null +++ b/types/test.ts @@ -0,0 +1,142 @@ +/* eslint-disable */ +import Twitter, { TwitterOptions, Stream } from 'twitter-lite'; + +const options: TwitterOptions = { + subdomain: "api", + version: "1.1", + consumer_key: 'foobar', + consumer_secret: 'foobar', + access_token_key: 'foobar', + access_token_secret: 'foobar', +}; + +const UserAuthedTwitter = new Twitter(options); + +UserAuthedTwitter + .get("account/verify_credentials") + .then(results => { + console.log("results", results); + }) + .catch(console.error); + +const AppAuthedTwitter = new Twitter({ + consumer_key: 'foobar', + consumer_secret: 'foobar', +}); + +(async () => { + const user = new Twitter({ + consumer_key: "abc", + consumer_secret: "def" + }); + + const response = await user.getBearerToken(); + const app = new Twitter({ + bearer_token: response.access_token + }); +})(); + +// OAuth Flow +AppAuthedTwitter + .getRequestToken("http://callbackurl.com") + .then(res => + console.log({ + reqTkn: res.oauth_token, + reqTknSecret: res.oauth_token_secret + }) + ) + .catch(console.error); + +AppAuthedTwitter + .getAccessToken({ + oauth_verifier: 'oauthVerifier', + oauth_token: 'oauthToken' + }) + .then(res => + console.log({ + accTkn: res.oauth_token, + accTknSecret: res.oauth_token_secret, + userId: res.user_id, + screenName: res.screen_name + }) + ); + +// Tweeting a thread +async function tweetThread(thread: string[]) { + let lastTweetID = ""; + for (const status of thread) { + const tweet = await UserAuthedTwitter.post("statuses/update", { + status, + in_reply_to_status_id: lastTweetID, + auto_populate_reply_metadata: true + }); + lastTweetID = tweet.id_str; + } +} + +const thread = ["First tweet", "Second tweet", "Third tweet"]; +tweetThread(thread).catch(console.error); + +// Streams +const parameters = { + track: "#bitcoin,#litecoin,#monero", + follow: "422297024,873788249839370240", // @OrchardAI, @tylerbuchea + locations: "-122.75,36.8,-121.75,37.8", // Bounding box - San Francisco +}; + +const stream: Stream = UserAuthedTwitter.stream("statuses/filter", parameters) + .on("start", response => console.log("start")) + .on("data", tweet => console.log("data", tweet.text)) + .on("ping", () => console.log("ping")) + .on("error", error => console.log("error", error)) + .on("end", response => console.log("end")); + +// To stop the stream: +process.nextTick(() => stream.destroy()); // emits "end" and "error" events + +// API v2 +const V2Client = new Twitter({ + version: "2", // version "1.1" is the default (change for v2) + extension: false, // true is the default (this must be set to false for v2 endpoints) + consumer_key: "abc", // from Twitter. + consumer_secret: "def", // from Twitter. + access_token_key: "uvw", // from your User (oauth_token) + access_token_secret: "xyz" // from your User (oauth_token_secret) +}); + +// Methods +const rateLimits = UserAuthedTwitter.get("statuses/show", { + id: "1016078154497048576" +}); + +UserAuthedTwitter.post("friendships/create", { + screen_name: "dandv" +}); + +UserAuthedTwitter.put( + "direct_messages/welcome_messages/update", + { + id: 'abc' + }, + { + message_data: { + text: "Welcome!!!" + } + } +); + +// Headers +(async () => { + const tweets = await UserAuthedTwitter.get("statuses/home_timeline"); + console.log(`Rate: ${tweets._headers.get('x-rate-limit-remaining')} / ${tweets._headers.get('x-rate-limit-limit')}`); + const delta = (tweets._headers.get('x-rate-limit-reset') * 1000) - Date.now(); + console.log(`Reset: ${Math.ceil(delta / 1000 / 60)} minutes`); +})(); + +// Regression test for https://github.com/draftbit/twitter-lite/issues/130 +(async () => { + const { + oauth_token: requestToken, + oauth_token_secret: requestTokenSecret + } = await UserAuthedTwitter.getRequestToken('callback url'); +})(); diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 0000000..f3dd73d --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES6", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "twitter-lite": ["."] + } + } +} diff --git a/types/tslint.json b/types/tslint.json new file mode 100644 index 0000000..2df2f68 --- /dev/null +++ b/types/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "dtslint/dtslint.json", + "rules": { + "no-unnecessary-generics": false, + "no-redundant-jsdoc": false + } +}