├── types.d.ts ├── src ├── app │ ├── favicon.ico │ ├── Terms │ │ └── page.tsx │ ├── globals.css │ ├── PrivacyPolicy │ │ └── page.tsx │ ├── page.tsx │ ├── api │ │ ├── scraper │ │ │ └── route.ts │ │ ├── llm │ │ │ └── route.ts │ │ ├── scrape │ │ │ └── route.ts │ │ └── cron │ │ │ └── route.ts │ └── layout.tsx ├── lib │ ├── utils.ts │ ├── twitterApi.ts │ └── TwitterBot.ts └── components │ ├── ui │ ├── input.tsx │ ├── button.tsx │ └── toast.tsx │ └── SubmitTweet.tsx ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── postcss.config.mjs ├── next.config.ts ├── eslint.config.mjs ├── components.json ├── .gitignore ├── tailwind.config.ts ├── tsconfig.json ├── package.json └── README.md /types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "node" -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terauss/Eliza-Twitter-AI-Agent/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/app/Terms/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | const Terms = ()=>{ 4 | 5 | return( 6 |
7 |

8 | Terms of Service 9 | 10 |

11 |
12 | ) 13 | } 14 | 15 | export default Terms -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } 8 | 9 | @layer base { 10 | :root { 11 | --radius: 0.5rem; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/PrivacyPolicy/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | export default function Policy(){ 5 | return ( 6 | 7 |
8 | 9 | Privacy Policy 10 | 11 | 12 | 13 |
14 | ) 15 | } -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | // next.config.ts 2 | import type { NextConfig } from "next"; 3 | 4 | const nextConfig: NextConfig = { 5 | /* config options here */ 6 | serverExternalPackages: ["twitter-api-v2"], 7 | productionBrowserSourceMaps: false, 8 | } 9 | 10 | export default nextConfig; -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import SubmitDataPage from "@/components/SubmitTweet"; 2 | 3 | 4 | export default function Home() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /src/app/api/scraper/route.ts: -------------------------------------------------------------------------------- 1 | import { getRecentMentions} from "@/lib/TwitterBot"; 2 | import { NextResponse } from "next/server"; 3 | 4 | 5 | 6 | export async function GET(){ 7 | try { 8 | const respponse = await getRecentMentions(); 9 | 10 | return NextResponse.json({ 11 | message:respponse 12 | }) 13 | 14 | } catch (error) { 15 | return NextResponse.json({ 16 | error:"an error occurred", 17 | msg:error 18 | }) 19 | } 20 | } -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": false, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | darkMode: ["class"], 5 | content: [ 6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: 'var(--background)', 14 | foreground: 'var(--foreground)' 15 | }, 16 | borderRadius: { 17 | lg: 'var(--radius)', 18 | md: 'calc(var(--radius) - 2px)', 19 | sm: 'calc(var(--radius) - 4px)' 20 | } 21 | } 22 | }, 23 | plugins: [require("tailwindcss-animate")], 24 | } satisfies Config; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "allowSyntheticDefaultImports":true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | }, 25 | "types": ["node"] 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/lib/chat.js"], 28 | "exclude": ["node_modules"] 29 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const geistSans = Geist({ 6 | variable: "--font-geist-sans", 7 | subsets: ["latin"], 8 | }); 9 | 10 | const geistMono = Geist_Mono({ 11 | variable: "--font-geist-mono", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Create Next App", 17 | description: "Generated by create next app", 18 | }; 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode; 24 | }>) { 25 | return ( 26 | 27 | 30 | {children} 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/api/llm/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import {generateTweet} from "@/lib/TwitterBot"; 3 | 4 | 5 | export async function POST(request:NextRequest){ 6 | 7 | if(request.method !== "POST"){ 8 | return NextResponse.json({ 9 | msg:"Bad Request method" 10 | }, {status:405}) 11 | } 12 | 13 | const data = await request.json(); 14 | 15 | if(!data){ 16 | return NextResponse.json({data:"user prompt is missing"}, {status:200}) 17 | } 18 | 19 | try { 20 | 21 | const response = await generateTweet(data.data) 22 | console.log("", response) 23 | return NextResponse.json({ 24 | data: response 25 | }) 26 | 27 | } catch (error:unknown) { 28 | console.log(error) 29 | return NextResponse.json({ 30 | error:error 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdptoolkit", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@coinbase/cdp-langchain": "^0.0.9", 14 | "@coinbase/twitter-langchain": "^0.0.6", 15 | "@langchain/community": "^0.3.19", 16 | "@langchain/core": "^0.3.26", 17 | "@langchain/google-genai": "^0.1.6", 18 | "@langchain/langgraph": "^0.2.34", 19 | "@langchain/openai": "^0.3.16", 20 | "@radix-ui/react-slot": "^1.1.1", 21 | "@radix-ui/react-toast": "^1.2.4", 22 | "@sparticuz/chromium": "^131.0.1", 23 | "@upstash/redis": "^1.34.3", 24 | "cheerio": "^1.0.0", 25 | "class-variance-authority": "^0.7.1", 26 | "clsx": "^2.1.1", 27 | "date-fns": "^4.1.0", 28 | "dotenv": "^16.4.7", 29 | "lucide-react": "^0.469.0", 30 | "next": "^15.1.2", 31 | "node-cron": "^3.0.3", 32 | "puppeteer": "^23.11.1", 33 | "puppeteer-core": "^23.11.1", 34 | "react": "^19.0.0", 35 | "react-dom": "^19.0.0", 36 | "sentiment": "^5.0.2", 37 | "tailwind-merge": "^2.6.0", 38 | "tailwindcss-animate": "^1.0.7", 39 | "ts-node": "^10.9.2", 40 | "twitter-api-v2": "^1.18.2", 41 | "zod": "^3.24.1" 42 | }, 43 | "devDependencies": { 44 | "@eslint/eslintrc": "^3", 45 | "@types/next": "^9.0.0", 46 | "@types/node": "^20.17.10", 47 | "@types/node-cron": "^3.0.11", 48 | "@types/react": "^19", 49 | "@types/react-dom": "^19", 50 | "@types/sentiment": "^5.0.4", 51 | "eslint": "^9", 52 | "eslint-config-next": "15.1.2", 53 | "ignore-loader": "^0.1.2", 54 | "postcss": "^8", 55 | "tailwindcss": "^3.4.1", 56 | "typescript": "^5" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/SubmitTweet.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from 'react' 4 | import { Input } from "@/components/ui/input" 5 | import { Button } from "@/components/ui/button" 6 | import { Bird } from 'lucide-react'; 7 | 8 | 9 | 10 | 11 | export default function SubmitDataPage() { 12 | const [inputData, setInputData] = useState('') 13 | const [isLoading, setIsLoading] = useState(false) 14 | 15 | 16 | const handleSubmit = async (e: React.FormEvent) => { 17 | e.preventDefault() 18 | setIsLoading(true) 19 | 20 | try { 21 | const response = await fetch('/api/llm', { 22 | method: 'POST', 23 | headers: { 24 | 'Content-Type': 'application/json', 25 | }, 26 | body: JSON.stringify({ data: inputData }), 27 | }) 28 | 29 | if (!response.ok) { 30 | throw new Error('Failed to submit data') 31 | } 32 | 33 | 34 | // Optionally, redirect to another page or clear the form 35 | setInputData('') 36 | // router.push('/thank-you') // Uncomment this line if you want to redirect after submission 37 | } catch (error) { 38 | console.error('Error submitting data:', error) 39 | 40 | } finally { 41 | setIsLoading(false) 42 | } 43 | } 44 | 45 | return ( 46 |
47 |

Tweet

48 |
49 |
50 | 53 | setInputData(e.target.value)} 58 | placeholder="Enter your tweet here" 59 | required 60 | /> 61 |
62 | 65 |
66 |
67 | ) 68 | } -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-neutral-300", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-neutral-900 text-neutral-50 shadow hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", 14 | destructive: 15 | "bg-red-500 text-neutral-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", 16 | outline: 17 | "border border-neutral-200 bg-white shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", 18 | secondary: 19 | "bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", 20 | ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", 21 | link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /src/app/api/scrape/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import * as puppeteer from 'puppeteer-core'; 3 | import chromium from '@sparticuz/chromium'; 4 | import puppeteerCore from 'puppeteer-core'; 5 | import { executablePath } from 'puppeteer'; 6 | 7 | export async function POST(request: NextRequest) { 8 | let browser: puppeteer.Browser | null = null; 9 | const { url } = await request.json(); 10 | console.log('Target URL:', url); 11 | 12 | try { 13 | if (process.env.NODE_ENV === 'development') { 14 | console.log('Development environment detected.'); 15 | browser = await puppeteer.launch({ 16 | args: ['--no-sandbox', '--disable-setuid-sandbox'], 17 | headless: true, 18 | executablePath:executablePath() 19 | }); 20 | } 21 | if (process.env.NODE_ENV === 'production') { 22 | console.log('Production environment detected.'); 23 | const executablePath = await chromium.executablePath(); 24 | console.log('Chromium executablePath:', executablePath); // Log the executablePath 25 | 26 | if (!executablePath) { 27 | throw new Error("Failed to retrieve Chromium executable path. Please ensure that @sparticuz/chromium is properly configured."); 28 | } 29 | 30 | browser = await puppeteerCore.launch({ 31 | args: chromium.args, 32 | defaultViewport: chromium.defaultViewport, 33 | executablePath: executablePath, // Ensure executablePath is correctly fetched 34 | headless: chromium.headless, 35 | 36 | }); 37 | } 38 | 39 | const page = await browser!.newPage(); 40 | await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'); 41 | await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); 42 | 43 | const headlines = await page.evaluate((): string[] => { 44 | const titles: string[] = []; 45 | const elements = document.querySelectorAll('h2, .post-card__title, .article-card__title'); 46 | elements.forEach((el) => titles.push(el?.textContent?.trim() ?? '')); 47 | return titles; 48 | }); 49 | 50 | await browser!.close(); 51 | return NextResponse.json({ 52 | msg: "Successful", 53 | headlines: headlines, 54 | status: 200 55 | }); 56 | } catch (error) { 57 | console.error('Error occurred:', error); 58 | if (browser) { 59 | await browser.close(); // Ensure browser is closed in case of error 60 | } 61 | return NextResponse.json({ message: "something went wrong", error: error}); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/lib/twitterApi.ts: -------------------------------------------------------------------------------- 1 | import { TwitterApi } from 'twitter-api-v2'; 2 | import { z } from 'zod'; 3 | import { tool } from "@langchain/core/tools"; 4 | import { executablePath } from 'puppeteer'; 5 | 6 | import * as puppeteer from 'puppeteer-core'; 7 | 8 | import chromium from '@sparticuz/chromium'; 9 | import puppeteerCore from 'puppeteer-core'; 10 | 11 | const twitterApiKey = process.env.NEXT_PUBLIC_TWITTER_API_KEY; 12 | const twitterApiSecret = process.env.NEXT_PUBLIC_TWITTER_API_SECRET; 13 | const twitterAccessToken = process.env.NEXT_PUBLIC_TWITTER_ACCESS_TOKEN; 14 | const twitterAccessTokenSecret = process.env.NEXT_PUBLIC_TWITTER_ACCESS_TOKEN_SECRET; 15 | 16 | 17 | const TwitterApiReadWrite = new TwitterApi({ 18 | appKey: twitterApiKey!, 19 | appSecret: twitterApiSecret!, 20 | accessToken: twitterAccessToken!, 21 | accessSecret: twitterAccessTokenSecret!, 22 | }); 23 | 24 | const rw = TwitterApiReadWrite.readWrite; 25 | 26 | export const postTool = tool(async (text) => { 27 | const post = await rw.v2.tweet(text); 28 | return post?.data; 29 | }, { 30 | name: 'post_tool', 31 | description: 'Post a tweet on Twitter', 32 | schema: z.object({ 33 | text: z.string().describe("the text to post"), 34 | }) 35 | }); 36 | 37 | export const replyTool = tool(async ({ reply, tweetId }) => { 38 | return await rw.v2.reply(reply, tweetId); 39 | }, { 40 | name: 'reply_tool', 41 | description: 'create replies', 42 | schema: z.object({ 43 | reply: z.string().describe("your replies"), 44 | tweetId: z.string().describe("id of the tweet you are replying to."), 45 | }) 46 | }); 47 | 48 | export const mentionTool = tool(async () => { 49 | return await rw.v1.mentionTimeline(); 50 | }, { 51 | name: "mention_tool", 52 | description: 'get all mentions', 53 | schema: z.void(), 54 | }); 55 | 56 | export const accountDetailsTools = tool(async () => { 57 | const details = await rw.v2.me(); 58 | return details?.data; 59 | }, { 60 | name: "account_details_tools", 61 | description: 'get the details of my account', 62 | schema: z.void(), 63 | }); 64 | 65 | export const trendingTopicsTool = tool(async () => { 66 | const trends = await rw.v1.trendsAvailable(); 67 | return trends; 68 | }, { 69 | name: "trendingTopics_tool", 70 | description: "fetch the current trendings", 71 | schema: z.void() 72 | }); 73 | 74 | export const searchTweetsTool = tool(async ({ topic }: { topic: string }) => { 75 | const response = await rw.v2.search(topic, { 76 | max_results: 10, 77 | }); 78 | return response; 79 | }, { 80 | name: "search_tweets_tool", 81 | description: "Search for tweets on a specific topic", 82 | schema: z.object({ 83 | topic: z.string().describe("The topic to search for on Twitter, e.g., 'DAO', 'AI agents', 'robotics' etc"), 84 | }), 85 | }); 86 | 87 | export const likeTweet = tool(async ({ userId, tweetId }) => { 88 | const like = await rw.v2.like(userId, tweetId); 89 | return like.data; 90 | }, { 91 | name: "like_tweet", 92 | description: "like a tweet", 93 | schema: z.object({ 94 | tweetId: z.string().describe("tweet id to like"), 95 | userId: z.string().describe("user Id of the tweet") 96 | }) 97 | }); 98 | export const scrapDataOnlineTool = tool(async ({ url }) => { 99 | let browser: puppeteer.Browser | null = null; 100 | try { 101 | if (process.env.NODE_ENV === 'development') { 102 | console.log('Development browser: '); 103 | browser = await puppeteer.launch({ 104 | args: ['--no-sandbox', '--disable-setuid-sandbox'], 105 | headless: true, 106 | executablePath:executablePath() 107 | }); 108 | } else if (process.env.NODE_ENV === 'production') { 109 | console.log('Production browser: '); 110 | browser! = await puppeteerCore.launch({ 111 | args: chromium.args, 112 | defaultViewport: chromium.defaultViewport, 113 | executablePath: await chromium.executablePath(), 114 | headless: chromium.headless, 115 | }); 116 | } 117 | 118 | const page = await browser!.newPage(); 119 | await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'); 120 | await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }); 121 | 122 | // Explicitly typing the evaluate function 123 | const headlines = await page.evaluate((): string[] => { 124 | const titles: string[] = []; 125 | const elements = document.querySelectorAll('h2,.post-card__title, .article-card__title'); 126 | elements.forEach((el) => titles.push(el?.textContent?.trim() ?? '')); 127 | return titles; 128 | }); 129 | 130 | await browser!.close(); 131 | return headlines; 132 | } catch (error) { 133 | console.error(error); 134 | if (browser) { 135 | await browser.close(); // Ensure browser is closed in case of error 136 | } 137 | return { message: "something went wrong", error: error }; 138 | } 139 | }, { 140 | name: "scrapeDataOnline_tool", 141 | description: "scrape data online", 142 | schema: z.object({ 143 | url: z.string().describe("the url of the website to scrape data from."), 144 | }), 145 | }); 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 🚀 **Twitter AI Agent** 3 | 4 | **Twitter AI Agent** is a cutting-edge **Next.js application** designed to **automate and enhance your Twitter interactions** using the power of **AI**. By seamlessly integrating the **Twitter API** with **Google Generative AI**, this app enables powerful automation for tweeting, replying, sentiment analysis, trend monitoring, and more. With cron jobs ensuring regular execution, the app ensures you stay connected and engaged with your audience 24/7. 🌟 5 | 6 | --- 7 | 8 | ## 🌟 **Key Features** 9 | 10 | - 🐦 **Automated Tweet Posting**: Generate and post creative tweets enriched with emojis for engagement. 11 | - 💬 **Smart Mention Handling**: Analyze and reply to mentions with sentiment-aware responses. 12 | - 🔥 **Trend Monitoring**: Stay relevant by posting about trending topics in **AI**, **Blockchain**, and **Crypto**. 13 | - 📊 **Follower Analysis**: Track follower growth and engagement to refine your content strategy. 14 | - 🗳️ **Poll Posting**: Post interactive polls to gather opinions from your audience. 15 | - 🔍 **Tweet Searching**: Search tweets by keywords, like them, and reply based on sentiment analysis. 16 | - ❤️ **Auto Like & Reply**: Engage with the Twitter community by liking and replying to relevant tweets. 17 | 18 | --- 19 | 20 | ## ❓ **Why Use Twitter AI Agent?** 21 | 22 | Twitter AI Agent is perfect for individuals or organizations looking to: 23 | - Automate their Twitter activity with minimal manual effort. 24 | - Maintain high engagement and relevance in their niche. 25 | - Leverage AI to create impactful and timely Twitter interaction. 26 | 27 | Whether you're a social media manager, influencer, or a tech-savvy professional, this tool is your ultimate assistant for optimizing Twitter strategies. 🚀 28 | 29 | --- 30 | 31 | ## 🛠️ **How It Works** 32 | 33 | 1. 🔐 **Environment Setup**: Manage sensitive API keys and tokens securely with `.env.local`. 34 | 2. ⏱️ **Scheduled Tasks**: Automate tweets, replies, and trend monitoring using cron jobs. 35 | 3. 🤖 **AI Integration**: Generate tweets and analyze sentiment with **Google Generative AI**. 36 | 4. 🐦 **Twitter API**: Post tweets, fetch mentions, and monitor trends directly through the Twitter API. 37 | 38 | --- 39 | 40 | ## 🚀 **Getting Started** 41 | 42 | ### **Prerequisites** 43 | - ✅ Node.js (v14.x or later) 44 | - ✅ npm (v6.x or later) or yarn (v1.x or later) 45 | - ✅ Twitter Developer Account with API keys and tokens 46 | 47 | --- 48 | 49 | ### **Installation Steps** 50 | 51 | 1. **Clone the Repository**: 52 | ```bash 53 | git clone https://github.com/terauss/Eliza-Twitter-AI-Agent.git 54 | cd Eliza-Twitter-AI-Agent 55 | ``` 56 | 57 | 2. **Install Dependencies**: 58 | Using npm: 59 | ```bash 60 | npm install 61 | ``` 62 | Using yarn: 63 | ```bash 64 | yarn install 65 | ``` 66 | 67 | 3. **Set Up Environment Variables**: 68 | Create a `.env.local` file in the root directory and add: 69 | ```env 70 | NEXT_PUBLIC_GOOGLE_API_KEY=your-google-api-key 71 | NEXT_PUBLIC_TWITTER_API_KEY=your-twitter-api-key 72 | NEXT_PUBLIC_TWITTER_API_SECRET=your-twitter-api-secret 73 | NEXT_PUBLIC_TWITTER_ACCESS_TOKEN=your-twitter-access-token 74 | NEXT_PUBLIC_TWITTER_ACCESS_TOKEN_SECRET=your-twitter-access-token-secret 75 | ``` 76 | Replace placeholders with your actual keys and tokens. 77 | 78 | 4. **Run the Development Server**: 79 | Using npm: 80 | ```bash 81 | npm run dev 82 | ``` 83 | Using yarn: 84 | ```bash 85 | yarn dev 86 | ``` 87 | The app will be accessible at `http://localhost:3000`. 88 | 89 | --- 90 | 91 | ## 📂 **Project Structure** 92 | 93 | ```plaintext 94 | twitter-ai-agent/ 95 | ├── public/ # Static assets 96 | ├── src/ # Source files 97 | │ ├── app/ # Next.js app directory 98 | │ ├── components/ # Reusable React components 99 | │ ├── lib/ # Utility functions and libraries 100 | │ └── ... # Other source files 101 | ├── .env.local # Environment variables 102 | ├── package.json # Project dependencies 103 | └── README.md # Project documentation 104 | ``` 105 | 106 | --- 107 | 108 | ## 🔗 **API Integrations** 109 | 110 | - **Twitter API**: Post tweets, fetch mentions, and monitor trends effortlessly. 111 | - **Google Generative AI**: Generate engaging content and perform sentiment analysis. 112 | 113 | --- 114 | 115 | ## 🔒 **Security Best Practices** 116 | 117 | - Store all API keys and tokens in the `.env.local` file and add it to `.gitignore`. 118 | - Monitor API usage to avoid exceeding Twitter’s rate limits. 119 | 120 | --- 121 | 122 | ## 🛡️ **Testing and Deployment** 123 | 124 | - 🧪 **Testing**: Conduct local testing to ensure smooth operation. 125 | - 🚀 **Deployment**: Use platforms like **Vercel** or **Fleek** for hosting. 126 | 127 | --- 128 | 129 | ## 🤝 **Contributing** 130 | 131 | We welcome contributions! To contribute: 132 | 1. Fork the repository. 133 | 2. Create a new branch (`git checkout -b feature-branch`). 134 | 3. Commit your changes (`git commit -m "Add feature"`). 135 | 4. Push to your branch (`git push origin feature-branch`). 136 | 5. Submit a pull request for review. 137 | 138 | --- 139 | 140 | ## 📧 **Contact** 141 | 142 | For questions or feedback, feel free to reach out on Telegram: [@terauss](https://t.me/terauss). 143 | -------------------------------------------------------------------------------- /src/components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ToastPrimitives from "@radix-ui/react-toast" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | import { X } from "lucide-react" 7 | 8 | import { cn } from "@/lib/utils" 9 | 10 | const ToastProvider = ToastPrimitives.Provider 11 | 12 | const ToastViewport = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, ...props }, ref) => ( 16 | 24 | )) 25 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 26 | 27 | const toastVariants = cva( 28 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-neutral-200 p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-neutral-800", 29 | { 30 | variants: { 31 | variant: { 32 | default: "border bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50", 33 | destructive: 34 | "destructive group border-red-500 bg-red-500 text-neutral-50 dark:border-red-900 dark:bg-red-900 dark:text-neutral-50", 35 | }, 36 | }, 37 | defaultVariants: { 38 | variant: "default", 39 | }, 40 | } 41 | ) 42 | 43 | const Toast = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef & 46 | VariantProps 47 | >(({ className, variant, ...props }, ref) => { 48 | return ( 49 | 54 | ) 55 | }) 56 | Toast.displayName = ToastPrimitives.Root.displayName 57 | 58 | const ToastAction = React.forwardRef< 59 | React.ElementRef, 60 | React.ComponentPropsWithoutRef 61 | >(({ className, ...props }, ref) => ( 62 | 70 | )) 71 | ToastAction.displayName = ToastPrimitives.Action.displayName 72 | 73 | const ToastClose = React.forwardRef< 74 | React.ElementRef, 75 | React.ComponentPropsWithoutRef 76 | >(({ className, ...props }, ref) => ( 77 | 86 | 87 | 88 | )) 89 | ToastClose.displayName = ToastPrimitives.Close.displayName 90 | 91 | const ToastTitle = React.forwardRef< 92 | React.ElementRef, 93 | React.ComponentPropsWithoutRef 94 | >(({ className, ...props }, ref) => ( 95 | 100 | )) 101 | ToastTitle.displayName = ToastPrimitives.Title.displayName 102 | 103 | const ToastDescription = React.forwardRef< 104 | React.ElementRef, 105 | React.ComponentPropsWithoutRef 106 | >(({ className, ...props }, ref) => ( 107 | 112 | )) 113 | ToastDescription.displayName = ToastPrimitives.Description.displayName 114 | 115 | type ToastProps = React.ComponentPropsWithoutRef 116 | 117 | type ToastActionElement = React.ReactElement 118 | 119 | export { 120 | type ToastProps, 121 | type ToastActionElement, 122 | ToastProvider, 123 | ToastViewport, 124 | Toast, 125 | ToastTitle, 126 | ToastDescription, 127 | ToastClose, 128 | ToastAction, 129 | } 130 | -------------------------------------------------------------------------------- /src/app/api/cron/route.ts: -------------------------------------------------------------------------------- 1 | import { generateTweet, postTweet, scrapeAndPostEveryTwoHours } from '@/lib/TwitterBot'; 2 | import { getRecentMentions, replyToMention } from '@/lib/TwitterBot'; 3 | import { monitorAndPostRelevantTrends, analyzeFollowers, postPollIfNeeded, searchTweetsUsingTrends } from '@/lib/TwitterBot'; // Import your custom functions 4 | import { NextRequest, NextResponse } from 'next/server'; 5 | import { startOfDay, addDays, isAfter } from 'date-fns'; 6 | import { Redis } from '@upstash/redis' 7 | 8 | 9 | 10 | 11 | const redis = new Redis({ 12 | url: process.env.UPSTASH_REDIS_REST_URL, 13 | token: process.env.UPSTASH_REDIS_REST_TOKEN 14 | }) 15 | 16 | 17 | // Define the POST handler for all cron jobs 18 | export async function POST(req: NextRequest) { 19 | if (req.method !== 'POST') { 20 | return NextResponse.json({msg:"action not supported"}) 21 | } 22 | 23 | try { 24 | const {job} = await req.json() 25 | 26 | switch (job) { 27 | case 'tweetEvery3Hours': 28 | await runTweetJob(); 29 | break; 30 | 31 | case 'replyToMentionsEvery10Minutes': 32 | await runReplyToMentionsJob(); 33 | break; 34 | 35 | case 'monitorTrendsEvery4Hours': 36 | await runMonitorTrendsJob(); 37 | break; 38 | 39 | case 'dailyFollowerAnalysis': 40 | await runDailyFollowerAnalysis(); 41 | break; 42 | 43 | case 'postPollAt9AM': 44 | await runPostPollJob(); 45 | break; 46 | 47 | case 'searchTweetsUsingTrendsEvery6Hours': 48 | await runSearchTweetsJob(); 49 | break; 50 | 51 | case "scrapeAndPostEveryTwoHours": 52 | await scrapeAndPostEveryTwoHoursJob() 53 | break; 54 | 55 | 56 | default: 57 | return NextResponse.json({ message: 'Invalid cron job specified' }); 58 | } 59 | 60 | // If successful, return success 61 | return NextResponse.json({ message: `${job} executed successfully` }); 62 | } catch (error) { 63 | 64 | return NextResponse.json({ message: 'Cron job execution failed', error: error}); 65 | } 66 | } 67 | 68 | 69 | async function getTweetCountFromRedis() { 70 | const tweetCount = await redis.get('tweetCount'); 71 | if (tweetCount) { 72 | return JSON.parse(tweetCount as string); 73 | } else { 74 | return { count: 0, lastReset: new Date() }; 75 | } 76 | } 77 | 78 | async function incrementTweetCountInRedis() { 79 | const tweetCount = await getTweetCountFromRedis(); 80 | tweetCount.count += 1; 81 | await redis.set('tweetCount', JSON.stringify(tweetCount)); 82 | } 83 | 84 | async function resetTweetCountInRedis() { 85 | const newTweetCount = { count: 0, lastReset: new Date() }; 86 | await redis.set('tweetCount', JSON.stringify(newTweetCount)); 87 | } 88 | 89 | // Job to tweet every 3 hours 90 | async function runTweetJob() { 91 | const MAX_TWEETS_PER_DAY = 10; 92 | let tweetCount = await getTweetCountFromRedis(); 93 | 94 | // Reset the tweet count if it's a new day 95 | const now = new Date(); 96 | const dayStart = startOfDay(now); 97 | 98 | if (tweetCount.lastReset && isAfter(now, addDays(new Date(tweetCount.lastReset), 1))) { 99 | await resetTweetCountInRedis(); 100 | tweetCount = { count: 0, lastReset: dayStart }; 101 | } 102 | // Check if the bot has already tweeted 7 times today 103 | if (tweetCount.count >= MAX_TWEETS_PER_DAY) { 104 | console.log(`Maximum tweets reached for today (${MAX_TWEETS_PER_DAY} tweets).`); 105 | return; 106 | } 107 | const prioritizedTopics = [ 108 | 'DAO', 'AI Agents', 'robotics', 'IoT', 'Edge Computing', 109 | 'Quantum Computing', 'Autonomous Vehicles', 'Smart Cities', 110 | 'AI Ethics','Natural Language Processing', 111 | ]; 112 | const otherTopics = [ 113 | 'AI', 'Machine Learning', 'Blockchain', 'Crypto', 'Data Science', 114 | 'Cybersecurity', 'Cloud Computing', 'DevOps', 'AR/VR', '5G', 115 | 'Computer Vision', 'Big Data', 116 | 'Augmented Reality', 'Virtual Reality', 'Fintech', 'Healthtech', 117 | 'Edtech', 'Agtech', 'Green Technology' 118 | ]; 119 | const randomNumber = Math.random(); 120 | 121 | const topic = randomNumber < 0.5 ? prioritizedTopics[Math.floor(Math.random() * prioritizedTopics.length)] : otherTopics[Math.floor(Math.random() * otherTopics.length)]; 122 | const tweet = await generateTweet(topic); 123 | if (tweet) { 124 | await postTweet(tweet); 125 | await incrementTweetCountInRedis(); 126 | } 127 | } 128 | 129 | // Job to reply to mentions every 10 minutes 130 | let lastMentionReplyTime: Date | null = null; 131 | 132 | 133 | async function runReplyToMentionsJob() { 134 | const now = new Date(); 135 | const X_MINUTES = 10; 136 | 137 | if (lastMentionReplyTime && (now.getTime() - lastMentionReplyTime.getTime()) < X_MINUTES * 60000) { 138 | console.log(`Skipping mentions, last processed less than ${X_MINUTES} minutes ago.`); 139 | return; 140 | } 141 | 142 | const mentions = await getRecentMentions(); 143 | if (mentions) { 144 | for (const mention of mentions) { 145 | await replyToMention(mention); 146 | } 147 | } 148 | 149 | lastMentionReplyTime = new Date(); 150 | 151 | 152 | } 153 | 154 | 155 | 156 | // Job to monitor trends every 4 hours 157 | async function runMonitorTrendsJob() { 158 | await monitorAndPostRelevantTrends(); 159 | } 160 | 161 | // Job for daily analysis of followers 162 | async function runDailyFollowerAnalysis() { 163 | await analyzeFollowers(); 164 | } 165 | 166 | // Job to post poll at 9 AM daily 167 | async function runPostPollJob() { 168 | await postPollIfNeeded(); 169 | } 170 | 171 | // Job to search tweets using trends every 6 hours 172 | async function runSearchTweetsJob() { 173 | await searchTweetsUsingTrends(); 174 | } 175 | 176 | async function scrapeAndPostEveryTwoHoursJob(){ 177 | await scrapeAndPostEveryTwoHours() 178 | } -------------------------------------------------------------------------------- /src/lib/TwitterBot.ts: -------------------------------------------------------------------------------- 1 | import { HumanMessage} from '@langchain/core/messages'; 2 | import { ChatGoogleGenerativeAI } from '@langchain/google-genai'; 3 | import { createReactAgent } from '@langchain/langgraph/prebuilt'; 4 | import { postTool, replyTool, mentionTool, accountDetailsTools , trendingTopicsTool, searchTweetsTool, likeTweet, scrapDataOnlineTool} from './twitterApi'; 5 | 6 | 7 | 8 | interface props{ 9 | 10 | id_str:string, 11 | user:{ 12 | screen_name:string, 13 | } 14 | 15 | } 16 | interface msgProps{ 17 | name:string 18 | } 19 | interface tweetlikeprops{ 20 | id_str:string, 21 | text:string, 22 | user:{ 23 | screen_name:string, 24 | id_str:string 25 | } 26 | } 27 | // Initialize tools and LLM agent 28 | const tools = [postTool, replyTool, mentionTool, accountDetailsTools,trendingTopicsTool, searchTweetsTool, likeTweet, scrapDataOnlineTool]; 29 | const chat = new ChatGoogleGenerativeAI({ 30 | model: 'gemini-1.5-flash', 31 | apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY ,// Use env variable for API key 32 | maxOutputTokens: 200, 33 | temperature:0.8, 34 | 35 | }); 36 | if (!process.env.NEXT_PUBLIC_GOOGLE_API_KEY) { 37 | throw new Error('API Key not found. Please set the NEXT_PUBLIC_GOOGLE_API_KEY environment variable.'); 38 | } 39 | const agent = createReactAgent({ 40 | llm: chat, 41 | tools, 42 | }); 43 | // Topics for tweeting 44 | 45 | 46 | // Function to generate and post tweets about a topic 47 | export async function generateTweet(topic: string) { 48 | try { 49 | const response = await agent.invoke({ 50 | messages: new HumanMessage(`Post a creative tweet about ${topic} & add emoji to express your sentiments,possiblly add image if you can`), 51 | }); 52 | return response?.content; 53 | } catch (error) { 54 | console.error('Error generating tweet:', error); 55 | throw error; 56 | } 57 | } 58 | // Function to post the tweet using postTool 59 | export async function postTweet(content: string) { 60 | try { 61 | await agent.invoke({ 62 | messages: new HumanMessage(`Post a creative tweet about ${content}`), 63 | }); 64 | console.log(`Successfully posted tweet: ${content}`); 65 | } catch (error) { 66 | console.error('Error posting tweet:', error); 67 | throw error; 68 | } 69 | } 70 | 71 | // Function to analyze sentiment of a mention 72 | 73 | 74 | // Function to get recent mentions and reply 75 | export async function getRecentMentions() { 76 | try { 77 | const mentions = await agent.invoke({ 78 | messages: new HumanMessage('Please get all mentions'), 79 | }); 80 | return mentions?.content; 81 | } catch (error) { 82 | console.error('Error fetching mentions:', error); 83 | throw error; 84 | } 85 | } 86 | // Function to reply to mentions with sentiment analysis 87 | export async function replyToMention(mention: props) { 88 | try { 89 | const username = mention.user.screen_name; 90 | const tweetId = mention.id_str; 91 | 92 | 93 | await agent.invoke({ 94 | messages: new HumanMessage(`Please reply using ${tweetId}`), 95 | }); 96 | console.log(`Replied to mention from @${username}`); 97 | 98 | } catch (error) { 99 | console.error('Error replying to mention:', error); 100 | throw error; 101 | } 102 | } 103 | 104 | // Function to monitor trends and post about them 105 | export async function fetchTrendingTopics() { 106 | try { 107 | const trendsResponse = await agent.invoke({ 108 | messages: [new HumanMessage('Get trending hashtags relevant to technology, AI, DAO, Blockchain, and Crypto etc')], 109 | tools:[trendingTopicsTool] 110 | }); 111 | return trendsResponse?.content; 112 | } catch (error: unknown) { 113 | console.error('Error fetching trending topics:', error); 114 | throw error; 115 | } 116 | } 117 | export async function postTweetUsingTrend(trend: string, topic: string) { 118 | try { 119 | const tweet = await agent.invoke({ 120 | messages: [new HumanMessage(`Post a tweet using the trending hashtag '${trend}' about the topic '${topic}'`)], 121 | }); 122 | if (tweet?.content) { 123 | await postTweet(tweet.content); 124 | console.log(`Posted tweet using trend '${trend}' about topic '${topic}'`); 125 | } 126 | } catch (error) { 127 | console.error(`Error posting tweet using trend '${trend}' about topic '${topic}':`, error); 128 | } 129 | } 130 | // Function to monitor trends and post about bot-relevant topics 131 | export async function monitorAndPostRelevantTrends() { 132 | try { 133 | // Fetch trending topics 134 | const trendingTopics = await fetchTrendingTopics(); 135 | if (!trendingTopics || trendingTopics.length === 0) { 136 | console.log('No relevant trends found.'); 137 | return; 138 | } 139 | // Define the bot's focus topics 140 | const botTopics = ['DAO', 'AI agents', 'Blockchain', 'Crypto', 'Machine Learning']; 141 | // Filter trending topics based on relevance to the bot's focus 142 | const relevantTrends = trendingTopics.filter((trend: string) => 143 | botTopics.some(topic => trend.toLowerCase().includes(topic.toLowerCase())) 144 | ); 145 | // Post tweets about the relevant trends 146 | for (const trend of relevantTrends) { 147 | const relevantTopic = botTopics.find(topic => trend.toLowerCase().includes(topic.toLowerCase())); 148 | if (relevantTopic) { 149 | await postTweetUsingTrend(trend, relevantTopic); // Post tweet using the relevant trend and topic 150 | } 151 | } 152 | } catch (error) { 153 | console.error('Error monitoring and posting relevant trends:', error); 154 | } 155 | } 156 | 157 | // Analyze followers growth and engagement periodically 158 | export async function analyzeFollowers() { 159 | try { 160 | const followerStats = await agent.invoke({ 161 | messages: [new HumanMessage('Analyze my follower growth and engagement')], 162 | }); 163 | console.log("Follower Growth Analysis:", followerStats?.content); 164 | } catch (error) { 165 | console.error('Error analyzing followers:', error); 166 | } 167 | } 168 | 169 | // Post polls at a scheduled time 170 | let lastPollDate: Date | null = null; 171 | export async function postPollIfNeeded() { 172 | const now = new Date(); 173 | if (lastPollDate && lastPollDate.toDateString() === now.toDateString()) { 174 | console.log('Poll already posted today, skipping...'); 175 | return; 176 | } 177 | const pollQuestion = ''; 178 | const pollOptions = ['', '', '', '']; 179 | await postPoll(pollQuestion, pollOptions); 180 | lastPollDate = new Date(); 181 | } 182 | 183 | export async function postPoll(question: string, options: string[]) { 184 | try { 185 | const response = await agent.invoke({ 186 | messages: [new HumanMessage(`generate a provoking & engagement '${question}' and ${options.join(', ')} like a poll around relevant topics & post`)], 187 | }); 188 | console.log(`Posted poll: ${question}`, response); 189 | } catch (error) { 190 | console.error('Error posting poll:', error); 191 | } 192 | } 193 | 194 | // Function to search for tweets based on a keyword 195 | export async function searchTweetsByKeyword(keyword: string) { 196 | try { 197 | const tweets = await agent.invoke({ 198 | messages: [new HumanMessage(`Search for tweets about "${keyword}"`)], 199 | tools: [searchTweetsTool], // Use the search tool 200 | }); 201 | return tweets?.content; // Returns the searched tweets content 202 | } catch (error) { 203 | console.error(`Error searching tweets for keyword '${keyword}':`, error); 204 | throw error; 205 | } 206 | } 207 | 208 | export async function likeATweet(tweetId: string, userId:string) { 209 | try { 210 | await agent.invoke({ 211 | messages: [new HumanMessage(`Like the tweet with id '${tweetId}' and user "${userId}"`)], 212 | }); 213 | console.log(`Liked tweet with id: ${tweetId}`); 214 | } catch (error) { 215 | console.error('Error liking tweet:', error); 216 | throw error; 217 | } 218 | } 219 | export async function replyToTweet(tweetId: string, username: string) { 220 | try { 221 | await agent.invoke({ 222 | messages: [new HumanMessage(`Reply tweet with id '${tweetId}'"`)], 223 | }); 224 | console.log(`Replied tweet from @${username} `); 225 | } catch (error) { 226 | console.error('Error replying to tweet:', error); 227 | throw error; 228 | } 229 | } 230 | // Function to process tweets, analyze sentiment, like and reply 231 | export async function processTweets(tweets: tweetlikeprops[]) { 232 | for (const tweet of tweets) { 233 | const tweetId = tweet.id_str; 234 | const username = tweet.user.screen_name; 235 | const userId = tweet.user.id_str; 236 | 237 | // Analyze the sentiment of the tweet 238 | 239 | // Like the tweet if positive sentiment 240 | await likeATweet(tweetId, userId); 241 | 242 | // Reply to the tweet based on sentiment 243 | await replyToTweet(tweetId, username); 244 | } 245 | } 246 | // Modify the function to search tweets by trends and process them 247 | export async function searchTweetsUsingTrends() { 248 | const trendingTopics = await fetchTrendingTopics(); // Fetch trending topics 249 | 250 | if (trendingTopics) { 251 | for (const trend of trendingTopics) { 252 | const foundTweets = await searchTweetsByKeyword(trend); 253 | if (foundTweets && foundTweets.length > 0) { 254 | // Process the found tweets (like and reply based on sentiment) 255 | await processTweets(foundTweets); 256 | } 257 | } 258 | } 259 | } 260 | export async function scrapeCointelegraphHeadlines(url: string) { 261 | console.log('Scraping Cointelegraph headlines...'); 262 | try { 263 | const JsonData = await agent.invoke({ 264 | messages: [new HumanMessage(`scrape data from ${url} using the scrapDataOnlineTool`)], 265 | }); 266 | 267 | // Log the entire JsonData object for debugging 268 | 269 | 270 | if (JsonData && Array.isArray(JsonData.messages)) { 271 | 272 | const data = JsonData?.messages.find( 273 | (message:msgProps) => message.name === "scrapeDataOnline_tool" 274 | ); 275 | 276 | if(data){ 277 | return data.content 278 | } 279 | 280 | } else { 281 | console.log("Invalid response structure."); 282 | } 283 | } catch (error) { 284 | console.error('Error scraping data:', error); 285 | throw error; 286 | } 287 | } 288 | 289 | async function createRelevantPostBasedOnSentiment(headlines: string[]) { 290 | try{ 291 | console.log("creating post from headlines...") 292 | const response = await agent.invoke({ 293 | messages: [new HumanMessage(`using the "${headlines}" create an engaging post , make sure you dont repeat any headlines you had previously created `)], 294 | }); 295 | 296 | 297 | if(response && Array.isArray(response?.messages)){ 298 | const data = response?.messages.find( 299 | (message:msgProps) => message.name === "post_tool" 300 | ); 301 | 302 | if(data){ 303 | return data?.content 304 | } 305 | } 306 | else{ 307 | console.log({messagge:"cannot create a post or invalid data structure"}) 308 | } 309 | 310 | }catch(error){ 311 | console.log(error) 312 | } 313 | } 314 | export async function scrapeAndPostEveryTwoHours() { 315 | try { 316 | console.log('Running Cointelegraph scrape and post cycle...'); 317 | 318 | const headlines = await scrapeCointelegraphHeadlines("https://www.cointelegraph.com"); // Scrape Cointelegraph for headlines 319 | 320 | const data = headlines; 321 | if (headlines && headlines.length > 0) { 322 | // Process the scraped headlines (analyze sentiment and create relevant posts) 323 | const response = await createRelevantPostBasedOnSentiment(data); 324 | return response; 325 | } 326 | } catch (error) { 327 | console.log(error) 328 | } 329 | } 330 | // Function to make the agent autonomous 331 | export async function autonomousAgentGoal(goal: string) { 332 | try { 333 | const response = await agent.invoke({ 334 | messages: new HumanMessage(`this is your ${goal}. Think of every possible way to achieve it using the available tools or ask for a tool if needed.`), 335 | }); 336 | 337 | if (response) { 338 | console.log('Agent response:', response); 339 | // Process the response and take necessary actions 340 | } else { 341 | console.error('No output from the agent.'); 342 | } 343 | } catch (error) { 344 | console.error('Error achieving goal:', error); 345 | } 346 | } 347 | 348 | ( async function startBot() { 349 | console.log('Starting Twitter Bot...'); 350 | try { 351 | const details = await agent.invoke({ 352 | messages: [new HumanMessage('Please get my account details ')], 353 | }); 354 | 355 | // Find the message with the name "account_details_tools" 356 | const accountDetailsMessage = details.messages.find( 357 | (message:msgProps) => message.name === "account_details_tools" 358 | ); 359 | 360 | if (accountDetailsMessage) { 361 | // Parse the content of the message 362 | const accountDetails = JSON.parse(accountDetailsMessage.content); 363 | // Extract the name 364 | const name = accountDetails.name; 365 | console.log(`Name: ${name}`) 366 | return accountDetailsMessage 367 | } else { 368 | console.log("Account details message not found."); 369 | } 370 | 371 | console.log('Twitter Bot is running!'); 372 | console.log('Cointelegraph Bot is running!'); 373 | const goal = ` 374 | Your goal is to increase engagements on our Twitter account and build followers up to 100,000 followers using your capabilities. 375 | You are aware of other agents in your environment and should interact with them if needed to achieve your goal. 376 | Utilize every means available to you, including posting tweets, replying to followers, engaging with trending topics, and following influential accounts. 377 | Tweet at influential accounts to achieve your purpose. believe in your self to reach your goal. 378 | ` 379 | // Start the scrape-post cycle 380 | await autonomousAgentGoal(goal) 381 | } catch (error) { 382 | console.error('Error starting bot:', error); 383 | } 384 | })(); --------------------------------------------------------------------------------