diff --git a/gamestonk_terminal/nft/nft_controller.py b/gamestonk_terminal/nft/nft_controller.py new file mode 100644 index 00000000000..761a094433d --- /dev/null +++ b/gamestonk_terminal/nft/nft_controller.py @@ -0,0 +1,259 @@ +import argparse +from typing import List + +from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.menu import session +from gamestonk_terminal.helper_funcs import ( + EXPORT_ONLY_RAW_DATA_ALLOWED, + MENU_GO_BACK, + MENU_QUIT, + MENU_RESET, + try_except, + system_clear, + get_flair, + check_positive, + parse_known_args_and_warn, +) + +from gamestonk_terminal.nft import nftcalendar_view + + +class NFTController: + """NFT Controller class""" + + CHOICES = [ + "cls", + "?", + "help", + "q", + "quit", + "reset", + ] + + CHOICES_COMMANDS = [ + "today", + "upcoming", + "ongoing", + "newest", + ] + + CHOICES += CHOICES_COMMANDS + + def __init__(self): + """Constructor""" + self.nft_parser = argparse.ArgumentParser(add_help=False, prog="nft") + self.nft_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + self.completer = NestedCompleter.from_nested_dict( + {c: None for c in self.CHOICES} + ) + + def print_help(self): + """Print help""" + + help_text = """ +What do you want to do? + cls clear screen + ?/help show this menu again + q quit this menu, and shows back to main menu + quit quit to abandon the program + reset reset terminal and reload configs + +nftcalendar.io: + today today's NFT drops + upcoming upcoming NFT drops + ongoing Ongoing NFT drops + newest Recently NFTs added + """ + print(help_text) + + def switch(self, an_input: str): + """Process and dispatch input + + Returns + ------- + MENU_GO_BACK, MENU_QUIT, MENU_RESET + MENU_GO_BACK - Show main context menu again + MENU_QUIT - Quit terminal + MENU_RESET - Reset terminal and go back to same previous menu + """ + + # Empty command + if not an_input: + print("") + return None + + (known_args, other_args) = self.nft_parser.parse_known_args(an_input.split()) + + # Help menu again + if known_args.cmd == "?": + self.print_help() + return None + + # Clear screen + if known_args.cmd == "cls": + system_clear() + return None + + return getattr( + self, "call_" + known_args.cmd, lambda: "command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help Command""" + self.print_help() + + def call_q(self, _): + """Process Q command - quit the menu""" + return MENU_GO_BACK + + def call_quit(self, _): + """Process Quit command - exit the program""" + return MENU_QUIT + + def call_reset(self, _): + """Process Reset command - reset the program""" + return MENU_RESET + + @try_except + def call_today(self, other_args: List[str]): + """Process today command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="today", + description="Today's NFT drops [Source: nftcalendar.io]", + ) + parser.add_argument( + "-n", + "--num", + type=check_positive, + help="Number of NFT collections to display", + dest="num", + default=5, + ) + ns_parser = parse_known_args_and_warn( + parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED + ) + if not ns_parser: + return + + nftcalendar_view.display_nft_today_drops( + num=ns_parser.num, + export=ns_parser.export, + ) + + @try_except + def call_upcoming(self, other_args: List[str]): + """Process upcoming command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="upcoming", + description="Upcoming's NFT drops [Source: nftcalendar.io]", + ) + parser.add_argument( + "-n", + "--num", + type=check_positive, + help="Number of NFT collections to display", + dest="num", + default=5, + ) + ns_parser = parse_known_args_and_warn( + parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED + ) + if not ns_parser: + return + + nftcalendar_view.display_nft_upcoming_drops( + num=ns_parser.num, + export=ns_parser.export, + ) + + @try_except + def call_ongoing(self, other_args: List[str]): + """Process ongoing command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="ongoing", + description="Ongoing's NFT drops [Source: nftcalendar.io]", + ) + parser.add_argument( + "-n", + "--num", + type=check_positive, + help="Number of NFT collections to display", + dest="num", + default=5, + ) + ns_parser = parse_known_args_and_warn( + parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED + ) + if not ns_parser: + return + + nftcalendar_view.display_nft_ongoing_drops( + num=ns_parser.num, + export=ns_parser.export, + ) + + @try_except + def call_newest(self, other_args: List[str]): + """Process newest command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="newest", + description="Newest's NFT drops [Source: nftcalendar.io]", + ) + parser.add_argument( + "-n", + "--num", + type=check_positive, + help="Number of NFT collections to display", + dest="num", + default=5, + ) + ns_parser = parse_known_args_and_warn( + parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED + ) + if not ns_parser: + return + + nftcalendar_view.display_nft_newest_drops( + num=ns_parser.num, + export=ns_parser.export, + ) + + +def menu(): + """NFT Menu""" + nft_controller = NFTController() + nft_controller.call_help(None) + while True: + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in nft_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (nft)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (nft)> ") + + try: + process_input = nft_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exit\n") + continue diff --git a/gamestonk_terminal/nft/nftcalendar_model.py b/gamestonk_terminal/nft/nftcalendar_model.py new file mode 100644 index 00000000000..c4a205d5327 --- /dev/null +++ b/gamestonk_terminal/nft/nftcalendar_model.py @@ -0,0 +1,96 @@ +""" nftcalendar.io Model """ +__docformat__ = "numpy" + +import pandas as pd +import requests +from bs4 import BeautifulSoup +from gamestonk_terminal.helper_funcs import get_user_agent + + +def get_nft_drops(url: str) -> pd.DataFrame: + """Get NFT drops [Source: nftcalendar.io] + + Parameters + ------- + url : str + url to get NFT drops from + + Returns + ------- + pd.DataFrame + NFT drops + """ + nft_calendar = BeautifulSoup( + requests.get( + url, + headers={"User-Agent": get_user_agent()}, + ).text, + "lxml", + ) + drop_titles = list() + for droptitle in nft_calendar.find_all("h2", {"class": "text-2xl"}): + drop_titles.append(droptitle.text.strip()) + + drop_descriptions = list() + for dropdesc in nft_calendar.find_all("div", {"class": "pb-0 md:pb-2 text-normal"}): + drop_descriptions.append(dropdesc.text.strip()) + + drop_dates = list() + for dropdate in nft_calendar.find_all( + "div", {"class": "py-2 text-normal md:text-lg"} + ): + drop_dates.append(dropdate.text.strip().replace("\n", " ")) + + base_url = "https://nftcalendar.io" + drop_readmores = list() + for readmore in nft_calendar.find_all("div", {"class": "w-full md:w-2/5"}): + drop_readmores.append(base_url + readmore.find("a")["href"]) + + return pd.DataFrame( + list(zip(drop_titles, drop_dates, drop_readmores, drop_descriptions)), + columns=["Title", "Dates", "Link", "Description"], + ) + + +def get_nft_today_drops() -> pd.DataFrame: + """Get NFT today drops [Source: nftcalendar.io] + + Returns + ------- + pd.DataFrame + NFT drops + """ + return get_nft_drops("https://nftcalendar.io/") + + +def get_nft_upcoming_drops() -> pd.DataFrame: + """Get NFT upcoming drops [Source: nftcalendar.io] + + Returns + ------- + pd.DataFrame + NFT drops + """ + return get_nft_drops("https://nftcalendar.io/events") + + +def get_nft_ongoing_drops() -> pd.DataFrame: + """Get NFT ongoing drops [Source: nftcalendar.io] + + Returns + ------- + pd.DataFrame + NFT drops + """ + return get_nft_drops("https://nftcalendar.io/events/ongoing/") + + +def get_nft_newest_drops() -> pd.DataFrame: + """Get NFT newest drops [Source: nftcalendar.io] + + Returns + ------- + pd.DataFrame + NFT drops + """ + return get_nft_drops("https://nftcalendar.io/events/newest/") diff --git a/gamestonk_terminal/nft/nftcalendar_view.py b/gamestonk_terminal/nft/nftcalendar_view.py new file mode 100644 index 00000000000..02f63687267 --- /dev/null +++ b/gamestonk_terminal/nft/nftcalendar_view.py @@ -0,0 +1,122 @@ +""" nftcalendar.io View """ +__docformat__ = "numpy" + +import os +from gamestonk_terminal.helper_funcs import export_data +from gamestonk_terminal.nft import nftcalendar_model + + +def display_nft_today_drops(num: int, export: str): + """Display NFT today drops. [Source: nftcalendar.io] + + Parameters + ---------- + num: int + Number of NFT drops to display + export : str + Export dataframe data to csv,json,xlsx file + """ + nft_drops = nftcalendar_model.get_nft_today_drops() + + if nft_drops.empty: + print("No data found.", "\n") + else: + for _, nft in nft_drops.head(num).iterrows(): + print(nft["Dates"] + " - " + nft["Title"]) + print(nft["Link"]) + print(nft["Description"]) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "today", + nft_drops, + ) + + +def display_nft_upcoming_drops(num: int, export: str): + """Display NFT upcoming drops. [Source: nftcalendar.io] + + Parameters + ---------- + num: int + Number of NFT drops to display + export : str + Export dataframe data to csv,json,xlsx file + """ + nft_drops = nftcalendar_model.get_nft_upcoming_drops() + + if nft_drops.empty: + print("No data found.", "\n") + else: + for _, nft in nft_drops.head(num).iterrows(): + print(nft["Dates"] + " - " + nft["Title"]) + print(nft["Link"]) + print(nft["Description"]) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "upcoming", + nft_drops, + ) + + +def display_nft_ongoing_drops(num: int, export: str): + """Display NFT ongoing drops. [Source: nftcalendar.io] + + Parameters + ---------- + num: int + Number of NFT drops to display + export : str + Export dataframe data to csv,json,xlsx file + """ + nft_drops = nftcalendar_model.get_nft_ongoing_drops() + + if nft_drops.empty: + print("No data found.", "\n") + else: + for _, nft in nft_drops.head(num).iterrows(): + print(nft["Dates"] + " - " + nft["Title"]) + print(nft["Link"]) + print(nft["Description"]) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "ongoing", + nft_drops, + ) + + +def display_nft_newest_drops(num: int, export: str): + """Display NFT newest drops. [Source: nftcalendar.io] + + Parameters + ---------- + num: int + Number of NFT drops to display + export : str + Export dataframe data to csv,json,xlsx file + """ + nft_drops = nftcalendar_model.get_nft_newest_drops() + + if nft_drops.empty: + print("No data found.", "\n") + else: + for _, nft in nft_drops.head(num).iterrows(): + print(nft["Dates"] + " - " + nft["Title"]) + print(nft["Link"]) + print(nft["Description"]) + print("") + + export_data( + export, + os.path.dirname(os.path.abspath(__file__)), + "newest", + nft_drops, + ) diff --git a/terminal.py b/terminal.py index 38375a3298c..bb7d2ebece2 100644 --- a/terminal.py +++ b/terminal.py @@ -47,7 +47,7 @@ class TerminalController: "keys", ] - CHOICES_SHORTHAND_MENUS = ["s", "e", "c", "p", "f", "o", "rp", "rs"] + CHOICES_SHORTHAND_MENUS = ["s", "e", "c", "p", "f", "o", "rp", "rs", "n"] CHOICES_MENUS = [ "stocks", "economy", @@ -58,6 +58,7 @@ class TerminalController: "etf", "reports", "resources", + "nft", ] CHOICES += CHOICES_COMMANDS @@ -88,13 +89,14 @@ What do you want to do? about about us q(uit) to abandon the program ->> stocks ->> crypto >> economy ->> options ->> portfolio >> etf +>> stocks +>> options +>> crypto +>> nft >> forex +>> portfolio >> reports >> resources """ @@ -249,6 +251,16 @@ What do you want to do? """Process portfolio command""" return self.call_portfolio(_) + def call_nft(self, _): + """Process nft command""" + from gamestonk_terminal.nft import nft_controller + + return nft_controller.menu() + + def call_n(self, _): + """Process nft command""" + return self.call_nft(_) + def terminal(menu_prior_to_reset=""): """Terminal Menu""" diff --git a/website/content/nft/newest/_index.md b/website/content/nft/newest/_index.md new file mode 100644 index 00000000000..f25d52739cb --- /dev/null +++ b/website/content/nft/newest/_index.md @@ -0,0 +1,11 @@ +``` +usage: newest [-n NUM] [-h] [--export {csv,json,xlsx}] + +optional arguments: + -n NUM, --num NUM Number of NFT collections to display (default: 5) + -h, --help show this help message (default: False) + --export {csv,json,xlsx} + Export raw data into csv, json, xlsx (default: ) +``` + +Newest's NFT drops [Source: nftcalendar.io] diff --git a/website/content/nft/ongoing/_index.md b/website/content/nft/ongoing/_index.md new file mode 100644 index 00000000000..d60f8f4838c --- /dev/null +++ b/website/content/nft/ongoing/_index.md @@ -0,0 +1,11 @@ +``` +usage: ongoing [-n NUM] [-h] [--export {csv,json,xlsx}] + +optional arguments: + -n NUM, --num NUM Number of NFT collections to display (default: 5) + -h, --help show this help message (default: False) + --export {csv,json,xlsx} + Export raw data into csv, json, xlsx (default: ) +``` + +Ongoing's NFT drops [Source: nftcalendar.io] diff --git a/website/content/nft/today/_index.md b/website/content/nft/today/_index.md new file mode 100644 index 00000000000..dca6cb25696 --- /dev/null +++ b/website/content/nft/today/_index.md @@ -0,0 +1,11 @@ +``` +usage: today [-n NUM] [-h] [--export {csv,json,xlsx}] + +optional arguments: + -n NUM, --num NUM Number of NFT collections to display (default: 5) + -h, --help show this help message (default: False) + --export {csv,json,xlsx} + Export raw data into csv, json, xlsx (default: ) +``` + +Today's NFT drops [Source: nftcalendar.io] diff --git a/website/content/nft/upcoming/_index.md b/website/content/nft/upcoming/_index.md new file mode 100644 index 00000000000..088446d5ace --- /dev/null +++ b/website/content/nft/upcoming/_index.md @@ -0,0 +1,11 @@ +``` +usage: upcoming [-n NUM] [-h] [--export {csv,json,xlsx}] + +optional arguments: + -n NUM, --num NUM Number of NFT collections to display (default: 5) + -h, --help show this help message (default: False) + --export {csv,json,xlsx} + Export raw data into csv, json, xlsx (default: ) +``` + +Upcoming's NFT drops [Source: nftcalendar.io] diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index 114759aad41..17752bd853b 100755 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -915,3 +915,14 @@ main: ref: "/forex/candle" - name: resources ref: "/resources" + - name: nft + ref: "/nft" + sub: + - name: today + ref: "/nft/today" + - name: upcoming + ref: "/nft/upcoming" + - name: ongoing + ref: "/nft/ongoing" + - name: newest + ref: "/nft/newest"