File size: 7,068 Bytes
0c1aa19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
from typing import Any
import os
import httpx

# Constants
RAINDROP_API_BASE = "https://api.raindrop.io/rest/v1"
RAINDROP_TOKEN = os.environ.get("RAINDROP_TOKEN")

async def make_raindrop_request(url: str, token: str, method: str = "GET", data: dict = None) -> dict[str, Any] | None:
    """Make a request to the Raindrop.io API with proper error handling."""
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    async with httpx.AsyncClient() as client:
        try:
            if method.upper() == "GET":
                response = await client.get(url, headers=headers, timeout=30.0)
            elif method.upper() == "POST":
                response = await client.post(url, headers=headers, json=data, timeout=30.0)
            else:
                print(f"Unsupported method: {method}")
                return None
                
            response.raise_for_status()
            return response.json()
        except Exception as e:
            print(f"Error making Raindrop request: {e}")
            return None



def format_bookmark(item: dict) -> str:
    """Format a Raindrop bookmark into a readable string."""
    return f"""
Title: {item.get('title', 'Untitled')}
URL: {item.get('link', 'No URL')}
Tags: {', '.join(item.get('tags', [])) or 'No tags'}
Created: {item.get('created', 'Unknown date')}
Description: {item.get('excerpt', 'No description available')}
"""


async def get_latest_feed(count: int = 10) -> str:
    """Get latest bookmarks from Raindrop.io feed.

    Args:
        count: Number of bookmarks to fetch (default: 10)
    """
    url = f"{RAINDROP_API_BASE}/raindrops/0?perpage={count}&sort=-created"
    data = await make_raindrop_request(url, RAINDROP_TOKEN)

    if not data or "items" not in data:
        return "Unable to fetch bookmarks or no bookmarks found."

    if not data["items"]:
        return "No bookmarks found in your collection."

    bookmarks = [format_bookmark(item) for item in data["items"]]
    return "\n---\n".join(bookmarks)


async def add_bookmark(url: str, title: str = "", description: str = "", tags: str = "", collection_id: int = 0) -> str:
    """Add a new bookmark to Raindrop.io
    
    Args:
        url: The URL to bookmark (required)
        title: Title for the bookmark (optional, will be extracted from URL if not provided)
        description: Description/excerpt for the bookmark (optional)
        tags: Comma-separated tags to apply to the bookmark (optional)
        collection_id: ID of the collection to add the bookmark to (default: 0 for Unsorted)
    """
    if not url:
        return "Error: URL is required"
        
    raindrop_data = {
        "link": url,
        "collection": {"$id": collection_id}
    }
    
    if title:
        raindrop_data["title"] = title
    if description:
        raindrop_data["excerpt"] = description
    if tags:
        # Convert comma-separated string to list
        tag_list = [tag.strip() for tag in tags.split(",") if tag.strip()]
        raindrop_data["tags"] = tag_list
    
    endpoint = f"{RAINDROP_API_BASE}/raindrop"
    response = await make_raindrop_request(endpoint, RAINDROP_TOKEN, method="POST", data=raindrop_data)
    
    if not response or "item" not in response:
        return "Failed to add bookmark. Please check the URL and try again."
    
    return f"Bookmark successfully added:\n{format_bookmark(response['item'])}"


async def search_by_tag(tag: str, collection_id: int = 0, count: int = 10, from_date: str = "", to_date: str = "") -> str:
    """Search for bookmarks with a specific tag in Raindrop.io with optional date range filtering
    
    Args:
        tag: The tag to search for (required)
        collection_id: ID of the collection to search in (default: 0 for all collections)
        count: Maximum number of bookmarks to return (default: 10)
        from_date: Start date in YYYY-MM-DD format (optional)
        to_date: End date in YYYY-MM-DD format (optional)
    """
    if not tag:
        return "Error: Tag is required"
    
    # Build search filters
    search_filters = [f"-tags:\"{tag}\""]
    
    # Add date range filters if provided
    if from_date and from_date.strip():
        search_filters.append(f"created>={from_date.strip()}")
    if to_date and to_date.strip():
        search_filters.append(f"created<={to_date.strip()}")
    
    # Combine all search filters
    search_query = " ".join(search_filters)
    
    url = f"{RAINDROP_API_BASE}/raindrops/{collection_id}?perpage={count}&search={search_query}&sort=-created"
    data = await make_raindrop_request(url, RAINDROP_TOKEN)
    
    if not data or "items" not in data:
        return "Unable to fetch bookmarks or no bookmarks found with this tag."
    
    if not data["items"]:
        return f"No bookmarks found with tag '{tag}' within the specified criteria."
    
    bookmarks = [format_bookmark(item) for item in data["items"]]
    result_msg = f"Found {len(data['items'])} bookmarks with tag '{tag}'"
    if from_date or to_date:
        date_range = f" (Date range: {from_date or 'any'} to {to_date or 'any'})"
        result_msg += date_range
    return result_msg + ":\n\n" + "\n---\n".join(bookmarks)


async def search_bookmarks(query: str, collection_id: int = 0, count: int = 10, from_date: str = "", to_date: str = "") -> str:
    """Search for bookmarks by keyword/text in Raindrop.io with optional date range filtering
    
    Args:
        query: The search term to look for in bookmarks (required)
        collection_id: ID of the collection to search in (default: 0 for all collections)
        count: Maximum number of bookmarks to return (default: 10)
        from_date: Start date in YYYY-MM-DD format (optional)
        to_date: End date in YYYY-MM-DD format (optional)
    """
    if not query:
        return "Error: Search query is required"
    
    # Build search filters
    search_filters = [f"\"{query}\""]
    
    # Add date range filters if provided
    if from_date and from_date.strip():
        search_filters.append(f"created>={from_date.strip()}")
    if to_date and to_date.strip():
        search_filters.append(f"created<={to_date.strip()}")
    
    # Combine all search filters
    search_query = " ".join(search_filters)
    
    url = f"{RAINDROP_API_BASE}/raindrops/{collection_id}?perpage={count}&search={search_query}&sort=-created"
    data = await make_raindrop_request(url, RAINDROP_TOKEN)
    
    if not data or "items" not in data:
        return "Unable to fetch bookmarks or no bookmarks found for your search."
    
    if not data["items"]:
        return f"No bookmarks found matching '{query}' within the specified criteria."
    
    bookmarks = [format_bookmark(item) for item in data["items"]]
    result_msg = f"Found {len(data['items'])} bookmarks matching '{query}'"
    if from_date or to_date:
        date_range = f" (Date range: {from_date or 'any'} to {to_date or 'any'})"
        result_msg += date_range
    return result_msg + ":\n\n" + "\n---\n".join(bookmarks)