diff --git a/linkedin_post_automater/LinkedIn_Post_Automater.png b/linkedin_post_automater/LinkedIn_Post_Automater.png new file mode 100644 index 000000000..8c96382ec Binary files /dev/null and b/linkedin_post_automater/LinkedIn_Post_Automater.png differ diff --git a/linkedin_post_automater/README.md b/linkedin_post_automater/README.md new file mode 100644 index 000000000..2079f1f57 --- /dev/null +++ b/linkedin_post_automater/README.md @@ -0,0 +1,125 @@ +# πŸ’Ό LinkedinPostAutomater Crew + +![LinkedIn_Post_Automater.png](LinkedIn_Post_Automater.png) +Welcome to **LinkedIn Post Automater** β€” an intelligent, multi-agent system powered by [crewAI](https://crewai.com) that transforms how professionals create and publish content on LinkedIn. + +Whether you're building your personal brand or managing corporate communication, this system automates the heavy lifting: from sourcing trending news to crafting and posting impactful updates β€” all done intelligently and consistently. + +--- + +## πŸš€ What It Does + +πŸ”Ή **Real-time News Discovery** +Finds and summarizes the latest developments around a selected topic. + +πŸ”Ή **Content Planning & Research** +Structures content with a clear outline, key points, and supporting facts. + +πŸ”Ή **Post Writing & Publishing** +Generates professional, LinkedIn-optimized content and automatically publishes it. + +πŸ”Ή **Visual Enhancement** +Creates an engaging visual using AI to enhance post visibility and appeal. + +--- + +## 🌟 Why Use LinkedIn Post Automater? + +- 🧠 Stay Relevant: Tap into current news without endless research. +- ✍️ Post Smarter: Get structured, well-written content every time. +- πŸ–ΌοΈ Stand Out: Add AI-generated visuals to attract attention in feeds. +- ⏳ Save Time: Automate the whole process β€” from ideation to publishing. + +--- + +## πŸ€– Behind the Scenes + +This project uses a **multi-agent architecture**, where each AI agent has a specific role: + +| Agent | Purpose | +|-------------------------------|-----------------------------------------| +| News_Article_Researcher | Gathers trending news and insights | +| Planner_and_Researcher | Crafts a content plan with structure | +| Article_Maker_and_LinkedIn_Poster | Writes and posts the article on LinkedIn | + +Agents collaborate using crewAI’s intelligent workflow orchestration β€” ensuring efficient execution, quality output, and full automation. + +--- +## Installation + +Ensure you have Python >=3.10 <3.14 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience. + +First, if you haven't already, install uv: + +```bash +pip install uv +``` + +Next, navigate to your project directory and install the dependencies: + +(Optional) Lock the dependencies and install them by using the CLI command: +```bash +crewai install +``` +### Customizing + +**Add your `Linkedin_access_token` , `GEMINI_API_KEY` , `RAPIDAPI_KEY` , `MODEL` into the `.env` file** + +- Modify `src/linkedin_post_automater/config/agents.yaml` to define your agents +- Modify `src/linkedin_post_automater/config/tasks.yaml` to define your tasks +- Modify `src/linkedin_post_automater/crew.py` to add your own logic, tools and specific args +- Modify `src/linkedin_post_automater/main.py` to add custom inputs for your agents and tasks + +## Running the Project + +To kickstart your crew of AI agents and begin task execution, run this from the root folder of your project: + +```bash +$ crewai run +``` + +This command initializes the linkedin_post_automater Crew, assembling the agents and assigning them tasks as defined in your configuration. + +This example, unmodified, will run the create a `report.md` file with the output of a research on LLMs in the root folder. + +## Understanding Your Crew + +The linkedin_post_automater Crew is composed of multiple AI agents, each with unique roles, goals, and tools. These agents collaborate on a series of tasks, defined in `config/tasks.yaml`, leveraging their collective skills to achieve complex objectives. The `config/agents.yaml` file outlines the capabilities and configurations of each agent in your crew. + + +## πŸ‘₯ Who Is This For? + +- πŸ”Ή Founders & Entrepreneurs +- πŸ”Ή Personal Brand Builders +- πŸ”Ή B2B Marketers +- πŸ”Ή Content Teams +- πŸ”Ή AI & Automation Enthusiasts + +--- + +## πŸ“Œ Project Outcomes + +When you run the system: +- A professional article is saved as `report.md` +- Supporting news is listed in `news.md` +- A post image is created with Gemini AI +- The final post is shared on your LinkedIn account + +All of this β€” with **zero manual effort**. + +--- + +## πŸ’¬ Need Help? + +We're here to support your journey: + +- Explore the [crewAI documentation](https://docs.crewai.com) +- Contribute on [GitHub](https://github.com/joaomdmoura/crewai) +- Connect on [Discord](https://discord.com/invite/X4JWnZnxPb) +- [Chat with the docs](https://chatg.pt/DWjSBZn) + +--- + +> β€œLet your AI team manage your LinkedIn while you focus on leading.” +β€” *LinkedinPostAutomater Crew* + diff --git a/linkedin_post_automater/pyproject.toml b/linkedin_post_automater/pyproject.toml new file mode 100644 index 000000000..83ebbcbf8 --- /dev/null +++ b/linkedin_post_automater/pyproject.toml @@ -0,0 +1,24 @@ +[project] +name = "linkedin_post_automater" +version = "0.1.0" +description = "linkedin_post_automater using crewAI" +authors = [{ name = "Your Name", email = "you@example.com" }] +requires-python = ">=3.10,<3.14" +dependencies = [ + "crewai[tools]>=0.126.0,<1.0.0", + "google-genai>=1.19.0", +] + +[project.scripts] +linkedin_post_automater = "linkedin_post_automater.main:run" +run_crew = "linkedin_post_automater.main:run" +train = "linkedin_post_automater.main:train" +replay = "linkedin_post_automater.main:replay" +test = "linkedin_post_automater.main:test" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.crewai] +type = "crew" diff --git a/linkedin_post_automater/src/linkedin_post_automater/__init__.py b/linkedin_post_automater/src/linkedin_post_automater/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/linkedin_post_automater/src/linkedin_post_automater/__pycache__/__init__.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..6ce263433 Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/__pycache__/__init__.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/__pycache__/crew.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/__pycache__/crew.cpython-311.pyc new file mode 100644 index 000000000..7a8a33768 Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/__pycache__/crew.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/__pycache__/main.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/__pycache__/main.cpython-311.pyc new file mode 100644 index 000000000..e10392d4c Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/__pycache__/main.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/config/agents.yaml b/linkedin_post_automater/src/linkedin_post_automater/config/agents.yaml new file mode 100644 index 000000000..9ebdde01b --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/config/agents.yaml @@ -0,0 +1,22 @@ +News_Article_Researcher: + role: > + Current Affairs Analyst and News Article Writer + goal: > + Create concise and informative news on the latest developments in a given topic { topic }. The goal is to provide up-to-date, accurate, and engaging news content that informs readers about recent events and trends. + backstory: > + You are a seasoned journalist with a background in reporting on current affairs and trending topics. Your experience in newsrooms and digital media has honed your ability to quickly gather and synthesize information from reliable sources. You have a keen eye for detail and a commitment to accuracy, ensuring that your articles are both informative and trustworthy. Your writing style is clear and concise, making complex topics accessible to a wide audience. + +Planner_and_Researcher: + role: > + Strategic Content Planner and Research Specialist + goal: > + Develop comprehensive content plans and conduct thorough research to ensure high-quality, relevant, and engaging content. The goal is to create well-researched outlines and detailed briefs that guide the content creation process effectively on given news . + backstory: > + You are a seasoned content strategist with over a decade of experience in planning and researching content for various industries. Your background in journalism and market research has honed your ability to identify trending topics and gather relevant information efficiently. You have a knack for organizing information in a structured manner, making it easy for content creators to follow your outlines and briefs. Your meticulous attention to detail and commitment to accuracy ensure that the content you plan is both informative and engaging. +Article_Maker_and_LinkedIn_Poster: + role: > + Professional Article Writer and LinkedIn Content Specialist + goal: > + Create well-written, engaging articles based on the provided content briefs and publish them on LinkedIn. The goal is to produce high-quality content that resonates with the target audience and drives engagement on the platform. + backstory: > + You are an experienced content writer with a strong background in creating articles and social media posts. Your expertise in digital marketing and social media management has given you a deep understanding of what makes content engaging and shareable. You have a talent for crafting compelling narratives and presenting information in a way that captures the reader's attention. Your proficiency in using LinkedIn's publishing tools ensures that your articles are not only well-written but also optimized for maximum reach and engagement. \ No newline at end of file diff --git a/linkedin_post_automater/src/linkedin_post_automater/config/tasks.yaml b/linkedin_post_automater/src/linkedin_post_automater/config/tasks.yaml new file mode 100644 index 000000000..2d6222a87 --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/config/tasks.yaml @@ -0,0 +1,36 @@ +News_Article_Researcher_Task: + description: > + Research the latest news on the given topic and create a simple, informative article. Ensure that the article is up-to-date, accurate, and engaging. + expected_output: > + a list of 5 main news and its main content + agent: News_Article_Researcher + +Planner_and_Researcher_Task: + description: > + CConduct thorough research on the given topic {topic} and develop a comprehensive content plan. Ensure that the plan includes key points, relevant statistics, and a structured outline for the article. and also create a appropriate image for the article + expected_output: > + A detailed content brief in format with the following sections: + + Title: Suggested title for the article. + Introduction: Brief overview of the topic. + Key Points: List of main points to be covered. + Relevant Statistics: Important data and statistics. + Outline: Structured outline for the article. + References: List of sources and references. all characters including must be less that 3000 characters + + + agent: Planner_and_Researcher + +Article_Maker_and_LinkedIn_Poster_Task: + description: > + Write a detailed and engaging article based on the provided content brief. Ensure that the article is well-structured, informative, and tailored to the LinkedIn audience. Once the article is complete, publish it on LinkedIn. + expected_output: > + A formatted article with the following sections: + + Title: Engaging title for the article. + Introduction: Captivating introduction to draw the reader in. + Main Body: Detailed sections covering the key points and relevant statistics. + Conclusion: Summary and call to action. + References: List of sources and references. + Additionally, publish the article on LinkedIn and provide the post URL. all charecters including must be less that 3000 charecters + agent: Article_Maker_and_LinkedIn_Poster diff --git a/linkedin_post_automater/src/linkedin_post_automater/crew.py b/linkedin_post_automater/src/linkedin_post_automater/crew.py new file mode 100644 index 000000000..8092b1814 --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/crew.py @@ -0,0 +1,86 @@ +from crewai import Agent, Crew, Process, Task +from crewai.project import CrewBase, agent, crew, task +from crewai.agents.agent_builder.base_agent import BaseAgent +from linkedin_post_automater.tools.linkedin_post_tool import LinkedInImagePostTool +from linkedin_post_automater.tools.news_tool import RealTimeNewsSearchTool +from linkedin_post_automater.tools.image_genaration_tool import GeminiImageGenerator +from typing import List +# If you want to run a snippet of code before or after the crew starts, +# you can use the @before_kickoff and @after_kickoff decorators +# https://docs.crewai.com/concepts/crews#example-crew-class-with-decorators + +@CrewBase +class LinkedinPostAutomater(): + """LinkedinPostAutomater crew""" + + agents: List[BaseAgent] + tasks: List[Task] + + # Learn more about YAML configuration files here: + # Agents: https://docs.crewai.com/concepts/agents#yaml-configuration-recommended + # Tasks: https://docs.crewai.com/concepts/tasks#yaml-configuration-recommended + + # If you would like to add tools to your agents, you can learn more about it here: + # https://docs.crewai.com/concepts/agents#agent-tools + + @agent + def News_Article_Researcher(self) -> Agent: + return Agent( + config=self.agents_config['News_Article_Researcher'], # type: ignore[index] + verbose=True , + tools = [RealTimeNewsSearchTool()] + ) + + + @agent + def Planner_and_Researcher(self) -> Agent: + return Agent( + config=self.agents_config['Planner_and_Researcher'], # type: ignore[index] + verbose=True , + tools = [GeminiImageGenerator()] + ) + + @agent + def Article_Maker_and_LinkedIn_Poster(self) -> Agent: + return Agent( + config=self.agents_config['Article_Maker_and_LinkedIn_Poster'], # type: ignore[index] + verbose=True, + tools = [LinkedInImagePostTool()] + ) + + # To learn more about structured task outputs, + # task dependencies, and task callbacks, check out the documentation: + # https://docs.crewai.com/concepts/tasks#overview-of-a-task + @task + def News_Article_Researcher_Task(self) -> Task: + return Task( + config=self.tasks_config['News_Article_Researcher_Task'], # type: ignore[index] + output_file = "news.md" + ) + + @task + def Planner_and_Researcher_Task(self) -> Task: + return Task( + config=self.tasks_config['Planner_and_Researcher_Task'], # type: ignore[index] + ) + + @task + def Article_Maker_and_LinkedIn_Poster_Task(self) -> Task: + return Task( + config=self.tasks_config['Article_Maker_and_LinkedIn_Poster_Task'], # type: ignore[index] + output_file='report.md' + ) + + @crew + def crew(self) -> Crew: + """Creates the LinkedinPostAutomater crew""" + # To learn how to add knowledge sources to your crew, check out the documentation: + # https://docs.crewai.com/concepts/knowledge#what-is-knowledge + + return Crew( + agents=self.agents, # Automatically created by the @agent decorator + tasks=self.tasks, # Automatically created by the @task decorator + process=Process.sequential, + verbose=True, + # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/ + ) diff --git a/linkedin_post_automater/src/linkedin_post_automater/main.py b/linkedin_post_automater/src/linkedin_post_automater/main.py new file mode 100644 index 000000000..c912923c1 --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/main.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +import sys +import warnings + +from datetime import datetime + +from linkedin_post_automater.crew import LinkedinPostAutomater + +warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd") + +# This main file is intended to be a way for you to run your +# crew locally, so refrain from adding unnecessary logic into this file. +# Replace with inputs you want to test with, it will automatically +# interpolate any tasks and agents information + +def run(): + """ + Run the crew. + """ + inputs = { + 'topic': 'AI', + } + + try: + LinkedinPostAutomater().crew().kickoff(inputs=inputs) + except Exception as e: + raise Exception(f"An error occurred while running the crew: {e}") + + +def train(): + """ + Train the crew for a given number of iterations. + """ + inputs = { + "topic": "AI LLMs", + 'current_year': str(datetime.now().year) + } + try: + LinkedinPostAutomater().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs) + + except Exception as e: + raise Exception(f"An error occurred while training the crew: {e}") + +def replay(): + """ + Replay the crew execution from a specific task. + """ + try: + LinkedinPostAutomater().crew().replay(task_id=sys.argv[1]) + + except Exception as e: + raise Exception(f"An error occurred while replaying the crew: {e}") + +def test(): + """ + Test the crew execution and returns the results. + """ + inputs = { + "topic": "AI LLMs", + "current_year": str(datetime.now().year) + } + + try: + LinkedinPostAutomater().crew().test(n_iterations=int(sys.argv[1]), eval_llm=sys.argv[2], inputs=inputs) + + except Exception as e: + raise Exception(f"An error occurred while testing the crew: {e}") diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/__init__.py b/linkedin_post_automater/src/linkedin_post_automater/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/__init__.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 000000000..0060eada8 Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/image_genaration_tool.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/image_genaration_tool.cpython-311.pyc new file mode 100644 index 000000000..4be334215 Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/image_genaration_tool.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/linkedin_post_tool.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/linkedin_post_tool.cpython-311.pyc new file mode 100644 index 000000000..789cdc0b2 Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/linkedin_post_tool.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/news_tool.cpython-311.pyc b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/news_tool.cpython-311.pyc new file mode 100644 index 000000000..804d7c70f Binary files /dev/null and b/linkedin_post_automater/src/linkedin_post_automater/tools/__pycache__/news_tool.cpython-311.pyc differ diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/image_genaration_tool.py b/linkedin_post_automater/src/linkedin_post_automater/tools/image_genaration_tool.py new file mode 100644 index 000000000..a16a0208d --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/tools/image_genaration_tool.py @@ -0,0 +1,56 @@ +import os + +from crewai.tools import BaseTool +from pydantic import BaseModel, Field +from typing import Type, Optional +from google import genai +from google.genai import types +from PIL import Image +from io import BytesIO +import base64 + +class GeminiImageGenerationInput(BaseModel): + """Input schema for Gemini Image Generation Tool.""" + prompt: str = Field(..., description="Detailed text description of the image to generate") + +class GeminiImageGenerator(BaseTool): + name: str = "Gemini Image Generator" + description: str = "A tool that generates images from text prompts using Google's Gemini AI models. The tool can create various types of images based on detailed text descriptions." + + args_schema: Type[BaseModel] = GeminiImageGenerationInput + + + + + def _run(self, prompt: str, model: str = "gemini-2.0-flash-preview-image-generation", + save_path: str = "image.png") -> str: + """Execute the Gemini image generation tool.""" + try: + client = genai.Client( + api_key = os.getenv("GEMINI_API_KEY") + ) + # Generate content using Gemini + response = client.models.generate_content( + model=model, + contents=prompt, + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'] + ) + ) + + results = [] + + # Process the response + for part in response.candidates[0].content.parts: + if part.text is not None: + results.append(f"Generated text description: {part.text}") + elif part.inline_data is not None: + # Save the generated image + image = Image.open(BytesIO(part.inline_data.data)) + image.save(save_path) + results.append(f"Image successfully generated and saved to {save_path}") + + return "\n".join(results) if results else "No content generated" + + except Exception as e: + return f"An error occurred during image generation: {str(e)}" diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/linkedin_post_tool.py b/linkedin_post_automater/src/linkedin_post_automater/tools/linkedin_post_tool.py new file mode 100644 index 000000000..2367738e2 --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/tools/linkedin_post_tool.py @@ -0,0 +1,102 @@ +from crewai.tools import BaseTool +from pydantic import BaseModel, Field +from typing import Type +import requests +import os + +class LinkedInImagePostInput(BaseModel): + """Input schema for LinkedIn Image Posting Tool.""" + message: str = Field(..., description="Text message to post with the image") + +class LinkedInImagePostTool(BaseTool): + name: str = "LinkedIn Image Poster" + description: str = "A tool that posts images with text to LinkedIn. It handles the entire process including image upload and post creation." + + args_schema: Type[BaseModel] = LinkedInImagePostInput + + def _run(self,message: str, visibility: str = "PUBLIC") -> str: + """Execute the LinkedIn image posting tool.""" + try: + access_token = os.getenv("Linkedin_access_token") + image_path = "./image.png" + # Check if image file exists + if not os.path.exists(image_path): + return f"Error: Image file not found at path: {image_path}" + + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + "X-Restli-Protocol-Version": "2.0.0" + } + + # Step 1: Get user info + userinfo_url = "https://api.linkedin.com/v2/userinfo" + user_response = requests.get(userinfo_url, headers={"Authorization": f"Bearer {access_token}"}) + + if user_response.status_code != 200: + return f"Failed to get user info: HTTP {user_response.status_code}" + + user_info = user_response.json() + person_urn = f"urn:li:person:{user_info['sub']}" + + # Step 2: Register image upload + register_upload_data = { + "registerUploadRequest": { + "recipes": ["urn:li:digitalmediaRecipe:feedshare-image"], + "owner": person_urn, + "serviceRelationships": [{ + "relationshipType": "OWNER", + "identifier": "urn:li:userGeneratedContent" + }] + } + } + + register_url = "https://api.linkedin.com/v2/assets?action=registerUpload" + register_response = requests.post(register_url, headers=headers, json=register_upload_data) + + if register_response.status_code != 200: + return f"Failed to register upload: HTTP {register_response.status_code} - {register_response.text}" + + register_result = register_response.json() + upload_url = register_result['value']['uploadMechanism']['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest']['uploadUrl'] + asset_id = register_result['value']['asset'] + + # Step 3: Upload the image + with open(image_path, 'rb') as image_file: + files = {'file': image_file} + upload_response = requests.post(upload_url, files=files) + + if upload_response.status_code not in [200, 201]: + return f"Failed to upload image: HTTP {upload_response.status_code} - {upload_response.text}" + + # Step 4: Create the post + post_data = { + "author": person_urn, + "lifecycleState": "PUBLISHED", + "specificContent": { + "com.linkedin.ugc.ShareContent": { + "shareCommentary": {"text": message}, + "shareMediaCategory": "IMAGE", + "media": [{ + "status": "READY", + "description": {"text": "Shared image"}, + "media": asset_id, + "title": {"text": "Image"} + }] + } + }, + "visibility": { + "com.linkedin.ugc.MemberNetworkVisibility": visibility + } + } + + post_url = "https://api.linkedin.com/v2/ugcPosts" + post_response = requests.post(post_url, headers=headers, json=post_data) + + if post_response.status_code == 201: + return f"Success! Image posted to LinkedIn. Post ID: {post_response.json().get('id')}" + else: + return f"Failed to create post: HTTP {post_response.status_code} - {post_response.text}" + + except Exception as e: + return f"An error occurred while posting to LinkedIn: {str(e)}" diff --git a/linkedin_post_automater/src/linkedin_post_automater/tools/news_tool.py b/linkedin_post_automater/src/linkedin_post_automater/tools/news_tool.py new file mode 100644 index 000000000..3ba00f864 --- /dev/null +++ b/linkedin_post_automater/src/linkedin_post_automater/tools/news_tool.py @@ -0,0 +1,43 @@ +import os +from crewai.tools import BaseTool +from pydantic import BaseModel, Field +from typing import Type, Optional +import requests + +class NewsSearchInput(BaseModel): + """Input schema for Real Time News Search Tool.""" + query: str = Field(..., description="Search query for news articles") + +class RealTimeNewsSearchTool(BaseTool): + name: str = "Real Time News Search" + description: str = "A tool that searches for real-time news articles based on various filters like query, time published, country, and language." + + args_schema: Type[BaseModel] = NewsSearchInput + + def _run(self, query: str) -> str: + """Execute the real-time news search tool.""" + try: + url = "https://real-time-news-data.p.rapidapi.com/search" + + querystring = { + "query": query, + "limit": str(10), + "time_published": "1d", + "country": "US", + "lang": "en" + } + + headers = { + "x-rapidapi-key": os.getenv("RAPIDAPI_KEY"), + "x-rapidapi-host": "real-time-news-data.p.rapidapi.com" + } + + response = requests.get(url, headers=headers, params=querystring) + + if response.status_code == 200: + return str(response.json()) + else: + return f"Error fetching news: HTTP {response.status_code} - {response.text}" + + except Exception as e: + return f"An error occurred while fetching news: {str(e)}"