Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
824a439158 | |||
7943467f1c | |||
55c3307b74 | |||
f54c0af88c | |||
e27edba78e | |||
8ab616e5fd | |||
1749a8659a | |||
5592c0e7cd | |||
b8a482f86c |
6 changed files with 796 additions and 825 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -128,4 +128,5 @@ dmypy.json
|
|||
# Pyre type checker
|
||||
.pyre/
|
||||
setup.py
|
||||
utils
|
||||
utils
|
||||
praw.ini
|
||||
|
|
1313
poetry.lock
generated
1313
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "src"
|
||||
version = "0.2.2"
|
||||
version = "0.5.0"
|
||||
description = ""
|
||||
authors = ["Joe Kaufeld <opensource@joekaufeld.com>"]
|
||||
|
||||
|
@ -11,9 +11,12 @@ httpx = "^0.23.0"
|
|||
click = "^8.1.3"
|
||||
rich = "^12.5.1"
|
||||
markdownify = "^0.11.6"
|
||||
praw = "^7.7.0"
|
||||
html5lib = "^1.1"
|
||||
lxml = "^6.0.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
poetry = "^1.1.14"
|
||||
[poetry.group.dev.dependencies]
|
||||
poetry = "^2"
|
||||
black = "^22.6.0"
|
||||
|
||||
[build-system]
|
||||
|
|
109
src/cli.py
109
src/cli.py
|
@ -6,14 +6,16 @@ import uuid
|
|||
|
||||
import click
|
||||
import httpx
|
||||
from praw import Reddit
|
||||
from rich import pretty
|
||||
from rich.status import Status
|
||||
from rich.traceback import install
|
||||
from shiv.bootstrap import current_zipfile
|
||||
|
||||
import src
|
||||
from src.helpers import flip_char
|
||||
from src.joplin import process_joplin_posts
|
||||
from src.helpers import flip_char, load_config, write_config
|
||||
from src.joplin import process_joplin_posts, get_folders, BASE_URL
|
||||
from src.wordle import wordle
|
||||
from src.art import BANNERS
|
||||
|
||||
|
||||
|
@ -26,12 +28,12 @@ from src.art import BANNERS
|
|||
@click.option(
|
||||
"--update",
|
||||
is_flag=True,
|
||||
help="Check Gitea for a new version and auto-update.",
|
||||
help="Check Forgejo for a new version and auto-update.",
|
||||
)
|
||||
def main(ctx, update):
|
||||
"""Launch a utility or drop into a command line REPL if no command is given."""
|
||||
if update:
|
||||
update_from_gitea()
|
||||
update_from_forgejo()
|
||||
sys.exit()
|
||||
elif ctx.invoked_subcommand is None:
|
||||
banner = random.choice(BANNERS)
|
||||
|
@ -115,8 +117,100 @@ def beautify(words: list[str]):
|
|||
click.echo("".join(new_beautiful_string))
|
||||
|
||||
|
||||
def update_from_gitea():
|
||||
"""Get the newest release from Gitea and install it."""
|
||||
@main.command()
|
||||
def get_saved_from_reddit() -> None:
|
||||
"""Get saved posts from reddit."""
|
||||
|
||||
def _process(item):
|
||||
fullname = item.fullname
|
||||
if fullname.startswith("t3"):
|
||||
# we got a post
|
||||
title = f"{item.subreddit.display_name} - {item.title}"
|
||||
body = item.selftext if item.selftext else item.url
|
||||
elif fullname.startswith("t1"):
|
||||
# comment time
|
||||
try:
|
||||
author_name = item.author.name
|
||||
except AttributeError:
|
||||
author_name = "[deleted]"
|
||||
title = (
|
||||
f"{item.submission.subreddit.display_name} - {item.submission.title}"
|
||||
)
|
||||
body = f"Comment from {author_name}:\n\n{item.body}"
|
||||
else:
|
||||
click.echo(f"Not sure how to process https://reddit.com{item.permalink}")
|
||||
return
|
||||
|
||||
if item.over_18:
|
||||
title = "🔴 " + title
|
||||
body = "https://reddit.com" + item.permalink + "\n\n" + body
|
||||
|
||||
joplin = BASE_URL + cfg["JOPLIN_PORT"]
|
||||
notes_url = joplin + f"/notes/?token={cfg.get('JOPLIN_TOKEN')}"
|
||||
click.echo(f"Processing {title}...")
|
||||
httpx.post(
|
||||
notes_url,
|
||||
json={
|
||||
"title": title,
|
||||
"body": body,
|
||||
"parent_id": cfg.get("joplin_saved_posts_folder_id"),
|
||||
},
|
||||
)
|
||||
item.unsave()
|
||||
|
||||
cfg = load_config()
|
||||
# because 2fa is enabled, we have to get the code first
|
||||
twofa_code = click.prompt("What's the current 2fa code?", type=str)
|
||||
if not cfg.get("REDDIT_CLIENT_ID"):
|
||||
cfg["REDDIT_CLIENT_ID"] = ""
|
||||
if not cfg.get("REDDIT_CLIENT_SECRET"):
|
||||
cfg["REDDIT_CLIENT_SECRET"] = ""
|
||||
if not cfg.get("REDDIT_PASSWORD"):
|
||||
cfg["REDDIT_PASSWORD"] = ""
|
||||
if not cfg.get("REDDIT_USERNAME"):
|
||||
cfg["REDDIT_USERNAME"] = ""
|
||||
if not cfg.get("joplin_saved_posts_folder_id"):
|
||||
cfg["joplin_saved_posts_folder_id"] = ""
|
||||
|
||||
write_config(cfg)
|
||||
|
||||
username = cfg.get("REDDIT_USERNAME", "")
|
||||
|
||||
r = Reddit(
|
||||
client_id=cfg.get("REDDIT_CLIENT_ID", ""),
|
||||
client_secret=cfg.get("REDDIT_CLIENT_SECRET", ""),
|
||||
username=username,
|
||||
password=f"{cfg.get('REDDIT_PASSWORD', '')}:{twofa_code}",
|
||||
user_agent=f"getter of saved posts, u/{username}",
|
||||
)
|
||||
|
||||
try:
|
||||
r.user.me()
|
||||
except Exception as e:
|
||||
click.echo(f"Cannot connect to reddit: {e}")
|
||||
return
|
||||
|
||||
if not cfg.get("joplin_saved_posts_folder_id"):
|
||||
folders = get_folders()
|
||||
click.echo("Pick the folder that I should write your saved posts to:")
|
||||
for count, option in enumerate(folders["items"]):
|
||||
click.echo(f"{count} - {option['title']}")
|
||||
folder_position = click.prompt("Number of folder?", type=int)
|
||||
folder_name = folders["items"][folder_position]["title"]
|
||||
folder_id = folders["items"][folder_position]["id"]
|
||||
|
||||
click.echo(f"Got it, will write to {folder_name}, ID {folder_id}.")
|
||||
cfg["joplin_saved_posts_folder_id"] = folder_id
|
||||
write_config(cfg)
|
||||
|
||||
for _ in range(10):
|
||||
click.echo("Getting new posts...")
|
||||
for item in r.user.me().saved(limit=None):
|
||||
_process(item)
|
||||
|
||||
|
||||
def update_from_forgejo():
|
||||
"""Get the newest release from Forgejo and install it."""
|
||||
status = Status("Checking for new release...")
|
||||
status.start()
|
||||
|
||||
|
@ -126,7 +220,7 @@ def update_from_gitea():
|
|||
if response.status_code != 200:
|
||||
status.stop()
|
||||
click.echo(
|
||||
f"Something went wrong when talking to Gitea; got a"
|
||||
f"Something went wrong when talking to Forgejo; got a"
|
||||
f" {response.status_code} with the following content:\n"
|
||||
f"{response.content}"
|
||||
)
|
||||
|
@ -150,6 +244,7 @@ def update_from_gitea():
|
|||
status.stop()
|
||||
click.echo(f"Updated to {release_data['tag_name']}! 🎉")
|
||||
|
||||
main.add_command(wordle)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -105,18 +105,20 @@ def process_joplin_posts():
|
|||
httpx.get(
|
||||
joplin
|
||||
+ f"/notes/{blob['id']}?fields=body&token={c.get('JOPLIN_TOKEN')}"
|
||||
).json()['body'].strip()
|
||||
)
|
||||
.json()["body"]
|
||||
.strip()
|
||||
)
|
||||
click.echo(f"Processing {url}...")
|
||||
site = httpx.get(url, follow_redirects=True)
|
||||
soup = bs4.BeautifulSoup(site.content, features="html5lib")
|
||||
# clean up that schizz
|
||||
title = soup.title.text
|
||||
[t.extract() for t in soup(['script', 'head', 'style'])]
|
||||
[t.extract() for t in soup(["script", "head", "style"])]
|
||||
body = md(str(soup))
|
||||
httpx.put(
|
||||
joplin + f"/notes/{blob['id']}?token={c.get('JOPLIN_TOKEN')}",
|
||||
json={'title': title, 'body': body}
|
||||
json={"title": title, "body": body},
|
||||
)
|
||||
|
||||
click.echo(resp)
|
||||
|
|
179
src/wordle.py
Normal file
179
src/wordle.py
Normal file
|
@ -0,0 +1,179 @@
|
|||
import random
|
||||
|
||||
import click
|
||||
import httpx
|
||||
from rich.status import Status
|
||||
|
||||
from src.helpers import base_folder
|
||||
|
||||
|
||||
all_valid_guesses = "https://gist.githubusercontent.com/itsthejoker/7d4c0d10a4e97cc7493e765d9e848a53/raw/b3970cc79bf88c872dd67c3896d8a8673377b0fd/wordle-nyt-allowed-guesses-update-12546.txt"
|
||||
all_answers = "https://gist.githubusercontent.com/itsthejoker/1c7b8b97c454ce464aa5ea7f187d81e9/raw/1a2e59929b833925af800298d1003e48d90b5898/wordle-nyt-answers-alphabetical.txt"
|
||||
answers_path = base_folder / "opsbox_wordle_answers.txt"
|
||||
guesses_path = base_folder / "opsbox_wordle_guesses.txt"
|
||||
|
||||
|
||||
def download_wordle_data():
|
||||
with httpx.Client() as client:
|
||||
with open(answers_path, "w") as f:
|
||||
f.write(client.get(all_answers).text)
|
||||
with open(guesses_path, "w") as f:
|
||||
f.write(client.get(all_valid_guesses).text)
|
||||
|
||||
|
||||
def check_if_need_to_download_data():
|
||||
if not answers_path.exists() or not guesses_path.exists():
|
||||
download_wordle_data()
|
||||
|
||||
|
||||
def load_data():
|
||||
status = Status("Setting up game...")
|
||||
status.start()
|
||||
|
||||
check_if_need_to_download_data()
|
||||
with open(answers_path, "r") as f:
|
||||
answers = f.read().splitlines()
|
||||
with open(guesses_path, "r") as f:
|
||||
guesses = f.read().splitlines()
|
||||
guesses += answers # answers are valid guesses
|
||||
|
||||
status.stop()
|
||||
return answers, guesses
|
||||
|
||||
|
||||
def print_keyboard(guessed_word_list: list[str], keyboard_display: dict[str, int]):
|
||||
first_row = "qwertyuiop"
|
||||
second_row = "asdfghjkl"
|
||||
third_row = "zxcvbnm"
|
||||
click.echo()
|
||||
all_letters_guessed = set("".join(guessed_word_list))
|
||||
for count, item in enumerate([first_row, second_row, third_row]):
|
||||
click.echo(" " * count if count != 2 else " " * 3, nl=False)
|
||||
for letter in item:
|
||||
click.echo(" ", nl=False)
|
||||
if letter in all_letters_guessed and keyboard_display.get(letter) == 2:
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="green", bold=True), nl=False
|
||||
)
|
||||
elif letter in all_letters_guessed and keyboard_display.get(letter) == 1:
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="yellow", bold=True), nl=False
|
||||
)
|
||||
elif letter in all_letters_guessed:
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="white", bold=True), nl=False
|
||||
)
|
||||
else:
|
||||
click.echo(click.style(letter.capitalize(), fg="white"), nl=False)
|
||||
click.echo("\n")
|
||||
|
||||
|
||||
def print_word_matrix(
|
||||
answer: str,
|
||||
letter_counts: dict[str, int],
|
||||
guessed_word_list: list[str],
|
||||
keyboard_display: dict[str, int],
|
||||
) -> None:
|
||||
for guess in guessed_word_list:
|
||||
guess_letter_counts = {}
|
||||
click.echo(" ", nl=False)
|
||||
for count, letter in enumerate(guess):
|
||||
add_newline = count == 4
|
||||
guess_letter_counts[letter] = guess_letter_counts.get(letter, 0) + 1
|
||||
if letter == answer[count]:
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="green", bold=True),
|
||||
nl=add_newline,
|
||||
)
|
||||
keyboard_display[letter] = 2
|
||||
elif (
|
||||
letter in answer
|
||||
and guess_letter_counts[letter] <= letter_counts[letter]
|
||||
):
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="yellow", bold=True),
|
||||
nl=add_newline,
|
||||
)
|
||||
|
||||
if keyboard_display.get(letter) != 2:
|
||||
keyboard_display[letter] = 1
|
||||
elif (
|
||||
letter in answer and guess_letter_counts[letter] > letter_counts[letter]
|
||||
):
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="white", bold=True),
|
||||
nl=add_newline,
|
||||
)
|
||||
else:
|
||||
click.echo(
|
||||
click.style(letter.capitalize(), fg="white", bold=True),
|
||||
nl=add_newline,
|
||||
)
|
||||
if len(guessed_word_list) < 6 and answer not in guessed_word_list:
|
||||
click.echo()
|
||||
click.echo("You have ", nl=False)
|
||||
click.echo(
|
||||
click.style(str(6 - len(guessed_word_list)), fg="white", bold=True),
|
||||
nl=False,
|
||||
)
|
||||
click.echo(f" guess{'es' if len(guessed_word_list) != 4 else ''} left.")
|
||||
|
||||
|
||||
def get_unique(sequence):
|
||||
seen = set()
|
||||
return [x for x in sequence if not (x in seen or seen.add(x))]
|
||||
|
||||
|
||||
@click.command(help="Start a new game of Wordle!")
|
||||
@click.option("--debug", is_flag=True, help="Debug mode")
|
||||
def wordle(debug):
|
||||
click.echo()
|
||||
click.echo("Starting a new game of Wordle!")
|
||||
click.echo("You have six tries to find a 5 letter word.")
|
||||
click.echo(
|
||||
"Gray letters are not in the word, yellow letters are in the word but in the"
|
||||
" wrong place, and green letters are in the right place."
|
||||
)
|
||||
click.echo("Good luck!")
|
||||
click.echo()
|
||||
answers, guesses = load_data()
|
||||
|
||||
todays_word = random.choice(answers)
|
||||
# every key is a letter, the value is 1 if it's yellow and 2 if it's green
|
||||
keyboard_display = {}
|
||||
if debug:
|
||||
click.echo(f"Today's word is {todays_word}")
|
||||
|
||||
guessed_words = []
|
||||
letter_set = get_unique(todays_word)
|
||||
letter_counts = {letter: todays_word.count(letter) for letter in letter_set}
|
||||
|
||||
while True:
|
||||
if len(guessed_words) == 6:
|
||||
click.echo("You lose!")
|
||||
click.echo("The word was: ", nl=False)
|
||||
click.echo(click.style(str(6 - len(todays_word)), fg="white", bold=True))
|
||||
click.echo("Better luck next time!")
|
||||
break
|
||||
|
||||
guess = click.prompt("Enter a guess")
|
||||
guess = guess.lower()
|
||||
click.echo()
|
||||
|
||||
if len(guess) < 5 or len(guess) > 5:
|
||||
click.echo("Guesses must be 5 letters.")
|
||||
continue
|
||||
|
||||
if guess not in guesses:
|
||||
click.echo("Sorry, that's not a valid guess. Try again!")
|
||||
continue
|
||||
|
||||
guessed_words += [guess]
|
||||
|
||||
print_word_matrix(todays_word, letter_counts, guessed_words, keyboard_display)
|
||||
print_keyboard(guessed_words, keyboard_display)
|
||||
|
||||
click.echo()
|
||||
if guess == todays_word:
|
||||
click.echo("You win!")
|
||||
break
|
Loading…
Add table
Reference in a new issue