diff --git a/.gitignore b/.gitignore index 894a44c..fa532a7 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +# PyCharm +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b4e4cc3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Elia Onishchouk + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..540b720 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include requirements.txt \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3882315 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Introduction to Python. Hometask + +RSS reader is a command-line utility which receives [RSS](wikipedia.org/wiki/RSS) URL and prints results in human-readable format. + + +Utility provides the following interface: +```shell +usage: rss_reader.py [-h] [--version] [--json] [--verbose] [--limit LIMIT] + [--date DATE] [--to-html TO_HTML] [--to-fb2 TO_FB2] + [--colorize] + source + +Pure Python command-line RSS reader. + +positional arguments: + source RSS URL + +optional arguments: + -h, --help show this help message and exit + --version Prints version info + --json Prints result as JSON in stdout + --verbose Outputs verbose status messages + --limit LIMIT Limits news topics if this parameter provided + --date DATE Shows news of specific date + --to-html TO_HTML Converts news into html format and save to a specified + path + --to-fb2 TO_FB2 Converts news into fb2 format and save to a specified + path + --colorize Colorizes the cmd output +``` + +With the argument `--json` the program converts the news into [JSON](https://en.wikipedia.org/wiki/JSON) format. + +With the argument `--limit` the program prints given number of news. + +With the argument `--verbose` the program prints all logs in stdout. + +With the argument `--version` the program prints in stdout it's current version and complete it's work. + +With the argument `--date` the program prints or saves news of source from specific date stored if there are any. + +With the argument `--to-html` the program saves news from source to the given path as a html file. + +With the argument `--to-fb2` the program saves news from source to the given path as a fb2 file. + +With the argument `--colorize` the program colorizes output in cmd. + +# Caching + +This program stores data in `"home directory"/rss_reader_cache`. In this directory images in folder `images` are stored and a `cache.json` file is located. It stores all data independent of `--date` attribute. \ No newline at end of file diff --git a/json_schema.json b/json_schema.json new file mode 100644 index 0000000..78aba63 --- /dev/null +++ b/json_schema.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + type": "object", + "title": "News feed json schema", + "required": [ + "Feed", + "Items" + ], + "properties": { + "Feed": { + "type": "string", + "title": "Feed", + "description": "The title of the feed" + }, + "Items": { + "type": "array", + "title": "News", + "items": { + "type": "object", + "title": "news", + "required": [ + "title", + "description", + "link", + "pubDate", + "source" + ], + "properties": { + "title": { + "type": "string", + "title": "Title", + "description": "The title of the news" + }, + "description": { + "type": "string", + "title": "Description", + "description": "The description of the news" + }, + "link": { + "type": "string", + "title": "Link", + "description": "The origin link of the news" + }, + "pubDate": { + "type": "string", + "title": "Date", + "description": "The date this news was published" + }, + "source": { + "type": "object", + "title": "Links inside the description", + "required": [ + "images_links", + "href_links", + "video_links" + ], + "properties": { + "images_links": { + "type": "array", + "title": "Images links", + "items": { + "type": "string", + "title": "Image link", + "description": "The source of the image" + } + }, + "href_links": { + "type": "array", + "title": "Hyper references", + "items": { + "type": "string", + "title": "URL link", + "description": "The source of the hyper reference" + } + }, + "video_links": { + "type": "array", + "title": "Video links", + "items": { + "type": "string", + "title": "Video link", + "description": "The source of the video" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2a6104e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +feedparser +bs4 +colorama \ No newline at end of file diff --git a/rss_reader/__init__.py b/rss_reader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rss_reader/rss_reader.py b/rss_reader/rss_reader.py new file mode 100644 index 0000000..084a89f --- /dev/null +++ b/rss_reader/rss_reader.py @@ -0,0 +1,675 @@ +import argparse +import feedparser +import logging +import html +import json +import urllib +import colorama +from base64 import b64encode +from pathlib import Path +from bs4 import BeautifulSoup +from rss_reader import version as vers + + +class Converter: + """This class is used to convert news feed to either html or fb2 version""" + + def __init__(self, news_feed): + """ + Constructor of Converter class. It assigns encoding value additionally + :param NewsFeed news_feed: A NewsFeed object that contains news feed + """ + + self.news_feed = news_feed + self.encoding = self.news_feed.items[0].encoding + + def convert_to_html(self, path, limit, date): + """ + This function creates a html file with news feed + + :param str path: The path where new file will be saved + :param int limit: The number of news to be saved + :param str date: Optional: if exists than resulting html file will only contain news from specific date + """ + + logging.info('Converting to html') + path_object = Path(path) + path_object.mkdir(parents=True, exist_ok=True) + path_object /= 'news feed.html' + with path_object.open('w', encoding="utf-8") as html_file: + html_file.write(self.create_html(limit, date)) + + logging.info('Converting to html successful') + + def convert_to_fb2(self, path, limit): + """ + This function creates a fb2 file with news feed + + :param str path: The path where new file will be saved + :param int limit: The number of news to be saved + """ + + logging.info('Converting to fb2') + path_object = Path(path) + path_object.mkdir(parents=True, exist_ok=True) + path_object /= 'news feed.fb2' + with path_object.open('w', encoding=self.encoding) as fb2_file: + fb2_file.write(self.create_fb2(limit)) + + logging.info('Converting to fb2 successful') + + def create_html(self, limit, date): + """ + This function creates a html version of news feed + + :param int limit: The number of news to be created + :param str date: Optional: if exists than resulting html implementation will contain news from specific date + :return: a html like news feed + :rtype: str + """ + + logging.info('Creating html text') + limit = checking_limit(limit, self.news_feed.items) + news = '\n'.join([item.create_div(date) for item in self.news_feed.items[:limit]]) # ???? + + return """ + + + +

News Feed

+ {0} + + + """.format(news) + + def create_fb2(self, limit): + """ + This function creates a fb2 version of news feed + + :param int limit: The number of news to be created + :return: a fb2 like news feed + :rtype: str + """ + + logging.info('Creating fb2 text') + limit = checking_limit(limit, self.news_feed.items) + news = ''.join([item.create_section() for item in self.news_feed.items[:limit]]) + binaries = ''.join([item.create_binary() for item in self.news_feed.items[:limit]]) + + return """ + + + + newspapers + RSS Reader + el0ny + + + {myprog} + {vers} + + + + {news} + + {binaries} + + """.format(myprog=__name__, vers=vers.__version__, + news=news, encoding=self.encoding, + binaries=binaries) + + +class NewsFeed: + """Base class for news feed""" + + def __init__(self, feed_title, items): + """ + This constructor only initializes two values, nothing else + + :param str feed_title: The title of news feed + :param list items: A list of Item objects, basically, a list of news + """ + + self.feed_title = feed_title + self.items = items + + def print_feed(self, _json, limit, colorize): + """ + This function allows to print news in cmd either in json or str format + + :param colorize: If true than colorize the output in cmd + :param bool _json: If true than the news will be in json format, otherwise in str format + :param int limit: The number of news to be printed + """ + + limit = checking_limit(limit, self.items) + if _json: + self.print_to_json(limit) + else: + self.print_to_console(limit, colorize) + + def create_json(self, is_cached, limit): + """ + This function allows to create json like dict of news + + :param bool is_cached: If true then json will be ready to be saved, otherwise, to be printed + :param int limit: The number of news to be printed + :return: A json like dict of news + :rtype: dict + """ + + return {'Feed': self.feed_title, 'Items': [item.return_item(is_cached) for item in self.items[:limit]]} + + def print_to_json(self, limit): + """ + This function allows to print news in cmd in json format + + :param int limit: The number of news to be printed + """ + + logging.info('Printing news in json format') + print(json.dumps(self.create_json(0, limit))) + + def print_to_console(self, limit, colorize): + """ + This function allows to print news in cmd in str format + + :param colorize: If true than colorize the output in cmd + :param int limit: The number of news to be printed + """ + + logging.info('Printing news in console format') + print('Feed: {0}'.format(self.feed_title)) + for item in self.items[:limit]: + item.print_to_console(colorize) + logging.info('Printed %s news', limit) + + def save_news(self, limit): + """ + This function allows to save news in a json file in homedirectory/rss_reader_cache/cache.json + + :param int limit: The number of news to be saved + """ + logging.info('Saving news') + news_to_save = self.create_json(1, limit)['Items'] + existing_news = load_from_cache() + news_to_save += [item for item in existing_news if item not in news_to_save] + path = Path.home().joinpath('rss_reader_cache') + cache_file = "cache.json" + path.mkdir(parents=True, exist_ok=True) + filepath = path / cache_file + with filepath.open('w') as json_file: + json.dump(news_to_save, json_file) + logging.info('Saving news successful') + + +class Item: + """ + Class for single news item from news feed + Attributes of the class can vary depend on if this item is created from loading from cache, or from parsed feed + They are: + str title News title + str pubDate Published date in it's original form + str link Link to the news + str description Description of the news + dict links A dict with href, image, video links + str date_string (optional: only from cache) Published date in YYYYMMDD format + str source (optional: only from cache) Rss source + str encoding (optional: only from cache) Encoding of the news + """ + def __init__(self, news_dict): + for key in news_dict: + setattr(self, key, news_dict[key]) + + def print_to_console(self, colorize): + """ + This function allows to print one news item in console + + :param colorize: If true than colorize the output in cmd + """ + title_color = '' + date_color = '' + link_color = '' + description_color = '' + href_color = '' + image_color = '' + video_color = '' + divider_color = '' + if colorize: + colorama.init(autoreset=True) + title_color = colorama.Fore.MAGENTA + date_color = colorama.Fore.WHITE + link_color = colorama.Fore.LIGHTBLACK_EX + description_color = colorama.Fore.LIGHTYELLOW_EX + colorama.Back.BLACK + href_color = colorama.Fore.GREEN + image_color = colorama.Fore.LIGHTGREEN_EX + video_color = colorama.Fore.CYAN + divider_color = colorama.Fore.LIGHTWHITE_EX + colorama.Back.LIGHTWHITE_EX + print(colorama.Fore.GREEN) + print(title_color + '\nTitle: {0}'.format(self.title)) + print(date_color + 'Date: {0}'.format(self.pubDate)) + print(link_color + 'Link: {0} \n'.format(self.link)) + print(description_color + self.description) + print() + + if self.links['href_links']: + print(href_color + '\nLinks:') + for link in self.links['href_links']: + print(href_color + link) + + if self.links['images_links']: + print(image_color + '\nImages:') + for link in self.links['images_links']: + print(image_color + link) + + if self.links['video_links']: + print(video_color + '\nVideos:') + for link in self.links['video_links']: + print(video_color + link) + + print(divider_color + '\n//////////////////////////////////////////////////////////////////////////') + + def create_div(self, date): + """ + This function creates a div block of news needed for html convertation + + :param str date: Optional: if exists than resulting div implementation will only contain news from specific date + :return: A string representation of div block of news + :rtype: str + """ + + return """ +
+

{title}

+ {pubDate} +

+

{description}

+ Read More +


+
+ """.format(title=html.escape(self.title), pubDate=self.pubDate, + description=self.insert_hrefs(self.description, date), link=self.link) + + def create_section(self): + """ + This function creates a section block of news needed for fb2 convertation + + :return: A string representation of section block of news + :rtype: str + """ + + logging.info('Creating section') + description = html.escape(self.description) + return """ +
+ <p>{title}</p> +

{pubDate}

+

{description}

+
+ """.format(title=html.escape(self.title), pubDate=self.pubDate, + description=self.insert_hrefs_fb2(self.description)) + + def insert_hrefs(self, description, date): + """ + This function inserts href links in description needed for html convertation + + :param str description: The original description of news + :param str date: Optional: if exists than resulting description will only contain news from specific date + :return: A description with inserted href links + :rtype: str + """ + + description = self.insert_images(html.escape(description), date) + description = self.insert_videos(description) + for href_link in self.links['href_links']: + href_raw = description[description.find(' [link '):description.find(']', description.find(' [link '))+1] + href_content = href_raw[href_raw.find(' | ')+3:len(href_raw)-1] + href_html = '{content}'.format(href=href_link[href_link.find(': ')+2:], + content=href_content) + description = description.replace(href_raw, href_html) + logging.info('href inserted') + return description + + def insert_images(self, description, date): + """ + This function inserts images in description needed for html convertation + + :param str description: The original description of news + :param str date: Optional: if exists than resulting description will only contain news from specific date + :return: A description with inserted image links + :rtype: str + """ + logging.info('Image inserted') + for image_link in self.links['images_links']: + image_raw = description[description.find(' [image '):description.find(']', description.find(' [image '))+1] + image_alt = image_raw[image_raw.find(' | ') + 3:len(image_raw) - 1] + source = image_link[image_link.find(': ') + 2:] + if date: + image_name = source.split('/')[-1] + image_name = image_name.translate(str.maketrans('', '', '.?><"*:|')) + '.jpg' + path = Path.home().joinpath('rss_reader_cache/image') + source = path / image_name + image_html = '{alt}'.format(src=source, alt=image_alt) + description = description.replace(image_raw, image_html) + return description + + def insert_videos(self, description): + """ + This function inserts video links in description needed for html convertation + (I thought that I can convert them into full videos, but then I realised that it was a bad idea, + so I decided to just keep that part, although it isn't necessary anymore + + :param str description: The original description of news + :param str date: Optional: if exists than resulting description will only contain news from specific date + :return: A description with inserted video links + :rtype: str + """ + logging.info('Video inserted') + for video_link in self.links['video_links']: + video_href = description[description.find(' [video '):description.find(']', description.find(' [video '))+1] + logging.info(video_href) + source = video_link[video_link.find(': ') + 2:] + image_html = '{content}'.format(src=source, content=video_href[1:]) + description = description.replace(video_href, image_html) + return description + + def create_binary(self): + """ + This function creates a with b64 images needed for fb2 convertation + + :return: A string in format with images inside + """ + + logging.info('Creating binaries') + binaries = '' + if not self.links['images_links']: + return '' + for image_link in self.links['images_links']: + source = image_link[image_link.find(': ') + 2:] + image_name = source.split('/')[-1] + if source == '': + image_name = '.jpg' + encoded_string = '/9j/4AAQSkZJRgABAQEAXgBeAAD/4RpwRXhpZgAATU0AKgAAAAgABgALAAIAAAAmAAAIYgESAAMAAAABAAEAAAExAAIAAAAmAAAIiAEyAAIAAAAUAAAIrodpAAQAAAABAAAIwuocAAcAAAgMAAAAVgAAEUYc6gAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFdpbmRvd3MgUGhvdG8gRWRpdG9yIDEwLjAuMTAwMTEuMTYzODQAV2luZG93cyBQaG90byBFZGl0b3IgMTAuMC4xMDAxMS4xNjM4NAAyMDE5OjExOjMwIDE0OjQ2OjAyAAAGkAMAAgAAABQAABEckAQAAgAAABQAABEwkpEAAgAAAAMwMQAAkpIAAgAAAAMwMQAAoAEAAwAAAAEAAQAA6hwABwAACAwAAAkQAAAAABzqAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAxOToxMTozMCAxNDo0MTo1MQAyMDE5OjExOjMwIDE0OjQxOjUxAAAAAAYBAwADAAAAAQAGAAABGgAFAAAAAQAAEZQBGwAFAAAAAQAAEZwBKAADAAAAAQAC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MTE8L3htcDpDcmVhdGVEYXRlPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/eHBhY2tldCBlbmQ9J3cnPz7/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/wAALCABWAFYBAREA/8QAHAAAAwADAQEBAAAAAAAAAAAABAUGAgMHAQgA/8QAOxAAAQMDAgMFBQYDCQAAAAAAAQIDBAAFEQYhBxIxEyJBUWEIMkJxgRQVFiNSwZKhsRckcoKRotHS8P/aAAgBAQAAPwD6O3Ua3NMc2+KPYiZxtR7UDPhRSLcfKs/u75f6VqctxA6UI9Bx4Uvfi48KBcZ5TsKw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' + else: + logging.info('Image name %s', image_name) + image_name = image_name.translate(str.maketrans('', '', '.?><"*:|')) + '.jpg' + path = Path.home().joinpath('rss_reader_cache/image') + source = path / image_name + with open(source, "rb") as image_file: + encoded_string = b64encode(image_file.read()).decode() + + binaries += '{data}'\ + .format(src=image_name, data=encoded_string) + return binaries + + def insert_hrefs_fb2(self, description): + """ + This function allows find and insert links into description + (That is also a rudimental function. Originally I wanted to make hrefs to web links, which are stored as + notes. But something went wrong and not all rss were working correctly. So now it just makes links that are + empty) + + :param str description: Were to find those hrefs + :return: Resulting description with inserted href links + :rtype: str + """ + + logging.info('href inserted') + description = self.insert_images_fb2(html.escape(description)) + for href_link in self.links['href_links']: + href_raw = description[description.find(' [link '):description.find(']', description.find(' [link '))+1] + href_content = href_raw[href_raw.find(' | ')+3:len(href_raw)-1] + href_fb2 = '{content}'.format(href=href_link[href_link.find(': ')+2:], + content=href_content) + description = description.replace(href_raw, href_fb2) + return description + + def insert_images_fb2(self, description): + """ + This function allows find and insert links to images into description + + :param str description: Were to find those images + :return: Resulting description with inserted image links + :rtype: str + """ + logging.info('Image inserted') + for image_link in self.links['images_links']: + image_raw = description[description.find(' [image '):description.find(']', description.find(' [image '))+1] + image_alt = image_raw[image_raw.find(' | ') + 3:len(image_raw) - 1] + source = image_link[image_link.find(': ') + 2:] + image_name = source.split('/')[-1] + image_name = image_name.translate(str.maketrans('', '', '.?><"*:|')) + '.jpg' + image_html = '{alt}'.format(src=image_name, alt=image_alt) + description = description.replace(image_raw, image_html) + return description + + def return_item(self, is_cached): + """ + This function returns the content of this object as a dict + + :param bool is_cached: If true than the result dict will be able to be cached + :return: A dict with this object's content + :rtype: dict + """ + + item_content = {'title': self.title, 'description': self.description, + 'link': self.link, 'pubDate': self.pubDate, 'links': self.links} + if is_cached: + item_content['date_string'] = self.date_string + item_content['source'] = self.source + item_content['encoding'] = self.encoding + return item_content + + +def set_argparse(): + """ + This function allows to get needed parameters from command line + + :return: An object with all needed parameters inside + """ + + parser = argparse.ArgumentParser(description='Pure Python command-line RSS reader.') + parser.add_argument('source', type=str, help='RSS URL') + + parser.add_argument('--version', action='version', version='%(prog)s v'+vers.__version__, + help='Prints version info') + parser.add_argument('--json', action='store_true', help='Prints result as JSON in stdout') + parser.add_argument('--verbose', action='store_true', help='Outputs verbose status messages') + parser.add_argument('--limit', type=int, default=-1, help='Limits news topics if this parameter provided') + parser.add_argument('--date', type=str, help='Shows news of specific date') + parser.add_argument('--to-html', dest='to_html', type=str, + help='Converts news into html format and save to a specified path') + parser.add_argument('--to-fb2', dest='to_fb2', type=str, + help='Converts news into fb2 format and save to a specified path') + parser.add_argument('--colorize', action='store_true', help='Colorizes the cmd output') + return parser.parse_args() + + +def find_images(soup): + """ + This function allows to extract image links from parsed feed + + :param bs4.BeautifulSoup soup: A beautifulsoup representation of parsed news feed + :return: A list of found image links + :rtype: list + """ + + logging.info('Starting image finding') + image_iterator = 0 + images_links = [] + + for img in soup.findAll('img'): + + image_iterator += 1 + if 'alt' in img.attrs and img['alt'] != '': + replaced_data = ' [image {0} | {1}] '.format(image_iterator, img['alt']) + else: + replaced_data = ' [image {0}]'.format(image_iterator) + src = img['src'] + + if src != '': + image_name = src.split('/')[-1] + image_name = image_name.translate(str.maketrans('', '', '.?><"*:|')) + '.jpg' + path = Path.home().joinpath('rss_reader_cache/image') + path.mkdir(parents=True, exist_ok=True) + filepath = path / image_name + if filepath.is_file(): + logging.info('Image already exists') + else: + urllib.request.urlretrieve(src, filepath) + images_links.append('[{0}]: {1}'.format(image_iterator, src)) + soup.find('img').replace_with(replaced_data) + + logging.info('Image finding finished. Found %s images', image_iterator) + return images_links + + +def find_href(soup): + """ + This function allows to extract href links from parsed feed + + :param bs4.BeautifulSoup soup: A beautifulsoup representation of parsed news feed + :return: A list of found href links + :rtype: list + """ + + logging.info('Starting link finding') + href_iterator = 0 + href_links = [] + for href in soup.findAll('a'): + if 'href' in href.attrs: + href_iterator += 1 + link = href['href'] + if href.text != '': + replaced_data = ' [link {0} | {1}] '.format(href_iterator, href.text) + else: + replaced_data = ' [link {0}] '.format(href_iterator) + href_links.append('[{0}]: {1}'.format(href_iterator, link)) + href.replace_with(replaced_data) + logging.info('Link finding finished. Found %s links', href_iterator) + return href_links + + +def find_videos(soup): + """ + This function allows to extract