Skip to content

Instantly share code, notes, and snippets.

@bathtime
Last active March 7, 2018 12:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bathtime/d69b307094ce65c503cc4e6c7aa9ed5b to your computer and use it in GitHub Desktop.
Save bathtime/d69b307094ce65c503cc4e6c7aa9ed5b to your computer and use it in GitHub Desktop.
// This program checks and/or monitors your emails,
// prints a count of new mail, and runs user a defined
// command upon a new email.
//
// Note: A file containing only the users last email
// count will be stored your computer in:
//
// ~/.config/mailcount
//
// The goals of this project:
//
// 1. < 100 lines code
// 2. Simple & elegant coding
// 3. Fast & efficient execution.
//
// "Do one thing,
// and do it well."
//
// —Linux Credo
//
// Compile with:
// g++ -O -Wall mailnum.cpp -o mailnum -L/usr/include/curl/lib -lcurl -std=gnu++14
//
// To check mail and exit:
// $ mailnum
//
// To reset mail count (needed so program doesn't keep alerting
// of new mail):
// $ mailnum -r
//
// To continue checking mail every 600 seconds and open
// mutt upon a new mail (checks default to every 300 seconds):
// $ mailnum -c 600 "mutt"
//
// To continue checking mail and run the command 'beep'
// at every check ('beep' application must be installed):
// $ mailnum -c -a beep
//
#include<experimental/string_view>
#include<iostream>
#include<fstream>
#include<string>
#include<curl/curl.h>
#include<cstring>
#include<algorithm>
#include<sys/types.h>
#include<pwd.h>
#include<unistd.h>
#include<chrono>
#include<thread>
using namespace std;
string data; // Will hold the url's contents
size_t writeCallback(char* buf, size_t size, size_t nmemb, void* up)
{
/* Callback must have this declaration; buf is a pointer to the data
that curl has for us size*nmemb is the size of the buffer */
for (unsigned int c = 0; c < size*nmemb; c++)
data.push_back(buf[c]);
return size*nmemb; // Tell curl how many bytes we handled
}
int main(int argc, char* argv[])
{
// Set defaults if they are not entered as parameters
long secsToNextCheck = 300; // Seconds between mail checks (if keepChecking ON)
bool keepChecking = 0; // Keep checking mail at intervals?
bool isRead = 0; // Are you just telling the program you read the mail?
bool keepAlerting = 0; // Repeat new mail command each check?
bool hasAlerted = 0;
bool showHelp = 0;
std::string newMailCmd = ""; // Command to run upon new mail
std::string tmpStr; // Needed to help establish if argv is a num
// Sort out parameters and set variables
for(int pNum = 1; pNum < argc; pNum++) {
tmpStr = argv[pNum];
// First check if it is a number. Use new efficient string_view func :)
if (!std::experimental::string_view(argv[pNum]).empty() && std::all_of(tmpStr.begin(), tmpStr.end(), ::isdigit))
secsToNextCheck = stoi(argv[pNum]);
else if (std::experimental::string_view(argv[pNum]) == "-r")
isRead = 1;
else if (std::experimental::string_view(argv[pNum]) == "-a")
keepAlerting = 1;
else if (std::experimental::string_view(argv[pNum]) == "-c")
keepChecking = 1;
else if (std::experimental::string_view(argv[pNum]) == "-h")
showHelp = 1;
else{ // Remaining parameters must be the command to run
/* User didn't put alert command in quotations, so add a space
before each command to space properly. */
if (newMailCmd != "")
newMailCmd += " ";
newMailCmd += argv[pNum];
}
}
if (showHelp) {
std::cout << "mailnum v0.1 2018\n\n"
"This program checks and monitors your new email count, "
"prints a count of new mail, and executes a user defined "
"command upon a new email.\n\n"
"mailnum [- options ...] [check freq. in seconds] [command args]\n\n"
"Options include:\n"
"[ -r ] Mark as read\n"
"[ -c ] Check mail continuously\n"
"[ -a ] Repeat mail alert command\n"
"[ -h ] Print this text\n";
return 0;
}
const char * charNewMailCmd = newMailCmd.c_str(); // system() won't accept a string
std::chrono::milliseconds timespan(secsToNextCheck * 1000); // secs -> millisecs
// Find user's home directory to store mail count
struct passwd *pw = getpwuid(getuid()); // Set up to get ~/
std::string homeDir = pw->pw_dir;
std::string fileName = homeDir + "/.config/mailcount";
// Keep looping until the definition of '1' is no longer equal to 'true'
while (1) {
CURL* curl; // Our curl object
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
// Email server information:
curl_easy_setopt(curl, CURLOPT_USERNAME, "myUser@myEmail.com");
curl_easy_setopt(curl, CURLOPT_PASSWORD, "myPasswordGoesHere" );
curl_easy_setopt(curl, CURLOPT_URL, "imaps://mail.myEmailProvider.com:993/INBOX" );
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "EXAMINE INBOX" );
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCallback);
// May be used to assist in debugging
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // Tell curl to output its progress
curl_easy_perform(curl);
std::size_t pos = data.find("UIDNEXT");
std::string trueCount = data.substr(pos + 8, 3);
data = "";
// Don't want any leaks!
curl_easy_cleanup(curl);
curl_global_cleanup();
// Email count established above, so compare with file system count
std::string lastCount;
ifstream testFile(fileName);
// Check if file exists or contains data
if (testFile.fail())
{
// Make .config if not found
tmpStr = "mkdir -p " + homeDir + "/.config/";
const char * mkDirCharCmd = tmpStr.c_str(); // system() won't take a string
system(mkDirCharCmd);
testFile.close();
std::ofstream file(fileName);
file << trueCount;
file.close();
lastCount = trueCount;
}else{ // The file is there, so test its contents!
getline (testFile,lastCount);
testFile.close();
// Check if content is a number or empty
if (lastCount.empty() || !std::all_of(lastCount.begin(), lastCount.end(), ::isdigit))
{
std::ofstream file(fileName);
file << trueCount;
file.close();
lastCount = trueCount;
}
}
testFile.close();
int newMail = stoi(trueCount) - stoi(lastCount);
if (newMail || isRead)
{
if (isRead) // We saw it, so update lastCount and exit
{
std::ofstream file(fileName);
file << trueCount;
file.close();
if (!newMail)
std::cout << "No new mail to mark as read." << endl;
else
std::cout << "Mail marked as read." << endl;
return 0; // No need to stick around!
}else{
std::cout << "[" << newMail << " new email";
// Append an 's' if +1 emails
if (newMail > 1)
std::cout << "s] " << endl;
else
std::cout << "] " << endl;
if (!hasAlerted) {
system(charNewMailCmd);
hasAlerted = 1;
} else if (keepAlerting)
system(charNewMailCmd);
}
}
if (keepAlerting && !keepChecking)
std::cout << "Cannot keep alerting when set to check once." << endl;
// Wait until next mail check (convert to milliseconds)
if (keepChecking)
std::this_thread::sleep_for(timespan);
else
return 0;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment