creddit 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
creddit/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ creddit - A CLI client for Reddit
3
+ """
4
+
5
+ from .terminal import run as run
creddit/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from creddit.terminal import run
2
+
3
+ if __name__ == "__main__":
4
+ run()
creddit/config.py ADDED
@@ -0,0 +1,113 @@
1
+ """
2
+ A file that stores all the variables that much be read from the env file. Helps me keep track of them at one place, instead of reading and using them where they are needed. This application relies on env for config.
3
+
4
+ Config is store in a JSON file. TOML, YAML, and ini were options but
5
+
6
+ TOML - built in library can't write. Don't want to package another module.
7
+ YAML - Built in and ugly
8
+ .ini - Ugly
9
+
10
+ """
11
+
12
+ from json import JSONDecodeError
13
+ from json import dump as json_dump
14
+ from json import load as json_load
15
+ from os import getenv
16
+ from pathlib import Path
17
+ from sys import platform
18
+ from typing import Literal, NotRequired, TypedDict
19
+
20
+
21
+ class Config(TypedDict):
22
+ ignored_users: list[str]
23
+ no_of_posts_to_print: int
24
+ ignore_all_mod_posts: bool
25
+ default_subreddit: NotRequired[str]
26
+
27
+
28
+ default_config: Config = {
29
+ "ignored_users": ["2soccer2bot", "AutoModerator"],
30
+ "no_of_posts_to_print": 10,
31
+ "ignore_all_mod_posts": True,
32
+ }
33
+
34
+ config_folder: Path
35
+
36
+ if platform == "win32":
37
+ config_folder = Path(getenv("LOCALAPPDATA", "./config/"))
38
+ elif platform == "darwin":
39
+ config_folder = Path("~/Library/Application Support/")
40
+ elif platform == "linux":
41
+ config_folder = Path("~/.config/")
42
+ else:
43
+ config_folder = Path("./config/")
44
+
45
+ config_folder = config_folder / "creddit"
46
+ config_folder.mkdir(parents=True, exist_ok=True)
47
+
48
+ # It's actually a cursed thing that you can divide paths.
49
+ config_path: Path = config_folder / "config.json"
50
+ """The path of the config"""
51
+
52
+
53
+ def check_config_existence() -> bool:
54
+ """Checks if a config file exists, and that it valid
55
+
56
+ Returns:
57
+ bool: True if file exists and contains all required keys
58
+ """
59
+ if not Path(config_path).exists():
60
+ return False
61
+
62
+ with open(config_path, "r") as f:
63
+ try:
64
+ config = json_load(f)
65
+ except JSONDecodeError:
66
+ return False
67
+ if not (config or (default_config.keys() < config.keys())):
68
+ return False
69
+
70
+ return True
71
+
72
+
73
+ def read_config(config_path: Path = config_path) -> Config:
74
+ """Read the config file"""
75
+
76
+ with open(config_path, "r") as f:
77
+ config: Config = json_load(f)
78
+
79
+ required_keys = default_config.keys()
80
+
81
+ if required_keys <= config.keys():
82
+ # Looks like all keys are present
83
+ return config
84
+
85
+ raise RuntimeError("Looks like some values in the config file are missing")
86
+
87
+
88
+ def create_config(c: dict | Config = default_config) -> bool:
89
+ required_keys = default_config.keys()
90
+
91
+ if required_keys <= c.keys():
92
+ # All the must have keys are present
93
+ with open(config_path, "w+") as f:
94
+ json_dump(c, f, indent=4, sort_keys=True)
95
+ return True
96
+
97
+ return False
98
+
99
+
100
+ def edit_config(new_config: dict) -> bool:
101
+ c = read_config()
102
+ key: Literal[
103
+ "ignored_users",
104
+ "no_of_posts_to_print",
105
+ "ignore_all_mod_posts",
106
+ "default_subreddit",
107
+ ]
108
+ for key in new_config:
109
+ c[key] = new_config[key]
110
+ with open(config_path, "w") as f:
111
+ json_dump(c, f, indent=4, sort_keys=True)
112
+ return True
113
+ return False
creddit/py.typed ADDED
File without changes
creddit/reddit.py ADDED
@@ -0,0 +1,123 @@
1
+ """
2
+ Manage API connections with and responses from Reddit
3
+ """
4
+
5
+ from functools import lru_cache
6
+
7
+ from requests import get as r_get
8
+ from requests.exceptions import ConnectionError
9
+
10
+
11
+ @lru_cache(maxsize=None)
12
+ def get_api_response(url: str) -> dict:
13
+ """
14
+ Returns the json-encoded content of the api response, by requesting the given URL using the built-in requests library.
15
+ Parameters:
16
+ url (str):URL to load using requests library
17
+
18
+ Returns:
19
+ requests.Response
20
+ """
21
+
22
+ # Caching the resposes for every URL, since I might be calling it again and again for posts/comments, etc, and the response doesn't really change in a few minutes. When the application is run next (after a few hours, days, weeks), it would be running as a fresh instance and the response would be fetched again.
23
+
24
+ headers = {
25
+ "User-Agent": "cli:reddit-cli:v1.0.0 (by /u/vishalnandagopal)"
26
+ # "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/130.0"
27
+ }
28
+ try:
29
+ api_response = r_get(url=url, headers=headers)
30
+ if api_response.status_code != 200:
31
+ raise RuntimeError(f"Error with API - \n{api_response}")
32
+ return api_response.json()
33
+ except ConnectionError:
34
+ raise ConnectionError("You seem to be offline. Try connecting to a network.")
35
+
36
+
37
+ def get_posts_in_a_subreddit(
38
+ subreddit: str, limit: int = 10, after_post: str | None = None
39
+ ) -> list[dict]:
40
+ """
41
+ Constructs the api_url to load the subreddit, and loads the api_response and returns it as a dict.
42
+
43
+ Parameters:
44
+ limit (int):number of posts that should be present in the json response.
45
+
46
+ after_post (int):The posts that are after this id in the feed are loaded.
47
+
48
+ For example if you give the ID of the 9th post (eg: yjbttz) and limit=10, the posts 10,11,12 and so on till 19 are loaded.
49
+
50
+ Returns
51
+ Dict: Subreddit response as key value pair of index and post_details.
52
+ """
53
+
54
+ subreddit_url = f"https://api.reddit.com/r/{subreddit}?limit={limit}"
55
+
56
+ if after_post:
57
+ subreddit_url += f"&after=t3_{after_post}"
58
+
59
+ subreddit_response = get_api_response(subreddit_url)
60
+ return subreddit_response["data"]["children"]
61
+
62
+
63
+ def get_post_dict(post_id) -> dict:
64
+ """
65
+ Constructs the api_url to load the comments of a post, and loads the api_response and returns it as a tuple, with the first element being the post text, and the second element being a dict of the comments.
66
+
67
+ Parameters:
68
+ post_id (str):The ID of the post to load comments for. Eg: yjbttz.
69
+
70
+ Returns:
71
+ dict: The JSON response of the API, parsed as a dict
72
+ """
73
+
74
+ # LRU caching this function doesn't really help, since it only calls the API and returns it. The API function is already cached using the same lru_cache decorator.
75
+ post_url = f"https://api.reddit.com/{post_id}"
76
+ return get_api_response(post_url)
77
+
78
+
79
+ def get_post_text(post_id: str) -> str:
80
+ """
81
+ Fetches the text of the post, if any
82
+
83
+ Parameters:
84
+ post_id (str): The post ID
85
+
86
+ Returns:
87
+ str: The text of the post
88
+ """
89
+ try:
90
+ return get_post_dict(post_id)[0]["data"]["children"][0]["data"]["selftext"]
91
+ except KeyError:
92
+ return ""
93
+
94
+
95
+ def get_comments_dict(post_id: str) -> dict[int, dict]:
96
+ """
97
+ Fetches the dict for the post and returns only a tuple of the text in the post (if any), and the a dict of the comments. the api_url to load the comments of a post, and loads the api_response and returns it as a tuple, with the first element being the post text, and the second element being a dict of the comments.
98
+
99
+ Parameters:
100
+ post_id (str):The ID of the post to load comments for. Eg: yjbttz.
101
+
102
+ Returns:
103
+ dict: The comments dict. Key value pair of int and each comment's dict
104
+ """
105
+ post_comments_response = get_post_dict(post_id)
106
+ return post_comments_response[1]["data"]["children"]
107
+
108
+
109
+ def get_link_in_post(post_id: str) -> str:
110
+ """
111
+ Fetches the link in the post. If it is a reddit video, it sends the fallback URL so that it links directly to the video. Reddit redirects video links to the post page, which is unecessary
112
+
113
+ Parameters:
114
+ post_id (str): The ID of the post to fetch the posted link
115
+
116
+ Returns:
117
+ str: The link
118
+ """
119
+ _ = get_post_dict(post_id)[0]["data"]["children"][0]["data"]
120
+ if _["is_video"]:
121
+ return _["media"]["reddit_video"]["fallback_url"]
122
+ else:
123
+ return _["url"]
creddit/terminal.py ADDED
@@ -0,0 +1,367 @@
1
+ """
2
+ Handle all terminal functions, such as printing using different colors, taking care of user input after it, etc
3
+ """
4
+
5
+ from html import unescape
6
+ from subprocess import Popen as background_run_command
7
+ from subprocess import run as run_command
8
+ from textwrap import TextWrapper
9
+ from typing import Any, Dict
10
+ from urllib.parse import urlparse
11
+ from webbrowser import open as web_open
12
+
13
+ from colorama import Fore
14
+
15
+ from .config import Config, check_config_existence, create_config, read_config
16
+ from .reddit import get_link_in_post, get_post_dict, get_posts_in_a_subreddit
17
+
18
+ colors_to_use_in_terminal: tuple[str, ...] = (
19
+ Fore.RED,
20
+ Fore.GREEN,
21
+ Fore.YELLOW,
22
+ Fore.CYAN,
23
+ Fore.MAGENTA,
24
+ )
25
+ # Fore.BLUE is very ugly in the terminal. Ineligible
26
+
27
+ num_of_colors = len(colors_to_use_in_terminal)
28
+
29
+ ending_separator = f"\n{'-' * 116}\n"
30
+
31
+ config: Config
32
+
33
+
34
+ ignored_users: list[str]
35
+ """Posts by these users won't be printed"""
36
+
37
+
38
+ no_of_posts_to_print: int
39
+ """The number of posts to print each time"""
40
+
41
+
42
+ ignore_all_mod_posts: bool
43
+ """Whether to ignore mod posts. Currently not implemented"""
44
+
45
+
46
+ def introduce() -> None:
47
+ """
48
+ User using the app for the first time? Call this!
49
+ """
50
+
51
+ print(
52
+ f"Welcome to {Fore.RED}cReddit{Fore.RESET}! Looks like you're using this app for a first time"
53
+ )
54
+ if input(
55
+ f"Would you like set the config (default subreddits, ignored users, etc) [Y/n] [{Fore.GREEN}Y{Fore.RESET}]"
56
+ ).casefold() in {"", "y"}:
57
+ c: Dict[str, Any] = dict()
58
+
59
+ # No of posts of print
60
+ c["no_of_posts_to_print"] = int(
61
+ input(f"No of posts to print in the terminal? [{Fore.GREEN}10{Fore.RESET}]")
62
+ or 10
63
+ )
64
+
65
+ # Does the user want to ignore any users?
66
+ c["ignored_users"] = input(
67
+ f"Users you wish to ignore? (Eg: Automoderator, 2soccer2bot). Separate multiple values with a comma [{Fore.GREEN}2soccer2bot,AutoModerator{Fore.RESET}]"
68
+ ).split(",")
69
+ if c["ignored_users"] == [""]:
70
+ c["ignored_users"] = ["2soccer2bot", "AutoModerator"]
71
+ c["ignore_all_mod_posts"] = True
72
+
73
+ # Maybe the user wants to open a subreddit by default?
74
+ if input(
75
+ f"Would you like to open a subreddit by default every time? [Y/n] [{Fore.GREEN}Y{Fore.RESET}]"
76
+ ).casefold() in {"", "y"}:
77
+ _ = input("Enter default subreddit - r/")
78
+ while not _:
79
+ _ = input("Enter default subreddit - r/")
80
+
81
+ c["default_subreddit"] = _
82
+
83
+ create_config(c)
84
+ else:
85
+ create_config()
86
+
87
+
88
+ def exit_terminal(error: Exception | None) -> None:
89
+ """
90
+ Exits the terminal and resets the terminal color to the default color:
91
+ """
92
+
93
+ print(Fore.RESET)
94
+ if error:
95
+ print(repr(error))
96
+ exit()
97
+
98
+
99
+ def take_input_after_sub_print(text: str) -> str:
100
+ """
101
+ After a subreddit's post has been printed, the user's input is taken. He can give any of the 4 as input
102
+ 1. Empty input (by just entering) - Will reach more posts
103
+ 2. Post number - Will open the comments for that post
104
+ 3. Postnumber and "o" (Like "1o") to open the post in the web browser and to read the comments
105
+ 4. r/subreddit_name - Indicates he wants to stop reading this subreddit and continue to another one.
106
+
107
+ Parameters:
108
+ text (str): The text to print while taking input
109
+
110
+ Returns:
111
+ str: The input given
112
+ """
113
+ try:
114
+ user_choice = input(text).casefold()
115
+ if not user_choice:
116
+ return ""
117
+ if (user_choice.endswith("o")) and not user_choice[:-1].isnumeric():
118
+ raise ValueError("Enter a valid number before the o")
119
+ if (user_choice.startswith("r/")) and (not user_choice.isalpha()):
120
+ raise ValueError(
121
+ "Please enter the name of the subreddit as r/subreddit_name"
122
+ )
123
+ if (
124
+ not (user_choice.endswith("o") or user_choice.startswith("r/"))
125
+ and not user_choice.isnumeric()
126
+ ):
127
+ raise ValueError(
128
+ 'Please enter a valid number to read the comments, "number0" to open the link, or r/subreddit_name to read posts from a different sub'
129
+ )
130
+ return user_choice
131
+ except ValueError as e:
132
+ print(e)
133
+ return take_input_after_sub_print(text)
134
+ except (KeyboardInterrupt, EOFError):
135
+ print("\nExited the program")
136
+ exit_terminal(error=None)
137
+ except Exception as e:
138
+ exit_terminal(error=e)
139
+ return ""
140
+
141
+
142
+ def handle_user_choice_after_a_post(
143
+ printed_posts: list[str], titles: list[str], subreddit: str, start_count: int = 0
144
+ ) -> None:
145
+ """
146
+ After a subs posts are printed, take input using another function and handle it accordingly. Open the comments, the link or another subreddit
147
+
148
+ Parameters:
149
+ printed_posts (list[str]): The list of all post_ids currently printed in the terminal. Used to handle input according to he number the user chooses.
150
+ titles: (list[str]): The titles of all the posts currently printed in the terminal. Used to quickly print it before fetching comments.
151
+ subreddit (str): To continue after the last post, in case the user wants to read more posts from the subreddit
152
+ """
153
+ user_choice = take_input_after_sub_print(
154
+ """Enter the post number you want to read the comments for, or click enter to read more posts:\nTo open link and also view comments, type "o" after the number. Like "2o": """
155
+ )
156
+ if user_choice.endswith("o") or user_choice.isnumeric():
157
+ if user_choice.endswith("o"):
158
+ # Wants to open the post before reading comments
159
+
160
+ num_choice = int(user_choice[:-1])
161
+ open_post_link(printed_posts[num_choice - 1])
162
+ elif user_choice.isnumeric():
163
+ # Wants to read comments of a post
164
+
165
+ num_choice = int(user_choice)
166
+ if num_choice > len(printed_posts) or num_choice < 1:
167
+ print(
168
+ f"{Fore.RED}Number must be >= 1 and <= {len(printed_posts)}{Fore.RESET}"
169
+ )
170
+ return handle_user_choice_after_a_post(
171
+ printed_posts, titles, subreddit, start_count
172
+ )
173
+
174
+ # Colored post title
175
+ post_title = f"{colors_to_use_in_terminal[(num_choice - 1) % num_of_colors]}{titles[num_choice - 1]}{Fore.RESET}"
176
+ print(post_title)
177
+
178
+ print_post_comments(printed_posts[num_choice - 1])
179
+ handle_user_choice_after_a_post(printed_posts, titles, subreddit, start_count)
180
+ else:
181
+ # Wants to read more posts from the same subreddit
182
+ last_post_id = printed_posts[-1]
183
+ print_subreddit_posts(
184
+ subreddit, post_id_to_start_from=last_post_id, start_count=start_count
185
+ )
186
+
187
+
188
+ def print_subreddit_posts(
189
+ subreddit: str,
190
+ post_id_to_start_from: str | None = None,
191
+ start_count: int = 0,
192
+ ) -> None:
193
+ """
194
+ Prints the subreddit posts when you give a subreddit name
195
+
196
+ subrredit_dict_data is the dict subreddit_dict["data"], and subreddit_dict_data["children"] is a list.
197
+
198
+ Parameters:
199
+ subreddit (str): Name of the subreddit.
200
+ post_id_to_start_from (str) : Used to connstruct the API url to ensure only the posts needed after a particular post ID are loaded from the server.
201
+ start_count (int) : Post number to start from in the api_response.
202
+ """
203
+
204
+ subreddit_dict = get_posts_in_a_subreddit(
205
+ subreddit,
206
+ limit=(no_of_posts_to_print),
207
+ after_post=post_id_to_start_from,
208
+ )
209
+
210
+ printed_posts: list[str] = list()
211
+ titles: list[str] = list()
212
+ # printed_posts is supposed to be of the format ["post_id",...], where printed_posts[i-1] is the id of the i'th post printed in the terminal
213
+
214
+ print(f"r/{subreddit}", end="\n\n")
215
+ for entry in subreddit_dict:
216
+ if entry["data"]["author"] not in ignored_users:
217
+ print_post_body(entry, start_count)
218
+ printed_posts.append(entry["data"]["id"])
219
+ titles.append(entry["data"]["title"])
220
+ start_count += 1
221
+
222
+ print(Fore.RESET, end="")
223
+
224
+ handle_user_choice_after_a_post(printed_posts, titles, subreddit, start_count)
225
+
226
+
227
+ def print_post_body(specific_entry: dict[str, dict], start_count: int) -> None:
228
+ """
229
+ Called from `print_subreddit_posts()`, this functions handles the printing of a body content for a post.
230
+
231
+ Parameters:
232
+ specific_entry dict[str,dict]: Dict containing details of a comments, fetched using the Reddit API.
233
+ """
234
+
235
+ print(
236
+ colors_to_use_in_terminal[start_count % num_of_colors]
237
+ + f"{start_count + 1}. {unescape(specific_entry['data']['title'])}"
238
+ )
239
+ try:
240
+ if (
241
+ specific_entry["data"]["link_flair_richtext"][1]["t"].lower()
242
+ == "official source"
243
+ ):
244
+ post_url_source = (
245
+ f" URL - {specific_entry['data']['url']}\n Official Source"
246
+ )
247
+ else:
248
+ post_url_source = f" URL - {specific_entry['data']['url']}"
249
+ except (KeyError, IndexError):
250
+ post_url_source = f" URL - {specific_entry['data']['url']}"
251
+ print(
252
+ f"{post_url_source}{Fore.RESET}",
253
+ end=ending_separator,
254
+ )
255
+
256
+
257
+ def print_comment(specific_comment_entry: dict) -> None:
258
+ """
259
+ Called from `print_post_comments()`, this functions handles the printing for a specific comment
260
+
261
+ Parameters:
262
+ specific_entry (str): Dict containing details of a comments, fetched using the Reddit API.
263
+ """
264
+
265
+ prefix = """ |
266
+ |--- """
267
+ wrapper = TextWrapper(
268
+ initial_indent=prefix, width=150, subsequent_indent=" | "
269
+ )
270
+ # Prints the comment text and it's author by giving the correct indendation to the left.
271
+ print(
272
+ wrapper.fill(
273
+ unescape(specific_comment_entry["body"])
274
+ + " --- u/"
275
+ + unescape(specific_comment_entry["author"])
276
+ )
277
+ )
278
+
279
+
280
+ def print_post_comments(post_id: str) -> None:
281
+ """
282
+ Prints the subreddit posts when you give a subreddit name
283
+
284
+ printed_posts is supposed to be of the format ["post_id"], where printed_posts[i-1] is the id of the i'th post printed in the terminal
285
+
286
+ Parameters:
287
+ post_id (str): ID of the post for which we have to load comments.
288
+ post_title (str): The title of the post to print before printing out the comments
289
+ """
290
+
291
+ post_comments_dict = get_post_dict(post_id)[1]["data"]["children"]
292
+ count = 0
293
+
294
+ for entry in post_comments_dict:
295
+ if count < 10:
296
+ if entry["data"]["author"] not in ignored_users:
297
+ print_comment(entry["data"])
298
+ count += 1
299
+ else:
300
+ break
301
+
302
+ print(Fore.RESET, end=ending_separator)
303
+
304
+
305
+ def open_post_link(post_id: str) -> None:
306
+ """
307
+ When the link mentioned in a post is passed to this, it tries to determine if it is a video that can be opened in MPV. If possible, it opens it directly in MPV. Else, for other links, it opens it in the defauly web browser using the `webbrowser` module.
308
+
309
+ Parameters:
310
+ post_id (str):
311
+ """
312
+
313
+ link = get_link_in_post(post_id)
314
+ domain = str(urlparse(link).netloc)
315
+ if domain.casefold() in {
316
+ "v.redd.it",
317
+ }:
318
+ if run_command(["mpv", "--version"]):
319
+ print("Opening it in MPV")
320
+ # Open the video using MPV in a loop
321
+ background_run_command(["mpv", link, "--loop"])
322
+ else:
323
+ print("Opening in browser")
324
+ web_open(link)
325
+ else:
326
+ web_open(link)
327
+
328
+
329
+ def cls() -> None:
330
+ """This somehow clears the screen. https://stackoverflow.com/a/50560686"""
331
+ print("\033[H\033[J", end="")
332
+
333
+
334
+ def run() -> None:
335
+ """
336
+ Callable function to be used while running from the terminal
337
+ """
338
+ global \
339
+ config, \
340
+ ignored_users, \
341
+ no_of_posts_to_print, \
342
+ ignore_all_mod_posts, \
343
+ default_subreddit
344
+
345
+ # Clear screen
346
+ cls()
347
+
348
+ if not check_config_existence():
349
+ introduce()
350
+
351
+ config = read_config()
352
+
353
+ ignored_users = config["ignored_users"]
354
+
355
+ no_of_posts_to_print = int(config["no_of_posts_to_print"])
356
+ subreddit = (
357
+ str(config["default_subreddit"])
358
+ if "default_subreddit" in config
359
+ else input("Enter the subreddit you want to visit: r/")
360
+ )
361
+
362
+ ignore_all_mod_posts = bool(config["ignore_all_mod_posts"])
363
+
364
+ if not subreddit:
365
+ print("No subreddit entered.")
366
+ exit_terminal(error=None)
367
+ print_subreddit_posts(subreddit)
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: creddit
3
+ Version: 0.0.1
4
+ Summary: An unofficial cli client for Reddit, aimed at people who regularly browse specific subreddits or topics instead of reading the homepage (r/all).
5
+ Author-email: Vishal Nandagopal <dev@vishalnandagopal.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.13
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: colorama>=0.4.6
11
+ Requires-Dist: python-dotenv>=1.1.0
12
+ Requires-Dist: requests>=2.32.3
13
+ Dynamic: license-file
14
+
15
+ # creddit - A CLI client for Reddit
16
+
17
+ An unofficial cli client for Reddit, aimed at people who regularly browse specific subreddits or topics instead of reading the homepage (r/all).
18
+
19
+ ### Features:
20
+
21
+ - Distraction free. Can browse a few posts and continue with your day. No flashy gifs, awards, ads or posts.
22
+
23
+ - Browse a subreddit by entering just the name.
24
+
25
+ - Prints a link to the article mentioned in every post, for you to continue reading it in your browser (`ctrl+click`), in case you are interested.
26
+
27
+ - Can browse top comments for every post just by entering the number shown beside the post in the terminal.
28
+
29
+ Feel free to send a PR with any additional features, or make an issue if you have any feature requests!
30
+
31
+ This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree.
32
+
33
+ ### How to use?
34
+
35
+ Clone the repo and install the thing as a package
36
+
37
+ ```sh
38
+ git clone --depth 1 https://github.com/vishalnandagopal/creddit/ creddit
39
+ cd creddit
40
+ pip install uv
41
+ uv sync
42
+ uv build
43
+ pip install ./dist/creddit-1.0.0-py3-none-any.whl
44
+ ```
45
+
46
+ And to finally run it,
47
+
48
+ ```sh
49
+ python -m creddit
50
+ ```
51
+
52
+ ### Features to add
53
+
54
+ - Publish to pip
55
+ - More guidance on MPV, and alternatives if it isn't installed
56
+
57
+ (Copyright (c) 2024 Vishal N)
@@ -0,0 +1,11 @@
1
+ creddit/__init__.py,sha256=D6gRU_goqZR58NMSDfgdiWMVi20YDsBRUDUQxT66EGM,81
2
+ creddit/__main__.py,sha256=fKdyZ_tkgzfFwJkC98x6-eXBYcXMyEmFKBBXAWjwUQU,75
3
+ creddit/config.py,sha256=ngZ9qYM9_NVbMAcvEQ81fRY6Ohz3InW0KSUVydXPYso,3204
4
+ creddit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ creddit/reddit.py,sha256=ugmtumjWw2GdqiAwbuO_gyco3U1z6dG3dMa2BWlwZZ0,4605
6
+ creddit/terminal.py,sha256=SNLDZQWs2NbbU2wysG5D7sN9l11IosgexDTgjz0TtSY,12855
7
+ creddit-0.0.1.dist-info/licenses/LICENSE,sha256=TfGELALVa4NFNIOMn6asE14LycjEIGoHL6O8fm0WRHU,1105
8
+ creddit-0.0.1.dist-info/METADATA,sha256=jzDRIX5qiRR0w79hxZ7j8g9ypN8coCZOel9sPYm0sJc,1835
9
+ creddit-0.0.1.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
10
+ creddit-0.0.1.dist-info/top_level.txt,sha256=Q2-CEVcpBfG-GG9VrGszVWASy03D0PcOpYUvTnugl-0,8
11
+ creddit-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.4.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Vishal N
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ creddit