From 8ad3b65807c3d60fd63fdd4b33563c2cbbec21f3 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 00:51:52 +0800 Subject: [PATCH 01/20] Update README.rst --- README.rst | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index f425bb2..b0549bb 100644 --- a/README.rst +++ b/README.rst @@ -4,16 +4,9 @@ rss2email done simple. Delivers news from feeds (RSS, Atom, ...) to your mail box. -How to install +Required Env ~~~~~~~~~~~~~~ - -Simply check out the Git repository or download the Python file. - -**Docker**:: - - docker build -t feed2mail . - -**Alternatively, manual virtualenv**:: +**Required**:: pip install html2text feedparser From 6dcbfca0432802537c118ee856f593ad3964f3be Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 00:52:29 +0800 Subject: [PATCH 02/20] Delete Dockerfile --- Dockerfile | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index cfbc0f4..0000000 --- a/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM python:2-alpine - -RUN pip install feedparser html2text - -COPY feed2mail.py config.py / - -ENV SEEN_FILE=/seen/seen - -CMD python2 feed2mail.py From ef0dfa119010695a924a56068c43478bbbbc8c0a Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 00:52:47 +0800 Subject: [PATCH 03/20] Delete example_config.py --- example_config.py | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 example_config.py diff --git a/example_config.py b/example_config.py deleted file mode 100644 index 9d06f27..0000000 --- a/example_config.py +++ /dev/null @@ -1,16 +0,0 @@ -SENDER_MAIL = 'send@er.tld' -RECIPIENT_MAIL = 'rec@ipient.tld' -SMTP_SERVER = 'ser.ver.tld' -SMTP_USE_TLS = False -SMTP_PORT = None # can be absent/set to None for the default value - -# A list of feeds to fetch. -# Items may be `(feed_url, group_name)` tuples. -# Entries of feeds that make a group won't be sent twice of they appear -# on multiple feeds (often seen on News Sites that offer topic feeds) -FEEDS = [ - 'http://foobar.org/feed.rss', - 'http://blah.com/feed.atom', - ('http://www.reddit.com/r/python/.rss', 'reddit'), - ('http://www.reddit.com/r/programming/.rss', 'reddit') -] From c96aebaceecbd99c273a9ca2b2052f0b1020a758 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 00:52:54 +0800 Subject: [PATCH 04/20] Delete feed2mail.py --- feed2mail.py | 274 --------------------------------------------------- 1 file changed, 274 deletions(-) delete mode 100644 feed2mail.py diff --git a/feed2mail.py b/feed2mail.py deleted file mode 100644 index 41848c0..0000000 --- a/feed2mail.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python - -""" Copyright 2010-2017 Jonas Haag . ISC-licensed. """ -import os -import sys -import time -import smtplib -import email, email.utils, email.mime.text -import pickle - -import feedparser -import html2text - -import config - - -def warn(feed_url, status, msg): - print >> sys.stderr, \ - 'WARNING: %s HTTP %d: %s' % (feed_url, status, msg) - -def log(msg): - print msg - - -class BufferedUnicode(object): - """ - Simple pseudo unicode string. StringIO wasn't worth importing. - >>> buf = BufferedUnicode() - >>> buf += 'hello' - >>> buf += u' world!' - >>> buf.as_unicode() - 'hello world!' - """ - def __init__(self): - self._buf = [] - - def __iadd__(self, other): - try: - self._buf.append(unicode(other)) - return self - except UnicodeDecodeError: - raise TypeError('Expected unicode') - - def as_unicode(self): - return u''.join(self._buf) - - -def my_html2text(s): - return html2text.html2text(s.replace('\n', ' ')) - - -def force_plaintext(element): - if 'html' in element.type: - return my_html2text(element.value) - return element.value - - -def fetch_entries(feed_url, seen_entries): - log('Fetching %r...' % feed_url) - feed = feedparser.parse(feed_url) - - if feed.bozo: - status = feed.get('status', 404) - if status != 200: - warn(feed_url, status, feed.bozo_exception) - if 400 <= status < 600: - return - - for entry in feed.entries: - if 'id' not in entry: - assert entry.link - entry.id = entry.link - if entry.id in seen_entries: - log('Already saw entry %r' % entry.id) - continue - log('Got new entry %r' % entry.id) - seen_entries.add(entry.id) - try: entry['feed_author'] = feed.feed['author'] - except KeyError: pass - try: entry['feed_title'] = feed.feed['title_detail'] - except KeyError: pass - yield entry - - -def select_plaintext_body(entry): - """ - Returns the first plaintext body that can be found in `entry`, - or the first HTML body converted to plaintext using ``html2text`` - of none was found. - - Returns ``None`` if no bodies are found at all. - """ - bodies = entry.get('content', []) + [entry.get('summary_detail')] - bodies = filter(None, bodies) - if not bodies: - return None - for body in bodies: - if body.type == 'text/plain': - return body.value - return my_html2text(bodies[0].value) - - -def select_plaintext_title(entry): - """ - Returns the entry's title, converted to plaintext if needed, - or ``None`` if no title is found. - """ - try: - return force_plaintext(entry['title_detail']) - except KeyError: - pass - - -def select_timestamp(entry): - """ - Returns the date and time `entry` was updated, published or created - (respectively) as a time-tuple. - """ - for attr in ('updated', 'published', 'created'): - try: - return entry['%s_parsed'] % attr - except KeyError: - pass - return time.gmtime() - - -def generate_mail_for_entry(entry): - # the entry's title: - title = select_plaintext_title(entry) - # the entry's content: - body = select_plaintext_body(entry) - # the entry's permalink - link = entry.get('link', entry.id) - # the date+time the entry was updated/published: - timestamp = select_timestamp(entry) - # the entry's feed's title: - feed_title = force_plaintext(entry.get('feed_title')) - # the entry's author: - author = (entry.get('author') and entry.get('author').strip() or None) - # the feed's author: - feed_author = entry.get('feed_author') - # files attached to the entry: - enclosures = entry.get('enclosures', []) - - subject, author, body = format_mail( - entry.id, - link, - title, - timestamp, - author, - body, - feed_title, - feed_author, - enclosures, - ) - - mail = email.mime.text.MIMEText( - body.encode('utf-8'), - 'plain', - 'utf-8' - ) - mail['To'] = config.RECIPIENT_MAIL - mail['Subject'] = author + ': ' + subject - mail['From'] = config.SENDER_MAIL - mail['Date'] = email.utils.formatdate(time.mktime(timestamp)) - mail['X-RSS-Entry-ID'] = entry.id - - return entry.id, mail.as_string().replace('\n', '\r\n') - - -def format_mail(id, link, title, timestamp, author, body, - feed_title, feed_author, enclosures): - """ - Returns a `(subject, author, body)` tuple, forming the mail's - Subject and From headers and the mail's body, respectively. - - All arguments passed expect for `id` and `timestamp` can be ``None``. - - The returned tuple's items *must* be strings (they can be empty, though). - """ - if not title: - if body: - title = body[:70] + '...' - else: - title = link - - if feed_title: - author = feed_title - else: - if not author: - author = feed_author or '' - - content = BufferedUnicode() - content += title + '\n' + (link or id) - if enclosures: - content += ' [%d enclosures]' % len(enclosures) - - if body: - content += '\n\n' - content += body - content += '\n' - - if enclosures: - content += '-' * 20 - for enclosure in enclosures: - try: - length = int(float(enclosure.length)) - except (ValueError, AttributeError): - length = -1 - content += '\nEnclosure: %s (%s, %d bytes)' \ - % (enclosure.href, enclosure.type, length) - - return title.strip(), author.strip(), content.as_unicode() - - -format_mail = getattr(config, 'format_mail', format_mail) - - -def main(): - SEEN_FILE = os.environ.get('SEEN_FILE', '.seen') - - if os.path.exists(SEEN_FILE): - with open(SEEN_FILE, 'r') as fobj: - seen = pickle.load(fobj) - else: - seen = {} - - mail_queue = [] - - for feed in config.FEEDS: - if isinstance(feed, (list, tuple)): - feed, feed_id = feed - else: - feed_id = feed - seen.setdefault(feed_id, set()) - for entry in fetch_entries(feed, seen[feed_id]): - mail_queue.append(generate_mail_for_entry(entry)) - - mails = len(mail_queue) - sent = error = 0 - if mails: - smtp_server = smtplib.SMTP( - config.SMTP_SERVER, - getattr(config, 'SMTP_PORT', None) - ) - if getattr(config, 'SMTP_USE_TLS', False): - smtp_server.starttls() - if hasattr(config, 'SMTP_USERNAME') or hasattr(config, 'SMTP_PASSWORD'): - smtp_server.login( - getattr(config, 'SMTP_USERNAME', None), - getattr(config, 'SMTP_PASSWORD', None) - ) - for entry_id, mail in mail_queue: - log('Sending mail for entry %r...' % entry_id) - try: - smtp_server.sendmail( - config.SENDER_MAIL, - config.RECIPIENT_MAIL, - mail, - ) - sent += 1 - except: - import traceback - traceback.print_exc() - error += 1 - - log('-' * 20) - log('Sent %d of %d mails (%d errors)' % (sent, mails, error)) - - with open(SEEN_FILE, 'w') as fobj: - pickle.dump(seen, fobj) - -if __name__ == '__main__': - main() From 61046f2e266d58b312d30fa07f5d5ba2cfb4a58f Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 00:53:21 +0800 Subject: [PATCH 05/20] Add files via upload --- config.py | 15 ++++++ data/Nature | Bin 0 -> 90052 bytes data/Science | Bin 0 -> 41679 bytes rss2email.py | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 config.py create mode 100644 data/Nature create mode 100644 data/Science create mode 100644 rss2email.py diff --git a/config.py b/config.py new file mode 100644 index 0000000..e213975 --- /dev/null +++ b/config.py @@ -0,0 +1,15 @@ +SENDER_MAIL = 'Sender Name ' +RECIPIENT_MAIL = 'recipient_email@email.com' +SMTP_SERVER = 'smtp.mail.com' +SMTP_USE_TLS = True +SMTP_PORT = 587 # can be absent/set to None for the default value +SMTP_USERNAME = 'smtp_username@email.com' +SMTP_PASSWORD = 'smtp_password' +# A list of feeds to fetch. +# Items must be `(feed_url, group_name)` tuples. +# Entries of feeds that make a group won't be sent twice of they appear +# on multiple feeds (often seen on News Sites that offer topic feeds) +FEEDS = [ + ('https://www.science.org/action/showFeed?type=axatoc','Science'), + ('http://feeds.nature.com/nature/rss/current','Nature'), +] \ No newline at end of file diff --git a/data/Nature b/data/Nature new file mode 100644 index 0000000000000000000000000000000000000000..502e17c9fb3db04eb2b0d5e114a6c4866f62ba24 GIT binary patch literal 90052 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 Ocsa5IpNN>> buf = BufferedUnicode() + >>> buf += 'hello' + >>> buf += ' world!' + >>> buf.as_unicode() + 'hello world!' + """ + def __init__(self): + self._buf = [] + + def __iadd__(self, other): + try: + self._buf.append(str(other)) + return self + except UnicodeDecodeError: + raise TypeError('Expected unicode') + + def as_unicode(self): + return ''.join(self._buf) + +def news_content_form(news): + content = BufferedUnicode() + # news link + try: + content += ('' % news.link) + except: + content += ('' % news.id) + # news title + content += news.title + '
' + # news author + try: + content += ('Author: %s...
' % news.author[:40].replace('\n',' ')) + except: + pass + # news time + for time_news in ('updated', 'published', 'created'): + try: + content += ('%s time: %s
' % (time_news,news[time_news])) + break + except KeyError: + pass + # news summary + try: + content += ('%s
' % html2text.html2text(news.summary)) + except: + pass + # content break + content += '

' + return content.as_unicode() + +def mail_content_form(entry): + body = BufferedUnicode() + for news in entry: + body += news_content_form(news) + return body.as_unicode() + +def mail_form(entry,mail_title): + mail_body = mail_content_form(entry) + mail = email.mime.text.MIMEText( + mail_body.encode('utf-8'), + 'html', + 'utf-8' + ) + mail['To'] = config.RECIPIENT_MAIL + mail['Subject'] = mail_title + mail['From'] = config.SENDER_MAIL + mail['Date'] = email.utils.formatdate(localtime=True) + return mail.as_string() + +def data_compare(feed_content,mail_title): + try: + with open('./data/%s' % mail_title,'rb') as file: + pre_data = pickle.load(file) + except: + with open('./data/%s' % mail_title,'wb') as file: + pickle.dump(feed_content,file) + return feed_content + if sorted(pre_data[0]) == sorted(feed_content[0]): + print('[Warning] No update. Would not send new email.') + return None + else: + with open('./data/%s' % mail_title,'wb') as file: + pickle.dump(feed_content,file) + return feed_content + +def main(): + smtp_server = smtplib.SMTP( + config.SMTP_SERVER, + getattr(config, 'SMTP_PORT', None) + ) + if getattr(config, 'SMTP_USE_TLS', False): + smtp_server.starttls() + if hasattr(config, 'SMTP_USERNAME') or hasattr(config, 'SMTP_PASSWORD'): + smtp_server.login( + getattr(config, 'SMTP_USERNAME', None), + getattr(config, 'SMTP_PASSWORD', None) + ) + for feed in config.FEEDS: + feed_url, mail_title = feed + print('[Info] Fetching %s: %s' % (mail_title,feed_url)) + feed_content = feedparser.parse(feed_url).entries + feed_content = data_compare(feed_content,mail_title) + if feed_content: + mail_content = mail_form(feed_content,mail_title) + try: + print('[Info] Email to %s from %s' % (config.RECIPIENT_MAIL,config.SENDER_MAIL)) + smtp_server.sendmail( + config.SENDER_MAIL, + config.RECIPIENT_MAIL, + mail_content, + ) + except: + import traceback + traceback.print_exc() + print('[Info] Finish') + +if __name__ == '__main__': + main() \ No newline at end of file From e899ee85a71f20dad048c540a037b40331e8c02e Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:00:48 +0800 Subject: [PATCH 06/20] Update README.rst --- README.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index b0549bb..43e1ea4 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,8 @@ rss2email done simple. Delivers news from feeds (RSS, Atom, ...) to your mail box. +Digested HTML format + Required Env ~~~~~~~~~~~~~~ **Required**:: @@ -12,20 +14,17 @@ Required Env How to use it? ~~~~~~~~~~~~~~ -1. ``cp example_config.py config.py``. +1. ``cp rss2email.py config.py data``. 2. Edit ``config.py``. -3. Run feed2mail every *N* seconds/hours/decades. For Docker setup:: - - docker run -v /path/to/your/seen/file:/seen feed2mail - - For manual virtualenv setup, simply run ``feed2mail.py``. - -I've found a bug! -~~~~~~~~~~~~~~~~~ -Great! `Please open a ticket`_. - -.. _Please open a ticket: http://github.com/jonashaag/feed2mail/issues/ - +3. run in python:: + + python rss2email.py + +4. Task:: + + vim crontab + minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py + License? ~~~~~~~~ ISC From 7ad8ecb0d3863476e2cb7ea540f9ee3dc84783f8 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:01:31 +0800 Subject: [PATCH 07/20] Update README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 43e1ea4..a6ac9ed 100644 --- a/README.rst +++ b/README.rst @@ -16,11 +16,11 @@ How to use it? ~~~~~~~~~~~~~~ 1. ``cp rss2email.py config.py data``. 2. Edit ``config.py``. -3. run in python:: +**3. run in python**:: python rss2email.py -4. Task:: +**4. Task**:: vim crontab minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py From 7342d04440966174a9d05529624cdb27e9f8cc99 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:06:02 +0800 Subject: [PATCH 08/20] Update README.rst --- README.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a6ac9ed..487d210 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Delivers news from feeds (RSS, Atom, ...) to your mail box. Digested HTML format -Required Env +**Required Env**:: ~~~~~~~~~~~~~~ **Required**:: @@ -25,6 +25,13 @@ How to use it? vim crontab minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py +RSS Link +~~~~~~~~~~~~~~ +**Science**:: + https://www.science.org/action/showFeed?type=axatoc +**Nature**:: + https://www.science.org/action/showFeed?type=axatoc + License? ~~~~~~~~ ISC From fc2a77f24d16f36e5aed4b19d594e21b95c610c4 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:06:37 +0800 Subject: [PATCH 09/20] Update README.rst --- README.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 487d210..a0e839a 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,8 @@ Delivers news from feeds (RSS, Atom, ...) to your mail box. Digested HTML format -**Required Env**:: +**Required Env** ~~~~~~~~~~~~~~ -**Required**:: pip install html2text feedparser @@ -27,9 +26,9 @@ How to use it? RSS Link ~~~~~~~~~~~~~~ -**Science**:: +Science: https://www.science.org/action/showFeed?type=axatoc -**Nature**:: +Nature: https://www.science.org/action/showFeed?type=axatoc License? From 80b732bdc3f91b66aeb72a8e07867b1eaf0693a1 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:07:20 +0800 Subject: [PATCH 10/20] Update README.rst --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a0e839a..5753e50 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -feed2mail +rss2mail --------- rss2email done simple. @@ -15,6 +15,7 @@ How to use it? ~~~~~~~~~~~~~~ 1. ``cp rss2email.py config.py data``. 2. Edit ``config.py``. + **3. run in python**:: python rss2email.py From b2e74cc02c05d824af0dc87516a5d194888147af Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:07:44 +0800 Subject: [PATCH 11/20] Update README.rst --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 5753e50..65665fe 100644 --- a/README.rst +++ b/README.rst @@ -14,8 +14,7 @@ Digested HTML format How to use it? ~~~~~~~~~~~~~~ 1. ``cp rss2email.py config.py data``. -2. Edit ``config.py``. - +**2. Edit ``config.py``.** **3. run in python**:: python rss2email.py From 2e39b7f0338f70589a0e186d17e35dec1c3f454c Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:08:51 +0800 Subject: [PATCH 12/20] Update README.rst --- README.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 65665fe..402a9da 100644 --- a/README.rst +++ b/README.rst @@ -14,12 +14,9 @@ Digested HTML format How to use it? ~~~~~~~~~~~~~~ 1. ``cp rss2email.py config.py data``. -**2. Edit ``config.py``.** -**3. run in python**:: - - python rss2email.py - -**4. Task**:: +2. Edit ``config.py``. +3. run in python ``python rss2email.py`` +4. Task:: vim crontab minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py From 8c294f03c1859965a02a4e3d24ed1db015fff9af Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:09:23 +0800 Subject: [PATCH 13/20] Update README.rst --- README.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 402a9da..b4fa321 100644 --- a/README.rst +++ b/README.rst @@ -17,10 +17,9 @@ How to use it? 2. Edit ``config.py``. 3. run in python ``python rss2email.py`` 4. Task:: - - vim crontab - minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py - + ``vim crontab`` + ``minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py`` + RSS Link ~~~~~~~~~~~~~~ Science: From ba89ee22d651441bb19ca65f77548ec308a11759 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:09:38 +0800 Subject: [PATCH 14/20] Update README.rst --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b4fa321..b3f46df 100644 --- a/README.rst +++ b/README.rst @@ -16,8 +16,9 @@ How to use it? 1. ``cp rss2email.py config.py data``. 2. Edit ``config.py``. 3. run in python ``python rss2email.py`` -4. Task:: +4. Task: ``vim crontab`` + ``minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py`` RSS Link From 04c3be25105828e559ad331e3fc5f87fba3d6661 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:10:00 +0800 Subject: [PATCH 15/20] Update README.rst --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b3f46df..0b73473 100644 --- a/README.rst +++ b/README.rst @@ -17,8 +17,9 @@ How to use it? 2. Edit ``config.py``. 3. run in python ``python rss2email.py`` 4. Task: + ``vim crontab`` - + ``minute hour day_of_month month day_of_week user_name cd ./location && python rss2email.py`` RSS Link From 3f7a4fa8a7fa7c3e1d3299f09979a1e00d8fb4ac Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:11:05 +0800 Subject: [PATCH 16/20] Delete .gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2b544a7..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -config.py -.seen -*.pyc From 2d1c33c88f47dffb6c99be23940c21c79683e8f0 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:11:59 +0800 Subject: [PATCH 17/20] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0b73473..3374426 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,4 @@ -rss2mail +rss2email --------- rss2email done simple. From 6a5bacbe1193f1a58ee9df43c727f3a5ded1d312 Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 01:14:33 +0800 Subject: [PATCH 18/20] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3374426..97f5c57 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ Digested HTML format How to use it? ~~~~~~~~~~~~~~ -1. ``cp rss2email.py config.py data``. +1. ``cp -r rss2email.py config.py /data``. 2. Edit ``config.py``. 3. run in python ``python rss2email.py`` 4. Task: From 6f1554304524962e8b59d0386ef0ffef6a7b6b1e Mon Sep 17 00:00:00 2001 From: MoaxWang <100744149+MoaxWang@users.noreply.github.com> Date: Mon, 16 May 2022 09:46:56 +0800 Subject: [PATCH 19/20] Update LICENSE --- LICENSE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LICENSE b/LICENSE index 8ddf75d..3dfc053 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,7 @@ https://github.com/jonashaag/feed2mail +https://github.com/MoaxWang/rss2mail Copyright (c) 2010-2013 Jonas Haag and contributors (see Git logs). +Copyright (c) 2022-2022 Wenqiang.Wang@monash.edu and contributors (see Git logs). Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above From bf962aeee6cfe21777f7bce0c71a3b11d0dad88e Mon Sep 17 00:00:00 2001 From: MoaxWang Date: Thu, 30 Jun 2022 19:36:45 +0800 Subject: [PATCH 20/20] Delete LICENSE --- LICENSE | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3dfc053..0000000 --- a/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -https://github.com/jonashaag/feed2mail -https://github.com/MoaxWang/rss2mail -Copyright (c) 2010-2013 Jonas Haag and contributors (see Git logs). -Copyright (c) 2022-2022 Wenqiang.Wang@monash.edu and contributors (see Git logs). - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.