diff --git a/src/pages/api/premium/checkBundles.ts b/src/pages/api/premium/checkBundles.ts index 8374045f..108faf81 100644 --- a/src/pages/api/premium/checkBundles.ts +++ b/src/pages/api/premium/checkBundles.ts @@ -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, @@ -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' }); } @@ -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(); - } } } diff --git a/src/pages/api/premium/status.ts b/src/pages/api/premium/status.ts index d86efd91..2fd0550d 100644 --- a/src/pages/api/premium/status.ts +++ b/src/pages/api/premium/status.ts @@ -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'; diff --git a/src/pages/api/premium/twitterKol.ts b/src/pages/api/premium/twitterKol.ts new file mode 100644 index 00000000..c53f9307 --- /dev/null +++ b/src/pages/api/premium/twitterKol.ts @@ -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' + }); + } +} \ No newline at end of file diff --git a/src/pages/api/premium/twitterSearch.ts b/src/pages/api/premium/twitterSearch.ts index f5f3c4b6..c7b13a3f 100644 --- a/src/pages/api/premium/twitterSearch.ts +++ b/src/pages/api/premium/twitterSearch.ts @@ -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, @@ -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; diff --git a/src/pages/api/premium/twitterTrending.ts b/src/pages/api/premium/twitterTrending.ts index 79cde074..e1e5e36b 100644 --- a/src/pages/api/premium/twitterTrending.ts +++ b/src/pages/api/premium/twitterTrending.ts @@ -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 { @@ -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(); // 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) { diff --git a/src/utils/checkApiKey.ts b/src/utils/checkApiKey.ts new file mode 100644 index 00000000..dafffb3d --- /dev/null +++ b/src/utils/checkApiKey.ts @@ -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 => { + 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('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(); + } + } +} \ No newline at end of file diff --git a/src/utils/trackedAccounts.ts b/src/utils/trackedAccounts.ts new file mode 100644 index 00000000..3e3aea0b --- /dev/null +++ b/src/utils/trackedAccounts.ts @@ -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' +]; \ No newline at end of file