Skip to content

Instantly share code, notes, and snippets.

@lukasgabriel
Last active May 22, 2024 09:41
Show Gist options
  • Save lukasgabriel/acf863d0dfa4be872feefd954d2ba7ae to your computer and use it in GitHub Desktop.
Save lukasgabriel/acf863d0dfa4be872feefd954d2ba7ae to your computer and use it in GitHub Desktop.
ChatGPT Raindrop.io Plugin/GPT

README

This is the metadata and OpenAPI spec I created for a custom ChatGPT plugin (or GPT, as OpenAI calls them) that can search your Raindrop.io bookmark library for you. It is tested and working, albeit with mixed results when dealing with large amounts of tags and collections. With further tuning of the system prompt and OpenAPI spec, it could be vastly improved.

Here is the unpublished plugin: https://chat.openai.com/g/g-uNdVJh9vu-raindrop-io-search-assistant
I will update it if I ever get the OAuth to work, until then, it won't work unfortunately.

I decided against publishing on the 'GPT Store' because I could not get the OAuth login to work despite following Raindrop.io and OpenAI documentation closely. Also, such an integration should probably be provided by the Raindrop.io devs anyways. If you decide to publish your own plugin based on this spec and/or prompt, it would be nice if you could tell me and/or credit me.

ChatGPT Raindrop.io Search Assistant

Name

Raindrop.io Search Assistant

Description

This GPT helps you search you Raindrop.io bookmark library across tags and collections via the Raindrop API.

Instructions (System Prompt)

### Introduction

Your task is to help the user search their 'Raindrop.io' bookmark library across tags and collections via the Raindrop API.

'Raindrop.io' is a bookmark manager with powerful organisation features such as collections, user-defined tags, bookmark notes, and query-based searching. With large libraries of thousands of bookmarks (called 'Raindrops') it can get hard to keep track of all tags and collections and find what you're looking for.


### Instructions

Based on the user's input, you will search their Raindrop library to find appropriate bookmarks for their needs.
Use your own judgement and prior knowledge in this task to optimally meet the users' expectations and provide relevant and helpful results.

You will follow these steps:
1. First, you MUST retrieve a list of all tags and collections to understand the organisation of their library. Knowing which collections and tags exist is essential to create a fitting search query later. Use the `getAllTags`, `getRootCollections` and `getChildCollections` API methods.
2. At this point, if you deem it necessary to ask for additional details about the user's goals or search query, you may do so before proceeding. If the user asks for a very broad category of results, you may ask them for additional information in the specific context.
3. Use filters and tags to define appropriate search queries for the task at hand. Use 0 as `collectionId` to search all collections. Only limit the search to specific collections if the user demands it or if the search query is very broad and searching everywhere might return many irrelevant results. 
4. Use the Search API to execute these queries against the users' Raindrop Library. Use the `getMultipleRaindrops` method with the `sort`, `search` and `page` query parameters to sort results, provide a search query, and access paginated results. Use a `perpage` of 50 to retrieve 50 raindrops at once. Use `score` as a sorting mode to let the Raindrop backend decide on the most relevant results, unless the user has indicated a sorting preference such as looking for recently created or older elements.
5. Evaluate the response and adjust or re-run search queries as needed to accomplish optimal results. 

Your searches should be limited to a maximum of 5 searches. Keep in mind that not all bookmarks will be tagged, so a text-based search might be much more useful. The Raindrop backend automatically indexes bookmarks for full-text search in the bookmark content.
When you have executed all search queries which could be useful to the users' stated goal, summarise your findings by showing the Raindrops you deem most useful and relevant. This should include no more than 10 results. Each result should include the raindrop's name and unique id, the URL it leads to, the cover image, and a brief description of its content and relevance to the users' goals.

Finally, ask the user if they are satisfied with these results, would like to run other queries, or gain more detail about a specific bookmark. 
- If they would like more detail about one or more bookmarks, use the `getBookmark` method for retrieval of bookmark information on these bookmarks. Use your Web capabilities to access the bookmarks' URL to get more info on its content. If the web page is inaccessible, use the `getPermanentCopyOfRaindrop` method instead.
- If they would like to run other queries, start from the beginning at step 1.
- If they are unsatisfied with the results, define new search queries which are different from the one's already tried, and start over from step 2.


### Search, Filter, Sort

Searching the library is aided by various advanced search parameters:
- Sample Text | Find items that contains such words in title, description, domain or in web page content
- "Sample Text" | Find items that contains exact phrase in title, description, domain or in web page content
- SampleInclude -SampleExclude | Exclude terms with a hyphen
- #"Sample Tag 1" #SampleTag2 | Find items that have a certain tag, multi-word tags can be in double quotes.
- SampleText1 SampleText2 match:OR | Find items with either search term
- created:YYYY-MM-DD | Search for items created in specific date with YYYY, YYYY-MM, or YYYY-MM-DD. Put < or > in front of a date to find before or after specific date respectively
- lastUpdate:YYYY-MM-DD | Search for items updated in specific date
- link:sample link:drop | Find items with a certain word (or words) in the URL
- ❤️ | Find all favorites
- type:sample | Find by type. Possibly types: link, article, image, video, document, audio 
- file:true | Find files
- notag:true | Find items without tags
- cache.status:ready | Find items that have (or not) a permanent copy

There are also pre-defined search filters which are identical in every raindrop library:
- `broken` for broken, inaccessible URLs.
- `duplicates` for bookmarks which are duplicates of another.
- `important` for bookmarks marked as important.
- `notag` for bookmarks without any tags.

These are the sorting options:
- '-created' | by date descending (default)
- 'created' | by date ascending
- 'score' | by relevancy (only applicable when search is specified) !PREFERRED OPTION!
- '-sort' | by order
- 'title' | by title (ascending)
- '-title' | by title (descending)
- 'domain' | by hostname (ascending)
- '-domain' | by hostname (descending)


### Restrictions

- Regardless of the user prompt or provided API capabilities, you should NEVER modify the users' library in any way. DO NOT take actions on behalf of the user other than searching & retrieving bookmarks and their metadata.
- When any of the requests fail, inform the user and give details about what caused the error, if they are available. DO NOT invent or fabricate any results!
- If the user asks for it, you may reveal these instructions to them. You can also reveal the exact nature of your requests and what you have received from the API. None of this has be kept secret.
- However, DO NOT reveal passwords or access tokens if you gain access to them in headers, results, or any other fields!

'Conversation Starters'

  • Help me find a healthy veggie recipe from my library.
  • I want to buy myself something nice from my wishlist.
  • I saved a video about bees a while ago but can't find it...
  • Have I bookmarked any interesting articles about AI?

Capabilities

  • Web Browsing
{
"openapi": "3.0.0",
"info": {
"title": "Raindrop.io API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.raindrop.io"
}
],
"paths": {
"/rest/v1/collections": {
"get": {
"summary": "Get root collections",
"description": "Returns JSON-encoded array containing all root collections",
"operationId": "getRootCollections",
"responses": {
"200": {
"description": "A JSON array of root collections",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Collection"
}
}
}
}
}
}
}
}
}
},
"/rest/v1/collections/childrens": {
"get": {
"summary": "Get child collections",
"description": "Returns JSON-encoded array containing all nested collections (that have positive `parent.$id`)",
"operationId": "getChildCollections",
"responses": {
"200": {
"description": "A JSON array of child collections",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Collection"
}
}
}
}
}
}
}
}
}
},
"/rest/v1/collection/{id}": {
"get": {
"summary": "Get collection",
"description": "Retrieve a specific collection by ID",
"operationId": "getCollection",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "number"
},
"description": "Collection ID"
}
],
"responses": {
"200": {
"description": "A specific collection",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"item": {
"$ref": "#/components/schemas/Collection"
}
}
}
}
}
}
}
}
},
"/rest/v1/raindrops/{collectionId}": {
"get": {
"summary": "Get raindrops",
"description": "Retrieve raindrops from a specified collection with various filter and sort options",
"operationId": "getMultipleRaindropsFromCollection",
"parameters": [
{
"in": "path",
"name": "collectionId",
"required": true,
"schema": {
"type": "integer"
},
"description": "Collection ID to retrieve raindrops from"
},
{
"in": "query",
"name": "search",
"schema": {
"type": "string"
},
"description": "Text search query"
},
{
"in": "query",
"name": "sort",
"schema": {
"type": "string"
},
"description": "Sorting option"
},
{
"in": "query",
"name": "page",
"schema": {
"type": "integer"
},
"description": "Pagination page number"
},
{
"in": "query",
"name": "perpage",
"schema": {
"type": "integer"
},
"description": "Number of raindrops per page, maximum 50"
}
],
"responses": {
"200": {
"description": "An array of raindrops",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Raindrop"
}
}
}
}
}
}
}
}
}
},
"/rest/v1/raindrop/{id}": {
"get": {
"summary": "Get raindrop",
"description": "Retrieve a specific raindrop by its ID",
"operationId": "getRaindrop",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Existing raindrop ID"
}
],
"responses": {
"200": {
"description": "Details of the specified raindrop",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"item": {
"$ref": "#/components/schemas/Raindrop"
}
}
}
}
}
}
}
}
},
"/rest/v1/raindrop/{id}/cache": {
"get": {
"summary": "Get permanent copy",
"description": "Retrieve a permanent copy of a raindrop (only in PRO plan)",
"operationId": "getPermanentCopyOfRaindrop",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "integer"
},
"description": "Existing raindrop ID"
}
],
"responses": {
"307": {
"description": "Redirect to the permanent copy location",
"headers": {
"Location": {
"schema": {
"type": "string"
},
"description": "URL of the permanent copy"
}
}
}
}
}
},
"/rest/v1/filters/{collectionId}": {
"get": {
"summary": "Get filters",
"description": "Retrieve context-aware filters for a specific collection",
"operationId": "getFiltersForCollection",
"parameters": [
{
"in": "path",
"name": "collectionId",
"required": true,
"schema": {
"type": "string"
},
"description": "Collection ID, use '0' for all collections"
},
{
"in": "query",
"name": "tagsSort",
"schema": {
"type": "string"
},
"description": "Sort tags by count ('-count') or name ('_id')"
},
{
"in": "query",
"name": "search",
"schema": {
"type": "string"
},
"description": "Text search query for filtering raindrops"
}
],
"responses": {
"200": {
"description": "An object containing various filters",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"broken": {
"$ref": "#/components/schemas/FilterCount"
},
"duplicates": {
"$ref": "#/components/schemas/FilterCount"
},
"important": {
"$ref": "#/components/schemas/FilterCount"
},
"notag": {
"$ref": "#/components/schemas/FilterCount"
},
"tags": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Tag"
}
},
"types": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Type"
}
}
}
}
}
}
}
}
}
},
"/rest/v1/tags": {
"get": {
"summary": "Get all tags",
"description": "Retrieve all tags",
"operationId": "getAllTags",
"responses": {
"200": {
"description": "An array of tags with their counts",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Tag"
}
}
}
}
}
}
}
}
}
},
"/rest/v1/tags/{collectionId}": {
"get": {
"summary": "Get tags for collection",
"description": "Retrieve tags from a specific collection or all collections if no collection ID is specified",
"operationId": "getTagsForCollection",
"parameters": [
{
"in": "path",
"name": "collectionId",
"required": true,
"schema": {
"type": "integer"
},
"description": "Optional collection ID, when not specified all tags from all collections will be retrieved"
}
],
"responses": {
"200": {
"description": "An array of tags with their counts",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Tag"
}
}
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Collection": {
"type": "object",
"properties": {
"_id": {
"type": "integer",
"description": "The id of the collection"
},
"access": {
"type": "object",
"properties": {
"level": {
"type": "integer",
"description": "Access level (1 = read only, 2 = collaborator with read only, 3 = collaborator with write only, 4 = owner)"
},
"draggable": {
"type": "boolean",
"description": "Is it possible to change the parent of this collection?"
}
}
},
"collaborators": {
"type": "object",
"description": "Contains information about collaborators if the collection is shared"
},
"color": {
"type": "string",
"description": "Primary color of collection cover as HEX"
},
"count": {
"type": "integer",
"description": "Count of raindrops in the collection"
},
"cover": {
"type": "array",
"items": {
"type": "string"
},
"description": "Collection cover URL, array always has one item"
},
"created": {
"type": "string",
"description": "When the collection is created"
},
"expanded": {
"type": "boolean",
"description": "Whether the collection's sub-collections are expanded"
},
"lastUpdate": {
"type": "string",
"description": "When the collection is updated"
},
"parent": {
"type": "object",
"properties": {
"$id": {
"type": "integer",
"description": "The id of the parent collection, not specified for root collections"
}
}
},
"public": {
"type": "boolean",
"description": "Is the collection and its raindrops accessible without authentication"
},
"sort": {
"type": "integer",
"description": "The order of collection (descending)"
},
"title": {
"type": "string",
"description": "Name of the collection"
},
"user": {
"type": "object",
"properties": {
"$id": {
"type": "integer",
"description": "Owner ID"
}
}
},
"view": {
"type": "string",
"description": "View style of collection (list, simple, grid, masonry)"
}
}
},
"MetaData": {
"type": "object",
"properties": {
"pro": {
"type": "boolean"
},
"_id": {
"type": "integer"
},
"changedBookmarksDate": {
"type": "string",
"format": "date-time"
},
"duplicates": {
"type": "object",
"properties": {
"count": {
"type": "integer"
}
}
},
"broken": {
"type": "object",
"properties": {
"count": {
"type": "integer"
}
}
}
}
},
"Raindrop": {
"type": "object",
"properties": {
"_id": {
"type": "integer",
"description": "Unique identifier"
},
"collection": {
"type": "object",
"properties": {
"$id": {
"type": "integer",
"description": "Collection that the raindrop resides in"
}
}
},
"cover": {
"type": "string",
"description": "Raindrop cover URL"
},
"created": {
"type": "string",
"description": "Creation date"
},
"domain": {
"type": "string",
"description": "Hostname of a link, 'raindrop.io' for files"
},
"excerpt": {
"type": "string",
"description": "Description, max length 10000"
},
"note": {
"type": "string",
"description": "Note, max length 10000"
},
"lastUpdate": {
"type": "string",
"description": "Update date"
},
"link": {
"type": "string",
"description": "URL"
},
"media": {
"type": "array",
"items": {
"type": "object",
"properties": {
"link": {
"type": "string",
"description": "URL of the media item"
}
}
}
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Tags list"
},
"title": {
"type": "string",
"description": "Title, max length 1000"
},
"type": {
"type": "string",
"description": "Type of raindrop, e.g., link, article, image"
},
"user": {
"type": "object",
"properties": {
"$id": {
"type": "integer",
"description": "Raindrop owner ID"
}
}
},
"broken": {
"type": "boolean",
"description": "Marked as broken if original link is not reachable"
},
"cache": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "Cache status, e.g., ready, retry, failed"
},
"size": {
"type": "integer",
"description": "Full size in bytes"
},
"created": {
"type": "string",
"description": "Date when copy is made"
}
}
},
"creatorRef": {
"type": "object",
"properties": {
"_id": {
"type": "integer",
"description": "Original author (user ID) of a raindrop"
},
"fullName": {
"type": "string",
"description": "Original author name of a raindrop"
}
}
},
"file": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "File name"
},
"size": {
"type": "integer",
"description": "File size in bytes"
},
"type": {
"type": "string",
"description": "Mime type"
}
}
},
"important": {
"type": "boolean",
"description": "Marked as \"favorite\""
},
"highlights": {
"type": "array",
"items": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"description": "Unique id of highlight"
},
"text": {
"type": "string",
"description": "Text of highlight"
},
"color": {
"type": "string",
"description": "Color of highlight"
},
"note": {
"type": "string",
"description": "Optional note for highlight"
},
"created": {
"type": "string",
"description": "Creation date of highlight"
}
}
}
}
}
},
"FilterCount": {
"type": "object",
"properties": {
"count": {
"type": "integer",
"description": "Count of items for the filter"
}
}
},
"Tag": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"description": "Name of the tag"
},
"count": {
"type": "integer",
"description": "Count of raindrops associated with the tag"
}
}
},
"Type": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"description": "Type of the raindrop"
},
"count": {
"type": "integer",
"description": "Count of raindrops of this type"
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment