Projects > Winston: Virtual Assistant

Winston: Virtual Assistant

Description

Winston is my virtual assistant. This project started as a fun way to ensure I never forgot an important date but has continued to evolve over the years into a clean way of providing daily updates as I wake up each morning. I spend the most time working on this repo because I'll wake up thinking something like "I should add weather information" and then decie to clean up or rework other parts of the code while I'm working on the original update. On the surface this is a fairly unimpressive program but it has proven to be helpful and fun to maintain.

Link to the GitHub Repository

Running the Program

This program is run using the following command structure:

python virt_asst.py

That said, I typically don't run this program manually. Instead, I have it set up to run each morning as a cronjob from my home desktop. This works pretty well but I think it is worth updating the desktop's settings to boot up automatically when power is available to prevent any power outages from blocking the email from sending while I am away for a few days.

Configuration

As the run command implies, the code runs from `personalAssistant.py`. After importing the necessary libraries we see this line:

config_path = g.configPath(True)

This function is in `func_general.py` and is determining from which computer the code is running. This is important for a few reasons. First, it allows for a local configuration path so you can store data you don't want to accidently end up on the public internet outside a github repo. Most of the time this would be handled using a `.gitignore` file but I like the idea of having additional separation from my data and a constantly evolving code base. Is this overkill for mostly birthday data? Absolutely, but these fun side projects are more about proven what I can do and not designing the most efficient codebase.

The second huge benefit that comes with a dynamic configuration path is that I can run this code quickly from each of my computers, with various operating systems, without needing a bunch of input variables in my run command. The down side is that local data files can get out of sync if you don't have an automated way to sync them. This can be solved by referencing a single source of truth like a Google Drive file.

Once you have your configuration file, these are a handful of variables that direct how the program with execute. Here are a few examples:

~ platform ~
This is mainly a reference to which computer configuration the program is using for debugging purposes.

~ emailInTerminal ~
This is a boolean field that determines when to print the email in your terminal or actually send an email. While you're making code updates, it can be faster to skip the time associated with sending and receiving an email.

~ emailPreview ~
When I added html formatting I added this output file as well that shows what was sent. This can be useful for development and debugging.

~ countriesFile ~
This is a list of every country and its capital and there is a similar file for states. I like keeping this information fresh in my mind. Ultimately this information appears at the end of the email as fake travel plans the assistant is currently completing.

~ datesFile ~
This is data for birthdays and anniversaries.

~ greeting ~
This is how the assistant greets you at the beginning or the email.

~ assistantName ~
Yes, you can give your assistant any name you want!

~ from_email ~
This is the sender email. Assuming you use your gmail account, you will need to setup an `App Password` that corresponds to this email address.

~ to_email ~
This is the email recipient.

~ subject ~
Using `default` will give you an email title of "Daily Correspondence" plus a date stamp. Alternatively, you can set a custom subject line here.

~ eventOutlook ~
This is how many days out the program will look to remind you of upcoming dates.

~ lastDeparture ~
The fake travel plans at the end of the email ensures that the previous destination is the next days starting location, assuming the emails are sent consecutively from the same computer. This helps reinforce the location and capital information as well as play into the travel idea. This field will alternate between "country" and "state" and works with the `lastStateId` or `lastCountryId` field to make the previously described functionality possible. This is automatically updated each time the program runs.

~ lastStateId ~
This is a rolling count to ensure that the state in the fake travel plans is always changing. This is automatically updated each time the program runs.

Got all that?! Whew... Great!

Continuing in `personalAssistant.py`, we see a with statement that then opens your configuration and grabs the corresponding values.

Building the Message

The main content of the message is then built using a series of function calls that look like this:

msg = cm.somethingMessage(msg,verbose)

At this point it is probably better to delve into the content for individual functions on Github itself but I will touch on a couple items. First I want to note that order does matter a bit. Somedays we are in more of a rush than others, obviously. If you duplicate this project for your own use and find yourself relying on birthday or credit card payment reminders, put that at the top. Also, if you add a lot more content, you'll like skim everything and get less value overall.

My message has a few pieces. I start with a weather forecast because I found myself checking that every morning anyway. Then I add a reminder to pay my credit card and take out the trash and recycling if that is coming up. Finally, I add upcoming birthdays and anniversaries. This is honestly where I usually stop reading and briefly skim the remaining message. The rest of the content includes a word of the day from my own word list, a quote of the day, and a sign off which includes fake travel plans for my assistant. I stated it earlier, but the point of the fake travel plans is to help me learn capitals of states and countries. I'll also note, there are occasionally small inconsistencies between the high and low temperature in the header of the email and the forecast in the body of the message. This is because I liked the forecast from one API but another weather API made high and low temperature far easier to access. Ultimately, I decide the inconsistency was fine because it was more likely to continue providing information if one API went down and it provided a more complete understanding of the weather.

Logs

At the end of the program I create a few logs. First you'll find a line like:

sl.infoMessage("Final correspondence message\n{0}".format(msg),verbose)

The log likely accumulated messages as the program ran but now it is logging a final entry containing the email that was sent. There is another line similar to:

gh.update_and_push_logs("Personal Assistant program completed successfully.")

This creates an updated `run_log.txt` file with information on the last time the program ran and then pushes that and any other changes automatically to Github in this repo's main branch. On one hand, this feels a bit reckless because I could push partial changes I was making the day before. On the other hand, I can access information on the last run from anywhere now and my Github status board turns green for that day.

Conclusion

There is a lot more here that I didn't touch on but I think this should give you a high level overview. Hopefully you feel like you can confidently explore the code on your own from here. Better yet, if you do duplicate this repo and build it out for your own use, please drop me a note on how it went and if you added any functionality you found particularly useful!