├── 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 |
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 | })();
--------------------------------------------------------------------------------