diff --git a/final_task/MANIFEST.in b/final_task/MANIFEST.in new file mode 100644 index 0000000..01b3e2b --- /dev/null +++ b/final_task/MANIFEST.in @@ -0,0 +1,2 @@ +include rss_reader\TimesNewRoman.ttf +include rss_reader\VERSION.txt \ No newline at end of file diff --git a/final_task/README.md b/final_task/README.md index 7af281f..0cbd240 100644 --- a/final_task/README.md +++ b/final_task/README.md @@ -1,3 +1,159 @@ -# Your readme here -Some text. -Checkout how to write this file using *markdown*. +# RSS-READER + +## What is rss-reader? + +This is small application for watching feed on your device. It shows shot information about the latest news and keeps previous news. + +## How to install? + +1. To install the application on your device must be python 3.7 and more. +2. Download this repository on your device. +3. Open command line(Terminal) in this directory. +4. Enter next command: +``` +python setup.py sdist +cd dist +pip install feedparser +pip install requests +pip install fpdf +pip install colored +pip install rss-reader-5.2.tar.gz +``` +5. Check workability with command: +``` +rss-reader -h +``` + +## Parameters: +``` +rss-reader -h +usage: rss-reader [-h] [--version] [--json] [--verbose] [--limit LIMIT] [--date DATE] [--to-html TO_HTML] [--to-pdf TO_PDF] [source] + +positional arguments: + source RSS URL + +optional arguments: + -h, --help show this help message and exit + --version Print version info + --json Print result as JSON in stdout + --verbose Outputs verbose status messages + --limit LIMIT Limit news topics if this parameter provided + --date DATE Obtaining the cached news without the Internet + --to-html TO_HTML The argument gets the path where the HTML news will be saved + --to-pdf TO_PDF The argument gets the path where the PDF news will be saved + --colorize Colorize text +``` + +##How using? + +Open command line(terminal) and enter command: `rss-reader https://news.yahoo.com/rss --limit 2`. +You will see something like this: + +``` +Yahoo News - Latest News & Headlines + +Title: After Sondland bombshell, Democrats look to expand Trump probe to Pompeo, others +Date: Thu, 21 Nov 2019 16:12:24 -0500 +Link: https://news.yahoo.com/after-sondland-bombshell-democrats-look-to-expand-trump-probe-to-pompeo-others-211224656.html +Description: [image: After Sondland bombshell, Democrats look to expand Trump probe to Pompeo, others] House Democrats, emboldened after Ambassador Gordon Sondland provided stunning testimony Wednesday, are debating whether to expand the investigation to other Trump administration officials. +Link on image: http://l1.yimg.com/uu/api/res/1.2/audgsHkjxGCXTuCGEgaejw--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2019-11/eb56ad70-0c98-11ea-955d-14c81854d8a0 + +Title: Exclusive: Acting Homeland Security chief Chad Wolf makes unannounced visit to privately funded border wall +Date: Thu, 21 Nov 2019 17:38:03 -0500 +Link: https://news.yahoo.com/acting-homeland-security-chief-chad-wolf-makes-unannounced-visit-to-privately-funded-border-wall-223803492.html +Description: [image: Exclusive: Acting Homeland Security chief Chad Wolf makes unannounced visit to privately funded border wall] Acting Homeland Security Secretary Chad Wolf made an unpublicized visit Wednesday to the site of a privately constructed border wall during his first official trip to the southern border, Yahoo News has confirmed. +Link on image: http://l2.yimg.com/uu/api/res/1.2/QqpSY1ue.EJVLS1afFZDkg--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2019-11/4dcbf870-0cad-11ea-bb6a-0080c3ca4a87 +``` + +In this example, you entered the url from which you wanted to see the news. + +You can also view the news on a specific date with the command `rss-reader --date 20190123` `20190123` is January 23, 2019. + +You can save any kind of news(selected by date or from the site) into two types of PDF or HTML file by `--to-pdf` and `--to-html` commands with a path argument (the path can be both local and global). +Example: `rss-reader --date 20190123 --to-pdf D:\` after executing the program, a message with the path to the pdf file will be displayed. + +``` +The recording has been completed in the file: +D:\20191123_Yahoo.pdf +``` + +##JSON format + +``` +{ + "title": "News by Mon, 18 Nov 2019 ", + "items": [ + { + "title": "The Problem with Hypersonic Missiles: \"None of this stuff works yet.\"", + "published": "Mon, 18 Nov 2019 02:26:00 -0500", + "link": "https://news.yahoo.com/problem-hypersonic-missiles-none-stuff-072600256.html", + "summary": "[image: The Problem with Hypersonic Missiles: \"None of this stuff works yet.\"]Don’t get too excited about hypersonic weapons, one prominent U.S. defense journalist advised. According to him, we still don’t know for sure whether the Mach-5-plus munitions actually work.", + "contain_image": true, + "link_on_image": "http://l2.yimg.com/uu/api/res/1.2/YOhob5Hzzh5bR_GUbztqLg--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en/the_national_interest_705/ef38aa18f65ca8d1c745a2f38867a67c" + }, + { + "title": "Ukraine ex-president named witness in power abuse probe", + "published": "Mon, 18 Nov 2019 12:33:39 -0500", + "link": "https://news.yahoo.com/ukraine-ex-president-named-witness-power-abuse-probe-173339651.html", + "summary": "[image: Ukraine ex-president named witness in power abuse probe]Ukraine's former president Petro Poroshenko has been designated a witness in a criminal investigation related to the nomination of judges, the state investigation bureau said on Monday. Poroshenko has been embroiled in a number of investigations since leaving office in May. \"His status is that of a witness,\" a spokeswoman for the state investigation bureau, which handles high-profile cases, told AFP.", + "contain_image": true, + "link_on_image": "http://l1.yimg.com/uu/api/res/1.2/gxuHKLF4ZjFgen0ZN5.vRA--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/http://media.zenfs.com/en_us/News/afp.com/9f494baee33192289c2a59c3f93e2bb40c49c4d5.jpg" + } + ] +} +``` + +* "title" - source name\ +* "items" - list with dictionary which contains one news information\ + * "title" - headline news + * "published" - date publication + * "link" - link + * "summary" - description +* "links" - this is a list with links to the image of the I-th news + +##How is data keeping? + +Data keep in local file which is located in directory: +Windows:`C:\Users\User\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\rss_reader` +Linux:\ +macOS: + +There is only json string in the file. JSON format: +``` +{ + "20191116" : { + "title" : "News by Sat, 16 Nov 2019 ", + "items" : [ + { + "contain_image" : true, + "link" : "https://news.yahoo.com/chile-police-stopped-rescue-workers-004537157.html", + "published" : "Sat, 16 Nov 2019 19:45:37 -0500", + "summary" : "[image: Chile police stopped rescue workers helping dying protester: human rights watchdog]Chile's independent human rights watchdog said on Saturday it would file a formal complaint for murder against police officers who allegedly prevented paramedics from attending a heart attack victim amid a protest Friday. Security forces firing tear gas, rubber bullets and water cannons made it impossible for rescue workers to properly treat the victim, Chile's publicly-funded National Institute for Human Rights said. Twenty-nine year old Abel Acuna died shortly after at a nearby Santiago hospital.", + "title" : "Chile police stopped rescue workers helping dying protester: human rights watchdog", + "link_on_image" : "http://l1.yimg.com/uu/api/res/1.2/msfkqScfjEDGkZw0FpwuYQ--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en-US/reuters.com/4fcd3b80fd76604f970966b9f77b32bd" + }, + { + "contain_image" : true, + "link" : "https://news.yahoo.com/heres-everything-know-mina-chang-205907562.html", + "published" : "Sat, 16 Nov 2019 16:39:46 -0500", + "summary" : "[image: Here's everything we know about Mina Chang, who rapidly rose from a self-described singer to a State Department official with a dubious résumé]A closer look at her history reveals the Trump official may have misrepresented her work history and educational background.", + "title" : "Here's everything we know about Mina Chang, who rapidly rose from a self-described singer to a State Department official with a dubious résumé", + "link_on_image" : "http://l.yimg.com/uu/api/res/1.2/SViNbM6UMykaG6aS3FTZPg--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en-US/business_insider_articles_888/6ce7057ecba60c171f6ee9381c8db196" + } + ] + } + "20191115" : { + "title" : "News by Sat, 15 Nov 2019 ", + "items" : [ + { + "contain_image" : true, + "link" : "https://news.yahoo.com/prince-andrew-didn-t-sex-004304182.html", + "published" : "Sat, 15 Nov 2019 19:43:04 -0500", + "summary" : "[image: Prince Andrew: I Didn’t Have Sex With Virginia Roberts Giuffre. I Was Eating Pizza.]Screenshot/BBCNewsnight/TwitterPrince Andrew says he is prepared to give evidence about his friendship with late accused child sex trafficker Jeffrey Epstein under oath, and claimed to have no recollection of the notorious photograph of him with his arm around the waist of alleged Epstein victim Virginia Roberts Giuffre being taken.Inside Jeffrey Epstein’s Creepy Parties With Prince AndrewTo support his latter claim, Andrew told the BBC’s Newsnight program on Saturday that the “traveling clothes” he was wearing in the picture were not clothes he would wear in London. The claim is demonstrably false, as there are images of him leaving London nightclub Chinawhite in July 2000, wearing almost the exact same outfit.Giuffre has accused Prince Andrew of having sex with her on three occasions when she was being trafficked by Epstein, an allegation he has denied. Andrew also insisted he couldn’t have had sex with Roberts on the night she claimed, as he was at a chain pizza restaurant, Pizza Express, in suburban Woking with his daughters. He said the pizza party was at about 4 p.m. or 5 p.m. and then he was at home.He sought to discredit Roberts’ account of dancing with him at Tramp nightclub, in which she described him as sweating profusely, by claiming that he was suffering from a post-combat condition that meant he didn’t sweat. He went on to dispute Giuffre's claim that he bought her a drink, saying he “didn’t know where the bar was” in Tramp, despite admitting having been there several times.“I don’t believe it’s a picture of me in London because... when I go out in London, I wear a suit and a tie. That’s what I would describe as my traveling clothes if I’m going overseas. I’ve got plenty of photographs of me dressed in that sort of kit but not there,” Andrew said.He repeatedly questioned the authenticity of the photograph of him and Giuffre: “I’m afraid to say that I don’t believe that photograph was taken in the way that has been suggested.”He repeatedly said he had “no memory” of the photograph being taken.At one stage he said: “I can’t, we can’t be certain as to whether or not that’s my hand on her left side.”Andrew gave a bewildering array of other reasons to support his claim that the picture was fake, including saying he had never been upstairs in Ghislaine Maxwell’s London home, and that Epstein never carried a camera (in fact, Giuffre claims the photo was taken on her camera). “Nobody can prove whether or not that photograph has been doctored, but I don’t recollect that photograph ever being taken.”Andrew flatly denied having sex with Giuffre, saying: “Without putting too fine a point on it, if you’re a man it is a positive act to have sex with somebody. You have to take some sort of positive action and so therefore if you try to forget, it’s very difficult to try and forget a positive action and I do not remember anything. I can’t.”“I’ve wracked my brain and thinking oh… when the first allegations, when the allegations came out originally I went, ‘Well that’s a bit strange, I don’t remember this,’ and then I’ve been through it and through it and through it over and over and over again, and no, nothing. It just never happened.”Interviewer Emily Maitlis also asked if Andrew believed rumors that Epstein had not in fact committed suicide.In response, Andrew appeared to show more than just a passing familiarity with the work of celebrity pathologist-for-hire, Dr. Michael Baden, who was hired by Epstein’s brother to observe the official autopsy.Jeffrey Epstein Camp Sent Pathologist Michael Baden to Watch Over His AutopsyAndrew replied: “I’m not one to be able to answer that question. I believe that centers around something to do with a bone in his neck, so whether or not if you commit suicide that bone breaks or something. But I’m afraid to say I’m not an expert, I have to take what the coroner says and he has ruled that it was suicide.”Baden claimed that a collection of neck fractures in Epstein’s hyoid bone and thyroid cartilage were “extremely unusual in suicidal hangings and could occur much more commonly in homicidal strangulation.”In fact, numerous studies show that hyoid and thyroid fractures are not rare in suicidal hangings, especially as people age.Emily Maitlis questions Prince Andrew on 'Newsnight'Screenshot/BBCNewsnight/TwitterMaitlis pressed Andrew about the circumstances in which a photograph was taken of himself and Jeffrey Epstein walking in Central Park, after Epstein had been convicted of a child sex offense. Andrew said he had been staying with Epstein with the express purpose of breaking off his friendship with Epstein because of his child sex conviction. He said that he had decided to break off the friendship in person because he believed it was the “honorable” thing to do. “I felt that doing it over the telephone was the chicken’s way of doing it. I had to go and see him and talk to him,” Andrew said.Asked why he stayed at the home of a “convicted sex offender,” Andrew said: “It was a convenient place to stay. I mean I’ve gone through this in my mind so many times. At the end of the day, with a benefit of all the hindsight that one can have, it was definitely the wrong thing to do. But at the time, I felt it was the honorable and right thing to do, and I admit fully that my judgement was probably colored by my tendency to be too honorable but that’s just the way it is.”Asked about the steady procession of young girls coming and going, Andrew said: “I wasn’t a party to any of that. I never saw them. I mean you have to understand that his house, I described it more as almost as a railway station if you know what I mean in the sense that there were people coming in and out of that house all the time. What they were doing and why they were there I had nothing to do with. So I’m afraid I can’t make any comment on that because I really don’t know.”Andrew repeatedly denied ever noticing there was anything amiss in Epstein’s behavior, despite, as he said, being a patron of British child protection charity the NSPCC.Andrew admitted that he had stayed with Epstein on his private island and flown on his private jet, and also confirmed that he invited Epstein to a party at Buckingham Palace, a shooting weekend at the Queen’s country estate, and to his daughter’s birthday party.Andrew said he was invited because he was the boyfriend of his old friend, Ghislaine Maxwell.Andrew said he did not regret his friendship with Epstein, saying, “the people that I met and the opportunities that I was given to learn either by him or because of him were actually very useful.”Maitlis later asked again if he regretted ever befriending Epstein, saying: “Do I regret the fact that he has quite obviously conducted himself in a manner unbecoming? Yes.”“Unbecoming?” Maitlis asked in disbelief: “He was a sex offender!”Andrew said: “I’m sorry, I’m being polite, I mean in the sense that he was a sex offender. But no, was I right in having him as a friend? At the time, bearing in mind this was some years before he was accused of being a sex offender. I don’t think there was anything wrong then. The problem was the fact that once he had been convicted… I stayed with him and that’s the bit that, as it were, I kick myself for on a daily basis, because it was not something that was becoming of a member of the royal family and we try and uphold the highest standards and practices and I let the side down, simple as that.”Maitlis asked Andrew if he would “be willing to testify or give a statement under oath if you were asked.”Andrew replied: “Well I’m like everybody else and I will have to take all the legal advice that there was before I was to do that sort of thing. But if push came to shove and the legal advice was to do so, then I would be duty bound to do so.”Maitlis concluded by offering Andrew the opportunity to say “anything you feel has been left unsaid that you would like to say now?”“No, I don’t think so,” drawled Andrew. “ I think you’ve probably dragged out most of what is required.”Read more at The Daily Beast.Get our top stories in your inbox every day. Sign up now!Daily Beast Membership: Beast Inside goes deeper on the stories that matter to you. Learn more.", + "title" : "Prince Andrew: I Didn’t Have Sex With Virginia Roberts Giuffre. I Was Eating Pizza.", + "link_on_image" : "http://l2.yimg.com/uu/api/res/1.2/hxEVKpLLNwh0Yqb4N6xvSg--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs-/https://media.zenfs.com/en-US/thedailybeast.com/1ca9a23aefca6c158388b9b9ab47eadc" + } + ] + } +} +``` diff --git a/final_task/__init__.py b/final_task/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/final_task/rss_reader/RssReaderException.py b/final_task/rss_reader/RssReaderException.py new file mode 100644 index 0000000..3453a48 --- /dev/null +++ b/final_task/rss_reader/RssReaderException.py @@ -0,0 +1,16 @@ +class RssReaderException(Exception): + def __init__(self, message): + self.expression = 'RssReaderException: ' + self.message = message + + +class FileException(RssReaderException): + def __init__(self, message): + self.expression = 'RssReaderException.FileException: ' + self.message = message + + +class ConnectException(RssReaderException): + def __init__(self, message): + self.expression = 'RssReaderException.ConnectException: ' + self.message = message \ No newline at end of file diff --git a/final_task/rss_reader/TimesNewRoman.ttf b/final_task/rss_reader/TimesNewRoman.ttf new file mode 100644 index 0000000..51261a0 Binary files /dev/null and b/final_task/rss_reader/TimesNewRoman.ttf differ diff --git a/final_task/rss_reader/VERSION.txt b/final_task/rss_reader/VERSION.txt new file mode 100644 index 0000000..11aa145 --- /dev/null +++ b/final_task/rss_reader/VERSION.txt @@ -0,0 +1 @@ +5.3 \ No newline at end of file diff --git a/final_task/rss_reader/__init__.py b/final_task/rss_reader/__init__.py new file mode 100644 index 0000000..6d3601c --- /dev/null +++ b/final_task/rss_reader/__init__.py @@ -0,0 +1 @@ +VERSION = '5.2' \ No newline at end of file diff --git a/final_task/rss_reader/decorators.py b/final_task/rss_reader/decorators.py new file mode 100644 index 0000000..9ab0f00 --- /dev/null +++ b/final_task/rss_reader/decorators.py @@ -0,0 +1,11 @@ +import logging + + +def functions_log(function): + def wrapper(*args, **kwargs): + logging.info(f'start function: {function.__name__}') + result = function(*args, **kwargs) + logging.info(f'end function: {function.__name__}') + return result + + return wrapper diff --git a/final_task/rss_reader/requirements.txt b/final_task/rss_reader/requirements.txt index e69de29..83d8f52 100644 --- a/final_task/rss_reader/requirements.txt +++ b/final_task/rss_reader/requirements.txt @@ -0,0 +1,4 @@ +feedparser==5.2.1 +colored==1.4.0 +fpdf=1.7.2 +requests==2.22.0 \ No newline at end of file diff --git a/final_task/rss_reader/rss_reader.py b/final_task/rss_reader/rss_reader.py index e69de29..7d09669 100644 --- a/final_task/rss_reader/rss_reader.py +++ b/final_task/rss_reader/rss_reader.py @@ -0,0 +1,89 @@ +import argparse +from typing import Union +import feedparser +import logging +import json +import sys +import os + +sys.path.append(os.path.abspath(os.path.dirname(__file__))) + + +import work_with_file +import work_with_text +import work_with_dict +import work_with_html +import work_with_pdf +import work_with_colorize +import RssReaderException +import work_with_feedparser +import __init__ + + +def set_start_setting(): + """setup start settings""" + parser = argparse.ArgumentParser() + parser.add_argument("source", help="RSS URL", nargs='?', default='', type=str) + parser.add_argument("--version", help="Print version info", action="store_true") + parser.add_argument("--json", help="Print result as JSON in stdout", action="store_true") + parser.add_argument("--verbose", help="Outputs verbose status messages", action="store_true") + parser.add_argument("--limit", help="Limit news topics if this parameter provided", type=int) + parser.add_argument("--date", help="Obtaining the cached news without the Internet", type=str) + parser.add_argument("--to-html", help="The argument gets the path where the HTML news will be saved", type=str) + parser.add_argument("--to-pdf", help="The argument gets the path where the PDF news will be saved", type=str) + parser.add_argument("--colorize", help="Colorize text", action="store_true") + args = parser.parse_args() + if not args.limit: + args.limit = -1 + if args.verbose: + logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%I:%M:%S %p', + stream=sys.stdout, level=logging.DEBUG) + else: + logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%I:%M:%S %p', + filename="sample.log", level=logging.DEBUG) + return args + + +def run(): + os.chdir(os.path.abspath(os.path.dirname(__file__))) + args = set_start_setting() + logging.info('the application is running') + logging.debug('args: ' + str(args)) + logging.info(os.getcwd()) + data = None + try: + if args.version: + pass + elif args.date: + data = work_with_file.read_feed_form_file(args.date) + elif args.source: + data = work_with_feedparser.get_object_feed(args.source) + data = work_with_dict.to_dict(data) + work_with_file.add_feed_to_file(data) + else: + raise RssReaderException.RssReaderException('How work with application?\nEnter in command line: rss-reader -h') + + if args.version: + result = f'RSS reader version {open("VERSION.txt").readline()}' + else: + if args.limit: + data = work_with_dict.limited_dict(data, args.limit) + if args.json: + result = json.dumps(data, ensure_ascii=False, indent=4) + elif args.to_html: + result = work_with_html.write_to_html_file(data, args.to_html) + elif args.to_pdf: + result = work_with_pdf.write_to_pdf_file(data, args.to_pdf) + elif args.colorize: + result = work_with_colorize.colorize_text(data) + else: + result = work_with_text.get_string_with_result(data, args.limit) + print(result) + except RssReaderException.RssReaderException as exc: + print(exc.expression) + print(exc) + logging.info('the application is finished') + + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/final_task/rss_reader/work_with_colorize.py b/final_task/rss_reader/work_with_colorize.py new file mode 100644 index 0000000..c5687b2 --- /dev/null +++ b/final_task/rss_reader/work_with_colorize.py @@ -0,0 +1,21 @@ +import colored + + +def colorize_text(data: dict): + yellow = colored.fg(11) + red = colored.fg(9) + green = colored.fg(82) + pink = colored.fg(200) + blue = colored.fg(20) + description_color = colored.fg(14) + default = colored.fg(230) + result = '\n' + result += f"{yellow} {data['title']} {default}\n\n" + for index_news, dict_news in enumerate(data['items']): + result += f"{green}Title: {red}{dict_news['title']}\n" + result += f"{green}Date: {pink}{dict_news['published']}\n" + result += f"{green}Link: {blue}{dict_news['link']}\n" + result += f"{green}Description: {description_color}{dict_news['summary']}\n" + if dict_news['contain_image']: + result += f"{green}Link on image: {blue}{dict_news['link_on_image']}{default}\n\n" + return result diff --git a/final_task/rss_reader/work_with_dict.py b/final_task/rss_reader/work_with_dict.py new file mode 100644 index 0000000..8fc5e2a --- /dev/null +++ b/final_task/rss_reader/work_with_dict.py @@ -0,0 +1,50 @@ +import json +from feedparser import FeedParserDict +import decorators +import work_with_text + + +@decorators.functions_log +def to_dict(data) -> dict: + """convert data to JSON format""" + structure = { + 'feed': [ + 'title' + ], + 'entries': [ + 'title', + 'published', + 'link', + 'summary' + ] + } + + result = {'title': '', 'items': []} + links_on_image = [] + if isinstance(data, FeedParserDict): + result['title'] = work_with_text.text_processing(data['feed']['title']) + + for item in data['entries']: + temp = { + 'title': work_with_text.text_processing(item['title']), + 'published': work_with_text.text_processing(item['published']), + 'link': work_with_text.text_processing(item['link']), + 'summary': work_with_text.text_processing(item['summary'], links_on_image) + } + result['items'].append(temp) + for index_link, link in enumerate(links_on_image): + if link: + result['items'][index_link]['contain_image'] = True + result['items'][index_link]['link_on_image'] = links_on_image[index_link] + else: + result['items'][index_link]['contain_image'] = False + return result + + +@decorators.functions_log +def limited_dict(data: dict, limit: int) -> dict: + result = { + 'title': data['title'], + 'items': data['items'][:limit], + } + return result diff --git a/final_task/rss_reader/work_with_feedparser.py b/final_task/rss_reader/work_with_feedparser.py new file mode 100644 index 0000000..a6bcb4e --- /dev/null +++ b/final_task/rss_reader/work_with_feedparser.py @@ -0,0 +1,30 @@ +import decorators +import RssReaderException +import feedparser +import os + + +@decorators.functions_log +def get_object_feed(url: str) -> feedparser.FeedParserDict: + try: + data = feedparser.parse(url) + try: + if data.status == 200: + if data['version']: + return data + else: + raise RssReaderException.ConnectException(f'There is no rss feed at this url: {url}') + else: + raise RssReaderException.ConnectException(f'HTTP Status Code {data.status}') + except AttributeError: + if os.path.isfile(url): + if data['version']: + return data + else: + raise RssReaderException.ConnectException(f'There is no rss feed at this url: {url}') + else: + raise RssReaderException.ConnectException(f'HTTP Status Code {data.status}') + except AttributeError: + raise RssReaderException.ConnectException(f'{url} - is not url(example url "https://google.com")') + except Exception as exc: + raise RssReaderException.ConnectException(exc) \ No newline at end of file diff --git a/final_task/rss_reader/work_with_file.py b/final_task/rss_reader/work_with_file.py new file mode 100644 index 0000000..0048395 --- /dev/null +++ b/final_task/rss_reader/work_with_file.py @@ -0,0 +1,50 @@ +import datetime +import os +import json +import decorators +import RssReaderException + + +@decorators.functions_log +def add_feed_to_file(dict_data: dict): + if os.path.exists('cache.json'): + with open('cache.json', 'r') as read_file: + dict_with_date = json.load(read_file) + else: + dict_with_date = {} + for news_index, news_dict in enumerate(dict_data['items']): + date = get_date(news_dict['published']) + if date in dict_with_date.keys(): + links_on_news = [] + for dict_news in dict_with_date[date]['items']: + links_on_news.append(dict_news['link']) + if news_dict['link'] not in links_on_news: + dict_with_date[date]['items'].append(news_dict) + else: + dict_with_date[date] = { + 'title': f"News by {news_dict['published'][:news_dict['published'].find(':') - 2]}", + 'items': [news_dict], + } + with open('cache.json', 'w') as file: + json.dump(dict_with_date, file) + + +@decorators.functions_log +def read_feed_form_file(date_str: str): + if os.path.exists('cache.json'): + with open('cache.json', 'r') as read_file: + dict_with_date = json.load(read_file) + if date_str in dict_with_date.keys(): + return dict_with_date[date_str] + else: + raise RssReaderException.FileException('Date ' + date_str + ' not found in cache.') + + else: + raise RssReaderException.FileException('Cache is empty. Please launch the app from the URL to the news site.\n'+\ + 'EXAMPLE: rss-reader https://news.yahoo.com/rss') + + +def get_date(input_str: str) -> str: + result = input_str[input_str.find(',') + 2: input_str.find(':') - 2].strip(' ') + result = datetime.datetime.strptime(result, '%d %b %Y') + return result.strftime('%Y%m%d') diff --git a/final_task/rss_reader/work_with_html.py b/final_task/rss_reader/work_with_html.py new file mode 100644 index 0000000..beb17e5 --- /dev/null +++ b/final_task/rss_reader/work_with_html.py @@ -0,0 +1,35 @@ +import os +import datetime +import RssReaderException + + +def write_to_html_file(data: dict, path: str): + if os.path.isdir(path): + date_str = datetime.datetime.now().date().strftime('%Y%m%d') + filename = os.path.join(path, date_str + '_' + + data['title'][:data['title'].find(' ')].replace(':', '').replace('.', '') + '.html') + with open(filename, 'w', encoding="utf-8") as file: + file.write(text_processing_for_html(data)) + return f'the recording has been completed in the file:\n{filename}' + else: + raise RssReaderException.FileException(f'{path} is not found') + + +def text_processing_for_html(data: dict): + style = '' + result = '
' + \ + 'The president is lashing out on Twitter over news first reported by the Washington Post ' + \
+ 'that he wanted the attorney general to hold a press conference declaring he had broken no laws dur' + \
+ 'ing the July 25 phone call in which he urged Ukraine’s new president to investigate his political ' + \
+ 'rival.
'
+ output = ['http://l2.yimg.com/uu/api/res/1.2/fMm6_rzTShdBsnaYua7fIA--/YXBwaWQ9eXRhY2h5b247aD04Njt3PTEzMDs' +
+ '-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2019-11/e87f47a0-016d-11e' +
+ 'a-ab5f-bdce0579bae9', 'Trump fumes about reports that he wanted Barr to host news conference c' +
+ 'learing him on Ukraine call']
+ self.assertEqual(work_with_text.get_img(entering), output)
+
+ entering = ''
+ output = ['link', 'alt']
+ self.assertEqual(work_with_text.get_img(entering), output)
+
+ entering = '
'
+ output = ['links', 'alt']
+ self.assertNotEqual(work_with_text.get_img(entering), output)
+
+ def test_text_processing(self):
+ links = []
+ entering = 'test'
+ output = 'test'
+ self.assertEqual(work_with_text.text_processing(entering, links), output)
+
+ entering = 'test_'
+ output = 'test'
+ self.assertNotEqual(work_with_text.text_processing(entering, links), output)
+
+ entering = '
test'
+ output = 'test'
+ self.assertEqual(work_with_text.text_processing(entering, links), output)
+
+ entering = '<>test<>'
+ output = 'test'
+ self.assertEqual(work_with_text.text_processing(entering, links), output)
+
+ entering = 'test<>'
+ output = '[image: text] test'
+ self.assertEqual(work_with_text.text_processing(entering, links), output)
+
+ def test_edit_key(self):
+ entering = 'key'
+ output = 'Key: '
+ self.assertEqual(work_with_text.edit_key(entering), output)
+
+ entering = 'published'
+ output = 'Date: '
+ self.assertEqual(work_with_text.edit_key(entering), output)
+
+ entering = 'summary'
+ output = 'Description: '
+ self.assertEqual(work_with_text.edit_key(entering), output)
+
+ def test_get_string_with_result(self):
+ entering_dict = {
+ 'title': 'titl',
+ 'items': [{
+ 'title': 't1',
+ 'published': 'date1',
+ 'link': 'link1',
+ 'summary': 'des1',
+ 'contain_image': False,
+ },
+ {
+ 'title': 't2',
+ 'published': 'date2',
+ 'link': 'link2',
+ 'summary': 'des2',
+ 'contain_image': True,
+ 'link_on_image': 'linkOnImg'
+ }
+ ],
+ }
+ output = '\ntitl\n\nTitle: t1\nDate: date1\nLink: link1\nDescription: des1\n\n' + \
+ 'Title: t2\nDate: date2\nLink: link2\nDescription: des2\nLink on image: linkOnImg\n\n'
+ self.assertEqual(work_with_text.get_string_with_result(entering_dict, 2), output)
+
+
+if __name__ == '__main__':
+ unittest.main()