├── raindrop ├── __init__.py ├── query_listener.py ├── preferences.py └── extension.py ├── requirements.txt ├── .flake8 ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug.yml └── workflows │ └── ci.yml ├── .gitignore ├── demo.gif ├── versions.json ├── images └── icon.png ├── main.py ├── .pre-commit-config.yaml ├── editorconfig ├── manifest.json ├── Makefile ├── LICENSE └── README.md /raindrop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-raindropio==0.0.4 -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .cache 4 | *.log 5 | .vscode -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpaz/ulauncher-raindrop/HEAD/demo.gif -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "required_api_version": "^2.0.0", "commit": "main" } 3 | ] 4 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brpaz/ulauncher-raindrop/HEAD/images/icon.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ Main Module """ 2 | 3 | from raindrop.extension import RaindropExtension 4 | 5 | if __name__ == '__main__': 6 | RaindropExtension().run() 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature 3 | title: "[Feature Request]: " 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Descprition? 9 | description: Please provide your feature request 10 | validations: 11 | required: true 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: yapf 5 | name: yapf 6 | entry: make format 7 | language: system 8 | types: [python] 9 | pass_filenames: false 10 | - id: lint 11 | name: flake8 12 | entry: make lint 13 | language: system 14 | types: [python] 15 | pass_filenames: false 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Set up Python 11 | uses: actions/setup-python@v1 12 | with: 13 | python-version: 3.7 14 | - name: Install dependencies 15 | run: | 16 | python -m pip install --upgrade pip 17 | pip install -r requirements.txt 18 | pip install flake8 19 | - name: Lint with flake8 20 | run: make lint 21 | -------------------------------------------------------------------------------- /editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # EditorConfig helps developers define and maintain consistent 3 | # coding styles between different editors and IDEs 4 | # editorconfig.org 5 | 6 | root = true 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.py] 21 | indent_size = 4 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /raindrop/query_listener.py: -------------------------------------------------------------------------------- 1 | from ulauncher.api.client.EventListener import EventListener 2 | 3 | 4 | class KeywordQueryEventListener(EventListener): 5 | """ Listener that handles the user input """ 6 | 7 | def on_event(self, event, extension): 8 | 9 | kw_id = extension.get_keyword_id(event.get_keyword()) 10 | query = event.get_argument() or "" 11 | 12 | if kw_id == 'kw_open': 13 | return extension.show_open_app_menu() 14 | 15 | if kw_id == 'kw_unsorted': 16 | return extension.unsorted(query) 17 | 18 | return extension.search(query) 19 | -------------------------------------------------------------------------------- /raindrop/preferences.py: -------------------------------------------------------------------------------- 1 | """ Extension preferences Listeners """ 2 | 3 | from ulauncher.api.client.EventListener import EventListener 4 | from raindropio import API 5 | 6 | 7 | class PreferencesEventListener(EventListener): 8 | """ Handles preferences initialization event """ 9 | def on_event(self, event, extension): 10 | """ Handle event """ 11 | extension.rd_client = API(event.preferences["access_token"]) 12 | 13 | 14 | class PreferencesUpdateEventListener(EventListener): 15 | """ Handles Preferences Update event """ 16 | def on_event(self, event, extension): 17 | if event.id == 'access_token': 18 | extension.rd_client = API(event.new_value) 19 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "required_api_version": "^2.0.0", 3 | "name": "Raindrop", 4 | "description": "Search your Raindrop bookmarks", 5 | "developer_name": "Bruno Paz", 6 | "icon": "images/icon.png", 7 | "options": { 8 | "query_debounce": 0.2 9 | }, 10 | "preferences": [ 11 | { 12 | "id": "kw_open", 13 | "type": "keyword", 14 | "name": "Raindrop: Open In browser", 15 | "default_value": "rdopen" 16 | }, 17 | { 18 | "id": "kw_unsorted", 19 | "type": "keyword", 20 | "name": "Raindrop: Unsorted", 21 | "default_value": "rd:unsorted" 22 | }, 23 | { 24 | "id": "kw", 25 | "type": "keyword", 26 | "name": "Raindrop: Search", 27 | "default_value": "rd" 28 | }, 29 | { 30 | "id": "access_token", 31 | "type": "input", 32 | "name": "Raindrop Access Token" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | EXT_NAME:=com.github.brpaz.ulauncher-raindrop 3 | EXT_DIR:=$(shell pwd) 4 | 5 | .PHONY: help lint format link unlink deps dev setup 6 | .DEFAULT_GOAL := help 7 | 8 | setup: ## Setups the project 9 | pre-commit install 10 | 11 | lint: ## Run Lint 12 | @flake8 13 | 14 | format: ## Format code using yapf 15 | @yapf --in-place --recursive . 16 | 17 | link: ## Symlink the project source directory with Ulauncher extensions dir. 18 | @ln -s ${EXT_DIR} ~/.local/share/ulauncher/extensions/${EXT_NAME} 19 | 20 | unlink: ## Unlink extension from Ulauncher 21 | @rm -r ~/.local/share/ulauncher/extensions/${EXT_NAME} 22 | 23 | deps: ## Install Python Dependencies 24 | @pip3 install -r requirements.txt 25 | 26 | dev: ## Runs ulauncher on development mode 27 | ulauncher -v --dev --no-extensions |& grep "${EXT_NAME}" 28 | 29 | help: ## Show help menu 30 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bruno Paz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 2 | description: Report a bug 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | assignees: 6 | - brpaz 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Most issues regarding Ulauncher extensions are caused by missing Python dependencies that are required for the extension to work. 12 | 13 | If you get a `Type: KeyError` error when saving the extension preferences for example, a missing dependency is mostly the cause. 14 | 15 | Please make sure to check the project `README` for a list of Python extensions to install. 16 | 17 | Also Check [this](https://ulauncher-extension-doesnt-install-and-now.netlify.app/) website for more details. 18 | - type: textarea 19 | id: description 20 | attributes: 21 | label: Description? 22 | description: Please explain in detail the issue and the steps to reproduce it. 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: logs 27 | attributes: 28 | label: Extension Logs 29 | description: | 30 | Please copy and paste the extension logs. The best way to get them: 31 | - Close any Ulauncher instance that you have running (full close, not minimize) 32 | - From the command line run: `ulauncher -v --dev |& grep ""`, replacing with the name of the extension. (Ex: ulauncher-github) 33 | - Execute the action that is giving error and get the log output. 34 | render: shell 35 | -------------------------------------------------------------------------------- /raindrop/extension.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ulauncher.api.client.Extension import Extension 4 | from ulauncher.api.shared.event import KeywordQueryEvent, PreferencesEvent, PreferencesUpdateEvent 5 | from ulauncher.api.shared.item.ExtensionResultItem import ExtensionResultItem 6 | from ulauncher.api.shared.action.RenderResultListAction import RenderResultListAction 7 | from ulauncher.api.shared.action.OpenUrlAction import OpenUrlAction 8 | from raindrop.preferences import PreferencesEventListener, PreferencesUpdateEventListener 9 | from raindropio import Raindrop, CollectionRef 10 | from raindrop.query_listener import KeywordQueryEventListener 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class RaindropExtension(Extension): 16 | """ Main Extension Class """ 17 | 18 | def __init__(self): 19 | """ Initializes the extension """ 20 | super(RaindropExtension, self).__init__() 21 | self.subscribe(KeywordQueryEvent, KeywordQueryEventListener()) 22 | 23 | self.subscribe(PreferencesEvent, PreferencesEventListener()) 24 | self.subscribe(PreferencesUpdateEvent, 25 | PreferencesUpdateEventListener()) 26 | 27 | def get_keyword_id(self, keyword): 28 | for key, value in self.preferences.items(): 29 | if value == keyword: 30 | return key 31 | 32 | return "" 33 | 34 | def show_open_app_menu(self): 35 | """ Shows the menu to Open Raindrop website """ 36 | return RenderResultListAction([ 37 | ExtensionResultItem( 38 | icon='images/icon.png', 39 | name='Open Raindrop Website', 40 | on_enter=OpenUrlAction('https://app.raindrop.io')) 41 | ]) 42 | 43 | def search(self, query): 44 | drops = Raindrop.search( 45 | self.rd_client, 46 | word=query, 47 | perpage=10, 48 | collection=CollectionRef({"$id": 0}), 49 | ) 50 | 51 | if len(drops) == 0: 52 | return RenderResultListAction([ 53 | ExtensionResultItem( 54 | icon='images/icon.png', 55 | name='No results found matcing your criteria', 56 | highlightable=False) 57 | ]) 58 | 59 | items = [] 60 | for drop in drops: 61 | items.append( 62 | ExtensionResultItem(icon='images/icon.png', 63 | name=drop.title, 64 | description=drop.excerpt, 65 | on_enter=OpenUrlAction(drop.link))) 66 | return RenderResultListAction(items) 67 | 68 | def unsorted(self, query): 69 | drops = Raindrop.search( 70 | self.rd_client, 71 | word=query, 72 | perpage=10, 73 | collection=CollectionRef({"$id": -1}), 74 | ) 75 | 76 | if len(drops) == 0: 77 | return RenderResultListAction([ 78 | ExtensionResultItem( 79 | icon='images/icon.png', 80 | name='No results found matcing your criteria', 81 | highlightable=False) 82 | ]) 83 | 84 | items = [] 85 | for drop in drops: 86 | items.append( 87 | ExtensionResultItem(icon='images/icon.png', 88 | name=drop.title, 89 | description=drop.excerpt, 90 | on_enter=OpenUrlAction(drop.link))) 91 | return RenderResultListAction(items) 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ulauncher-raindrop 2 | 3 | > Search in your [Raindrop](https://raindrop.io/) bookmarks directly from [Ulauncher](https://ulauncher.io/). 4 | 5 | [![Ulauncher Extension](https://img.shields.io/badge/Ulauncher-Extension-yellowgreen.svg?style=for-the-badge)](https://ext.ulauncher.io/) 6 | [![CI Status](https://img.shields.io/github/workflow/status/brpaz/ulauncher-raindrop/CI?color=orange&label=actions&logo=github&logoColor=orange&style=for-the-badge)](https://github.com/brpaz/ulauncher-raindrop) 7 | [![License](https://img.shields.io/github/license/brpaz/ulauncher-raindrop.svg?style=for-the-badge)](LICENSE) 8 | 9 | ## Features 10 | 11 | * Search on your entire collection of Bookmarks. 12 | * Open a selected bookmark in the default browser 13 | * Open `https://app.raindrop.io` using the `rdopen` keyword 14 | 15 | ![demo](demo.gif) 16 | 17 | ## Requirements 18 | 19 | * [Ulauncher](https://github.com/Ulauncher/Ulauncher) > 5.0 20 | * Python >= 3.7 21 | * python-raindropio - `pip install --user python-raindropio==0.0.4` 22 | 23 | ## Install 24 | 25 | Open ulauncher preferences window -> extensions -> add extension and paste the following url: 26 | 27 | ``` 28 | https://github.com/brpaz/ulauncher-raindrop 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Configurations 34 | 35 | To be able to do calls to the Raindrop API you must create an access token and set it´s value on the extension settings. 36 | 37 | To generate a new token, go to [Integrations page](https://app.raindrop.io/settings/integrations) on Raindrop, and select `Create a new App` in the `Developers`section. Give in a name like `Ulauncher Extension`. 38 | 39 | After the app is created, click on it to open the details and create a "Test Token". 40 | 41 | ### Keywords 42 | 43 | The following keywords are specified by this extension: 44 | 45 | * ```rd ``` to to search on all your bookmarks. 46 | * ```rdopen``` to open Raindrop Web App. 47 | * ```rdunsorted``` to search on your `Unsorted` bookmarks 48 | 49 | ## Development 50 | 51 | ``` 52 | git clone https://github.com/brpaz/ulauncher-raindrop 53 | make link 54 | ``` 55 | 56 | The `make link` command will symlink the cloned repo into the appropriate location on the ulauncher extensions folder. 57 | 58 | To see your changes, stop ulauncher and run it from the command line with: `make dev`. 59 | 60 | ## Contributing 61 | 62 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 63 | 64 | 1. Fork the Project 65 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 66 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 67 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 68 | 5. Open a Pull Request 69 | 70 | ## 💛 Support the project 71 | 72 | If this project was useful to you in some form, I would be glad to have your support. It will help to keep the project alive and to have more time to work on Open Source. 73 | 74 | The sinplest form of support is to give a ⭐️ to this repo. 75 | 76 | You can also contribute with [GitHub Sponsors](https://github.com/sponsors/brpaz). 77 | 78 | [![GitHub Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-Sponsor%20Me-red?style=for-the-badge)](https://github.com/sponsors/brpaz) 79 | 80 | Or if you prefer a one time donation to the project, you can simple: 81 | 82 | Buy Me A Coffee 84 | 85 | --- 86 | ## License 87 | 88 | MIT © Bruno Paz 89 | --------------------------------------------------------------------------------