cmc/cleberg.net

My personal web garden & blog.

clone: git clone https://gitbay.org/cmc/cleberg.net.git

main: content/blog/2022-06-22-daily-poetry.org · raw

  1#+date:        [2022-06-22 Wed 00:00:00]
  2#+title:       Daily Poetry via Cron
  3#+description: A Python script that emails you a poem every morning via cron.
  4#+slug:        daily-poetry
  5#+filetags:    :personal:
  6
  7* Source Code
  8
  9I don't want to bury the lede here, so if you'd like to see the full source code
 10I use to email myself plaintext poems daily, visit the repository:
 11[[https://github.com/krazywarez/daily-poem][daily-poem.git]].
 12
 13* My Daily Dose of Poetry
 14
 15Most of my programming projects are small, random projects that are made
 16strictly to fix some small problem I have or enhance my quality of life.
 17
 18In this case, I was looking for a simply and easy way to get a daily dose of
 19literature or poetry to read in the mornings.
 20
 21However, I don't want to sign up for a random mailing list on just any
 22website. I also don't want to have to work to find the reading content
 23each morning, as I know I would simply give up and stop reading daily.
 24
 25Thus, I found a way to deliver poetry to myself in plain-text format, on
 26a daily basis, and scheduled to deliver automatically.
 27
 28* Prerequisites
 29
 30This solution uses Python and email, so the following process requires
 31the following to be installed:
 32
 331. A Simple Mail Transfer Protocol (SMTP) server, which can be as easy as
 34   installing =mailutils= if you're on a Debian-based distribution.
 352. Python (& pip!)
 363. The following Python packages: =email=, =smtplib=, =json=, and =requests=
 37
 38* Breaking Down the Logic
 39
 40I want to break down the logic for this program, as it's quite simple and
 41informational.
 42
 43** Required Packages
 44
 45This program starts with a simple import of the required packages, so I wanted
 46to explain why each package is used:
 47
 48#+begin_src python
 49from email.mime.text import MIMEText # Required for translating MIMEText
 50import smtplib # Required to process the SMTP mail delivery
 51import json # Required to parse the poetry API results
 52import requests # Required to send out a request to the API
 53#+end_src
 54
 55** Sending the API Request
 56
 57Next, we need to actually send the application programming interface (API)
 58request. In my case, I'm calling a random poem from the entire API. If you want,
 59you can call specific poems or authors from this API.
 60
 61#+begin_src python
 62json_data = requests.get('https://poetrydb.org/random').json()
 63#+end_src
 64
 65This gives us the following result in JSON (JavaScript Object Notation):
 66
 67#+begin_src json
 68[
 69    {
 70        "title": "Sonnet XXII: With Fools and Children",
 71        "author": "Michael Drayton",
 72        "lines": [
 73            "To Folly",
 74            "",
 75            "With fools and children, good discretion bears;",
 76            "Then, honest people, bear with Love and me,",
 77            "Nor older yet, nor wiser made by years,",
 78            "Amongst the rest of fools and children be;",
 79            "Love, still a baby, plays with gauds and toys,",
 80            "And, like a wanton, sports with every feather,",
 81            "And idiots still are running after boys,",
 82            "Then fools and children fitt'st to go together.",
 83            "He still as young as when he first was born,",
 84            "No wiser I than when as young as he;",
 85            "You that behold us, laugh us not to scorn;",
 86            "Give Nature thanks you are not such as we.",
 87            "Yet fools and children sometimes tell in play",
 88            "Some, wise in show, more fools indeed than they."
 89        ],
 90        "linecount": "15"
 91    }
 92]
 93#+end_src
 94
 95** Parsing the API Results
 96
 97In order to parse this into a readable format, we need to use the =json= package
 98and extract the fields we want. In the example below, I am grabbing every field
 99presented by the API.
100
101For the actual poem content, we need to loop over each line in the =lines=
102variable since each line is a separate string by default.
103
104#+begin_quote
105You /could/ also extract the title or author and make another call out
106to the API to avoid having to build the plaintext poem with a loop, but
107it just doesn't make sense to me to send multiple requests when we can
108create a simple loop on our local machine to work with the data we
109already have.
110
111For
112[[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]],
113look at the raw data response of this link to see the poem's lines
114returned in plaintext.
115#+end_quote
116
117#+begin_src python
118title = json_data[0]['title']
119author = json_data[0]['author']
120line_count = json_data[0]['linecount']
121lines = ''
122for line in json_data[0]['lines']:
123  lines = lines + line + "\n"
124#+end_src
125
126** Composing the Email
127
128Now that I have all the data I need, I just need to compose it into a message
129and prepare the message metadata.
130
131For my daily email, I want to see the title of the poem first, followed by the
132author, then a blank line, and finally the full poem. This code snippet combines
133that data and packages it into a MIMEText container, ready to be emailed.
134
135#+begin_src python
136msg_body = title + "\n" + author + "\n\n" + lines
137msg = MIMEText(msg_body)
138#+end_src
139
140Before we send the email, we need to prepare the metadata (subject, from, to,
141etc.):
142
143#+begin_src python
144sender_email = 'example@server.local'
145recipient_emails = ['user@example.com']
146msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
147msg['From'] = sender_email
148msg['To'] = recipient_email
149#+end_src
150
151** Sending the Email
152
153Now that I have everything ready to be emailed, the last step is to simply
154connect to an SMTP server and send the email out to the recipients. In my case,
155I installed =mailutils= on Ubuntu and let my SMTP server be =localhost=.
156
157#+begin_src python
158smtp_server = 'localhost'
159s = smtplib.SMTP(smtp_server)
160s.sendmail(sender_email, recipient_emails, msg.as_string())
161s.quit()
162#+end_src
163
164* The Result!
165
166Instead of including a screenshot, I've copied the contents of the email that
167was delivered to my inbox below since I set this process up in plaintext format.
168
169#+begin_src txt
170Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
171From: REDACTED
172To: REDACTED
173Subject: Your Daily Poem (36 lines)
174MIME-Version: 1.0
175Content-Transfer-Encoding: 8bit
176Content-Type: text/plain; charset=utf-8
177
178Sonnet XXII: With Fools and Children
179Michael Drayton
180
181With fools and children, good discretion bears;
182Then, honest people, bear with Love and me,
183Nor older yet, nor wiser made by years,
184Amongst the rest of fools and children be;
185Love, still a baby, plays with gauds and toys,
186And, like a wanton, sports with every feather,
187And idiots still are running after boys,
188Then fools and children fitt'st to go together.
189He still as young as when he first was born,
190No wiser I than when as young as he;
191You that behold us, laugh us not to scorn;
192Give Nature thanks you are not such as we.
193Yet fools and children sometimes tell in play
194Some, wise in show, more fools indeed than they.
195#+end_src
196
197* Scheduling the Daily Email
198
199Last, but not least, is scheduling this Python script with =crontab=. To
200schedule a script to run daily, you can add it to the =crontab= file. To do
201this, open =crontab= in editing mode:
202
203#+begin_src sh
204crontab -e
205#+end_src
206
207In the file, simply paste the following snippet at the bottom of the file and
208ensure that the file path is correctly pointing to wherever you saved your
209Python script:
210
211#+begin_src config
2120 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py
213#+end_src
214
215We have now set up the script and scheduled it to run daily at 08:00!