Skip to content

Commit

Permalink
add api key gated twitter endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
jongan69 committed Jan 13, 2025
1 parent f0a9a36 commit 9f5c11b
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 51 deletions.
22 changes: 4 additions & 18 deletions src/pages/api/premium/checkBundles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Connection, PublicKey } from '@solana/web3.js';
import { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '../db/connectDB';
import { checkApiKey } from '@utils/checkApiKey';

export default async function handler(
req: NextApiRequest,
Expand Down Expand Up @@ -38,19 +38,10 @@ export default async function handler(
return res.status(401).json({ error: 'API key is required' });
}

// Connect to MongoDB and verify API key
let mongoClient;
try {
mongoClient = await connectToDatabase();
const db = mongoClient.db("walletAnalyzer");

// Check if API key exists and is valid
const wallet = await db.collection('wallets').findOne({
apiKey: apiKey,
isPremium: true // Ensure the wallet has premium status
});

if (!wallet) {
const isValid = await checkApiKey(apiKey as string);

if (!isValid) {
return res.status(401).json({ error: 'Invalid or expired API key' });
}

Expand Down Expand Up @@ -129,10 +120,5 @@ export default async function handler(
} catch (error) {
console.error('Error processing request:', error);
return res.status(500).json({ error: 'Internal server error' });
} finally {
// Close MongoDB connection
if (mongoClient) {
await mongoClient.close();
}
}
}
4 changes: 1 addition & 3 deletions src/pages/api/premium/status.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { DEFAULT_TOKEN_3, TREND_SETTERS_NFT_COLLECTION } from '@utils/globals';
import { PublicKey } from '@solana/web3.js';
import { fetchTokenAccounts } from '@utils/tokenUtils';
import { TREND_SETTERS_NFT_COLLECTION } from '@utils/globals';
import { connectToDatabase } from '../db/connectDB';
import { checkNftOwnership } from '@utils/checkNftOwnership';

Expand Down
86 changes: 86 additions & 0 deletions src/pages/api/premium/twitterKol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { TwitterClient } from './twitterAuth';
import { Tweet } from 'agent-twitter-client';
import { TRACKED_ACCOUNTS } from '@utils/trackedAccounts';
import { checkApiKey } from '@utils/checkApiKey';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// Initialize scraper outside request handler to reuse connection
const client = TwitterClient.getInstance();

// Only allow GET requests
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}

const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key is required' });
}

try {
if (!client.isReady()) {
await client.initialize();
}

const isValid = await checkApiKey(apiKey as string);
if (!isValid) {
return res.status(401).json({ error: 'Invalid API key' });
}

// Fetch tweets from all tracked accounts
const results = await Promise.allSettled(
TRACKED_ACCOUNTS.map(async (username) => {
try {
const userTweets = await client.getUserTweets(username);
const tweets: Tweet[] = [];
for await (const tweet of userTweets) {
tweets.push(tweet);
}
return {
username,
tweets
};
} catch (error) {
console.error(`Error fetching tweets for ${username}:`, error);
return {
username,
tweets: []
};
}
})
);

// Filter out rejected promises and get fulfilled values
const allTweets = results
.filter((result): result is PromiseFulfilledResult<{username: string, tweets: Tweet[]}> =>
result.status === 'fulfilled'
)
.map(result => result.value);

// Filter out empty results and sort by date
const flattenedTweets = allTweets
.flatMap(({ username, tweets }) =>
tweets.map((tweet: Tweet) => ({
...tweet,
username
}))
);

// Return the results
return res.status(200).json({
success: true,
data: flattenedTweets
});

} catch (error) {
console.error('Twitter fetch error:', error);
return res.status(500).json({
success: false,
error: 'Failed to fetch tweets'
});
}
}
12 changes: 12 additions & 0 deletions src/pages/api/premium/twitterSearch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { TwitterClient } from './twitterAuth';
import { checkApiKey } from '@utils/checkApiKey';

export default async function handler(
req: NextApiRequest,
Expand All @@ -14,10 +15,21 @@ export default async function handler(
return res.status(405).json({ error: 'Method not allowed' });
}

const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key is required' });
}

try {
if (!client.isReady()) {
await client.initialize();
}

const isValid = await checkApiKey(apiKey as string);
if (!isValid) {
return res.status(401).json({ error: 'Invalid API key' });
}

// Get query parameters
const { query, limit = '100' } = req.body;

Expand Down
96 changes: 66 additions & 30 deletions src/pages/api/premium/twitterTrending.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { TwitterClient } from './twitterAuth';
import { Tweet } from 'agent-twitter-client';
import { TRACKED_ACCOUNTS } from '@utils/trackedAccounts';
import { checkApiKey } from '@utils/checkApiKey';

// List of accounts to track
const TRACKED_ACCOUNTS = [
'0xMert_',
'blknoiz06',
'DeeZe',
'Loopifyyy',
'0xMerp',
'optimizoor',
'DancingEddie_',
'VitalikButerin',
'notthreadguy',
'aeyakovenko',
'rajgokal',
'zhusu',
'vydamo_'
];
interface TickerMention {
ticker: string;
count: number;
tweets: Array<{
text: string;
username: string;
createdAt: Date;
url?: string;
}>;
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// Initialize scraper outside request handler to reuse connection
const client = TwitterClient.getInstance();

// Only allow GET requests
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' });
}

const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key is required' });
}

try {
if (!client.isReady()) {
await client.initialize();
}
// Fetch tweets from all tracked accounts

const isValid = await checkApiKey(apiKey as string);
if (!isValid) {
return res.status(401).json({ error: 'Invalid API key' });
}

const results = await Promise.allSettled(
TRACKED_ACCOUNTS.map(async (username) => {
try {
Expand All @@ -58,26 +62,58 @@ export default async function handler(
})
);

// Filter out rejected promises and get fulfilled values
const allTweets = results
.filter((result): result is PromiseFulfilledResult<{username: string, tweets: Tweet[]}> =>
result.status === 'fulfilled'
)
.map(result => result.value);

// Filter out empty results and sort by date
const flattenedTweets = allTweets
.flatMap(({ username, tweets }) =>
tweets.map((tweet: Tweet) => ({
...tweet,
username
}))
);
// Track mentioned tickers
const tickerMentions: { [key: string]: TickerMention } = {};
const seenTweets = new Set<string>(); // Track unique tweets by text + username

// Process tweets to find ticker mentions
allTweets.forEach(({ username, tweets }) => {
tweets.forEach((tweet: Tweet) => {
// Create unique key for tweet
const tweetKey = `${username}:${tweet.text}`;
if (seenTweets.has(tweetKey)) return; // Skip if we've seen this tweet
seenTweets.add(tweetKey);

const tickerMatches = tweet.text?.match(/\$([A-Za-z]{2,10})/g);

if (tickerMatches) {
// Use Set to get unique tickers from this tweet
const uniqueTickers = new Set(tickerMatches);
uniqueTickers.forEach(match => {
const ticker = match.toUpperCase();
if (!tickerMentions[ticker]) {
tickerMentions[ticker] = {
ticker,
count: 0,
tweets: []
};
}

tickerMentions[ticker].count++;
tickerMentions[ticker].tweets.push({
text: tweet.text ?? '',
username,
createdAt: new Date(),
url: tweet.urls?.[0] ?? undefined
});
});
}
});
});

// Convert to array and sort by mention count
const sortedTickers = Object.values(tickerMentions)
.sort((a, b) => b.count - a.count);

// Return the results
return res.status(200).json({
success: true,
data: flattenedTweets
data: sortedTickers
});

} catch (error) {
Expand Down
37 changes: 37 additions & 0 deletions src/utils/checkApiKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { connectToDatabase } from "@pages/api/db/connectDB";
import { MongoClient } from "mongodb";

interface WalletDocument {
apiKey: string;
isPremium: boolean;
}

export const checkApiKey = async (apiKey: string): Promise<boolean> => {
let mongoClient: MongoClient | null = null;

try {
// Validate API key format
if (!apiKey || typeof apiKey !== 'string') {
return false;
}

mongoClient = await connectToDatabase();
const db = mongoClient.db("walletAnalyzer");

// Check if API key exists and is valid
const wallet = await db.collection<WalletDocument>('wallets').findOne({
apiKey: apiKey,
isPremium: true // Ensure the wallet has premium status
});

return !!wallet;
} catch (error) {
console.error('Error checking API key:', error);
return false;
} finally {
// Always close the connection
if (mongoClient) {
await mongoClient.close();
}
}
}
38 changes: 38 additions & 0 deletions src/utils/trackedAccounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// List of accounts to track
export const TRACKED_ACCOUNTS = [
'0xMert_',
'blknoiz06',
'DeeZe',
'Loopifyyy',
'0xMerp',
'optimizoor',
'DancingEddie_',
'VitalikButerin',
'notthreadguy',
'aeyakovenko',
'rajgokal',
'zhusu',
'vydamo_',
'zachxbt',
'metaversejoji',
'casino616',
'ShockedJS',
'BastilleBtc',
'spunosounds',
'TheMisterFrog',
'0xGroovy',
'973Meech',
'rektober',
'frankdegods',
'orangie',
'imperooterxbt',
'sabby_eth',
'NokiTheTrader',
'moneymaykah_',
'rasmr_eth',
'MustStopMurad',
'staccoverflow',
'mememe69696969',
'dolonosolo',
// 'based16z'
];

0 comments on commit 9f5c11b

Please sign in to comment.