diff --git a/.gitignore b/.gitignore
index 894a44c..32a6946 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,3 +102,11 @@ venv.bak/
# mypy
.mypy_cache/
+
+.idea/
+
+*.db
+
+*.fb2
+
+*.html
diff --git a/Json structure.md b/Json structure.md
new file mode 100644
index 0000000..57cb35d
--- /dev/null
+++ b/Json structure.md
@@ -0,0 +1,21 @@
+{
+
+ "Source:":string,
+
+ "Feeds": [{"title": string,
+
+ "date": string,
+
+ "link":string,
+
+ "description": string,
+
+ "media": [{"url": string,
+
+ "type": string}],
+
+ "links": [{"url": string,
+
+ "type": "string"}]
+ }]
+}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ab35068
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+feedparser~=5.2
+requests~=2.22
diff --git a/rss_reader/__init__.py b/rss_reader/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/rss_reader/__main__.py b/rss_reader/__main__.py
new file mode 100644
index 0000000..51a024b
--- /dev/null
+++ b/rss_reader/__main__.py
@@ -0,0 +1,4 @@
+from .rss_reader import main
+
+if __name__ == '__main__':
+ main()
diff --git a/rss_reader/cache.py b/rss_reader/cache.py
new file mode 100644
index 0000000..04ba10e
--- /dev/null
+++ b/rss_reader/cache.py
@@ -0,0 +1,72 @@
+import sqlite3
+import logging
+
+
+file_path = 'cache.db'
+
+
+class Cache:
+ """"This class contains news and methods of work whit cache"""
+ cursor = None
+ conn = None
+
+ def __init__(self):
+ """This method initialize cursor to database"""
+ if self.cursor is None:
+ Cache._init_cursor()
+ else:
+ logger = logging.getLogger('rss_reader')
+ logger.error("This is singleton class. Use get_cursor")
+
+ @staticmethod
+ def _init_cursor():
+ Cache.conn = sqlite3.connect(file_path)
+ Cache.cursor = Cache.conn.cursor()
+ Cache.cursor.execute('''CREATE TABLE IF NOT EXISTS news(id INTEGER PRIMARY KEY,
+ title text, pub_date_key numeric, pub_date text, link text, description text, UNIQUE(link))''')
+ Cache.cursor.execute('''CREATE TABLE IF NOT EXISTS links( id INTEGER PRIMARY KEY,
+ link text, news numeric)''')
+ Cache.cursor.execute('''CREATE TABLE IF NOT EXISTS media( id INTEGER PRIMARY KEY,
+ link text, news numeric)''')
+
+ @staticmethod
+ def get_cursor():
+ """Static access method. """
+ if Cache.cursor is None:
+ Cache()
+ return Cache.cursor
+
+ @staticmethod
+ def commit():
+ """This method commit to database database"""
+ return Cache.conn.commit()
+
+ @staticmethod
+ def close():
+ """This method close connection to database"""
+ return Cache.conn.close()
+
+ @staticmethod
+ def print_news(date):
+ """This method print news to std from selected date to database"""
+ Cache.get_cursor()
+ Cache.cursor.execute('''SELECT * FROM news WHERE pub_date_key = ?''', (date,))
+ news = Cache.cursor.fetchall()
+ if len(news) == 0:
+ return 1
+ for elem in news:
+ print('\nTitle: ', elem[1])
+ print('Date: ', elem[3])
+ print('Link: ', elem[4])
+ print(f'Description: {elem[5]}\n')
+ Cache.cursor.execute('''SELECT * FROM links WHERE news= ?''', (elem[0],))
+ links = Cache.cursor.fetchall()
+ i = 1
+ for link in links:
+ print(f'Link[{i}]: ', link[1])
+ i = i + 1
+ Cache.cursor.execute('''SELECT * FROM media WHERE news= ?''', (elem[0],))
+ links = Cache.cursor.fetchall()
+ for link in links:
+ print(f'Link[{i}]: ', link[1])
+ i = i + 1
diff --git a/rss_reader/news.py b/rss_reader/news.py
new file mode 100644
index 0000000..68e9c46
--- /dev/null
+++ b/rss_reader/news.py
@@ -0,0 +1,186 @@
+import html
+import os
+import re
+import json
+import logging
+from .cache import Cache
+import base64
+import requests
+
+
+class News:
+ """This class contains news and methods of work whit news"""
+
+ http_header = 'http'
+ err_media_type = 'No type'
+
+ def __init__(self, feeds_dict, limit):
+
+ logger = logging.getLogger('rss_reader')
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ file_handler = logging.FileHandler('rss_reader_logs.log')
+ file_handler.setFormatter(formatter)
+ logger.addHandler(file_handler)
+ logger.setLevel(logging.INFO)
+ self.news = dict()
+ self.all_news = list()
+
+ self.name_of_source = feeds_dict.feed['title']
+
+ real_limit = len(feeds_dict.entries)
+ if limit > 0:
+ if limit < len(feeds_dict.entries):
+ real_limit = limit
+
+ cursor = Cache.get_cursor()
+
+ for i in range(real_limit):
+ list_to_cache = list()
+ self.news['title'] = html.unescape(feeds_dict.entries[i].title)
+ self.news['date'] = html.unescape(feeds_dict.entries[i].published)
+ self.news['link'] = html.unescape(feeds_dict.entries[i].link)
+ self.news['description'] = self.clean_from_tags(html.unescape(feeds_dict.entries[i].description))
+
+ date_dict = feeds_dict.entries[i].published_parsed
+ date_str = str(date_dict.tm_year) + str(date_dict.tm_mon) + str(date_dict.tm_mday)
+
+ list_to_cache.append(self.news['title'])
+ list_to_cache.append(date_str)
+ list_to_cache.append(self.news['date'])
+ list_to_cache.append(self.news['link'])
+ list_to_cache.append(self.news['description'])
+
+ self.news['media'] = self._parse_media(feeds_dict.entries[i])
+ self.news['links'] = self._parse_links(feeds_dict.entries[i])
+
+ self._cache_feed(list_to_cache, self.news['links'], self.news['media'], cursor)
+
+ self.all_news.append(self.news.copy())
+ Cache.close()
+
+ @staticmethod
+ def _parse_links(news_dict):
+ """This function parse links of feed"""
+ list_of_links = list()
+ if news_dict.links:
+ for elem in news_dict.links:
+ list_of_links.append({'url': elem.setdefault('url', None), 'type': elem.setdefault('type', None)})
+ return list_of_links
+
+ def _parse_media(self, news_dict):
+ """This function parse media of feed"""
+ if news_dict.setdefault('media_content', None):
+ media = list()
+ if news_dict.media_content:
+ for elem in news_dict.media_content:
+ if elem['url'].rfind(self.http_header, 0, len(elem['url'])) > 0:
+ # Some sources of news write two links in one string of media. And only second string is image
+ links = elem['url'].split(self.http_header)
+ media.append({'url': self.http_header + links[2], 'type': "img"})
+ else:
+ if elem.setdefault('url', None):
+ media.append({'url': elem.setdefault('url', None),
+ 'type': elem.setdefault('type', None)})
+ return media
+ else:
+ return ''
+
+ def _cache_feed(self, list_of_main_info, list_of_links, list_of_media, cursor):
+ """This function write feed to cache"""
+ cursor.execute('''INSERT or IGNORE INTO news (title, pub_date_key, pub_date, link, description)
+ VALUES(?,?,?,?,?)''', list_of_main_info)
+ ids = cursor.lastrowid
+
+ list_to_cache_of_links = list()
+ for elem in list_of_links:
+ list_to_cache_of_links.append(elem.setdefault('url', None))
+ list_to_cache_of_links.append(ids)
+ cursor.execute('''INSERT or IGNORE INTO links (link, news) VALUES(?,?)''', list_to_cache_of_links)
+ list_to_cache_of_links.clear()
+
+ list_to_cache_of_media = list()
+ for elem in list_of_media:
+ list_to_cache_of_media.append(elem.setdefault('url', None))
+ list_to_cache_of_media.append(ids)
+ cursor.execute('''INSERT or IGNORE INTO media (link, news) VALUES(?,?)''', list_to_cache_of_media)
+ list_to_cache_of_media.clear()
+
+ Cache.commit()
+
+ @staticmethod
+ def clean_from_tags(text_with_tags):
+ """This function delete tags from string"""
+ return re.sub('<.*?>', '', text_with_tags)
+
+ def print(self):
+ """This function print news to stdout in readable format"""
+ print(f'Source: {self.name_of_source}\n')
+ for elem in self.all_news:
+ print(f'Title: {elem["title"]}')
+ print(f'Date: {elem["date"]}')
+ print(f'Link: {elem["link"]}')
+ print(f'Description: {elem["description"]}\n')
+
+ j = 1
+ print('Links: ')
+ for link in elem['links']:
+ print(f'[{j}] {link["url"]} ({link["type"]})')
+ j = j + 1
+
+ if elem.setdefault('media', None):
+ print("Media: ")
+ for media in elem['media']:
+ print(f'[{j}] {media["url"]} ({media["type"]})')
+ j = j + 1
+
+ def to_json(self):
+ """This function returns JSON-string with news"""
+ return json.dumps({'Source:': self.name_of_source, 'Feeds': self.all_news}, ensure_ascii=False).encode('utf8')
+
+ def create_fb2(self, filepath):
+ if filepath[-4::] != ".fb2":
+ filename = filepath + ".fb2"
+ with open(filename, 'w', encoding="utf-8") as fb2_file:
+ fb2_file.write('\n')
+ fb2_file.write(f''' {self.name_of_source.replace("&", "&")} {elem["title"].replace("&", "&")} Date of posting: {elem["date"].replace("&", "&")} {elem["description"].replace("&", "&")} Source: {elem["link"]}
Date of posting: {elem["date"]}
') + html_file.write(f'{elem["description"]}
') + html_file.write(f'') + + for media in elem['media']: + if media['type'] != self.err_media_type: + html_file.write(f'