From ac46f2ae27c241b651c4462d01443f4dfc522600 Mon Sep 17 00:00:00 2001 From: Dave Gallant Date: Fri, 4 Oct 2019 21:06:53 -0400 Subject: [PATCH] add search command (#33) * add search command --- .pylintrc | 2 +- README.md | 8 +++++- rfd/__version__.py | 2 +- rfd/api.py | 18 ++++++------- rfd/cli.py | 48 ++++++++++++++++++++++++++++++--- rfd/models.py | 3 +++ rfd/search.py | 13 +++++++++ tests/test_api.py | 66 ++++++---------------------------------------- 8 files changed, 87 insertions(+), 73 deletions(-) create mode 100644 rfd/search.py diff --git a/.pylintrc b/.pylintrc index 5145c82..5cd673c 100644 --- a/.pylintrc +++ b/.pylintrc @@ -55,7 +55,7 @@ confidence= disable= attribute-defined-outside-init, bad-option-value, - duplicate-code, + bad-continuation, fixme, invalid-name, missing-docstring, diff --git a/README.md b/README.md index 9913a1f..28c749f 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,14 @@ pip install rfd ![rfd_demo_gif](https://user-images.githubusercontent.com/4519234/64501455-64836600-d28f-11e9-8381-3fbfda910230.gif) +### threads ```bash -rfd threads [--limit 10] +rfd threads [--forum-id 9] [--limit 10] +``` + +### search +```bash +rfd search pizza [--num-pages 100] ``` ## Tab Completion diff --git a/rfd/__version__.py b/rfd/__version__.py index 8f0cc77..633c60b 100644 --- a/rfd/__version__.py +++ b/rfd/__version__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -version = "0.2.1" +version = "0.3.0" diff --git a/rfd/api.py b/rfd/api.py index 0b910ad..329aca4 100644 --- a/rfd/api.py +++ b/rfd/api.py @@ -26,15 +26,15 @@ def get_safe_per_page(limit): return limit -def users_to_dict(users): - """Create a dictionary of user ids to usernames.""" - users_dict = {} +def create_user_map(users): + """Create a map of user ids to usernames.""" + m = dict() for user in users: - users_dict[user.get("user_id")] = user.get("username") - return users_dict + m[user.get("user_id")] = user.get("username") + return m -def get_threads(forum_id, limit): +def get_threads(forum_id, limit, page=1): """Get threads from rfd api Arguments: @@ -46,8 +46,8 @@ def get_threads(forum_id, limit): """ try: response = requests.get( - "{}/api/topics?forum_id={}&per_page={}".format( - API_BASE_URL, forum_id, get_safe_per_page(limit) + "{}/api/topics?forum_id={}&per_page={}&page={}".format( + API_BASE_URL, forum_id, get_safe_per_page(limit), page ) ) if response.status_code == 200: @@ -86,7 +86,7 @@ def get_posts(post): API_BASE_URL, post_id, 40, page ) ) - users = users_to_dict(response.json().get("users")) + users = create_user_map(response.json().get("users")) posts = response.json().get("posts") diff --git a/rfd/cli.py b/rfd/cli.py index 8d50a46..447b538 100644 --- a/rfd/cli.py +++ b/rfd/cli.py @@ -7,6 +7,7 @@ import sys import click from colorama import init, Fore, Style from .api import get_threads, get_posts +from .search import search_threads from .parsing import parse_threads from .__version__ import version as current_version @@ -88,7 +89,7 @@ def posts(post_id): @cli.command(short_help="Displays threads in the specified forum.") @click.option("--limit", default=10, help="Number of topics.") -@click.argument("forum_id", default=9) +@click.option("--forum-id", default=9, help="The forum id number") def threads(limit, forum_id): """Displays threads in the specified forum id. Defaults to 9. @@ -107,10 +108,10 @@ def threads(limit, forum_id): 88 \t cell phones """ _threads = parse_threads(get_threads(forum_id, limit), limit) - for i, thread in enumerate(_threads, 1): + for count, thread in enumerate(_threads, 1): click.echo( " " - + str(i) + + str(count) + "." + get_vote_color(thread.score) + Fore.RESET @@ -120,5 +121,46 @@ def threads(limit, forum_id): click.echo(Style.RESET_ALL) +@cli.command(short_help="Displays threads in the specified forum.") +@click.option("--num-pages", default=5, help="Number of pages to search.") +@click.option( + "--forum-id", default=9, help="The forum id number. Defaults to 9 (hot deals)." +) +@click.argument("keyword") +def search(num_pages, forum_id, keyword): + """Searches for deals based on a keyword in the specified forum id. + + Popular forum ids: + + \b + 9 \t hot deals + 14 \t computer and electronics + 15 \t offtopic + 17 \t entertainment + 18 \t food and drink + 40 \t automotive + 53 \t home and garden + 67 \t fashion and apparel + 74 \t shopping discussion + 88 \t cell phones + """ + + count = 0 + for page in range(1, num_pages): + _threads = parse_threads(get_threads(forum_id, 100, page=page), limit=100) + for thread in search_threads(threads=_threads, keyword=keyword): + count += 1 + click.echo( + " " + + str(count) + + "." + + get_vote_color(thread.score) + + Fore.RESET + + "[%s] %s" % (thread.dealer_name, thread.title) + ) + click.echo(Fore.BLUE + " {}".format(thread.url)) + click.echo(Style.RESET_ALL) + + if __name__ == "__main__": cli() diff --git a/rfd/models.py b/rfd/models.py index 2727182..b362b3e 100644 --- a/rfd/models.py +++ b/rfd/models.py @@ -6,6 +6,9 @@ class Thread: self.title = title self.url = url + def __repr__(self): + return "Thread(%s)" % self.title + class Post: def __init__(self, body, score, user): diff --git a/rfd/search.py b/rfd/search.py new file mode 100644 index 0000000..3666a50 --- /dev/null +++ b/rfd/search.py @@ -0,0 +1,13 @@ +def search_threads(threads, keyword=None): + """Match deal title and dealer names with keyword specified.""" + + if keyword is None: + return + + keyword = str(keyword) + + for deal in threads: + if keyword.lower() in deal.title.lower() or ( + deal.dealer_name and keyword.lower() in deal.dealer_name.lower() + ): + yield deal diff --git a/tests/test_api.py b/tests/test_api.py index 752f34c..b5dbe38 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,6 +1,7 @@ from rfd.api import extract_post_id from rfd.parsing import build_web_path, parse_threads + def test_build_web_path(): assert build_web_path("/test") == "https://forums.redflagdeals.com/test" @@ -18,62 +19,11 @@ def test_extract_post_id(): def test_parse_threads(threads_api_response): - assert len(parse_threads(threads_api_response, 10)) == len( - [ - { - "score": 0, - "title": "[Sponsored] 3 Months Free, Cable 75M, Unlimited Internet $34.99/30 " - "Days, Free Installation/Modem Rental", - "url": "https://forums.redflagdeals.com/carrytel-sponsored-3-months-free-cable-75m-unlimited-internet-34-99-30-days-free-installation-modem-rental-2197859/", - }, - { - "score": 92, - "title": "WyzeCam 1080p HD Wireless Smart Home Camera v2 $37.49", - "url": "https://forums.redflagdeals.com/amazon-ca-wyzecam-1080p-hd-wireless-smart-home-camera-v2-37-49-2191108/", - }, - { - "score": 8, - "title": "Jabra Elite 65T $169.99", - "url": "https://forums.redflagdeals.com/best-buy-jabra-elite-65t-169-99-2197916/", - }, - { - "score": 1, - "title": "Glad Cling Wrap Plastic Wrap, 300 Metre Roll - best price $9.47", - "url": "https://forums.redflagdeals.com/amazon-ca-glad-cling-wrap-plastic-wrap-300-metre-roll-best-price-9-47-2198211/", - }, - { - "score": 17, - "title": "Firman 3300 inverter generator $599", - "url": "https://forums.redflagdeals.com/costco-firman-3300-inverter-generator-599-2195171/", - }, - { - "score": 3, - "title": "HOT - KitchenAid Stand Mixer - $199", - "url": "https://forums.redflagdeals.com/walmart-hot-kitchenaid-stand-mixer-199-2198199/", - }, - { - "score": -1, - "title": "Seagate Expansion 4TB Portable External Hard Drive USB 3.0 " - "(STEA4000400) $119.92", - "url": "https://forums.redflagdeals.com/amazon-ca-seagate-expansion-4tb-portable-external-hard-drive-usb-3-0-stea4000400-119-92-2198164/", - }, - { - "score": 0, - "title": "WORKSHOP Wet Dry Vacs Ash Vacuum Cleaner WS0500ASH, 5-Gallon Ash " - "Vac 65% Off, Now: $48.54", - "url": "https://forums.redflagdeals.com/workshop-wet-dry-vacs-ash-vacuum-cleaner-ws0500ash-5-gallon-ash-vac-65-off-now-48-54-2198212/", - }, - { - "score": 4, - "title": "NBA 2K18 (Nintendo Switch) -$19.99 or $16.99 PM", - "url": "https://forums.redflagdeals.com/the-source-nba-2k18-nintendo-switch-19-99-16-99-pm-2198191/", - }, - { - "score": 5, - "title": "CROCS.CA 40%OFF Select style - at checkout", - "url": "https://forums.redflagdeals.com/crocs-crocs-ca-40-off-select-style-checkout-2198145/", - }, - ] - ) + limit = 10 + threads = parse_threads(threads_api_response, limit) + assert len(threads) == limit - assert len(parse_threads(None, 10)) is 0 + +def test_parse_threads_empty(): + + assert parse_threads(None, 10) == []