mirror of
https://github.com/davegallant/rfd.git
synced 2025-08-07 09:02:32 +00:00
Compare commits
58 Commits
v0.7.0
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
|
4e8a302253 | ||
|
dcd2a49037 | ||
|
224680a8e1 | ||
|
1f4dc1ace9 | ||
|
e24b7fbaed | ||
|
16ca06ef58 | ||
|
40cd26166e | ||
|
f4dd94cfff | ||
|
c148897a00 | ||
|
ac10720c68 | ||
|
260ef74d4e | ||
|
3df07dc65e | ||
|
b96cab1638 | ||
|
67d3f2db14 | ||
|
b0e11ec375 | ||
|
3421e107b6 | ||
|
3b3aeac80e | ||
|
a7e20b77e6 | ||
|
90af78c5f2 | ||
|
9e3a92d548 | ||
|
9c61c419aa | ||
|
c38d3f256b | ||
|
4fa889a64a | ||
|
aa06127990 | ||
|
47b7785e42 | ||
|
b8a863650f | ||
|
51b2982f28 | ||
|
cca01264c6 | ||
|
69ff622629 | ||
|
18f37bfef4 | ||
|
8cb12bc4b5 | ||
|
889f459437 | ||
|
6e36ddde92 | ||
|
554a057c7f | ||
|
aba15cf0c3 | ||
|
f422135014 | ||
|
c8a2d8990f | ||
|
e2e0323488 | ||
|
31ff0e480f | ||
|
d0103f0733 | ||
|
46af76750e | ||
|
8a9e84a30a | ||
|
062baa7707 | ||
|
31ed869d05 | ||
|
eb349c0ff9 | ||
|
f4afe538e5 | ||
|
0cb97f56e7 | ||
|
460ea6eef0 | ||
|
f73fdc71b6 | ||
|
398cb19161 | ||
|
1c28c9875e | ||
|
027e79739e | ||
|
2d98bc54f6 | ||
|
11837addf1 | ||
|
185700447e | ||
|
ae91f7e419 | ||
|
9ecc413eb0 | ||
|
6a4242434f |
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -4,5 +4,8 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
time: "8:00"
|
|
||||||
open-pull-requests-limit: 10
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
10
.github/workflows/black.yml
vendored
Normal file
10
.github/workflows/black.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name: Black
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: psf/black@stable
|
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v3
|
||||||
|
4
.github/workflows/pypi.yml
vendored
4
.github/workflows/pypi.yml
vendored
@@ -7,9 +7,9 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags')
|
if: startsWith(github.ref, 'refs/tags')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- name: Install pypa/build
|
- name: Install pypa/build
|
||||||
|
19
.github/workflows/tests.yml
vendored
19
.github/workflows/tests.yml
vendored
@@ -5,21 +5,32 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
python-version: [
|
||||||
|
"3.6",
|
||||||
|
"3.7",
|
||||||
|
"3.8",
|
||||||
|
"3.9",
|
||||||
|
"3.10",
|
||||||
|
]
|
||||||
|
fail-fast: false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install poetry==1.1.6
|
pip install poetry==1.1.13
|
||||||
poetry install
|
poetry install
|
||||||
|
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
run: |
|
run: |
|
||||||
git diff --name-only $TRAVIS_COMMIT_RANGE | xargs poetry run pre-commit run --files
|
git diff --name-only $TRAVIS_COMMIT_RANGE | xargs poetry run pre-commit run --files
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: poetry run make ci
|
run: poetry run make ci
|
||||||
|
@@ -1,4 +1,2 @@
|
|||||||
include rfd/VERSION
|
|
||||||
|
|
||||||
recursive-exclude * __pycache__
|
recursive-exclude * __pycache__
|
||||||
recursive-include tests *
|
recursive-include tests *
|
||||||
|
37
README.md
37
README.md
@@ -7,13 +7,14 @@
|
|||||||
<!-- BEGIN mktoc -->
|
<!-- BEGIN mktoc -->
|
||||||
- [Description](#description)
|
- [Description](#description)
|
||||||
- [Motivation](#motivation)
|
- [Motivation](#motivation)
|
||||||
- [Install](#install)
|
- [Installation](#installation)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [View Hot Deals](#view-hot-deals)
|
- [View Hot Deals](#view-hot-deals)
|
||||||
- [View and Sort Hot Deals](#view-and-sort-hot-deals)
|
- [View and Sort Hot Deals](#view-and-sort-hot-deals)
|
||||||
- [Search](#search)
|
- [Search](#search)
|
||||||
- [Advanced](#advanced)
|
- [Regex](#regex)
|
||||||
- [View Posts](#view-posts)
|
- [View Posts](#view-posts)
|
||||||
|
- [JSON Output](#json-output)
|
||||||
- [Shell Completion](#shell-completion)
|
- [Shell Completion](#shell-completion)
|
||||||
- [bash](#bash)
|
- [bash](#bash)
|
||||||
- [zsh](#zsh)
|
- [zsh](#zsh)
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
This is a CLI utility that allows you to view [Redflagdeals.com](https://forums.redflagdeals.com) on the command line.
|
This is a CLI utility that allows you to view [RedFlagDeals.com](https://forums.redflagdeals.com) on the command line.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -29,10 +30,22 @@ This is a CLI utility that allows you to view [Redflagdeals.com](https://forums.
|
|||||||
|
|
||||||
It is often faster to use a CLI than to load up a web page and navigate web elements. This tool can search for deals and sort them based on score and views. It is also able to load entire threads (without pagination) for additional analysis.
|
It is often faster to use a CLI than to load up a web page and navigate web elements. This tool can search for deals and sort them based on score and views. It is also able to load entire threads (without pagination) for additional analysis.
|
||||||
|
|
||||||
## Install
|
## Installation
|
||||||
|
|
||||||
```bash
|
### pip
|
||||||
pip install rfd
|
|
||||||
|
```sh
|
||||||
|
pip3 install --user rfd
|
||||||
|
```
|
||||||
|
|
||||||
|
This can also be installed with [pipx](https://github.com/pypa/pipx).
|
||||||
|
|
||||||
|
### brew
|
||||||
|
|
||||||
|
If you have [brew](https://brew.sh):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install davegallant/public/rfd
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -80,7 +93,7 @@ rfd threads --sort-by views --pages 10
|
|||||||
rfd search 'pizza'
|
rfd search 'pizza'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Advanced
|
#### Regex
|
||||||
|
|
||||||
Regular expressions can be used for search.
|
Regular expressions can be used for search.
|
||||||
|
|
||||||
@@ -98,6 +111,16 @@ rfd posts https://forums.redflagdeals.com/kobo-vs-kindle-2396227/
|
|||||||
|
|
||||||
This allows for easy grepping and searching for desired expressions.
|
This allows for easy grepping and searching for desired expressions.
|
||||||
|
|
||||||
|
### JSON Output
|
||||||
|
|
||||||
|
All commands support JSON output.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rfd threads --output json
|
||||||
|
```
|
||||||
|
|
||||||
## Shell Completion
|
## Shell Completion
|
||||||
|
|
||||||
Shell completion can be enabled if using `bash` or `zsh`.
|
Shell completion can be enabled if using `bash` or `zsh`.
|
||||||
|
919
poetry.lock
generated
919
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "rfd"
|
name = "rfd"
|
||||||
version = "0.7.0"
|
version = "0.9.0"
|
||||||
description = "view RedFlagDeals.com from the command line"
|
description = "view RedFlagDeals.com from the command line"
|
||||||
authors = ["Dave Gallant <davegallant@gmail.com>"]
|
authors = ["Dave Gallant <davegallant@gmail.com>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
@@ -8,17 +8,20 @@ readme = "README.md"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.6"
|
python = "^3.6"
|
||||||
beautifulsoup4 = "<4.9.4"
|
beautifulsoup4 = ">=4.10.1"
|
||||||
click = ">=7.0"
|
click = ">=7.0"
|
||||||
colorama = ">=0.4.3"
|
colorama = ">=0.4.3"
|
||||||
requests = ">=2.22.0"
|
requests = ">=2.22.0"
|
||||||
soupsieve = "<3.0"
|
soupsieve = "<3.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pre-commit = "1.21.0"
|
pre-commit = "2.1.1"
|
||||||
pylint = "^2.8.2"
|
pylint = "^2.12.0"
|
||||||
pytest = ">=4.6.6"
|
pytest = ">=4.6.6"
|
||||||
rope = "0.19.0"
|
rope = "1.1.1"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
rfd = "rfd.__main__:cli"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
0.7.0
|
|
@@ -1,12 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from os.path import dirname, abspath, join
|
|
||||||
|
|
||||||
|
|
||||||
def load_version():
|
|
||||||
with open(join(dirname(abspath(__file__)), "VERSION")) as handle:
|
|
||||||
return handle.read()
|
|
||||||
|
|
||||||
|
|
||||||
version = load_version()
|
|
40
rfd/api.py
40
rfd/api.py
@@ -27,7 +27,7 @@ def get_safe_per_page(limit):
|
|||||||
|
|
||||||
def create_user_map(users):
|
def create_user_map(users):
|
||||||
"""Create a map of user ids to usernames."""
|
"""Create a map of user ids to usernames."""
|
||||||
m = dict()
|
m = {}
|
||||||
for user in users:
|
for user in users:
|
||||||
m[user.get("user_id")] = user.get("username")
|
m[user.get("user_id")] = user.get("username")
|
||||||
return m
|
return m
|
||||||
@@ -47,12 +47,12 @@ def get_threads(forum_id, pages):
|
|||||||
try:
|
try:
|
||||||
for page in range(1, pages + 1):
|
for page in range(1, pages + 1):
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
"{}/api/topics?forum_id={}&per_page=40&page={}".format(
|
f"{API_BASE_URL}/api/topics?forum_id={forum_id}&per_page=40&page={page}"
|
||||||
API_BASE_URL, forum_id, page
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise Exception("When collecting threads, received a status code: %s" % response.status_code)
|
raise Exception(
|
||||||
|
f"When collecting threads, received a status code: {response.status_code}"
|
||||||
|
)
|
||||||
threads += response.json().get("topics")
|
threads += response.json().get("topics")
|
||||||
except JSONDecodeError as err:
|
except JSONDecodeError as err:
|
||||||
logging.error("Unable to decode threads. %s", err)
|
logging.error("Unable to decode threads. %s", err)
|
||||||
@@ -65,7 +65,7 @@ def get_posts(post):
|
|||||||
Args:
|
Args:
|
||||||
post (str): either full url or postid
|
post (str): either full url or postid
|
||||||
|
|
||||||
Yields:
|
Returns:
|
||||||
list(Post): Posts
|
list(Post): Posts
|
||||||
"""
|
"""
|
||||||
if is_valid_url(post):
|
if is_valid_url(post):
|
||||||
@@ -76,29 +76,33 @@ def get_posts(post):
|
|||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
"{}/api/topics/{}/posts?per_page=40&page=1".format(API_BASE_URL, post_id)
|
f"{API_BASE_URL}/api/topics/{post_id}/posts?per_page=40&page=1"
|
||||||
)
|
)
|
||||||
|
|
||||||
total_pages = response.json().get("pager").get("total_pages")
|
total_pages = response.json().get("pager").get("total_pages")
|
||||||
|
|
||||||
|
posts = []
|
||||||
|
|
||||||
for page in range(0, total_pages + 1):
|
for page in range(0, total_pages + 1):
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
"{}/api/topics/{}/posts?per_page={}&page={}".format(
|
f"{API_BASE_URL}/api/topics/{post_id}/posts?per_page=40&page={page}"
|
||||||
API_BASE_URL, post_id, 40, page
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
users = create_user_map(response.json().get("users"))
|
users = create_user_map(response.json().get("users"))
|
||||||
|
|
||||||
posts = response.json().get("posts")
|
current_posts = response.json().get("posts")
|
||||||
|
|
||||||
for i in posts:
|
for _post in current_posts:
|
||||||
# Sometimes votes is null
|
# Sometimes votes is null
|
||||||
if i.get("votes") is not None:
|
if _post.get("votes") is not None:
|
||||||
calculated_score = calculate_score(i)
|
calculated_score = calculate_score(_post)
|
||||||
else:
|
else:
|
||||||
calculated_score = 0
|
calculated_score = 0
|
||||||
yield Post(
|
posts.append(
|
||||||
body=strip_html(i.get("body")),
|
Post(
|
||||||
score=calculated_score,
|
body=strip_html(_post.get("body")),
|
||||||
user=users[i.get("author_id")],
|
score=calculated_score,
|
||||||
|
user=users[_post.get("author_id")],
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return posts
|
||||||
|
91
rfd/cli.py
91
rfd/cli.py
@@ -3,12 +3,24 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
try:
|
||||||
|
from importlib import metadata
|
||||||
|
except ImportError: # for Python<3.8
|
||||||
|
import importlib_metadata as metadata
|
||||||
from colorama import init
|
from colorama import init
|
||||||
from .api import get_threads, get_posts
|
from .api import get_threads, get_posts
|
||||||
from .threads import parse_threads, search_threads, sort_threads, generate_thread_output
|
from .threads import (
|
||||||
from .posts import generate_posts_output
|
parse_threads,
|
||||||
from .__version__ import version as current_version
|
search_threads,
|
||||||
|
sort_threads,
|
||||||
|
generate_thread_output,
|
||||||
|
ThreadEncoder,
|
||||||
|
)
|
||||||
|
from .posts import generate_posts_output, PostEncoder
|
||||||
|
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
||||||
@@ -18,12 +30,13 @@ logging.getLogger().addHandler(logging.StreamHandler())
|
|||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
return "rfd v" + current_version
|
return "rfd v" + metadata.version("rfd")
|
||||||
|
|
||||||
def print_version(ctx, value):
|
|
||||||
|
def print_version(ctx, _, value):
|
||||||
if not value or ctx.resilient_parsing:
|
if not value or ctx.resilient_parsing:
|
||||||
return
|
return
|
||||||
click.echo(get_version(), nl=False)
|
click.echo(get_version(), nl=True)
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +58,10 @@ def cli(ctx):
|
|||||||
|
|
||||||
@cli.command(short_help="Display all posts in a thread.")
|
@cli.command(short_help="Display all posts in a thread.")
|
||||||
@click.argument("post_id")
|
@click.argument("post_id")
|
||||||
def posts(post_id):
|
@click.option(
|
||||||
|
"--output", default=None, help="Defaults to custom formatting. Other options: json"
|
||||||
|
)
|
||||||
|
def posts(post_id, output):
|
||||||
"""Iterate all pages and display all posts in a thread.
|
"""Iterate all pages and display all posts in a thread.
|
||||||
|
|
||||||
post_id can be a full url or post id only
|
post_id can be a full url or post id only
|
||||||
@@ -57,12 +73,23 @@ def posts(post_id):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
click.echo_via_pager(generate_posts_output(get_posts(post=post_id)))
|
if output == "json":
|
||||||
|
click.echo_via_pager(
|
||||||
|
json.dumps(
|
||||||
|
get_posts(post=post_id),
|
||||||
|
cls=PostEncoder,
|
||||||
|
indent=2,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
click.echo_via_pager(generate_posts_output(get_posts(post=post_id)))
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
click.echo("Invalid post id.")
|
click.echo("Invalid post id.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except AttributeError:
|
except AttributeError as err:
|
||||||
click.echo("The RFD API did not return the expected data.")
|
click.echo("The RFD API did not return the expected data. %s", err)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -70,7 +97,10 @@ def posts(post_id):
|
|||||||
@click.option("--forum-id", default=9, help="The forum id number")
|
@click.option("--forum-id", default=9, help="The forum id number")
|
||||||
@click.option("--pages", default=1, help="Number of pages to show. Defaults to 1.")
|
@click.option("--pages", default=1, help="Number of pages to show. Defaults to 1.")
|
||||||
@click.option("--sort-by", default=None, help="Sort threads by")
|
@click.option("--sort-by", default=None, help="Sort threads by")
|
||||||
def threads(forum_id, pages, sort_by):
|
@click.option(
|
||||||
|
"--output", default=None, help="Defaults to custom formatting. Other options: json"
|
||||||
|
)
|
||||||
|
def threads(forum_id, pages, sort_by, output):
|
||||||
"""Display threads in the specified forum id. Defaults to 9 (hot deals).
|
"""Display threads in the specified forum id. Defaults to 9 (hot deals).
|
||||||
|
|
||||||
Popular forum ids:
|
Popular forum ids:
|
||||||
@@ -87,8 +117,21 @@ def threads(forum_id, pages, sort_by):
|
|||||||
74 \t shopping discussion
|
74 \t shopping discussion
|
||||||
88 \t cell phones
|
88 \t cell phones
|
||||||
"""
|
"""
|
||||||
_threads = sort_threads(parse_threads(get_threads(forum_id, pages)), sort_by=sort_by)
|
_threads = sort_threads(
|
||||||
click.echo_via_pager(generate_thread_output(_threads))
|
parse_threads(get_threads(forum_id, pages)), sort_by=sort_by
|
||||||
|
)
|
||||||
|
if output == "json":
|
||||||
|
|
||||||
|
click.echo_via_pager(
|
||||||
|
json.dumps(
|
||||||
|
sort_threads(_threads, sort_by=sort_by),
|
||||||
|
cls=ThreadEncoder,
|
||||||
|
indent=2,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
click.echo_via_pager(generate_thread_output(_threads))
|
||||||
|
|
||||||
|
|
||||||
@cli.command(short_help="Search deals based on a regular expression.")
|
@cli.command(short_help="Search deals based on a regular expression.")
|
||||||
@@ -97,8 +140,11 @@ def threads(forum_id, pages, sort_by):
|
|||||||
"--forum-id", default=9, help="The forum id number. Defaults to 9 (hot deals)."
|
"--forum-id", default=9, help="The forum id number. Defaults to 9 (hot deals)."
|
||||||
)
|
)
|
||||||
@click.option("--sort-by", default=None, help="Sort threads by")
|
@click.option("--sort-by", default=None, help="Sort threads by")
|
||||||
|
@click.option(
|
||||||
|
"--output", default=None, help="Defaults to custom formatting. Other options: json"
|
||||||
|
)
|
||||||
@click.argument("regex")
|
@click.argument("regex")
|
||||||
def search(pages, forum_id, sort_by, regex):
|
def search(pages, forum_id, sort_by, output, regex):
|
||||||
"""Search deals based on regex.
|
"""Search deals based on regex.
|
||||||
|
|
||||||
Popular forum ids:
|
Popular forum ids:
|
||||||
@@ -121,6 +167,17 @@ def search(pages, forum_id, sort_by, regex):
|
|||||||
_threads = parse_threads(get_threads(forum_id, pages=pages))
|
_threads = parse_threads(get_threads(forum_id, pages=pages))
|
||||||
for thread in search_threads(threads=_threads, regex=regex):
|
for thread in search_threads(threads=_threads, regex=regex):
|
||||||
matched_threads.append(thread)
|
matched_threads.append(thread)
|
||||||
click.echo_via_pager(
|
|
||||||
generate_thread_output(sort_threads(matched_threads, sort_by=sort_by))
|
if output == "json":
|
||||||
)
|
click.echo_via_pager(
|
||||||
|
json.dumps(
|
||||||
|
sort_threads(matched_threads, sort_by=sort_by),
|
||||||
|
indent=2,
|
||||||
|
sort_keys=True,
|
||||||
|
cls=ThreadEncoder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
click.echo_via_pager(
|
||||||
|
generate_thread_output(sort_threads(matched_threads, sort_by=sort_by))
|
||||||
|
)
|
||||||
|
23
rfd/posts.py
23
rfd/posts.py
@@ -1,21 +1,36 @@
|
|||||||
# pylint: disable=old-style-class
|
# pylint: disable=old-style-class
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
from .scores import get_vote_color
|
from .scores import get_vote_color
|
||||||
|
|
||||||
|
|
||||||
class Post:
|
class Post:
|
||||||
def __init__(self, body, score, user):
|
def __init__(self, body, score, user):
|
||||||
self.body = body
|
self.body = body
|
||||||
self.score = score
|
self.score = score
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
|
|
||||||
|
class PostEncoder(json.JSONEncoder):
|
||||||
|
def default(self, o):
|
||||||
|
if isinstance(o, Post):
|
||||||
|
return dict(
|
||||||
|
body=o.body,
|
||||||
|
score=o.score,
|
||||||
|
user=o.user,
|
||||||
|
)
|
||||||
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
def get_terminal_width():
|
def get_terminal_width():
|
||||||
_, columns = os.popen("stty size", "r").read().split()
|
_, columns = os.popen("stty size", "r").read().split()
|
||||||
return int(columns)
|
return int(columns)
|
||||||
|
|
||||||
|
|
||||||
def generate_posts_output(posts):
|
def generate_posts_output(posts):
|
||||||
output = ""
|
output = ""
|
||||||
output += ("-" * get_terminal_width())
|
output += "-" * get_terminal_width()
|
||||||
for post in posts:
|
for post in posts:
|
||||||
output += (
|
output += (
|
||||||
" -"
|
" -"
|
||||||
@@ -23,10 +38,10 @@ def generate_posts_output(posts):
|
|||||||
+ Fore.RESET
|
+ Fore.RESET
|
||||||
+ post.body
|
+ post.body
|
||||||
+ Fore.YELLOW
|
+ Fore.YELLOW
|
||||||
+ " ({})".format(post.user)
|
+ f" ({post.user})"
|
||||||
)
|
)
|
||||||
output += (Style.RESET_ALL)
|
output += Style.RESET_ALL
|
||||||
output += "\n"
|
output += "\n"
|
||||||
output += ("-" * get_terminal_width())
|
output += "-" * get_terminal_width()
|
||||||
output += "\n"
|
output += "\n"
|
||||||
return output
|
return output
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import re
|
import re
|
||||||
|
import json
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
from . import API_BASE_URL
|
from . import API_BASE_URL
|
||||||
from .scores import calculate_score, get_vote_color
|
from .scores import calculate_score, get_vote_color
|
||||||
|
|
||||||
# pylint: disable=old-style-class
|
|
||||||
class Thread:
|
class Thread:
|
||||||
def __init__(self, title, dealer_name, score, url, views):
|
def __init__(self, title, dealer_name, score, url, views):
|
||||||
self.dealer_name = dealer_name
|
self.dealer_name = dealer_name
|
||||||
@@ -13,11 +14,24 @@ class Thread:
|
|||||||
self.views = views
|
self.views = views
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Thread(%s)" % self.title
|
return f"Thread({self.title})"
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadEncoder(json.JSONEncoder):
|
||||||
|
def default(self, o):
|
||||||
|
if isinstance(o, Thread):
|
||||||
|
return dict(
|
||||||
|
dealer_name=o.dealer_name,
|
||||||
|
score=o.score,
|
||||||
|
title=o.title,
|
||||||
|
url=o.url,
|
||||||
|
views=o.views,
|
||||||
|
)
|
||||||
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
def build_web_path(slug):
|
def build_web_path(slug):
|
||||||
return "{}{}".format(API_BASE_URL, slug)
|
return f"{API_BASE_URL}{slug}"
|
||||||
|
|
||||||
|
|
||||||
def get_dealer(topic):
|
def get_dealer(topic):
|
||||||
@@ -88,12 +102,12 @@ def generate_thread_output(threads):
|
|||||||
+ "."
|
+ "."
|
||||||
+ get_vote_color(thread.score)
|
+ get_vote_color(thread.score)
|
||||||
+ Fore.RESET
|
+ Fore.RESET
|
||||||
+ "%s%s" % (dealer, thread.title)
|
+ f"{dealer}{thread.title}"
|
||||||
+ Fore.LIGHTYELLOW_EX
|
+ Fore.LIGHTYELLOW_EX
|
||||||
+ " (%d views)" % thread.views
|
+ f" ({thread.views} views)"
|
||||||
+ Fore.RESET
|
+ Fore.RESET
|
||||||
)
|
)
|
||||||
output += Fore.BLUE + " {}".format(thread.url)
|
output += Fore.BLUE + f" {thread.url}"
|
||||||
output += Style.RESET_ALL
|
output += Style.RESET_ALL
|
||||||
output += "\n\n"
|
output += "\n\n"
|
||||||
yield output
|
yield output
|
||||||
|
Reference in New Issue
Block a user