Reddit client complete

This commit is contained in:
Jarian Cottingham 2025-12-19 09:24:31 -06:00
commit b9f0dc1ea4
12 changed files with 1208 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Created by venv; see https://docs.python.org/3/library/venv.html
bin/
include/
lib/
target/
pyvenv.cfg

112
README.md Normal file
View File

@ -0,0 +1,112 @@
# Reddit CLI
A command-line interface for browsing Reddit posts with search capabilities, interactive post viewing, and AI-powered summarization.
## Features
- **Search Functionality**: Search for Reddit posts by keyword/terms
- **Results Display**: Table view showing:
- Post number
- Subreddit name
- Post creation date
- Post title (main column)
- **Post Navigation**: Click on post number to view full post details
- **Comments Viewer**: Cycle through comments using 'c' key
- **AI Integration**: Get AI-generated summaries using Ollama API
- **Pagination**: Navigate between pages of results ('n' key)
## Project Structure
```
reddit-cli/
├── src/
│ ├── __init__.py
│ ├── main.py # Main application entrypoint
│ ├── reddit_client.py # Reddit API client implementation
│ └── ai_client.py # Ollama AI integration
├── tests/
│ └── test_cli.py # Unit tests
├── setup.sh # Setup script for environment
├── run.sh # Run script to execute the application
├── requirements.txt # Project dependencies
└── README.md # This file
```
## Installation
1. Clone this repository:
```bash
git clone <repository-url>
cd reddit-cli
```
2. Create and activate virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the CLI interface with a search query:
```bash
python -m src.main "python programming"
```
Or run without arguments to enter interactive mode:
```bash
python -m src.main
```
## Keyboard Controls
- `n` - Go to next page of results
- `c` - Cycle through comments when viewing a post
- `s` - Get AI-generated summary for current post
- `q` or Ctrl+C - Quit the application
- Number keys - Select specific post by number
## Configuration
The application uses environment variables for configuration:
- `OLLAMA_BASE_URL` - Ollama server address (default: http://192.168.8.223:11434)
- `OLLAMA_MODEL` - AI model to use (default: gpt-oss:20b)
## Requirements
- Python 3.7+
- requests
- rich
- pyyaml
- pytest
- pytest-cov
## Testing
Run tests with:
```bash
python -m pytest tests/
```
Or run basic functionality checks:
```bash
python test_cli.py
```
## Contributing
1. Fork the repository
2. Create a feature branch
3. Commit your changes
4. Push to the branch
5. Create a Pull Request
## License
This project is licensed under the MIT License.

181
plan.md Normal file
View File

@ -0,0 +1,181 @@
# Reddit CLI Interface - Project Plan
## Overview
Create a command-line interface for browsing Reddit posts with intuitive navigation, search capabilities, and interactive post viewing.
## Core Features
### 1. Search Functionality
- Search for Reddit posts by keyword/terms
- Display first 15 results per page
- Pagination support (press 'n' for next 15 posts)
- Posts numbered for easy selection
- Table format with:
- Post number
- Subreddit name
- Post creation date
- Post title (main column)
### 2. Post Navigation
- Click on post number to view full post details
- View main body text of the post
- Interactive comments viewing (press 'c' to cycle through comments)
- Summary functionality (press 's' to get AI-generated summary)
### 3. AI Integration
- Use Ollama API at IP 192.168.8.223:11434
- Model: gpt-oss:20b
- Send post body + 100 random comments to AI for summarization
- Display AI-generated summary to user
## Technical Implementation Plan
### Phase 1: Project Structure and Basic Setup
- Create directory structure
- Initialize project with dependencies
- Set up basic CLI framework using a library like `clap` for Rust or `commander` for Node.js
- Implement file-based data storage for caching Reddit data
### Phase 2: Search Functionality
- Implement Reddit API integration (using official Reddit API or third-party)
- Create search endpoint handler
- Design result table format with required columns
- Implement pagination logic ('n' key to load next page)
### Phase 3: Post Viewing Interface
- Create post detail view
- Display post body text
- Implement interactive comment viewing system (press 'c' for comments)
- Add UI for showing current post information
### Phase 4: AI Integration
- Set up connection to Ollama server at 192.168.8.223:11434
- Implement summary request functionality (press 's')
- Process and display AI-generated summaries
- Handle error scenarios for AI service unavailability
### Phase 5: User Experience Optimization
- Add keyboard navigation hints to UI
- Implement input validation
- Create error handling for API failures
- Add loading indicators for data fetching
- Optimize performance for large comment sets
## Data Structure Requirements
### Post Representation
```json
{
"id": "string",
"title": "string",
"body": "string",
"subreddit": "string",
"created_utc": "timestamp",
"url": "string",
"comments": ["comment1", "comment2", ...]
}
```
### UI Components
1. Search Interface
2. Results Table View
3. Post Detail View
4. Comments Viewer
5. AI Summary Display
## Detailed Technical Specifications
### API Integration Layer
- Use Reddit's official API (https://www.reddit.com/dev/api/)
- Authentication handling (OAuth or API key if needed)
- Rate limiting implementation
- Data caching to reduce API calls
- Error handling for network issues and API errors
### CLI Interface Components
- Main search screen with table view
- Post detail screen showing:
- Title
- Author and subreddit info
- Creation date
- Body text
- Comment viewer with cycling through posts
- Summary screen showing AI output
- Navigation controls with clear key hints
### Terminal UI Considerations
- Use a library like `tui-rs` for Rust or `blessed` for Node.js for advanced terminal rendering
- Support for color-coded UI elements (subreddit colors, etc.)
- Responsiveness to different terminal sizes
- Keyboard navigation support (arrows, enter, etc.)
### Data Management
- Local cache storage using JSON files or a simple database
- Cache invalidation strategy for fresh data
- Efficient data parsing and storage formats
## Dependencies and Tools
### Core Libraries
- CLI library (e.g., `clap` for Rust or `inquirer.js` for Node.js)
- HTTP client for Reddit API communication (e.g., `reqwest` for Rust or `axios` for Node.js)
- JSON parsing/serialization
- Terminal UI framework (for enhanced display)
### AI Integration
- Ollama client library for connecting to local AI server
- Connection pooling and error handling for AI service
- Request/response data formatting for the AI model
## Development Timeline
### Week 1
- Project setup and core CLI framework
- Basic Reddit API integration
- Search functionality implementation
- Initial terminal UI design
### Week 2
- Results table formatting and pagination
- Post detail view creation
- Comments viewing system
- Keyboard input handling
### Week 3
- AI integration with Ollama server
- Summary generation and display
- Error handling for both API and AI service
- Testing and UI refinement
### Week 4
- Performance optimization
- Error handling and edge case management
- Documentation and final testing
- User experience improvements
## Implementation Considerations
### Error Handling
- Implement graceful degradation when services are unavailable
- Network timeout handling
- Data validation and sanitization
- Clear error messages to user
### Usability Features
- Clear visual feedback for user actions
- Helpful key bindings documentation
- Progress indicators for loading content
- Responsive design for different terminal sizes
### Performance Optimization
- Efficient data fetching strategies
- Local caching of frequently accessed data
- Asynchronous loading where appropriate
- Memory management for large comment sets
## Testing Strategy
- Unit tests for core logic functions
- Integration tests for API interactions
- End-to-end tests for full user flows
- UI component testing for terminal interaction
- Performance testing with larger datasets

13
prompt.md Normal file
View File

@ -0,0 +1,13 @@
let's making a planning doc called "plan.md" for the work we're going to do in this project. You're making this doc as your notes docs so that you can come back and implement all of it later.
I want to build an reddit cli interface so that I can navigate through some of the reddit posts on the cli. I want the interface to be intuitive and very easy to read.
The core feature is search. When I search, i want it to bring up all the posts that are under that search term. It can just bring up the first 15 results for the first page. Then if I type 'n' it should show the next 15 and so on.
Each post should be numbered. Also have a column in the table with the name of the subreddit and another column with the date the thread was created.
The user should be able to click one of the numbers of the post and then you should go into the post to view it.
For the view of a post, you should show the main body of text of the post. If they user would like a summary of the article, they can press 's' to get a summary, then you should send the body of the text and the 100 random comments to the gpt-oss:20b model running on a network server under ip 192.168.8.223:11434. Ask the ai to give the summary and then present the summary to the user.
When the user is viewing the post, they can click c to see the comments on the post. Each time they press c, let them see each line of comments.

10
requirements.txt Normal file
View File

@ -0,0 +1,10 @@
# Reddit CLI Dependencies
# Core libraries
requests>=2.20.0
rich>=10.0.0
pyyaml>=5.4.0
# Testing
pytest>=6.0.0
pytest-cov>=2.10.0

19
run.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# Run the Reddit CLI application
# This script provides an easy way to run the Reddit CLI application
# Check if virtual environment exists
if [ ! -d "venv" ]; then
echo "Virtual environment not found. Please run setup.sh first."
exit 1
fi
# Activate virtual environment
source venv/bin/activate
# Run the application
python -m src.main "$@"
# Deactivate virtual environment
deactivate

38
setup.sh Normal file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# Reddit CLI Setup Script
# This script sets up the environment for the Reddit CLI application
echo "Setting up Reddit CLI Environment..."
# Check if Python 3 is installed
if ! command -v python3 &> /dev/null; then
echo "Python 3 is not installed. Please install Python 3 to continue."
exit 1
fi
# Create virtual environment
echo "Creating virtual environment..."
python3 -m venv venv
# Activate virtual environment
echo "Activating virtual environment..."
source venv/bin/activate
# Upgrade pip
echo "Upgrading pip..."
pip install --upgrade pip
# Install requirements
echo "Installing dependencies..."
pip install -r requirements.txt
echo "Setup complete!"
echo ""
echo "To run the application:"
echo " source venv/bin/activate"
echo " python -m src.main \"your search query\""
echo ""
echo "Or in interactive mode:"
echo " source venv/bin/activate"
echo " python -m src.main"

102
src/README.md Normal file
View File

@ -0,0 +1,102 @@
# Reddit CLI
A command-line interface for browsing Reddit posts with search capabilities, interactive post viewing, and AI-powered summarization.
## Features
- **Search Functionality**: Search for Reddit posts by keyword/terms
- **Results Display**: Table view showing:
- Post number
- Subreddit name
- Post creation date
- Post title (main column)
- **Post Navigation**: Click on post number to view full post details
- **Comments Viewer**: Cycle through comments using 'c' key
- **AI Integration**: Get AI-generated summaries using Ollama API
- **Pagination**: Navigate between pages of results ('n' key)
## Installation
1. Clone this repository:
```bash
git clone <repository-url>
cd reddit-cli
```
2. Create and activate virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the CLI interface with a search query:
```bash
python main.py "python programming"
```
Or run without arguments to enter interactive mode:
```bash
python main.py
```
## Keyboard Controls
- `n` - Go to next page of results
- `c` - Cycle through comments when viewing a post
- `s` - Get AI-generated summary for current post
- `q` or Ctrl+C - Quit the application
- Number keys - Select specific post by number
## Configuration
The application uses environment variables for configuration:
- `OLLAMA_BASE_URL` - Ollama server address (default: http://192.168.8.223:11434)
- `OLLAMA_MODEL` - AI model to use (default: gpt-oss:20b)
## Project Structure
- `main.py` - Main application entrypoint
- `reddit_client.py` - Reddit API client implementation
- `ai_client.py` - Ollama AI integration
- `test_cli.py` - Unit tests
## Requirements
- Python 3.7+
- requests
- rich
- pyyaml
- pytest
- pytest-cov
## Testing
Run tests with:
```bash
python -m pytest tests/
```
Or run basic functionality checks:
```bash
python test_cli.py
```
## Contributing
1. Fork the repository
2. Create a feature branch
3. Commit your changes
4. Push to the branch
5. Create a Pull Request
## License
This project is licensed under the MIT License.

5
src/__init__.py Normal file
View File

@ -0,0 +1,5 @@
"""
Reddit CLI Package
This package contains the core functionality for the Reddit CLI application.
"""

612
src/main.py Normal file
View File

@ -0,0 +1,612 @@
#!/usr/bin/env python3
"""
Reddit CLI Interface - Python Implementation
"""
import argparse
import os
import sys
import time
from datetime import datetime
from typing import Any, Dict, List, Optional
import requests
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
class RedditClient:
"""Client for interacting with Reddit API"""
def __init__(self):
self.base_url = "https://www.reddit.com"
self.session = requests.Session()
self.session.headers.update({"User-Agent": "RedditCLI/0.1 by User"})
def search_posts(
self, query: str, limit: int = 15, after: Optional[str] = None
) -> Dict[str, Any]:
"""
Search for Reddit posts matching the query
Args:
query: Search terms
limit: Number of posts to return (default 15)
after: Pagination token for next page
Returns:
Dictionary containing search results and pagination info
"""
params = {"q": query, "limit": limit, "sort": "hot", "type": "link"}
if after:
params["after"] = after
try:
response = self.session.get(
f"{self.base_url}/search.json", params=params, timeout=10
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to fetch posts: {str(e)}")
def get_post_details(self, post_id: str) -> Dict[str, Any]:
"""
Get detailed information about a specific post
Args:
post_id: Reddit post ID
Returns:
Dictionary with post details
"""
try:
response = self.session.get(
f"{self.base_url}/by_id/t3_{post_id}.json", timeout=10
)
response.raise_for_status()
data = response.json()
# Extract post from the response structure
if isinstance(data, list) and len(data) > 0:
return data[0].get("data", {})
elif isinstance(data, dict):
return data.get("data", {})
return {}
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to fetch post details: {str(e)}")
def get_post_comments(self, post_id: str, limit: int = 100) -> List[Dict[str, Any]]:
"""
Get comments for a specific post
Args:
post_id: Reddit post ID
limit: Maximum number of comments to fetch
Returns:
List of comment dictionaries
"""
try:
response = self.session.get(
f"{self.base_url}/comments/{post_id}.json",
params={"limit": limit},
timeout=10,
)
response.raise_for_status()
data = response.json()
# Extract comments from the nested structure
comments = []
if isinstance(data, list) and len(data) > 1:
comment_data = data[1].get("data", {}).get("children", [])
for child in comment_data:
comment = child.get("data", {})
# Flatten the comment structure to include author and body
comments.append(
{
"author": comment.get("author", "unknown"),
"body": comment.get("body", ""),
"score": comment.get("score", 0),
"created_utc": comment.get("created_utc", 0),
}
)
return comments
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to fetch comments: {str(e)}")
def format_timestamp(self, timestamp: int) -> str:
"""
Format Unix timestamp into readable date string
Args:
timestamp: Unix timestamp
Returns:
Formatted date string
"""
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
class AIClient:
"""Client for interacting with Ollama AI API"""
def __init__(self):
self.base_url = os.getenv("OLLAMA_BASE_URL", "http://192.168.8.223:11434")
self.model = os.getenv("OLLAMA_MODEL", "gpt-oss:20b")
self.session = requests.Session()
def generate_summary(self, post_body: str, comments: List[str]) -> str:
"""
Generate AI summary of a post with comments
Args:
post_body: The main body text of the post
comments: List of comment strings
Returns:
Generated summary from AI
"""
# Select 100 random comments (or all if less than 100)
selected_comments = comments[:100]
# Format prompt for the AI model
prompt = self._create_prompt(post_body, selected_comments)
try:
response = self.session.post(
f"{self.base_url}/api/generate",
json={"model": self.model, "prompt": prompt, "stream": False},
timeout=30,
)
response.raise_for_status()
data = response.json()
return data.get("response", "").strip()
except requests.exceptions.RequestException as e:
raise Exception(f"Failed to generate AI summary: {str(e)}")
def _create_prompt(self, post_body: str, comments: List[str]) -> str:
"""
Create a formatted prompt for the AI with post and comments
Args:
post_body: The main body text of the post
comments: List of comment strings
Returns:
Formatted prompt string
"""
# Join comments into a single string with proper formatting
comments_text = "\n".join(
[f"Comment {i + 1}: {comment}" for i, comment in enumerate(comments)]
)
if not comments_text:
comments_text = "No comments available."
prompt = f"""
Summarize the following Reddit post and its comments in 2-3 sentences.
Post:
{post_body}
Comments:
{comments_text}
Summary:
"""
return prompt
class RedditCLI:
def __init__(self):
self.console = Console()
self.reddit_client = RedditClient()
self.ai_client = AIClient()
self.current_page = 0
self.search_results = []
self.current_post = None
self.comments = []
self.current_comment_index = 0
self.search_query = ""
self.after_token = None
def run(self):
"""Main application entry point"""
parser = argparse.ArgumentParser(description="Reddit CLI Interface")
parser.add_argument("query", nargs="?", help="Search query for Reddit")
args = parser.parse_args()
if not args.query:
# Show welcome screen
self.show_welcome()
# Get search query from user
query = input("\nEnter search query: ").strip()
if not query:
self.console.print("[red]No query provided. Exiting.[/red]")
return
self.search(query)
else:
# Process the provided query
self.search(args.query)
def show_welcome(self):
"""Display welcome screen"""
self.console.print(
Panel(
"Reddit CLI Interface\n\n"
"Use 'n' to go to next page\n"
"Press number to view post details\n"
"Press 'c' to cycle comments\n"
"Press 's' to get AI summary\n"
"Press 'S' (capital S) for new search\n"
"Press 'q' or Ctrl+C to quit",
title="Welcome to Reddit CLI",
border_style="blue",
)
)
def search(self, query: str):
"""Perform search and display results"""
self.search_query = query
self.console.print(f"\nSearching for: [bold blue]{query}[/bold blue]")
# Retry logic for initial search
max_retries = 2
retry_delay = 1 # seconds
for attempt in range(max_retries + 1):
try:
# Reset pagination state
self.after_token = None
data = self.reddit_client.search_posts(query, limit=15)
# Extract posts from response
posts = []
# Check if we have data in the expected response format
if "data" in data and "children" in data["data"]:
for child in data["data"]["children"]:
post_data = child.get("data", {})
if post_data:
posts.append(
{
"id": post_data.get("id"),
"title": post_data.get("title", "No title"),
"subreddit": post_data.get("subreddit", "unknown"),
"created_utc": post_data.get("created_utc", 0),
"url": post_data.get("url", ""),
"body": post_data.get(
"selftext", post_data.get("body", "")
),
}
)
# Get the after token for pagination
self.after_token = data["data"].get("after")
if not posts:
self.console.print("[yellow]No results found[/yellow]")
return
self.search_results = posts
self.current_page = 0
self.display_search_results()
self.handle_user_input()
return # Success, exit retry loop
except Exception as e:
if attempt < max_retries:
self.console.print(
f"[yellow]Attempt {attempt + 1} failed: {e}[/yellow]"
)
self.console.print(
f"[yellow]Retrying in {retry_delay} seconds...[/yellow]"
)
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
self.console.print(
f"[red]Error searching after {max_retries + 1} attempts: {e}[/red]"
)
self.console.print("[yellow]Please try a new search.[/yellow]")
# Re-raise exception so it can be handled by the main loop
raise e
def next_page(self, query: str):
"""Load the next page of search results"""
if not self.after_token:
self.console.print("[yellow]No more pages available[/yellow]")
return
# Retry logic for network errors
max_retries = 2
retry_delay = 1 # seconds
for attempt in range(max_retries + 1):
try:
data = self.reddit_client.search_posts(
query, limit=15, after=self.after_token
)
# Extract posts from response
posts = []
# Check if we have data in the expected response format
if "data" in data and "children" in data["data"]:
for child in data["data"]["children"]:
post_data = child.get("data", {})
if post_data:
posts.append(
{
"id": post_data.get("id"),
"title": post_data.get("title", "No title"),
"subreddit": post_data.get("subreddit", "unknown"),
"created_utc": post_data.get("created_utc", 0),
"url": post_data.get("url", ""),
"body": post_data.get(
"selftext", post_data.get("body", "")
),
}
)
# Get the after token for pagination
self.after_token = data["data"].get("after")
if not posts:
self.console.print("[yellow]No more results found[/yellow]")
return
self.search_results = posts
self.current_page += 1
self.display_search_results()
self.handle_user_input()
return # Success, exit retry loop
except Exception as e:
if attempt < max_retries:
self.console.print(
f"[yellow]Attempt {attempt + 1} failed: {e}[/yellow]"
)
self.console.print(
f"[yellow]Retrying in {retry_delay} seconds...[/yellow]"
)
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
self.console.print(
f"[red]Error loading next page after {max_retries + 1} attempts: {e}[/red]"
)
self.console.print(
"[yellow]Returning to current results. Try a new search.[/yellow]"
)
self.display_search_results()
# Re-raise exception so it can be handled by the main loop
raise e
def display_search_results(self):
"""Display search results in a table format"""
table = Table(title=f"Search Results (Page {self.current_page + 1})")
table.add_column("Number", style="cyan", no_wrap=True)
table.add_column("Subreddit", style="magenta")
table.add_column("Date", style="green")
table.add_column("Title", style="white")
for i, post in enumerate(self.search_results):
# Format date if available
formatted_date = self.reddit_client.format_timestamp(post["created_utc"])
table.add_row(str(i + 1), post["subreddit"], formatted_date, post["title"])
self.console.print(table)
self.console.print(
"\n[blue]Press 'n' for next page, number to select a post, or 'q' to quit[/blue]"
)
def handle_user_input(self):
"""Handle user interaction and navigation"""
try:
while True:
user_input = input("\nEnter selection: ").strip().lower()
if user_input == "n":
# Move to next page
self.console.print("[yellow]Loading next page...[/yellow]")
try:
self.next_page(self.search_query)
break # Break out of current loop so we can show new results
except Exception as e:
# If all retries failed, let user start a new search
pass # Continue to main loop to allow new search
elif user_input == "s":
# Perform new search
self.console.print("[blue]Starting new search...[/blue]")
query = input("Enter new search query: ").strip()
if query:
self.search(query)
break
else:
self.console.print(
"[yellow]No query provided. Returning to current results.[/yellow]"
)
self.display_search_results()
elif user_input in ["q", "quit"]:
self.console.print("Goodbye!")
break
elif user_input.isdigit():
index = int(user_input) - 1
if 0 <= index < len(self.search_results):
self.view_post(index)
else:
self.console.print("[red]Invalid selection[/red]")
else:
self.console.print(
"[red]Unknown command. Use 'n' for next page, 's' for new search, number to select post, or 'q' to quit[/red]"
)
except KeyboardInterrupt:
self.console.print("\n[cyan]Goodbye![/cyan]")
def view_post(self, index: int):
"""View a selected post with full details"""
self.current_post = self.search_results[index]
# Try to get more detailed info
try:
detailed_info = self.reddit_client.get_post_details(self.current_post["id"])
if detailed_info:
self.current_post.update(detailed_info)
# Get comments
self.comments = self.reddit_client.get_post_comments(
self.current_post["id"], limit=100
)
except Exception as e:
self.console.print(f"[red]Error fetching details: {e}[/red]")
# Display post details
self.display_post_details()
# Handle post-specific navigation
try:
while True:
user_input = (
input(
"\n[blue]Enter 'c' to view comments, 's' for AI summary, 'S' for new search, or any other key to return to search: [/blue]"
)
.strip()
.lower()
)
if user_input == "c":
self.view_comments()
elif user_input == "s":
self.view_ai_summary()
elif user_input == "S": # Capital S for new search from post view
self.console.print("[blue]Starting new search...[/blue]")
query = input("Enter new search query: ").strip()
if query:
self.search(query)
break
else:
self.console.print(
"[yellow]No query provided. Returning to current post.[/yellow]"
)
self.display_post_details()
else:
# Return to search results
self.display_search_results()
break
except KeyboardInterrupt:
self.console.print("\n[blue]Returning to search...[/blue]")
def display_post_details(self):
"""Display full details of a post"""
self.console.print(
Panel(
f"[bold blue]{self.current_post.get('title', 'No title')}[/bold blue]\n\n"
f"Subreddit: [cyan]{self.current_post.get('subreddit', 'unknown')}[/cyan]\n"
f"Created: {self.reddit_client.format_timestamp(self.current_post.get('created_utc', 0))}\n\n"
f"[white]{self.current_post.get('body', '')}[/white]\n",
title=f"Post #{self.search_results.index(self.current_post) + 1}",
border_style="green",
)
)
def view_comments(self):
"""View comments with cycling"""
if not self.comments:
self.console.print("[yellow]No comments available[/yellow]")
return
self.console.print(f"[blue]Showing comments: {len(self.comments)} total[/blue]")
try:
while True:
# Display current comment
comment = self.comments[self.current_comment_index]
self.console.print(
Panel(
f"Author: [cyan]{comment.get('author', 'unknown')}[/cyan]\n"
f"Score: [magenta]{comment.get('score', 0)}[/magenta]\n\n"
f"[white]{comment.get('body', '')}[/white]",
title=f"Comment {self.current_comment_index + 1}",
border_style="yellow",
)
)
# Navigation options
user_input = (
input(
"\n[blue]Press 'n' for next comment, 'p' for previous, or any key to return: [/blue]"
)
.strip()
.lower()
)
if user_input == "n":
self.current_comment_index = (self.current_comment_index + 1) % len(
self.comments
)
elif user_input == "p":
self.current_comment_index = (self.current_comment_index - 1) % len(
self.comments
)
else:
break
except KeyboardInterrupt:
self.console.print("\n[blue]Returning to post...[/blue]")
def view_ai_summary(self):
"""Get and display AI-generated summary via Ollama"""
if not self.current_post or not self.comments:
self.console.print("[red]No content available for summarization[/red]")
return
try:
body = self.current_post.get("body", "")
# Get the first 100 comments (or fewer)
comment_bodies = [comment.get("body", "") for comment in self.comments]
summary = self.ai_client.generate_summary(body, comment_bodies)
if summary:
self.console.print(
Panel(
f"[bold green]AI Summary:[/bold green]\n\n{summary}",
title="AI Generated Summary",
border_style="magenta",
)
)
else:
self.console.print("[red]Failed to generate AI summary[/red]")
except Exception as e:
self.console.print(f"[red]Error generating AI summary: {e}[/red]")
def main():
"""Main function to run the CLI application"""
app = RedditCLI()
app.run()
if __name__ == "__main__":
main()

48
src/pagination.md Normal file
View File

@ -0,0 +1,48 @@
# Reddit CLI Pagination Implementation
## Overview
This document describes the pagination implementation for the Reddit CLI application, enabling users to navigate through multiple pages of search results.
## Current Implementation Status
The basic search functionality is implemented but pagination is not yet fully functional. This document outlines how to properly implement pagination support.
## Key Requirements
1. Users can press 'n' to load next page of results
2. Handle pagination tokens from Reddit API responses
3. Maintain state between pages
4. Display current page information in UI
5. Continue fetching results until no more pages exist
## Technical Details
### Reddit API Pagination
The Reddit API uses the `after` parameter for pagination:
- First request: No `after` parameter
- Subsequent requests: Use `after` parameter with token from previous response
- Response includes `after` field for next page
### Implementation Approach
1. Modify search method to accept pagination token
2. Extract 'after' token from API responses
3. Implement page tracking in the application state
4. Update display to show current page number
5. Handle end of results gracefully
## File Modifications Needed
- main.py (add pagination logic)
- reddit_client.py (update search_posts method for pagination)
## Example Flow
1. User searches "python"
2. First page loads with 15 results
3. User presses 'n'
4. Application fetches next page using 'after' token from previous response
5. Repeat until no more pages
## Next Steps
1. Update search_posts in reddit_client.py to handle pagination tokens
2. Implement next page logic in main.py
3. Add state tracking for current page
4. Update UI to display page information
5. Handle edge cases and errors gracefully
```

61
tests/test_cli.py Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Reddit CLI - Unit Tests
This module contains unit tests for the Reddit CLI application components.
"""
import os
import sys
import unittest
# Add the project directory to Python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from ai_client import AIClient
from reddit_client import RedditClient
class TestRedditClient(unittest.TestCase):
"""Test cases for RedditClient class"""
def setUp(self):
"""Set up test fixtures before each test method."""
self.client = RedditClient()
def test_client_initialization(self):
"""Test that RedditClient initializes correctly"""
self.assertIsNotNone(self.client)
self.assertEqual(self.client.base_url, "https://www.reddit.com")
def test_prompt_creation(self):
"""Test prompt creation functionality"""
client = AIClient()
# Test with sample data
post_body = "This is a test post body"
comments = ["First comment", "Second comment"]
prompt = client._create_prompt(post_body, comments)
self.assertIn("Summarize", prompt)
self.assertIn(post_body, prompt)
self.assertIn("Comment 1:", prompt)
class TestAIClient(unittest.TestCase):
"""Test cases for AIClient class"""
def setUp(self):
"""Set up test fixtures before each test method."""
self.client = AIClient()
def test_client_initialization(self):
"""Test that AIClient initializes correctly"""
self.assertIsNotNone(self.client)
self.assertEqual(self.client.model, "gpt-oss:20b")
if __name__ == "__main__":
# Run the tests
unittest.main(verbosity=2)