@standard-crypto/farcaster-js-neynar
Version:
A tool for interacting with Farcaster via Neynar APIs.
269 lines • 10.1 kB
JavaScript
import { CastApi, SignerApi, CastParamType, ReactionApi, ReactionType, FollowsApi, Configuration, FeedApi, UserApi, } from './openapi/index.js';
import axios, { AxiosError } from 'axios';
import { silentLogger } from '../logger.js';
import { generateSignature, mnemonicToAddress } from '../utils.js';
const BASE_PATH = 'https://api.neynar.com/v2';
export class NeynarV2APIClient {
logger;
apis;
/**
* Instantiates a NeynarV1APIClient
*
* Note: A Wallet must be provided if the API client is to mint new AuthTokens
*/
constructor(apiKey, { logger = silentLogger, axiosInstance, } = {}) {
this.logger = logger;
if (apiKey === '') {
throw new Error('Attempt to use an authenticated API method without first providing an api key');
}
if (axiosInstance === undefined) {
axiosInstance = axios.create();
}
axiosInstance.defaults.decompress = true;
axiosInstance.interceptors.response.use((response) => response, (error) => {
if (NeynarV2APIClient.isApiErrorResponse(error)) {
const apiErrors = error.response.data;
this.logger.warn(`API errors: ${JSON.stringify(apiErrors)}`);
}
throw error;
});
const config = new Configuration({
basePath: BASE_PATH,
apiKey: apiKey,
});
this.apis = {
signer: new SignerApi(config, undefined, axiosInstance),
feed: new FeedApi(config, undefined, axiosInstance),
cast: new CastApi(config, undefined, axiosInstance),
user: new UserApi(config, undefined, axiosInstance),
reaction: new ReactionApi(config, undefined, axiosInstance),
follow: new FollowsApi(config, undefined, axiosInstance),
};
}
/**
* Utility for parsing errors returned by the Neynar API servers. Returns true
* if the given error is caused by an error response from the server, and
* narrows the type of `error` accordingly.
*/
static isApiErrorResponse(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error) {
if (!(error instanceof AxiosError))
return false;
return (error.response?.data !== undefined && 'message' in error.response.data);
}
/**
* Fetches an existing Signer. See [Neynar documentation](https://docs.neynar.com/reference/get-signer)
* for more details.
*
*/
async fetchSigner(signerUuid) {
try {
const response = await this.apis.signer.signer({ signerUuid });
return response.data;
}
catch (error) {
if (NeynarV2APIClient.isApiErrorResponse(error)) {
const status = error.response.status;
if (status === 404) {
return null;
}
}
throw error;
}
}
/**
* Creates and registers a Signer for an fid. See Neynar documentation[1](https://docs.neynar.com/reference/create-signer),[2](https://docs.neynar.com/reference/register-app-fid)
* for more details.
*/
async createSigner(developerMnemonic, deadline) {
const custodyAddress = mnemonicToAddress(developerMnemonic);
const userResponse = await this.apis.user.lookupUserByCustodyAddress({ custodyAddress });
const developerFid = userResponse.data.user.fid;
const createSignerResponse = await this.apis.signer.createSigner();
const signer = createSignerResponse.data;
// default deadline is 24 hours
const defaultDeadline = Math.floor(Date.now() / 1000) + 86400;
// create signature
const signature = await generateSignature(signer.public_key, developerFid, developerMnemonic, deadline ?? defaultDeadline);
const request = {
registerSignerKeyReqBody: {
signer_uuid: signer.signer_uuid,
app_fid: developerFid,
deadline: deadline ?? defaultDeadline,
signature: signature,
},
};
const response = await this.apis.signer.registerSignedKey(request);
return response.data;
}
/**
* Get reverse chronological feed for a user based on their follow graph. See [Neynar documentation](https://docs.neynar.com/reference/get-feed)
*/
async *fetchFeed(fid, options) {
let cursor;
while (true) {
const response = await this.apis.feed.feed({
feedType: options?.feedType ?? 'following',
filterType: options?.filterType,
fid: fid,
fids: options?.fids,
parentUrl: options?.parentUrl,
cursor: cursor,
limit: options?.pageSize,
});
// yield current page of casts
yield* response.data.casts;
// prep for next page
if (response.data.next.cursor === null) {
break;
}
cursor = response.data.next.cursor;
}
}
/**
* Gets information about an individual cast. See [Neynar documentation](https://docs.neynar.com/reference/get-cast)
*/
async fetchCast(castHash) {
try {
const response = await this.apis.cast.cast({
type: CastParamType.Hash,
identifier: castHash,
});
return response.data.cast;
}
catch (error) {
if (NeynarV2APIClient.isApiErrorResponse(error)) {
const status = error.response.status;
if (status === 404) {
return null;
}
}
throw error;
}
}
/**
* Gets information about an array of casts. See [Neynar documentation](https://docs.neynar.com/reference/get-array-of-casts)
*/
async fetchCasts(castHashes) {
try {
const response = await this.apis.cast.casts({
casts: castHashes.join(','),
});
return response.data.result.casts;
}
catch (error) {
if (NeynarV2APIClient.isApiErrorResponse(error)) {
const status = error.response.status;
if (status === 404) {
return null;
}
}
throw error;
}
}
/**
* Publishes a cast for the currently authenticated user. See [Neynar documentation](https://docs.neynar.com/reference/post-a-cast)
*/
async publishCast(signerUuid, text, options) {
const request = {
postCastReqBody: {
signer_uuid: signerUuid,
text: text,
embeds: options?.embeds,
parent: options?.replyTo,
},
};
const response = await this.apis.cast.postCast(request);
return response.data.cast;
}
/**
* Delete a cast. See [Neynar documentation](https://docs.neynar.com/reference/delete-a-cast)
*/
async deleteCast(signerUuid, castHash) {
const body = {
signer_uuid: signerUuid,
target_hash: castHash,
};
await this.apis.cast.deleteCast({ deleteCastReqBody: body });
}
/**
* Search for Usernames. See [Neynar documentation](https://docs.neynar.com/reference/search-usernames)
*/
async searchUsernames(query, viewerFid) {
const response = await this.apis.user.userSearch({
q: query,
viewerFid: viewerFid,
});
return response.data.result.users;
}
/**
* Update User Profile. See [Neynar documentation](https://docs.neynar.com/reference/update-user-profile)
*/
async updateUserProfile(signerUuid, updates) {
const response = await this.apis.user.updateUser({
updateUserReqBody: {
signer_uuid: signerUuid,
bio: updates.bio,
pfp_url: updates.pfp_url,
url: updates.url,
username: updates.username,
display_name: updates.display_name,
},
});
return response.data;
}
/**
* React to a cast. See [Neynar documentation](https://docs.neynar.com/reference/post-a-reaction)
*/
async reactToCast(signerUuid, reaction, castHash) {
const body = {
signer_uuid: signerUuid,
reaction_type: reaction === 'like' ? ReactionType.Like : ReactionType.Recast,
target: castHash,
};
const response = await this.apis.reaction.postReaction({
reactionReqBody: body,
});
return response.data;
}
/**
* Remove a reaction to a cast. See [Neynar documentation](https://docs.neynar.com/reference/delete-a-reaction)
*/
async removeReactionToCast(signerUuid, reaction, castHash) {
const body = {
signer_uuid: signerUuid,
reaction_type: reaction === 'like' ? ReactionType.Like : ReactionType.Recast,
target: castHash,
};
const response = await this.apis.reaction.deleteReaction({
reactionReqBody: body,
});
return response.data;
}
/**
* Follow users. See [Neynar documentation](https://docs.neynar.com/reference/follow-a-user)
*/
async followUsers(signerUuid, fids) {
const body = {
signer_uuid: signerUuid,
target_fids: fids,
};
const response = await this.apis.user.followUser({ followReqBody: body });
return response.data;
}
/**
* Unfollow users. See [Neynar documentation](https://docs.neynar.com/reference/unfollow-a-user)
*/
async unfollowUsers(signerUuid, fids) {
const body = {
signer_uuid: signerUuid,
target_fids: fids,
};
const response = await this.apis.user.unfollowUser({
followReqBody: body,
});
return response.data;
}
}
//# sourceMappingURL=NeynarV2APIClient.js.map