3 # Mastomail is a Python script that fetches replies from your Mastodon
4 # account and emails them to you. Each email includes the reply, the
5 # time it was posted, a URL to view the reply on Mastodon, and the
6 # toot being replied to.
8 # Uncomment and run register_app() and authenticate_user() on your
9 # first run, then comment them out for subsequent runs.
11 # Copyright (C) 2024 Jason Self <j@jxself.org>
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
18 # This program is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <https://www.gnu.org/licenses/>.
26 from mastodon import Mastodon
28 from email.mime.text import MIMEText
29 from html.parser import HTMLParser
30 from email.utils import formatdate
32 # Helper class to strip HTML tags except URLs
33 class MLStripper(HTMLParser):
38 self.convert_charrefs = True
41 def handle_starttag(self, tag, attrs):
43 href = [v for k, v in attrs if k == 'href']
45 self.text.append(href[0]) # Append the URL
47 def handle_data(self, d):
51 return ''.join(self.text)
53 # Function to strip HTML tags but keep URLs
59 # Register your application with Mastodon
63 api_base_url = 'https://mastodon.social',
64 to_file = 'pytooter_clientcred.secret'
67 # Authenticate your user account
68 def authenticate_user():
70 client_id='pytooter_clientcred.secret',
71 api_base_url='https://mastodon.social'
74 'your_email@example.com',
76 to_file='pytooter_usercred.secret'
79 # Load the Mastodon instance with all credentials
81 client_id='pytooter_clientcred.secret',
82 access_token='pytooter_usercred.secret',
83 api_base_url='https://mastodon.social'
86 # Function to fetch replies
87 def fetch_replies(since_id=None):
88 notifications = mastodon.notifications(since_id=since_id, limit=50)
89 replies = [n for n in notifications if n['type'] == 'mention']
92 # Function to format email content
93 def format_email(replies, my_mastodon_base_url):
96 content = strip_tags(reply['status']['content'])
97 created_at = reply['status']['created_at']
98 toot_id = reply['status']['id']
99 # Construct the URL to the toot on your account
100 reply_url = f"{my_mastodon_base_url}/web/statuses/{toot_id}"
102 in_reply_to_content = ""
103 if reply['status'].get('in_reply_to_id'):
104 in_reply_to_status = mastodon.status(reply['status']['in_reply_to_id'])
105 if in_reply_to_status:
106 in_reply_to_content = strip_tags(in_reply_to_status['content'])
108 email_content += f"Reply: {content}\nTime: {created_at}\nURL: {reply_url}\nIn reply to: {in_reply_to_content}\n\n"
111 # Function to send email
112 def send_email(content):
113 sender = 'your_email@example.com'
114 receiver = 'your_email@example.com'
115 msg = MIMEText(content)
116 msg['Subject'] = 'Mastodon Replies'
119 msg['Date'] = formatdate(localtime=True) # Add current date and time
122 with smtplib.SMTP('mail.example.com', 587) as server:
124 server.login(sender, 'your_password')
125 server.sendmail(sender, receiver, msg.as_string())
127 # Functions to save and load the last checked ID
128 def save_last_checked_id(last_id):
129 with open('last_checked_id.txt', 'w') as f:
130 f.write(str(last_id))
132 def load_last_checked_id():
134 with open('last_checked_id.txt', 'r') as f:
135 return int(f.read().strip())
136 except (FileNotFoundError, ValueError):
140 my_mastodon_base_url = 'https://mastodon.social' # Replace with your Mastodon instance URL
141 last_checked = load_last_checked_id()
142 new_replies = fetch_replies(since_id=last_checked)
144 email_content = format_email(new_replies, my_mastodon_base_url)
145 send_email(email_content)
146 latest_reply_id = new_replies[0]['id']
147 save_last_checked_id(latest_reply_id)
149 if __name__ == '__main__':