File: imapmunch
---------------
#!/usr/bin/env php
<?php
/**
* File: imapmunch
* Date: Fri 18 Jan 2013 05:30:45 PM CET
*
* easily searches/selects emails from imapserver, using filters, scans hashtags, prices and so on
*
* Usage: imapmunch [filteroptions] [--output=json|xml|csv]
*
* available outputfields:
*
* --numbers = show all scanned numbers
* --hashtags = show all scanned hashtags
* --prices = show all scanned prices
* other options:
*
* --exclude='string' = ignore all subjects which contain stringvalue(s) (commaseperated)
* --markseen = (not implemented yet ) fetch unread emails, and mark as 'read' (process each mail once)
* available filteroptions:
*
* -ALL = return all messages matching the rest of the criteria
* -ANSWERED = match messages with the \ANSWERED flag set
* -BCC='string' = match messages with 'string' in the Bcc: field
* -BEFORE='date' = match messages with Date: before 'date'
* -BODY='string' = match messages with 'string' in the body of the message
* -CC='string' = match messages with 'string' in the Cc: field
* -DELETED = match deleted messages
* -FLAGGED = match messages with the \FLAGGED (sometimes referred to as Important or Urgent) flag set
* -FROM='string' = match messages with 'string' in the From: field
* -KEYWORD='string' = match messages with 'string' as a keyword
* -NEW = match new messages
* -OLD = match old messages
* -ON='date' = match messages with Date: matching 'date'
* -RECENT = match messages with the \RECENT flag set
* -SEEN = match messages that have been read (the \SEEN flag is set)
* -SINCE='date' = match messages with Date: after 'date'
* -SUBJECT='string' = match messages with 'string' in the Subject:
* -TEXT='string' = match messages with text 'string'
* -TO='string' = match messages with 'string' in the To:
* -UNANSWERED = match messages that have not been answered
* -UNDELETED = match messages that are not deleted
* -UNFLAGGED = match messages that are not flagged
* -UNKEYWORD='string' = match messages that do not have the keyword 'string'
* -UNSEEN = match messages which have not been read yet
*
* Example: !./imapmunch -FROM='Leon' -SUBJECT='bill ' --prices | awk -F ',' '{print $5 }'
*
* Changelog:
*
* [Fri 18 Jan 2013 05:30:45 PM CET]
* first sketch from scratch
*
* @todo description
*
* Usage example:
* <code>
* ./imapmunch
* </code>
*
* @version $id$
* @copyright 2013 Coder of Salvation
* @author Coder of Salvation, sqz <info@leon.vankammen.eu>
* @package %package%
*
* @license BSD
* %license%
*
* Copyright 2013, Coder of Salvation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY CODER OF SALVATION ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of <copyright holder>.
*
*/
/* config gmail etc */
$hostname = '{imap.gmail.com:993/imap/ssl}INBOX';
$username = 'your@email.com';
$password = 'yourpassword';
$args = array();
function main($argv) {
global $args,$require;
if( count($argv) == 1 ) die(usage());
$args = $argv;
init($argv);
run($argv);
}
function run(){
global $args, $hostname, $username, $password;
$inbox = connect( $hostname, $username, $password );
$result = false;
foreach( $args as $arg ){ // perform searches and merge results
if( is_string($arg) && $arg[0] == "-" && $arg[1] != "-" ){
$searchTerm = str_replace( array("'","="), array('"'," "), substr( $arg, 1 ) );
$emails = search( $inbox, $searchTerm );
if( is_array($result) ){
foreach( $result as $k => $i )
if( !in_array($i,$emails) ) unset( $result[$k] );
}else $result = $emails;
}
}
display($inbox,$result);
close($inbox);
}
function init(){
global $args;
$str = false;
$opts = getopt(null, array('output:','hashtags::','prices::','numbers::','manual::','downloadpath::','downloadext::','exclude::'));
if( isset($opts['manual']) ) manual();
foreach( $args as $arg ) $str = !strstr( $arg, '--' ) && !strstr( $arg, basename(__FILE__) ) ? $arg : $str;
$args['parsed'] = array();
$args['parsed']['str'] = $str;
$args['parsed']['output'] = isset($opts['output']) ? trim($opts['output']) : 'csv';
$args['parsed']['hashtags'] = isset($opts['hashtags']) ? true : false;
$args['parsed']['prices'] = isset($opts['prices']) ? true : false;
$args['parsed']['numbers'] = isset($opts['numbers']) ? true : false;
$args['parsed']['downloadpath'] = isset($opts['downloadpath']) ? trim($opts['downloadpath']) : false;
$args['parsed']['downloadext'] = isset($opts['downloadext']) ? trim($opts['downloadext']) : false;
$args['parsed']['exclude'] = isset($opts['exclude']) ? explode(",",trim($opts['exclude'])) : false;
}
function usage(){
$txt = "Usage: imapmunch [filteroptions] [--output=json|xml|csv]\n\n";
$txt .= "available outputfields:\n\n";
$txt .= " --numbers = show all scanned numbers\n";
$txt .= " --hashtags = show all scanned hashtags\n";
$txt .= " --prices = show all scanned prices\n";
$txt .= "other options:\n\n";
$txt .= " --exclude='string' = ignore all subjects which contain stringvalue(s) (commaseperated)\n";
$txt .= " --markseen = (not implemented yet ) fetch unread emails, and mark as 'read' (process each mail once)\n";
$txt .= "available filteroptions:\n\n";
$txt .= " -ALL = return all messages matching the rest of the criteria\n";
$txt .= " -ANSWERED = match messages with the \\ANSWERED flag set\n";
$txt .= " -BCC='string' = match messages with 'string' in the Bcc: field\n";
$txt .= " -BEFORE='date' = match messages with Date: before 'date'\n";
$txt .= " -BODY='string' = match messages with 'string' in the body of the message\n";
$txt .= " -CC='string' = match messages with 'string' in the Cc: field\n";
$txt .= " -DELETED = match deleted messages\n";
$txt .= " -FLAGGED = match messages with the \\FLAGGED (sometimes referred to as Important or Urgent) flag set\n";
$txt .= " -FROM='string' = match messages with 'string' in the From: field\n";
$txt .= " -KEYWORD='string' = match messages with 'string' as a keyword\n";
$txt .= " -NEW = match new messages\n";
$txt .= " -OLD = match old messages\n";
$txt .= " -ON='date' = match messages with Date: matching 'date'\n";
$txt .= " -RECENT = match messages with the \\RECENT flag set\n";
$txt .= " -SEEN = match messages that have been read (the \\SEEN flag is set)\n";
$txt .= " -SINCE='date' = match messages with Date: after 'date'\n";
$txt .= " -SUBJECT='string' = match messages with 'string' in the Subject:\n";
$txt .= " -TEXT='string' = match messages with text 'string'\n";
$txt .= " -TO='string' = match messages with 'string' in the To:\n";
$txt .= " -UNANSWERED = match messages that have not been answered\n";
$txt .= " -UNDELETED = match messages that are not deleted\n";
$txt .= " -UNFLAGGED = match messages that are not flagged\n";
$txt .= " -UNKEYWORD='string' = match messages that do not have the keyword 'string'\n";
$txt .= " -UNSEEN = match messages which have not been read yet\n";
$txt .= "\nExample: !./imapmunch -FROM='Leon' -SUBJECT='bill ' --prices | awk -F ',' '{print $5 }'\n\n";
return $txt;
}
if ('cli' === php_sapi_name() && basename(__FILE__) === basename($argv[0])) {
main($argv);
}
function connect( $hostname, $username, $password ){
/* try to connect */
$inbox = imap_open($hostname,$username,$password) or die('Cannot connect to Gmail: ' . imap_last_error());
return $inbox;
}
function search( $inbox, $searchTerm ){
/* grab emails */
return imap_search($inbox, $searchTerm );
}
function getEmail($inbox, $emailno ){
/* for every email... */
$overview = imap_fetch_overview($inbox,$email_number,0);
/* get information specific to this email */
$message = imap_fetchbody($inbox,$email_number,2);
return $message;
}
function convertEmailsToArray( $inbox, $emails){
global $args;
$table = array();
rsort($emails);
$fields = array('id','date','subject','senderaddress','files');
foreach( $emails as $msgno ){
print_stderr("[x] checking email #{$msgno}\n");
$header = imap_headerinfo($inbox, $msgno );
$skip = false;
if( is_array($args['parsed']['exclude']) )
foreach( $args['parsed']['exclude'] as $exclude )
if( strstr( $header->subject, $exclude ) ) $skip = true;
if( $skip) continue;
$email = array();
$header->id = $msgno;
if( $args['parsed']['downloadpath'] ){
$files = downloadAttachements( $inbox, $msgno, $args['parsed']['downloadpath'], $args['parsed']['downloadext'] );
$header->files = $files ? $files : '';
}
foreach( $fields as $field ) $email[ $field ] = $header->$field;
preg_match( "/<.*>/", $email['senderaddress'], $matches );
$email['senderaddress'] = str_replace( array("<",">"), "", $matches[0]);
$table[] = $email;
}
if( $args['parsed']['hashtags'] || $args['parsed']['numbers'] || $args['parsed']['prices'] )
$table = interpretEmailArray($table, $fields);
return $table;
}
function interpretEmailArray( $emails, $fields ){
global $args;
if( $args['parsed']['hashtags'] ) $fields[] = "hashtags";
if( $args['parsed']['numbers'] ) $fields[] = "numbers";
$t = new tagParser();
if( $args['parsed']['prices'] )
foreach( $t->currencies as $currency => $v ) $fields[] = $currency;
foreach( $emails as $k => $email ){
$t->parse( $email['subject'] );
$parsed = $t->getParsed();
if( $args['parsed']['hashtags'] ) $emails[$k]['hashtags'] = implode(",", $parsed['tags']);
if( $args['parsed']['numbers'] ) $emails[$k]['numbers'] = implode(",", $parsed['numbers']);
if( $args['parsed']['prices'] )
foreach( $t->currencies as $currency => $v )
$emails[$k][$currency] = count($parsed['prices'][$currency]) ? $parsed['prices'][$currency][0] : 0;
//$emails[$k][$currency] = implode(",",$parsed['prices'][$currency]);
}
if( $args['parsed']['output'] == "csv" ){
array_unshift($emails, $fields);
}
return $emails;
}
function display( $inbox, $emails ){
global $args;
if( is_array($emails) ){
$table = convertEmailsToArray( $inbox, $emails );
}else return;
switch( $args['parsed']['output'] ){
case "json" : print( json_encode( (object)array( "emails" => $table) ) ); break;
case "csv" : $file = '/tmp/imapmunch.csv';
$fp = fopen( $file, 'w');
foreach ($table as $fields) fputcsv($fp, $fields);
fclose($fp);
print(file_get_contents($file));
unlink($file);
break;
case "xml" :
$xml = new SimpleXMLElement('<imap/>');
foreach( $table as $k => $v ) $table[$k] = array_flip($v);
array_walk_recursive( $table, array ($xml, 'addChild'));
print( $xml->asXML() );
break;
default: print_r($table); break;
}
}
function downloadAttachements( $inbox, $emailno, $downloadpath, $filetypes = false ){
if( !is_dir($downloadpath) || !is_writable($downloadpath) ) return;
$structure = imap_fetchstructure($inbox,$emailno);
$attachments = array();
if(isset($structure->parts) && count($structure->parts)) {
for($i = 0; $i < count($structure->parts); $i++) {
$attachments[$i] = array(
'is_attachment' => false,
'filename' => '',
'name' => '',
'attachment' => '');
if($structure->parts[$i]->ifdparameters) {
foreach($structure->parts[$i]->dparameters as $object) {
if(strtolower($object->attribute) == 'filename') {
$attachments[$i]['is_attachment'] = true;
$attachments[$i]['filename'] = $object->value;
}
}
}
if($structure->parts[$i]->ifparameters) {
foreach($structure->parts[$i]->parameters as $object) {
if(strtolower($object->attribute) == 'name') {
$attachments[$i]['is_attachment'] = true;
$attachments[$i]['name'] = $object->value;
}
}
}
if($attachments[$i]['is_attachment']) {
$file = $downloadpath.$emailno."_{$attachments[$i]['filename']}";
if( !is_file($file) ){
$attachments[$i]['attachment'] = imap_fetchbody($inbox, $emailno, $i+1);
if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
$attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
}
elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
$attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
}
}
}
} // for($i = 0; $i < count($structure->parts); $i++)
} // if(isset($structure->parts) && count($structure->parts))
$files = array();
if(count($attachments)!=0){
foreach($attachments as $at){
if($at['is_attachment']==1){
$i = 1;
$file = $downloadpath.$emailno."_{$at['filename']}";
$write = ( $filetypes == false );
if( is_array($filetypes) ){
foreach( $filetypes as $filetype )
if( strstr( $at['filename'], $filetype ) && $files[] = $file )
$write = true;
}
if( $write && !is_file($file) ){
$files[] = $file;
print_stderr("[email #{$emailno}] writing '{$file}'\n");
file_put_contents($file, $at['attachment']);
}
}
}
}
return implode(",", $files );
}
// write to stderr (so bash textmanipulation will not break)
function print_stderr($msg){
fwrite(STDERR,$msg);
}
function close($inbox){
/* close the connection */
imap_close($inbox);
}
/**
* File: class.tagparser.php
* Date: Fri 18 Jan 2013 05:30:45 PM CET
*
* easily parses tokens, hashtags,prices from strings
*
* Changelog:
*
* [Fri 18 Jan 2013 05:30:45 PM CET]
* first sketch from scratch
*
* @todo whatever your want
*
* Usage example:
* <code>
* $t = new tagParser();
* if( $t->parse( "hey #hoo #foo 123Euro" ) ){
* $arr = $t->getParsed();
* print_r($arr);
* }else die("oops");
* </code>
*
* @version $id$
* @copyright 2013 Coder of Salvation
* @author Coder of Salvation, sqz <info@leon.vankammen.eu>
* @package %package%
*
* @license BSD
* %license%
*
* Copyright 2013, Coder of Salvation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY CODER OF SALVATION ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of Coder of Salvation.
*
*/
class tagParser {
private $tokens = array();
private $currencyDefault = "forint";
private $priceOperator = -1; // numbers without + or - will be negative prices.
// note: use lowercase for optimal recognition
public $currencies = array( "forint" => array("ft","forint","huf"),
"euro" => array("e","eu","euro")
);
public function __construct(){
$this->reset();
}
/**
* Checks to see if a string ends with substring
*/
private function stringEndsWith($whole, $end) {
return @(strpos($whole, $end, strlen($whole) - strlen($end)) !== false);
}
/**
* Checks to see if a string starts with substring
*/
private function stringStartsWith($whole, $end) {
if(substr($whole, 0, strlen($end)) == $end) {
return true;
}
return false;
}
private function tokenise($text) {
$text = (string)$text;
$text = trim($text);
$text = preg_replace("/[^a-zA-Z0-9\ ]/", " $0 ", $text);
$text = preg_replace("/\ \ +/", " ", $text);
$words = explode(" ", $text);
return $words;
}
private function preProcess($text){
foreach( $this->currencies as $currency => $variations )
foreach( $variations as $variation )
$text = str_replace( " {$variation}", $variation, $text );
return $text;
}
private function getCurrency($token){
foreach( $this->currencies as $currency => $variations )
foreach( $variations as $variation )
if( $this->stringEndsWith( $variation, strtolower($token) ) )
return $currency;
return $this->currencyDefault;
}
private function stripCurrency($token){
foreach( $this->currencies as $currency => $variations )
foreach( $variations as $variation )
if( $this->stringEndsWith( strtolower($token), $variation ) )
return str_replace( $variation, "", strtolower($token) );
return $this->currencyDefault;
}
private function reset($text=false){
$this->tokens = array(
'tokens_all' => $this->tokenise($text),
'tokens_whitespace' => explode(" ",$text),
'tags' => array(),
'numbers' => array(),
'prices' => array(),
);
foreach( $this->currencies as $currency => $variations )
$this->tokens['prices'][$currency] = array();
}
public function parse( $text ){
$text = $this->preProcess($text);
$this->reset($text);
foreach( $this->tokens['tokens_whitespace'] as $token ){
// scan tags
if( $token[0] == "#" ) $this->tokens['tags'][] = strtolower($token);
// scan numbers and prices
$tokenStripped = $this->stripCurrency($token);
if( is_numeric($tokenStripped) ){
$currency = $this->getCurrency( $token );
$this->tokens['prices'][ $currency ][] = ($token[0] != "-" && $token[0] != "+") ?
$this->priceOperator * (int)$token :
(int)$token;
$this->tokens['numbers'][] = (int)$this->stripCurrency($token);
}
}
}
public function getParsed(){
return $this->tokens;
}
}
?>
edit the file and change the hostname,username,passwd to your likings