From 64d46955e2b827c406f503cabc1be8b6169e72dd Mon Sep 17 00:00:00 2001 From: Ivan Habunek Date: Sun, 16 Apr 2017 15:07:27 +0200 Subject: [PATCH] Add search command --- CHANGELOG.md | 5 +++++ README.rst | 51 ++++++++++++++++++------------------------- tests/test_console.py | 34 ++++++++++++++++++++++++++++- toot/api.py | 7 ++++++ toot/console.py | 48 ++++++++++++++++++++++++++++++++++++---- 5 files changed, 110 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4c1b2..007a805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog --------- +**0.5.0 (2016-04-16)** + +* Add `search` command +* Migrate from `optparse` to `argparse` + **0.4.0 (2016-04-15)** * Add `upload` command to post media diff --git a/README.rst b/README.rst index 54ccd71..aa10874 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,8 @@ Toot - Mastodon CLI interface Interact with Mastodon social networks from the command line. +.. image:: https://img.shields.io/travis/ihabunek/toot.svg?maxAge=3600&style=flat-square + :target: https://travis-ci.org/ihabunek/toot .. image:: https://img.shields.io/badge/author-%40ihabunek-blue.svg?maxAge=3600&style=flat-square :target: https://mastodon.social/@ihabunek .. image:: https://img.shields.io/github/license/ihabunek/pdf417-py.svg?maxAge=3600&style=flat-square @@ -21,11 +23,28 @@ Install using pip: pip install toot - Usage ----- -Firstly, you will need to login to a Mastodon instance: +Running ``toot`` displays a list of available commands. + +Running ``toot -h`` shows the documentation for the given command. + +=================== =============================================================== + Command Description +=================== =============================================================== + ``toot login`` Log into a Mastodon instance, saves access keys for later use. + ``toot logout`` Log out, deletes stored access keys. + ``toot auth`` Display current login details. + ``toot post`` Post a status to your timeline. + ``toot search`` Search for accounts or hashtags. + ``toot timeline`` Display recent items in your public timeline. +=================== =============================================================== + +Authentication +-------------- + +Before tooting, you need to login to a Mastodon instance: .. code-block:: @@ -51,31 +70,3 @@ And you can logout which will remove the stored access tokens: .. code-block:: toot logout - -Show timeline -~~~~~~~~~~~~~ - -To show recent items in your public timeline: - -.. code-block:: - - toot timeline - -Post status -~~~~~~~~~~~ - -To post a new status to your timeline: - -.. code-block:: - - toot post "Hello world!" - -Optionally attach an image or video to the status: - - toot post "Hello world!" --media=path/to/world.jpg - -To set post visibility: - - toot post "Hello world!" --visibility=unlisted - -Possible visibility values are: ``public`` (default), ``unlisted``, ``private``, ``direct``. They are documented `here `_. diff --git a/tests/test_console.py b/tests/test_console.py index 5d38e6f..0b57baf 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -3,7 +3,7 @@ import pytest import requests from toot import User, App -from toot.console import print_usage, cmd_post_status, cmd_timeline, cmd_upload +from toot.console import print_usage, cmd_post_status, cmd_timeline, cmd_upload, cmd_search from tests.utils import MockResponse @@ -138,3 +138,35 @@ def test_upload(monkeypatch, capsys): out, err = capsys.readouterr() assert "Uploading media" in out assert __file__ in out + + +def test_search(monkeypatch, capsys): + def mock_get(url, params, headers=None): + assert url == 'https://habunek.com/api/v1/search' + assert headers == {'Authorization': 'Bearer xxx'} + assert params == { + 'q': 'freddy', + 'resolve': False, + } + + return MockResponse({ + 'hashtags': ['foo', 'bar', 'baz'], + 'accounts': [{ + 'acct': 'thequeen', + 'display_name': 'Freddy Mercury' + }, { + 'acct': 'thequeen@other.instance', + 'display_name': 'Mercury Freddy' + }], + 'statuses': [], + }) + + monkeypatch.setattr(requests, 'get', mock_get) + + cmd_search(app, user, ['freddy']) + + out, err = capsys.readouterr() + assert "Hashtags:\n\033[32m#foo\033[0m, \033[32m#bar\033[0m, \033[32m#baz\033[0m" in out + assert "Accounts:" in out + assert "\033[32m@thequeen\033[0m Freddy Mercury" in out + assert "\033[32m@thequeen@other.instance\033[0m Mercury Freddy" in out diff --git a/toot/api.py b/toot/api.py index d3cc7b3..92b7707 100644 --- a/toot/api.py +++ b/toot/api.py @@ -108,3 +108,10 @@ def upload_media(app, user, file): return _post(app, user, '/api/v1/media', files={ 'file': file }) + + +def search(app, user, query, resolve): + return _get(app, user, '/api/v1/search', { + 'q': query, + 'resolve': resolve, + }) diff --git a/toot/console.py b/toot/console.py index 0cdf6d2..c0c07c7 100644 --- a/toot/console.py +++ b/toot/console.py @@ -16,7 +16,7 @@ from argparse import ArgumentParser, FileType from textwrap import TextWrapper from toot import DEFAULT_INSTANCE -from toot.api import create_app, login, post_status, timeline_home, upload_media +from toot.api import create_app, login, post_status, timeline_home, upload_media, search from toot.config import save_user, load_user, load_app, save_app, CONFIG_APP_FILE, CONFIG_USER_FILE @@ -84,6 +84,7 @@ def print_usage(): print(" toot logout - log out (delete saved access tokens)") print(" toot auth - shows currently logged in user and instance") print(" toot post - toot a new post to your timeline") + print(" toot search - search for accounts or hashtags") print(" toot timeline - shows your public timeline") print("") print("To get help for each command run:") @@ -233,6 +234,42 @@ def cmd_upload(app, user, args): print("Text URL: " + green(response['text_url'])) +def _print_accounts(accounts): + if not accounts: + return + + print("\nAccounts:") + for account in accounts: + acct = green("@{}".format(account['acct'])) + display_name = account['display_name'] + print("* {} {}".format(acct, display_name)) + + +def _print_hashtags(hashtags): + if not hashtags: + return + + print("\nHashtags:") + print(", ".join([green("#" + t) for t in hashtags])) + + +def cmd_search(app, user, args): + parser = ArgumentParser(prog="toot serach", + description="Search for content", + epilog="https://github.com/ihabunek/toot") + + parser.add_argument("query", help="The search query") + parser.add_argument("-r", "--resolve", action='store_true', default=False, + help="Whether to resolve non-local accounts") + + args = parser.parse_args(args) + + response = search(app, user, args.query, args.resolve) + + _print_accounts(response['accounts']) + _print_hashtags(response['hashtags']) + + def do_upload(app, user, file): print("Uploading media: {}".format(green(file.name))) return upload_media(app, user, file) @@ -251,8 +288,8 @@ def run_command(command, args): # Commands which require user to be logged in if not app or not user: - print(red("You are not logged in.")) - print(red("Please run `toot login` first.")) + print_error("You are not logged in.") + print_error("Please run `toot login` first.") return if command == 'logout': @@ -267,7 +304,10 @@ def run_command(command, args): if command == 'upload': return cmd_upload(app, user, args) - print(red("Unknown command '{}'\n".format(command))) + if command == 'search': + return cmd_search(app, user, args) + + print_error("Unknown command '{}'\n".format(command)) print_usage()