diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..01a4ef3 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-complexity = 10 +max-line-length = 127 +exclude = .git,__pycache__ +per-file-ignores = __init__.py:F401 diff --git a/.gitignore b/.gitignore index 6b253df..20b70cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__/ -.vscode/ \ No newline at end of file +.vscode/ +api_cache/ diff --git a/LICENSE b/LICENSE index 6fd18d8..d36798c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Crinibus +Copyright (c) 2022 Crinibus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 43e312f..5a60b9a 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,15 @@ A program that gives the user a list over what comes in tv*.
+Requires ```python3.9+``` + +
+ # How to use An example on how to use this program: ``` -python3 main.py -c dr1 -c tv2 --all +python3 main.py -c dr1 tv2 --all ``` This prints all the programs that run today for the channels DR1 and TV2. @@ -19,7 +23,7 @@ This prints all the programs that run today for the channels DR1 and TV2. Another example: ``` -python3 main.py -c dr1 -c tv2 -t 20:00 -d 1 +python3 main.py -c dr1 tv2 -t 20:00 -d 1 ``` This prints only the tv-shows that start or is running at 8 pm the next day on the channels DR1 and TV2. @@ -45,7 +49,6 @@ This prints the programs (with categories) that is currently running on the defa - ```-v``` or ```--verbose``` - ```-s [search-term]``` or ```--search [search-term]``` - ```--default-channels [channel]``` -- ```--default-space-seperator [space-seperator]``` - ```--justify-length [int]```
@@ -53,21 +56,23 @@ This prints the programs (with categories) that is currently running on the defa ## --channel -By using the flag ```-c``` or ```--channel``` you specify which channels you want the program to show tv-shows from. Replace "[channel]" with wanted channel, e.g. "dr1".
-You can specify multiple channels just by using the ```-c``` flag again.
+By using the flag ```-c``` or ```--channel``` you specify which channels you want the program to show tv-shows from. Replace "[channel]" with wanted channel(s), e.g. "dr1".
If no channel(s) is chosen, the default channels is used. You can change the default channels as described [here](#--default-channels) For example: ``` python3 main.py -c [channel_1] -c [channel_2] ``` -**OBS**: when specifing channels with a space such as "TV2 News", use a dash (-) instead of a space. E.g. "TV2 News" -> "TV2-News" +or +``` +python3 main.py -c [channel_1] [channel_2] +``` You can also specify "all" as the first channel to get all channels. Examples:
``` -python3 main.py -c dr1 -c tv2 --all +python3 main.py -c dr1 tv2 --all ``` This shows all the programs that run on DR1 and TV2 for today. @@ -135,7 +140,7 @@ This shows all the programs that have the category "film" on the default channel
``` -python3 main.py --category film --category drama +python3 main.py --category film drama ``` This shows all the programs that have either the category "film" or "drama" on the default channels for today. @@ -178,7 +183,7 @@ This shows all the programs on the default channels for today that have the word
``` -python3 main.py --search avis --search vejr +python3 main.py --search avis vejr ``` This shows all the programs on the default channels for today that have either the words "avis" or "vejr" in the title. @@ -197,18 +202,6 @@ This changes the default channels to "DR1", "TV2" and "CANAL 9".
-## --default-space-seperator - - -Example: -``` -python3 main.py --default-space-seperator - -``` -This changes the default space seperator to the sign "-". - -
- - ## --justify-length diff --git a/main.py b/main.py index fffd68b..39e1144 100644 --- a/main.py +++ b/main.py @@ -1,106 +1,22 @@ -# from format import Format -# from argument import argparse_setup -# from API import API -# from Config import Config import tvguide as tv -def print_currently_running(data_source: dict, user_channels: list) -> None: - channels_string = ", ".join(user_channels).upper() - - print(f"\n----- Showing currently running programs for: {channels_string} -----", end="") - for channel in user_channels: - data_source[channel].print_currently_running() - - -def print_program_times(data_source: dict, user_channels: list, user_times: list) -> None: - channels_string = ", ".join(user_channels).upper() - user_times_string = ", ".join(user_times) - - print(f"\n----- Showing programs that is running at: {user_times_string} for: {channels_string} -----", end="") - for channel in user_channels: - data_source[channel].print_times(user_times) - - -def print_program_categories(data_source: dict, user_channels: list, user_categories: list) -> None: - user_categories_string = ", ".join(user_categories) - - print(f"\n----- Searching for categories: {user_categories_string} -----", end="") - for channel in user_channels: - data_source[channel].print_categories(user_categories) - - -def print_program_searches(data_source: dict, user_channels: list, user_searches: list) -> None: - user_search_string = tv.Format.user_search(", ".join(user_searches)) - - print(f"\n----- Searching for keywords: {user_search_string} -----", end="") - for channel in user_channels: - data_source[channel].print_searches(user_searches) - - -def print_program_all(data_source: dict, user_channels: list) -> None: - user_channels_string = ", ".join(user_channels).upper() - - print(f"\n----- Showing all programs for: {user_channels_string} -----", end="") - for channel in user_channels: - data_source[channel].print_all_programs() - - -def change_defaults(args, data: dict): - if args.default_channels: - tv.Config.change_defaults_user_channels(args.default_channels) - default_channels_string = ", ".join(args.default_channels).upper() - print(f"Changed default channel(s) to: {default_channels_string}") - - if args.default_space_seperator: - tv.Config.change_space_seperator(args.default_space_seperator) - print(f"Changed space seperator to: {args.default_space_seperator}") - - if args.justify_length: - tv.Config.change_justify_length(args.justify_length) - print(f"Changed justify length to: {args.justify_length}") - - if not args.channel: - args.channel = tv.Config.get_defaults_user_channels() - default_channels_string = ", ".join(args.channel).upper() - print(f"No channel(s) chosen: using default channels ({default_channels_string})") - elif args.channel[0].lower() == "all": - args.channel = [channel for channel in data.keys()] - - -def print_programs(args, data): - if args.now: - print_currently_running(data, args.channel) - - if args.time: - print_program_times(data, args.channel, args.time) - - if args.category: - print_program_categories(data, args.channel, args.category) - - if args.search: - print_program_searches(data, args.channel, args.search) - - if args.all: - print_program_all(data, args.channel) - - def main(): args = tv.argparse_setup() - api_data = tv.API.get_data(args.day) + tvguide = tv.TvGuide() + + tvguide.parse_arguments(args) - my_data = tv.API.format_data(api_data, args.verbose) + api_data = tv.ApiManager.get_data(tvguide.relative_date) - change_defaults(args, my_data) + tvguide.parse_api_data(api_data) - print_programs(args, my_data) + tvguide.print_programs() if __name__ == "__main__": try: main() - except KeyError: - print("Check channel name or this scraper can't use this channel") except KeyboardInterrupt: print("Stopped by user") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dcff8a7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 127 diff --git a/requirements.txt b/requirements.txt index 4261e68..ad40ca9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests==2.24.0 \ No newline at end of file +requests>=2.24.0 +requests-cache>=0.9.6 \ No newline at end of file diff --git a/tvguide/API.py b/tvguide/API.py deleted file mode 100644 index 03413dd..0000000 --- a/tvguide/API.py +++ /dev/null @@ -1,31 +0,0 @@ -import requests -from .const import API_LINK, REQUEST_HEADER, REQUEST_COOKIES -from .format import Format -from .tv import Channel - - -class API: - @staticmethod - def get_link(relative_date: int) -> str: - return API_LINK.replace("{date}", Format.get_specified_date(relative_date)) - - @staticmethod - def get_data(relative_day: int) -> dict: - """Get formatted data from API""" - response = requests.get( - API.get_link(relative_day), - headers=REQUEST_HEADER, - cookies=REQUEST_COOKIES - ) - - return response.json() - - @staticmethod - def format_data(api_data: dict, verbose: bool) -> dict: - formatted_data = {} - - for channel in api_data: - temp_channel = Channel(channel, verbose) - formatted_data.update({temp_channel.name: temp_channel}) - - return formatted_data diff --git a/tvguide/Config.py b/tvguide/Config.py deleted file mode 100644 index 8034bf4..0000000 --- a/tvguide/Config.py +++ /dev/null @@ -1,61 +0,0 @@ -from configparser import ConfigParser -from .filemanager import Filemanager - - -class Config: - @staticmethod - def read(file_name: str) -> ConfigParser: - """Read user settings in {file_name}.ini""" - config = ConfigParser() - config.read(file_name) - return config - - @staticmethod - def write(file_name: str, config: ConfigParser) -> None: - """Write user settings in {file_name}.ini""" - with open(file_name, 'w') as default_file: - config.write(default_file) - - @staticmethod - def get_defaults_user_channels() -> list: - config = Config.read(f'{Filemanager.get_root_path()}/defaults.ini') - - defaultChannels = config['DefaultChannels']['channels'] - - return defaultChannels.split(',') - - @staticmethod - def change_defaults_user_channels(new_defaults: list) -> None: - config = Config.read(f'{Filemanager.get_root_path()}/defaults.ini') - - config['DefaultChannels']['channels'] = ','.join(new_defaults) - - Config.write('defaults.ini', config) - - @staticmethod - def get_space_seperator() -> str: - config = Config.read(f'{Filemanager.get_root_path()}/defaults.ini') - - return config['Misc']['spaceSeperator'] - - @staticmethod - def change_space_seperator(new_space_seperator: str) -> None: - config = Config.read(f'{Filemanager.get_root_path()}/defaults.ini') - - config['Misc']['spaceSeperator'] = new_space_seperator - - Config.write('defaults.ini', config) - - @staticmethod - def get_justify_length() -> int: - config = Config.read(f'{Filemanager.get_root_path()}/defaults.ini') - - return int(config['Misc']['justifyLength']) - - @staticmethod - def change_justify_length(new_length: int): - config = Config.read(f'{Filemanager.get_root_path()}/defaults.ini') - - config['Misc']['justifyLength'] = new_length - - Config.write('defaults.ini', config) diff --git a/tvguide/__init__.py b/tvguide/__init__.py index 135f832..4e4a10c 100644 --- a/tvguide/__init__.py +++ b/tvguide/__init__.py @@ -1,4 +1,5 @@ from .format import Format from .argument import argparse_setup -from .API import API -from .Config import Config \ No newline at end of file +from .api import ApiManager +from .config import ConfigManager +from .tvguide import TvGuide diff --git a/tvguide/api.py b/tvguide/api.py new file mode 100644 index 0000000..0d20fc5 --- /dev/null +++ b/tvguide/api.py @@ -0,0 +1,23 @@ +import requests +import requests_cache + +from tvguide.const import API_LINK, REQUEST_HEADER, REQUEST_COOKIES +from tvguide.format import Format +from tvguide.tv import Channel + +requests_cache.install_cache(cache_name="api_cache", expire_after=120, backend="filesystem") + + +class ApiManager: + @staticmethod + def get_link(relative_date: int) -> str: + date = Format.get_specified_date(relative_date) + return API_LINK.replace("{date}", date) + + @staticmethod + def get_data(relative_day: int) -> dict: + """Get formatted data from API""" + api_link = ApiManager.get_link(relative_day) + response = requests.get(api_link, headers=REQUEST_HEADER, cookies=REQUEST_COOKIES) + + return response.json() diff --git a/tvguide/argument.py b/tvguide/argument.py index 0159eb0..6a42066 100644 --- a/tvguide/argument.py +++ b/tvguide/argument.py @@ -6,94 +6,56 @@ def argparse_setup() -> ArgumentParser.parse_args: parser = ArgumentParser() parser.add_argument( - '-c', - '--channel', - dest='channel', - help='the channel with the user want to see programs from', + "-c", + "--channel", + dest="channels", + help="the channel with the user want to see programs from", nargs="*", action="extend", - type=str + type=str, ) parser.add_argument( - '-t', - '--time', - dest='time', - metavar='hh:mm', + "-t", + "--time", + dest="time", + metavar="hh:mm", help='the time the program starts. E.g. "20:00". Format is: "hh:mm"', nargs="*", action="extend", - type=str + type=str, ) - parser.add_argument( - '-a', - '--all', - help='show all programs for the chosen channel(s)', - action='store_true' - ) + parser.add_argument("-a", "--all", help="show all programs for the chosen channel(s)", action="store_true") parser.add_argument( - '-d', - '--day', - help='the relative day the programs is running, default today (0)', + "-d", + "--day", + help="the relative day the programs is running, default today (0)", choices=[-1, 0, 1, 2, 3, 4, 5, 6], type=int, - default=0 + default=0, ) parser.add_argument( - '--category', - dest='category', - help='only show the programs with the chosen category(s)', + "--category", + dest="category", + help="only show the programs with the chosen category(s)", nargs="*", action="extend", - type=str + type=str, ) - parser.add_argument( - '-n', - '--now', - help='only show the program(s) that is currently', - action='store_true' - ) + parser.add_argument("-n", "--now", help="only show the program(s) that is currently", action="store_true") - parser.add_argument( - '-v', - '--verbose', - help='show categories when using --all and --time', - action='store_true' - ) + parser.add_argument("-v", "--verbose", help="show categories when using --all and --time", action="store_true") parser.add_argument( - '--default-channels', - dest='default_channels', - help='change default channels to the chosen channel(s)', - nargs='*' + "--default-channels", dest="default_channels", help="change default channels to the chosen channel(s)", nargs="*" ) - parser.add_argument( - '--default-space-seperator', - dest='default_space_seperator', - help='change space seperator sign', - type=str - ) + parser.add_argument("--justify-length", dest="justify_length", help="change justify length", type=str) - parser.add_argument( - '--justify-length', - dest='justify_length', - help='change justify length', - type=str - ) - - parser.add_argument( - '-s', - '--search', - dest='search', - help='search for programs', - nargs="*", - action="extend", - type=str - ) + parser.add_argument("-s", "--search", dest="search", help="search for programs", nargs="*", action="extend", type=str) return parser.parse_args() diff --git a/tvguide/config.py b/tvguide/config.py new file mode 100644 index 0000000..afdbc75 --- /dev/null +++ b/tvguide/config.py @@ -0,0 +1,61 @@ +from typing import List +from configparser import ConfigParser +from functools import cache + +from tvguide.filemanager import Filemanager + + +class ConfigManager: + @staticmethod + def read(file_name: str) -> ConfigParser: + """Read user settings in {file_name}.ini""" + config = ConfigParser() + config.read(file_name) + return config + + @staticmethod + def write(file_name: str, config: ConfigParser) -> None: + """Write user settings in {file_name}.ini""" + with open(file_name, "w") as default_file: + config.write(default_file) + + @staticmethod + @cache + def read_defaults_config_file() -> ConfigParser: + return ConfigManager.read(Filemanager.defaults_ini_path) + + @staticmethod + def get_defaults_user_channels() -> List[str]: + config = ConfigManager.read_defaults_config_file() + + defaultChannels = config["DefaultChannels"]["channels"] + + return defaultChannels.split(",") + + @staticmethod + def change_defaults_user_channels(new_defaults: List[str]) -> None: + config = ConfigManager.read_defaults_config_file() + + config["DefaultChannels"]["channels"] = ",".join(new_defaults) + + ConfigManager.write(Filemanager.defaults_ini_path, config) + + @staticmethod + def get_justify_length() -> int: + config = ConfigManager.read_defaults_config_file() + + return int(config["Misc"]["justifyLength"]) + + @staticmethod + def change_justify_length(new_length: int): + config = ConfigManager.read_defaults_config_file() + + config["Misc"]["justifyLength"] = new_length + + ConfigManager.write(Filemanager.defaults_ini_path, config) + + @staticmethod + def get_timezone() -> str: + config = ConfigManager.read_defaults_config_file() + + return config["Timezone"]["timezone"] diff --git a/tvguide/const.py b/tvguide/const.py index 41f086f..67701d7 100644 --- a/tvguide/const.py +++ b/tvguide/const.py @@ -1,6 +1,8 @@ API_LINK = "https://tvtid-api.api.tv2.dk/api/tvtid/v1/epg/dayviews/{date}?ch=1&ch=2&ch=3&ch=4&ch=5&ch=6&ch=7&ch=8&ch=9&ch=10&ch=10&ch=11&ch=12&ch=14&ch=15&ch=19&ch=20&ch=25&ch=26&ch=31&ch=37&ch=39&ch=70&ch=71&ch=74&ch=77&ch=93&ch=94&ch=111&ch=116&ch=117&ch=118&ch=129&ch=130&ch=133&ch=135&ch=136&ch=142&ch=147&ch=153&ch=156&ch=157&ch=161&ch=162&ch=163&ch=164&ch=165&ch=174&ch=181&ch=185&ch=188&ch=191&ch=201&ch=215&ch=218&ch=219&ch=221&ch=240&ch=248&ch=570&ch=687&ch=689&ch=10061&ch=10066&ch=10070&ch=10093&ch=10104&ch=10111&ch=10145&ch=10153&ch=10159&ch=10262&ch=10341&ch=10435&ch=10455&ch=10510&ch=10511&ch=10621&ch=11572&ch=11610&ch=11611&ch=11612&ch=11613&ch=11614&ch=11615&ch=11616&ch=11617&ch=12034&ch=12396&ch=12566&ch=12697&ch=12948&ch=15032&ch=15049&ch=15129&ch=2147483561&ch=2147483566&ch=2147483567&ch=2147483568" -REQUEST_HEADER = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"} +REQUEST_HEADER = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36" +} REQUEST_COOKIES = {"cookies_are": "working"} @@ -110,98 +112,98 @@ "3": "tv2", "5": "tv3", "2": "dr2", - "31": "tv2-charlie", - "133": "tv2-news", - "7": "kanal-5", - "6": "tv3-plus", - "4": "tv2-zulu", - "10153": "dr-ramasjang", - "8": "kanal-4", - "77": "tv2-sport", - "2147483561": "tv2-sport-x", - "156": "tv3-sport", - "10093": "tv3-puls", + "31": "tv2 charlie", + "133": "tv2 news", + "7": "kanal 5", + "6": "tv3 plus", + "4": "tv2 zulu", + "10153": "dr ramasjang", + "8": "kanal 4", + "77": "tv2 sport", + "2147483561": "tv2 sport x", + "156": "tv3 sport", + "10093": "tv3 puls", "10066": "6eren", - "14": "disney-channel", - "12566": "tv2-fri", - "10111": "canal-9", - "70": "discovery-channel", + "14": "disney channel", + "12566": "tv2 fri", + "10111": "canal 9", + "70": "discovery channel", "118": "tlc", "153": "nickelodeon", - "94": "national-geographic-channel", - "12948": "tv3-max", + "94": "national geographic channel", + "12948": "tv3 max", "185": "cartoon", - "157": "disney-junior", + "157": "disney junior", "15": "dk4", "71": "mtv", - "93": "animal-planet", - "15049": "investigation-discovery", + "93": "animal planet", + "15049": "investigation discovery", "219": "vh1", - "37": "eurosport-2", + "37": "eurosport 2", "248": "boomerang", - "9": "viasat-film", - "10": "viasat-film-action", - "11": "viasat-film-hits", - "12": "viasat-film-family", - "19": "viasat-explorer", - "20": "viasat-nature", - "25": "c-more-first", - "26": "c-more-hits", - "39": "viasat-history", - "74": "disney-xd", - "111": "tv4-sverige", - "116": "discovery-world", + "9": "viasat film", + "10": "viasat film action", + "11": "viasat film hits", + "12": "viasat film family", + "19": "viasat explorer", + "20": "viasat nature", + "25": "c more first", + "26": "c more hits", + "39": "viasat history", + "74": "disney xd", + "111": "tv4 sverige", + "116": "discovery world", "129": "nrk2", "130": "nrk1", "135": "svt1", "136": "sv2", - "142": "tv2-norge", - "147": "discovery-hd-showcase", + "142": "tv2 norge", + "147": "discovery hd showcase", "161": "rtl", "162": "ard", "163": "zdf", "164": "3sat", - "165": "viasat-golf", - "174": "eurosport-1", + "165": "viasat golf", + "174": "eurosport 1", "181": "cnn", "188": "ndr", - "191": "bbc-world", - "201": "c-more-series", - "215": "travel-channel", + "191": "bbc world", + "201": "c more series", + "215": "travel channel", "218": "vox", - "221": "rtl-2", - "240": "super-rtl", - "570": "paramount-network", + "221": "rtl 2", + "240": "super rtl", + "570": "paramount network", "687": "xee", - "689": "viasat-ultra", - "10061": "bbc-earth", - "10070": "viasat-series", + "689": "viasat ultra", + "10061": "bbc earth", + "10070": "viasat series", "10104": "arte", - "10145": "sf-kanalen", + "10145": "sf kanalen", "10159": "history", - "10262": "kanal-hovedstaden", + "10262": "kanal hovedstaden", "10341": "folketinget", "10435": "tnt", - "10455": "nickelodeon-junior", + "10455": "nickelodeon junior", "10510": "sat1", "10511": "prosieben", "10621": "sport1", - "11572": "national-geographic-wild", - "11610": "tv2-nord-salto", - "11611": "tv-midt-vest", - "11612": "tv2-østjylland", - "11613": "tv2-øst", - "11614": "tv-syd", - "11615": "tv-fyn", + "11572": "national geographic wild", + "11610": "tv2 nord salto", + "11611": "tv midt vest", + "11612": "tv2 østjylland", + "11613": "tv2 øst", + "11614": "tv syd", + "11615": "tv fyn", "11616": "lorry", - "11617": "tv2-bornholm", - "12034": "tv3-sport-2-hd", - "12396": "bbc-brit", - "12697": "tv5-monde-europe", - "15032": "national-geographic-people", - "15129": "comedy-central", - "2147483566": "cs-go", - "2147483567": "zulu-comedy", + "11617": "tv2 bornholm", + "12034": "tv3 sport 2 hd", + "12396": "bbc brit", + "12697": "tv5 monde europe", + "15032": "national geographic people", + "15129": "comedy central", + "2147483566": "cs go", + "2147483567": "zulu comedy", "2147483568": "oiii", - "117": "discovery-science", + "117": "discovery science", } diff --git a/tvguide/defaults.ini b/tvguide/defaults.ini index e50c2fd..5d5d6bc 100644 --- a/tvguide/defaults.ini +++ b/tvguide/defaults.ini @@ -7,3 +7,6 @@ channels = dr1,tv2 spaceSeperator = * ; Change justify length here or use flag --justify-length justifyLength = 45 + +[Timezone] +timezone = Europe/Copenhagen diff --git a/tvguide/filemanager.py b/tvguide/filemanager.py index 8541b91..4581323 100644 --- a/tvguide/filemanager.py +++ b/tvguide/filemanager.py @@ -1,7 +1,7 @@ import pathlib + class Filemanager: - @staticmethod - def get_root_path(): - """Return root path of this repository""" - return pathlib.Path(__file__).parent.absolute() + # root path of this repository + root_path = pathlib.Path(__file__).parent.parent.absolute() + defaults_ini_path = f"{root_path}/tvguide/defaults.ini" diff --git a/tvguide/format.py b/tvguide/format.py index a4a76a1..630b219 100644 --- a/tvguide/format.py +++ b/tvguide/format.py @@ -1,14 +1,8 @@ from datetime import datetime, timedelta, timezone -from .Config import Config import pytz class Format: - @staticmethod - def channel_name(name: str) -> str: - name = name.replace("-", " ") - return name.upper() - @staticmethod def user_time(time: str) -> int: return int(time.replace(":", "")) @@ -18,29 +12,21 @@ def get_specified_date(relative_date: int) -> str: """Convert relative date to real date, so that if argument 'relative_date' is 1, it get converted to tomorrow""" date = datetime.today() date += timedelta(days=relative_date) - return date.strftime('%Y-%m-%d') + return date.strftime("%Y-%m-%d") @staticmethod def convert_unix_time(unix_time: int, toShow: bool): """Convert UNIX times to hour and minutes, e.g.: 1604692800 -> 2000 or 20:00\n - UNIX time is converted to timezone: Europe/Copenhagen""" + UNIX time is converted to timezone: Europe/Copenhagen""" copenhagen_timezone = pytz.timezone("Europe/Copenhagen") time = pytz.utc.localize(datetime.utcfromtimestamp(unix_time)).astimezone(copenhagen_timezone) if toShow: - return time.strftime('%H:%M') + return time.strftime("%H:%M") else: - return int(time.strftime('%H%M')) - - @staticmethod - def user_search(search: str) -> str: - search = search.lower() - - spaceSeperator = Config.get_space_seperator() + return int(time.strftime("%H%M")) - return search.replace(spaceSeperator, ' ') - @staticmethod def program_time_stop(time_start: int, time_stop: int) -> int: if time_start > time_stop: diff --git a/tvguide/tv.py b/tvguide/tv.py index b634a5d..e1ee1a5 100644 --- a/tvguide/tv.py +++ b/tvguide/tv.py @@ -1,91 +1,28 @@ -from datetime import datetime -from .const import CHANNEL_NUMBER_INDEX -from .format import Format, Config +from typing import Iterator, List import time - -class Channel: - def __init__(self, channel_info: dict, verbose: bool = False) -> None: - self.verbose = verbose - self.id = channel_info['id'] - self.name = CHANNEL_NUMBER_INDEX[self.id] - self.programs = [] - self.format_program_info(channel_info) - - def format_program_info(self, channel_info: dict) -> None: - for program in channel_info['programs']: - self.add_program(program) - - def add_program(self, program_info: dict) -> None: - new_program = Program(program_info, self.verbose) - self.programs.append(new_program) - - def print_all_programs(self) -> None: - print(f"\n{Format.channel_name(self.name)}:") - for program in self.programs: - print(program.start_time_and_title) - print() - - def print_searches(self, search_terms: list) -> None: - print(f"\n{Format.channel_name(self.name)}:") - for program in self.programs: - for search in search_terms: - search = Format.user_search(search) - if search in program.title.lower(): - print(program.time_and_title) - break - print() - - def print_categories(self, categories: list) -> None: - print(f"\n{Format.channel_name(self.name)}:") - for program in self.programs: - for category in categories: - category = category.lower() - if category in program.categories: - print(f"{program.time_and_title} ({category.capitalize()})") - break - elif program.categories == [] and category in ['nyheder']: - print(f"{program.time_and_title} ({category.capitalize()})") - break - print() - - def print_currently_running(self) -> None: - print(f"\n{Format.channel_name(self.name)}:") - for program in self.programs: - if program.is_running: - print(program.time_and_title) - print() - - def print_times(self, times: list) -> None: - print(f"\n{Format.channel_name(self.name)}:") - for program in self.programs: - for time in times: - time = Format.user_time(time) - if program.is_running_at(time): - print(program.time_and_title) - break - print() - - def __iter__(self): - return iter(self.programs) +from tvguide.const import CHANNEL_NUMBER_INDEX +from tvguide.format import Format +from tvguide.config import ConfigManager class Program: def __init__(self, program_info: dict, verbose: bool = False): self.info = program_info self.verbose = verbose - self.format_info() - - def format_info(self): - self.time_start_unix = self.info['start'] - self.time_stop_unix = self.info['stop'] - self.time_start = Format.convert_unix_time(self.time_start_unix, toShow=False) - self.time_stop = Format.program_time_stop(time_start=self.time_start, time_stop=Format.convert_unix_time(self.time_stop_unix, toShow=False)) - self.time_start_show = Format.convert_unix_time(self.time_start_unix, toShow=True) - self.time_stop_show = Format.convert_unix_time(self.time_stop_unix, toShow=True) - self.title = self.info['title'] - self.categories = [cat.lower() for cat in self.info['categories']] - self.id = self.info['id'] + self._format_info() + + def _format_info(self): + self.id: str = self.info["id"] + self.title: str = self.info["title"] + self.categories: List[str] = [category.lower() for category in self.info["categories"]] + self.time_start_unix: int = self.info["start"] + self.time_stop_unix: int = self.info["stop"] + self.time_start: int = Format.convert_unix_time(self.time_start_unix, toShow=False) + temp_time_stop = Format.convert_unix_time(self.time_stop_unix, toShow=False) + self.time_stop: int = Format.program_time_stop(time_start=self.time_start, time_stop=temp_time_stop) + self.time_start_show: str = Format.convert_unix_time(self.time_start_unix, toShow=True) + self.time_stop_show: str = Format.convert_unix_time(self.time_stop_unix, toShow=True) @property def time_and_title(self) -> str: @@ -104,7 +41,7 @@ def start_time_and_title(self) -> str: @property def time_and_title_and_category(self) -> str: time_and_title = f"{self.time_start_show} - {self.time_stop_show} > {self.title}" - justify_length = Config.get_justify_length() + justify_length = ConfigManager.get_justify_length() if len(time_and_title) > justify_length: time_and_title_cut = f"{time_and_title[:justify_length]}..." @@ -116,7 +53,7 @@ def time_and_title_and_category(self) -> str: @property def start_time_and_title_and_category(self) -> str: time_and_title = f"{self.time_start_show} > {self.title}" - justify_length = Config.get_justify_length() + justify_length = ConfigManager.get_justify_length() if len(time_and_title) > justify_length: time_and_title_cut = f"{time_and_title[:justify_length]}..." @@ -128,20 +65,88 @@ def start_time_and_title_and_category(self) -> str: @property def is_running(self) -> bool: time_now = time.time() + return self.time_start_unix <= time_now < self.time_stop_unix - if time_now == self.time_start_unix or (self.time_start_unix < time_now < self.time_stop_unix): - return True - - return False - - def is_running_at(self, time: str): - if self.time_start <= time < self.time_stop: - return True - - return False + def is_running_at(self, time: int) -> bool: + return self.time_start <= time < self.time_stop def __str__(self) -> str: return self.time_and_title def __repr__(self) -> str: return f"Program(program_info={self.info})" + + +class Channel: + def __init__(self, channel_info: dict, verbose: bool = False) -> None: + self.verbose = verbose + self.id: str = channel_info["id"] + self.name = CHANNEL_NUMBER_INDEX[self.id] + self.programs: List[Program] = [] + self._parse_channel_info(channel_info) + + def _parse_channel_info(self, channel_info: dict) -> None: + for program_dict in channel_info["programs"]: + program = Program(program_dict, self.verbose) + self.programs.append(program) + + def print_all_programs(self) -> None: + for program in self.programs: + print(program.start_time_and_title) + + def print_searches(self, search_terms: List[str]) -> None: + for search_term in search_terms: + programs_with_search_term = self._get_programs_with_search_term(search_term) + for program in programs_with_search_term: + print(program.time_and_title) + + def print_categories(self, categories: List[str]) -> None: + for category in categories: + programs_with_category = self._get_programs_with_category(category) + for program in programs_with_category: + print(f"{program.time_and_title} ({category.capitalize()})") + + def print_currently_running(self) -> None: + running_program = self._get_currently_running_program() + + if not running_program: + print( + "*Could not find a running program" + " - if the time right now is after midnight before 6 am, try getting programs for yesterday*" + ) + return + + print(running_program.time_and_title) + + def print_times(self, times: List[str]) -> None: + for program in self._get_programs_with_show_times(times): + print(program.time_and_title) + + def _get_currently_running_program(self) -> Program: + for program in self.programs: + if program.is_running: + return program + + def _get_programs_with_category(self, category: str) -> List[Program]: + programs_with_category: List[Program] = [] + for program in self.programs: + if category.lower() in program.categories or program.categories == [] and category in ["nyheder"]: + programs_with_category.append(program) + return programs_with_category + + def _get_programs_with_search_term(self, search_term: str) -> List[Program]: + programs_with_search_term: List[Program] = [] + for program in self.programs: + if search_term.lower() in program.title.lower(): + programs_with_search_term.append(program) + return programs_with_search_term + + def _get_programs_with_show_times(self, show_times: List[str]) -> Iterator[Program]: + for program in self.programs: + for show_time in show_times: + time_int = Format.user_time(show_time) + if program.is_running_at(time_int): + yield program + + def __iter__(self): + return iter(self.programs) diff --git a/tvguide/tvguide.py b/tvguide/tvguide.py new file mode 100644 index 0000000..74b0660 --- /dev/null +++ b/tvguide/tvguide.py @@ -0,0 +1,109 @@ +from typing import List +import argparse + +from tvguide.config import ConfigManager +from tvguide.tv import Channel + + +class TvGuide: + def __init__(self) -> None: + self.relative_date: int = 0 + self.verbose: bool = False + self.show_now: bool = False + self.show_all_channels: bool = False + self.show_all_programs: bool = False + self.search_times: bool = False + self.search_categories: bool = False + self.search_names: bool = False + self.input_times: List[str] = [] + self.input_channels: List[str] = [] + self.input_categories: List[str] = [] + self.input_search_terms: List[str] = [] + self.all_channels: List[Channel] = [] + + def parse_arguments(self, args: argparse.Namespace) -> None: + self.verbose = args.verbose + self.show_now = args.now + self.relative_date = args.day + self.show_all_programs = args.all + + if args.default_channels: + self._change_default_channels(args.default_channels) + + if args.justify_length: + self._change_default_justify_length(args.justify_length) + + if args.search: + self.search_names = True + self.input_search_terms = args.search + + if args.category: + self.search_categories = True + self.input_categories = args.category + + if args.time: + self.search_times = True + self.input_times = args.time + + if not args.channels: + self.input_channels = self.get_default_channels() + default_channels_string = ", ".join(self.input_channels).upper() + print(f"No channel(s) chosen - using default channels: {default_channels_string}") + elif "all" in args.channels: + self.show_all_channels = True + else: + self.input_channels = args.channels + + def parse_api_data(self, api_data: dict) -> None: + for channel_dict in api_data: + channel = Channel(channel_dict, self.verbose) + self.all_channels.append(channel) + + def print_programs(self) -> None: + channels = self.all_channels if self.show_all_channels else self.get_channels(self.input_channels) + + for channel in channels: + print(f"\n{channel.name.upper()}") + + if self.show_all_programs: + print("----- ALL PROGRAMS -----") + channel.print_all_programs() + print() + + if self.search_categories: + input_categories_string = ", ".join(self.input_categories) + print(f"----- PROGRAMS WITH CATEGORY(s): {input_categories_string} -----") + channel.print_categories(self.input_categories) + print() + + if self.search_times: + input_times_string = ", ".join(self.input_times) + print(f"----- RUNNING AT TIME(s): {input_times_string} -----") + channel.print_times(self.input_times) + print() + + if self.search_names: + input_search_terms_string = ", ".join(self.input_search_terms) + print(f"----- SEARCH TERM(s): {input_search_terms_string} -----") + channel.print_searches(self.input_search_terms) + print() + + if self.show_now: + print("----- CURRENTLY RUNNING -----") + channel.print_currently_running() + print() + + def get_channels(self, channel_names: List[str]) -> List[Channel]: + return [channel for channel in self.all_channels if channel.name in channel_names] + + def _change_default_channels(self, channels: List[str]) -> None: + ConfigManager.change_defaults_user_channels(channels) + + def _change_default_justify_length(self, justify_length: int) -> None: + ConfigManager.change_justify_length(justify_length) + + def get_default_channels(self) -> List[str]: + return ConfigManager.get_defaults_user_channels() + + def get_default_justify_length(self) -> int: + return ConfigManager.get_justify_length()