Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: PLhery/node-twitter-api-v2
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.19.1
Choose a base ref
...
head repository: PLhery/node-twitter-api-v2
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 1.20.0
Choose a head ref
  • 4 commits
  • 19 files changed
  • 4 contributors

Commits on Feb 2, 2025

  1. add community and searchCommunities endpoints (#561)

    enszrlu authored Feb 2, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    cjihrig Colin Ihrig
    Copy the full SHA
    d6ff320 View commit details

Commits on Feb 13, 2025

  1. feat: add clients for ads and ads sandbox (#566)

    dmty authored Feb 13, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    codebytere Shelley Vohr
    Copy the full SHA
    28c87b0 View commit details
  2. feat: add v2 upload media method (#562)

    * feat: add v2 upload media method
    
    * tests: add v2.uploadMedia tests
    
    * chore(waitForMediaProcessing): recursively poll for media processing status
    leeran7 authored Feb 13, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    targos Michaël Zasso
    Copy the full SHA
    a0b6ad7 View commit details
  3. upgrade to 1.20.0

    PLhery committed Feb 13, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    targos Michaël Zasso
    Copy the full SHA
    c1cfa86 View commit details
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -72,13 +72,13 @@ using descriptive typings for read/write/DMs rights, request parameters and resp

A small feature comparison with other libs:

| Package | API version(s) | Response typings | Media helpers | Pagination | Subdeps | Size (gzip) | Install size |
| -------------- | -------------- | ---------------- | ------------- | ---------- | --------------- | -------------:| -------------:|
| twitter-api-v2 | v1.1, v2, labs |||| 0 | ~23 kB | [![twitter-api-v2 install size badge](https://badgen.net/packagephobia/install/twitter-api-v2)](https://packagephobia.com/result?p=twitter-api-v2) |
| twit | v1.1 |||| 51 | ~214.5 kB | [![twit install size badge](https://badgen.net/packagephobia/install/twit)](https://packagephobia.com/result?p=twit) |
| twitter | v1.1 |||| 50 | ~182.1 kB | [![twitter install size badge](https://badgen.net/packagephobia/install/twitter)](https://packagephobia.com/result?p=twitter) |
| twitter-lite | v1.1, v2 |||| 4 | ~5.3 kB | [![twitter-lite install size badge](https://badgen.net/packagephobia/install/twitter-lite)](https://packagephobia.com/result?p=twitter-lite) |
| twitter-v2 | v2 |||| 7 | ~4.5 kB | [![twitter-v2 install size badge](https://badgen.net/packagephobia/install/twitter-v2)](https://packagephobia.com/result?p=twitter-v2) |
| Package | API version(s) | Response typings | Media helpers | Pagination | Subdeps | Size (gzip) | Install size |
| -------------- |---------------------| ---------------- | ------------- | ---------- | --------------- | -------------:| -------------:|
| twitter-api-v2 | v1.1, v2, labs, ads |||| 0 | ~23 kB | [![twitter-api-v2 install size badge](https://badgen.net/packagephobia/install/twitter-api-v2)](https://packagephobia.com/result?p=twitter-api-v2) |
| twit | v1.1 |||| 51 | ~214.5 kB | [![twit install size badge](https://badgen.net/packagephobia/install/twit)](https://packagephobia.com/result?p=twit) |
| twitter | v1.1 |||| 50 | ~182.1 kB | [![twitter install size badge](https://badgen.net/packagephobia/install/twitter)](https://packagephobia.com/result?p=twitter) |
| twitter-lite | v1.1, v2 |||| 4 | ~5.3 kB | [![twitter-lite install size badge](https://badgen.net/packagephobia/install/twitter-lite)](https://packagephobia.com/result?p=twitter-lite) |
| twitter-v2 | v2 |||| 7 | ~4.5 kB | [![twitter-v2 install size badge](https://badgen.net/packagephobia/install/twitter-v2)](https://packagephobia.com/result?p=twitter-v2) |

## Features

6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.20.0
------
- feat: add v2 upload media method #562 (@leeran7)
- feat: add clients for ads and ads sandbox #566 (@dmty)
- add community and searchCommunities endpoints #561 (@enszrlu)

1.19.1
------
- Add community id to tweet payload and response #560 (@enszrlu)
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twitter-api-v2",
"version": "1.19.1",
"version": "1.20.0",
"description": "Strongly typed, full-featured, light, versatile yet powerful Twitter API v1.1 and v2 client for Node.js.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
9 changes: 9 additions & 0 deletions src/ads-sandbox/client.ads-sandbox.read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import TwitterApiSubClient from '../client.subclient';
import { API_ADS_SANDBOX_PREFIX } from '../globals';

/**
* Base Twitter ads sandbox client with only read rights.
*/
export default class TwitterAdsSandboxReadOnly extends TwitterApiSubClient {
protected _prefix = API_ADS_SANDBOX_PREFIX;
}
18 changes: 18 additions & 0 deletions src/ads-sandbox/client.ads-sandbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { API_ADS_SANDBOX_PREFIX } from '../globals';
import TwitterAdsSandboxReadWrite from './client.ads-sandbox.write';

/**
* Twitter ads sandbox client with all rights (read/write)
*/
export class TwitterAdsSandbox extends TwitterAdsSandboxReadWrite {
protected _prefix = API_ADS_SANDBOX_PREFIX;

/**
* Get a client with read/write rights.
*/
public get readWrite() {
return this as TwitterAdsSandboxReadWrite;
}
}

export default TwitterAdsSandbox;
16 changes: 16 additions & 0 deletions src/ads-sandbox/client.ads-sandbox.write.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { API_ADS_SANDBOX_PREFIX } from '../globals';
import TwitterAdsSandboxReadOnly from './client.ads-sandbox.read';

/**
* Base Twitter ads sandbox client with read/write rights.
*/
export default class TwitterAdsSandboxReadWrite extends TwitterAdsSandboxReadOnly {
protected _prefix = API_ADS_SANDBOX_PREFIX;

/**
* Get a client with only read rights.
*/
public get readOnly() {
return this as TwitterAdsSandboxReadOnly;
}
}
9 changes: 9 additions & 0 deletions src/ads/client.ads.read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import TwitterApiSubClient from '../client.subclient';
import { API_ADS_PREFIX } from '../globals';

/**
* Base Twitter ads client with only read rights.
*/
export default class TwitterAdsReadOnly extends TwitterApiSubClient {
protected _prefix = API_ADS_PREFIX;
}
28 changes: 28 additions & 0 deletions src/ads/client.ads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { API_ADS_PREFIX } from '../globals';
import TwitterAdsReadWrite from './client.ads.write';
import TwitterAdsSandbox from '../ads-sandbox/client.ads-sandbox';

/**
* Twitter ads client with all rights (read/write)
*/
export class TwitterAds extends TwitterAdsReadWrite {
protected _prefix = API_ADS_PREFIX;
protected _sandbox?: TwitterAdsSandbox;

/**
* Get a client with read/write rights.
*/
public get readWrite() {
return this as TwitterAdsReadWrite;
}

/**
* Get Twitter Ads Sandbox API client
*/
public get sandbox() {
if (this._sandbox) return this._sandbox;
return this._sandbox = new TwitterAdsSandbox(this);
}
}

export default TwitterAds;
16 changes: 16 additions & 0 deletions src/ads/client.ads.write.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { API_ADS_PREFIX } from '../globals';
import TwitterAdsReadOnly from './client.ads.read';

/**
* Base Twitter ads client with read/write rights.
*/
export default class TwitterAdsReadWrite extends TwitterAdsReadOnly {
protected _prefix = API_ADS_PREFIX;

/**
* Get a client with only read rights.
*/
public get readOnly() {
return this as TwitterAdsReadOnly;
}
}
10 changes: 10 additions & 0 deletions src/client/index.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import TwitterApiv1 from '../v1/client.v1';
import TwitterApiv2 from '../v2/client.v2';
import { TwitterApiError } from '../types';
import TwitterApiReadWrite from './readwrite';
import TwitterAds from '../ads/client.ads';


// "Real" exported client for usage of TwitterApi.
@@ -11,6 +12,7 @@ import TwitterApiReadWrite from './readwrite';
export class TwitterApi extends TwitterApiReadWrite {
protected _v1?: TwitterApiv1;
protected _v2?: TwitterApiv2;
protected _ads?: TwitterAds;

/* Direct access to subclients */
public get v1() {
@@ -32,6 +34,14 @@ export class TwitterApi extends TwitterApiReadWrite {
return this as TwitterApiReadWrite;
}

/**
* Get Twitter Ads API client
*/
public get ads() {
if (this._ads) return this._ads;
return this._ads = new TwitterAds(this);
}

/* Static helpers */
public static getErrors(error: any) {
if (typeof error !== 'object')
2 changes: 2 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
@@ -3,3 +3,5 @@ export const API_V2_LABS_PREFIX = 'https://api.x.com/labs/2/';
export const API_V1_1_PREFIX = 'https://api.x.com/1.1/';
export const API_V1_1_UPLOAD_PREFIX = 'https://upload.x.com/1.1/';
export const API_V1_1_STREAM_PREFIX = 'https://stream.x.com/1.1/';
export const API_ADS_PREFIX = 'https://ads-api.x.com/12/';
export const API_ADS_SANDBOX_PREFIX = 'https://ads-api-sandbox.twitter.com/12/';
34 changes: 34 additions & 0 deletions src/types/v2/community.v2.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export interface CommunityV2 {
id: string;
name: string;
created_at: string;
}

export interface CommunityErrorV2 {
title: string;
type: string;
detail?: string;
status?: number;
}

export interface CommunityV2Result {
data: CommunityV2;
errors?: CommunityErrorV2[];
}

export interface CommunitiesV2Result {
data: CommunityV2[];
errors?: CommunityErrorV2[];
meta: {next_token?: string};
}

export interface CommunityByIDV2Params {
id: string;
}

export interface CommunitySearchV2Params {
query: string;
max_results?: number;
next_token?: string;
pagination_token?: string;
}
1 change: 1 addition & 0 deletions src/types/v2/index.ts
Original file line number Diff line number Diff line change
@@ -4,3 +4,4 @@ export * from './tweet.definition.v2';
export * from './user.v2.types';
export * from './spaces.v2.types';
export * from './list.v2.types';
export * from './community.v2.types';
37 changes: 37 additions & 0 deletions src/types/v2/media.v2.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export interface MediaV2UploadInitParams {
command: 'INIT';
media_type: string;
total_bytes: number;
media_category?: string;
}

export interface MediaV2UploadAppendParams {
command: 'APPEND';
media_id: string;
segment_index: number;
media: Buffer | string;
}

export interface MediaV2UploadFinalizeParams {
command: 'FINALIZE';
media_id: string;
}

export interface MediaV2ProcessingInfo {
state: 'pending' | 'in_progress' | 'failed' | 'succeeded';
check_after_secs?: number;
error?: {
code: number;
message: string;
};
}

export interface MediaV2UploadResponse {
data: {
id: string;
media_key: string;
size?: number;
expires_after_secs: number;
processing_info?: MediaV2ProcessingInfo;
};
}
20 changes: 20 additions & 0 deletions src/v2/client.v2.read.ts
Original file line number Diff line number Diff line change
@@ -56,6 +56,10 @@ import {
TweetV2HomeTimelineResult,
TweetUsageV2Params,
TweetV2UsageResult,
CommunityV2Result,
CommunitiesV2Result,
CommunityByIDV2Params,
CommunitySearchV2Params,
} from '../types';
import {
TweetSearchAllV2Paginator,
@@ -875,4 +879,20 @@ export default class TwitterApiv2ReadOnly extends TwitterApiSubClient {
public async usage(options: Partial<TweetUsageV2Params> = {}) {
return this.get<TweetV2UsageResult>('usage/tweets', options);
}

/**
* Returns a variety of information about a single Community specified by ID.
* https://docs.x.com/x-api/communities/communities-lookup-by-community-id
*/
public community(communityId: string, options: Partial<CommunityByIDV2Params> = {}) {
return this.get<CommunityV2Result>('communities/:id', options, { params: { id: communityId } });
}

/**
* Search for Communities based on keywords.
* https://docs.x.com/x-api/communities/search-communities
*/
public searchCommunities(query: string, options: Partial<CommunitySearchV2Params> = {}) {
return this.get<CommunitiesV2Result>('communities/search', { query, ...options });
}
}
80 changes: 80 additions & 0 deletions src/v2/client.v2.write.ts
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ import type {
} from '../types';
import TwitterApiv2LabsReadWrite from '../v2-labs/client.v2.labs.write';
import { CreateDMConversationParams, PostDMInConversationParams, PostDMInConversationResult } from '../types/v2/dm.v2.types';
import { MediaV2UploadAppendParams, MediaV2UploadFinalizeParams, MediaV2UploadInitParams, MediaV2UploadResponse } from '../types/v2/media.v2.types';

/**
* Base Twitter v2 client with read/write rights.
@@ -121,6 +122,85 @@ export default class TwitterApiv2ReadWrite extends TwitterApiv2ReadOnly {
return this.post<TweetV2PostTweetResult>('tweets', payload);
}

/**
* Uploads media to Twitter using chunked upload.
* https://docs.x.com/x-api/media/media-upload
*
* @param media The media buffer to upload
* @param options Upload options including media type and category
* @param chunkSize Size of each chunk in bytes (default: 1MB)
* @returns The media ID of the uploaded media
*/
public async uploadMedia(
media: Buffer,
options: { media_type: string; media_category?: string },
chunkSize: number = 1024 * 1024
): Promise<string> {
const initArguments: MediaV2UploadInitParams = {
command: 'INIT',
media_type: options.media_type,
total_bytes: media.length,
media_category: options.media_category,
};

const initResponse = await this.post<MediaV2UploadResponse>('media/upload', initArguments);
const mediaId = initResponse.data.id;

const chunks = Math.ceil(media.length / chunkSize);
for (let i = 0; i < chunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, media.length);
const mediaChunk = Uint8Array.prototype.slice.call(media, start, end);
const chunkedBuffer = Buffer.from(mediaChunk);

const appendArguments: MediaV2UploadAppendParams = {
command: 'APPEND',
media_id: mediaId,
segment_index: i,
media: chunkedBuffer,
};

await this.post('media/upload', appendArguments);
}

const finalizeArguments: MediaV2UploadFinalizeParams = {
command: 'FINALIZE',
media_id: mediaId,
};

const finalizeResponse = await this.post<MediaV2UploadResponse>('media/upload', finalizeArguments);
if (finalizeResponse.data.processing_info) {
await this.waitForMediaProcessing(mediaId);
}

return mediaId;
}

private async waitForMediaProcessing(mediaId: string): Promise<void> {
const response = await this.get<MediaV2UploadResponse>('media/upload', {
command: 'STATUS',
media_id: mediaId,
});

const info = response.data.processing_info;
if (!info) return;

switch (info.state) {
case 'succeeded':
return;
case 'failed':
throw new Error(`Media processing failed: ${info.error?.message}`);
case 'pending':
case 'in_progress': {
const waitTime = info?.check_after_secs;
if(waitTime && waitTime > 0) {
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
await this.waitForMediaProcessing(mediaId);
}
}
}
}

/**
* Reply to a Tweet on behalf of an authenticated user.
* https://developer.x.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets
File renamed without changes.
Loading