I wrote this script to log into POP3 mail accounts, check for unwanted senders and delete those mails automatically to keep my inbox clean.
It's only a proof of concept, a delete function isn't there atm.
It instead produces a debug text file with detailed info on the received server messages and recognised header data.
It's based on the winsock2 lib, but I shortened a lot of stuff to get a better understanding of the code. Basic error checking is still there, but it doesn't evaluate the given account data.
Sorry for the long lines of code here and there.
I haven't checked if ssl works via stunnel.
Edit:
Updated the code. The line to delete mails when a filter match occurs is still commented out.
It also keeps running and checks for mail every 60000 ms instead of closing after the first check.
Added a sample file for email notification in the task tray. The number of undeleted mails is handed over and shown as tooltip. When double clicked, it opens outlook express.
Edit 2:
Decoding of Q-encoded subject lines should work now. Filters for certain words in the subject can be added as needed.
; Email spam killer proof of concept ; Checks multiple accounts against a FilterList based an sender's address ; It logs into the accounts, and checks each mail's From, To and Subject field ; and produces debug output in Email-Check_Log.txt in the same directory ; Delete function line 79 but commented out ; ; Based on code by DerRaphael and some others #SingleInstance,Force #NoEnv ;#NoTrayIcon #Persistent SetBatchLines,-1 DetectHiddenWindows,On ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " DllCall("GetCurrentProcessId")) DetectHiddenWindows,Off hModule:=DllCall("LoadLibrary","str","Ws2_32.dll") Pop3Host1 := "your.pop.account1" Pop3Port1 := 110 Pop3User1 := "UserName1" Pop3Pass1 := "Password1" Pop3Host2 := "your.pop.account2" Pop3Port2 := 110 Pop3User2 := "UserName2" Pop3Pass2 := "Password2" [email protected]|[email protected] [email protected] FilterSubject= ;########################################################################################################################## ;######################################################## Auto-Execute Section ############################################ ;########################################################################################################################## FileDelete,Email-Check_Log.txt ; Debug AppendHeap= ; Debug GetTotalAccounts() loop { ValidMailNumber=0 loop,%TotalAccounts% { AccountNumber=%A_Index% Socket := WS2_Connect(Pop3Host%AccountNumber%,Pop3Port%AccountNumber%) WSA_AsyncSelect(Socket) AppendHeap:= "Using Socket " Socket "`n`nopen " Pop3Host%AccountNumber% " " Pop3Port%AccountNumber% "`n" AnswerHeap ; Debug WSA_send(Socket,"user " Pop3User%AccountNumber% "`r`n") AppendHeap:= AppendHeap "`nuser " Pop3User%AccountNumber% "`n" AnswerHeap ; Debug WSA_send(Socket,"pass " Pop3Pass%AccountNumber% "`r`n") AppendHeap:= AppendHeap "`npass " Pop3Pass%AccountNumber% "`n" AnswerHeap ; Debug WSA_send(Socket,"stat`r`n") AppendHeap:= AppendHeap "`nstat`n" AnswerHeap ; Debug StringSplit,MessageStats,AnswerHeap,%A_Space%, TotalMailNumber=%MessageStats2% if TotalMailNumber>0 { loop,%TotalMailNumber% { ThisMailNumber=%A_Index% WSA_send(Socket,"top " ThisMailNumber " 1`r`n") AppendHeap:= AppendHeap "`n`ntop " ThisMailNumber " 1`n" AnswerHeap ;Debug StringReplace,AnswerHeap,AnswerHeap,`r`n%A_Space%,%A_Space%,1 ; Unfold items Mail_From:=SubStr(AnswerHeap,InStr(AnswerHeap,"<",0,InStr(AnswerHeap,"`r`nFrom: ",0,1)+8)+1,InStr(AnswerHeap,">",0,InStr(AnswerHeap,"<",0,InStr(AnswerHeap,"`r`nFrom: ",0,1)+8)+1 )-InStr(AnswerHeap,"<",0,InStr(AnswerHeap,"`r`nFrom: ",0,1)+8)-1) Mail_To:=SubStr(AnswerHeap,InStr(AnswerHeap,"<",0,InStr(AnswerHeap,"`r`nTo: ",0,1)+6)+1,InStr(AnswerHeap,">",0,InStr(AnswerHeap,"<",0,InStr(AnswerHeap,"`r`nTo: ",0,1)+6)+1 )-InStr(AnswerHeap,"<",0,InStr(AnswerHeap,"`r`nTo: ",0,1)+6)-1) Mail_SubjectQ:=SubStr(AnswerHeap,InStr(AnswerHeap,"`r`nSubject: ",0,1)+11,InStr(AnswerHeap,"`r`n",0,InStr(AnswerHeap,"`r`nSubject: ",0,1)+11)-InStr(AnswerHeap,"`r`nSubject: ",0,1)-11) Mail_Date:=SubStr(AnswerHeap,InStr(AnswerHeap,"`r`nDate: ",0,1)+8,InStr(AnswerHeap,"`r`n",0,InStr(AnswerHeap,"`r`nDate: ",0,1)+8 )-InStr(AnswerHeap,"`r`nDate: ",0,1)-8) Mail_Subject:=Qdecode(Mail_SubjectQ) SendTimeUTC:=GetSendTimeUTC(Mail_Date) ; These are the present filter rules. It Checks if the From or To field of the mail has elements that appear ; in the FilterFrom, FilterTo and FilterSubjectVariable. Filters that target the can be realised, too ; The Date as available YYYYMMDDHH24MISS format in variable SendTimeUTC. KillThisMail=0 Loop,Parse,FilterFrom,`n,`r { if InStr(Mail_From,A_LoopField,false,1) KillThisMail=1 } Loop,Parse,FilterTo,`n,`r { if InStr(Mail_To,A_LoopField,false,1) KillThisMail=1 } Loop,Parse,FilterSubject,`n,`r { if InStr(Mail_Subject,A_LoopField,false,1) KillThisMail=1 } if KillThisMail=1 { ; WSA_send(Socket,"dele " ThisMailNumber "`r`n") ; Filter Match: Deletes this Email AppendHeap= ( %AppendHeap% ;########################################################################################################################## Results for Mail %ThisMailNumber%/%TotalMailNumber% on Account %AccountNumber%: From: %Mail_From% Subject: %Mail_Subject% Date: %Mail_Date% UTC: %SendTimeUTC% To: %Mail_To% Filter Hit! Trying to delete... dele %ThisMailNumber% %AnswerHeap% ;########################################################################################################################## ) } else { AppendHeap= ( %AppendHeap% ;########################################################################################################################## Results for Mail %ThisMailNumber%/%TotalMailNumber% on Account %AccountNumber%: From: %Mail_From% Subject: %Mail_Subject% Date: %Mail_Date% UTC: %SendTimeUTC% To: %Mail_To% ;########################################################################################################################## ) ValidMailNumber++ } } } FileAppend,%AppendHeap%,Email-Check_Log.txt WSA_send(Socket,"quit`r`n") WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") } if (ValidMailNumber>0) Run,%A_ScriptDir%\Email-Check-Icon.ahk %ValidMailNumber% ; Script to call if new Mails are here (for example tray notification) sleep,60000 ; (Check accounts every 10 minutes) } ExitApp ;########################################################################################################################## ;####################################################### Functions ######################################################## ;########################################################################################################################## WS2_Connect(HostName,Port) { Global ; Generate Structure for the lpWSAData as stated on http://msdn.microsoft.com/en-us/library/ms742213.aspx ; More on WSADATA (structure) to be found here: http://msdn.microsoft.com/en-us/library/ms741563(VS.85).aspx VarSetCapacity(WSAData, 32) WSA_Startup_result := DllCall("Ws2_32\WSAStartup", "UShort", "2", "UInt", &WSAData) if (ErrorLevel)||(WSA_Startup_result!=0) { msgbox,WSAStartup Error`nErrorLevel: %ErrorLevel%`nWSA_Startup_result: %WSA_Startup_result% WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") exitapp } IPAddress := "" ; gethostbyname returns information about a domainname into a Hostent Structure ; (http://msdn.microsoft.com/en-us/library/ms738524(VS.85).aspx) if ((PtrHostent:=DllCall("Ws2_32\gethostbyname","str",HostName)) != 0) { Loop,1 ; 3 is max No of retrieved addresses { If (PtrTmpIP := NumGet(NumGet(PtrHostent+12)+(offset:=(A_Index-1)*4),offset)) { IPAddress := (IPAddress) ? IPAddress "|" : "" Loop, 4 IPAddress .= NumGet(PtrTmpIP+offset,(A_Index-1 ),"UChar") "." ; Read our IP address IPAddress := SubStr(IPAddress,1,-1) } else Break ; No more IPs left } } else { msgbox,GetHostByName Error WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") exitapp } WSA_Port := DllCall("Ws2_32\htons", "UShort", Port) ; The htons function returns the value in TCP/IP network byte order. ; http://msdn.microsoft.com/en-us/library/ms738557(VS.85).aspx WSA_InetAddr := DllCall("Ws2_32\inet_addr", "Str", IPAddress) ; The inet_addr function converts a string containing an IPv4 dotted-decimal ; address into a proper address for the IN_ADDR structure. ; inet_addr: http://msdn.microsoft.com/en-us/library/ms738563(VS.85).aspx ; IN_ADDR: http://msdn.microsoft.com/en-us/library/ms738571(VS.85).aspx WSA_Socket := DllCall("Ws2_32\socket", "Int", "2", "Int", "1", "Int", "6") ; Supposed to return a descriptor referencing the new socket ; (http://msdn.microsoft.com/en-us/library/ms742212(VS.85).aspx) if (WSA_Socket = -1) { msgbox,WSA_Socket Error WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") exitapp } WSA_SockAddrNameLen := 16 ; Generate socketaddr structure for the connect function ; (http://msdn.microsoft.com/en-us/library/ms740496(VS.85).aspx) VarSetCapacity(WSA_SockAddr, WSA_SockAddrNameLen) NumPut("2", WSA_SockAddr, 0, "UShort") NumPut(WSA_Port,WSA_SockAddr,2,"UShort") NumPut(WSA_InetAddr, WSA_SockAddr, 4) WSA_Connect_result := DllCall("Ws2_32\connect" , "UInt", WSA_Socket , "UInt", &WSA_SockAddr , "Int" , WSA_SockAddrNameLen ) ; The connect function establishes a connection to a specified socket. ; (http://msdn.microsoft.com/en-us/library/ms737625(VS.85).aspx) if (WSA_Connect_result) { msgbox,Connect Error`nWSA_Connect_result: %WSA_Connect_result% WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") exitapp } return WSA_Socket } ;########################################################################################################################## WSA_AsyncSelect(WSA_Socket) { Global AnswerHeap= OnMessage("0x5000","WSA_recv") WSAAsyncSelectResult := DllCall("Ws2_32\WSAAsyncSelect" , "UInt", WSA_Socket , "UInt", ScriptMainWindowId , "UInt", "0x5000" , "Int", "0x1|0x20" ) ; The WSAAsyncSelect function requests Windows message-based notification of network events for a socket. ; (http://msdn.microsoft.com/en-us/library/ms741540(VS.85).aspx) if (WSAAsyncSelectResult) { msgbox,WSA_AsyncSelect Error`nWSAAsyncSelectResult: %WSAAsyncSelectResult% WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") exitapp } OKcounter=0 Loop { if AnswerSocket=%Socket% { if (instr(AnswerHeap,"+OK ",0,1)=1) break else if (instr(AnswerHeap,"+OK`r`n",0,1)=1) { If (SubStr(AnswerHeap,instr(AnswerHeap,".",0,0))=".`r`n") break else { OKcounter:=OKcounter+1 if OKcounter>5 break else sleep,100 continue } } else if (instr(AnswerHeap,"-ERR ",0,1)=1) { StringTrimLeft,FinalInfo,AnswerHeap,5 msgbox,%FinalInfo% WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") ExitApp } else sleep,100 } } Return } WSA_recv(wParam, lParam) ; Triggers upon Notification Handler when Receiving Messages { Global AnswerSocket, AnswerHeap Loop { VarSetCapacity(WSA_Buffer, 4096, 0) WSA_BufferLength := DllCall("Ws2_32\recv", "UInt", wParam, "Str", WSA_Buffer, "Int", "4096", "Int", 0) if (WSA_BufferLength = 0) break if (WSA_BufferLength = -1) { ; WSA_WOULDBLOCK (from http://www.sockets.com/) ; The socket is marked as non-blocking (non-blocking operation mode), and the requested operation is ; not complete at this time. The operation is underway, but as yet incomplete. if (DllCall("Ws2_32\WSAGetLastError") = "10035" ) return 1 break } AnswerSocket = %wParam% AnswerHeap=%AnswerHeap%%WSA_Buffer% } return } WSA_send(WSA_Socket, WSA_Data) { Global AnswerHeap, AnswerSocket, Socket AnswerHeap= Send_Result := DllCall("Ws2_32\send", "UInt", WSA_Socket, "Str", WSA_Data, "Int", StrLen(WSA_Data), "Int", 0) If (Send_Result = -1) { msgbox,WSA_send Error WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") exitapp } OKcounter=0 Loop { if AnswerSocket=%Socket% { if (instr(AnswerHeap,"+OK ",0,1)=1) break else if (instr(AnswerHeap,"+OK`r`n",0,1)=1) { If (SubStr(AnswerHeap,instr(AnswerHeap,".",0,0))=".`r`n") break else { OKcounter:=OKcounter+1 if OKcounter>5 break else sleep,100 continue } } else if (instr(AnswerHeap,"-ERR ",0,1)=1) { StringTrimLeft,FinalInfo,AnswerHeap,5 msgbox,%FinalInfo% WSA_CloseSocket(Socket) DllCall("Ws2_32\WSACleanup") ExitApp } else sleep,100 } } Return } WSA_CloseSocket(WSA_Socket) ; Closes Open Socket - Returns 0 on success { CloseSocket_Result := DllCall("Ws2_32\closesocket", "UInt", WSA_Socket) If (CloseSocket_Result != 0) { DllCall("Ws2_32\WSACleanup") msgbox,WSA_CloseSocket Error`nCloseSocket_Result: %CloseSocket_Result% exitapp } Return } autoByteFormat(size, decimalPlaces = 2) ; Not used atm { static size1 = "KB", size2 = "MB", size3 = "GB", size4 = "TB" sizeIndex := 0 while (size >= 1024) { sizeIndex++ size /= 1024.0 if (sizeIndex = 4) break } return (sizeIndex = 0) ? size " byte" . (size != 1 ? "s" : "") : round(size, decimalPlaces) . " " . size%sizeIndex% } GetTotalAccounts() { Global TotalAccounts=0 loop { if (Pop3Host%A_Index%!="") TotalAccounts=%A_Index% else break } return } UTC2Local(TimeCode) { TimeDiff:=A_Now EnvSub,TimeDiff,%A_NowUTC%,Minutes EnvAdd,TimeCode,%TimeDiff%,Minutes Return TimeCode } GetSendTimeUTC(DateTime) { Loop ;Remove double spaces in Date { IfNotInString,DateTime,%A_Space%%A_Space% break else StringReplace,DateTime,DateTime,%A_Space%%A_Space%,%A_Space%,1 } CounterF=0 ;Trim front spaces in Date Loop,Parse,DateTime,, { If A_LoopField=%A_Space% CounterF:=CounterF + 1 else break } StringTrimLeft,DateTime,DateTime,%CounterF% loop ;Trim back spaces in Date { StringRight,IsThisSpace,DateTime,1 If IsThisSpace=%A_Space% StringTrimRight,DateTime,DateTime,1 else break } If (InStr(DateTime,"`,",0,1)="4") { DayName:=SubStr(DateTime,1,3) Space1:=InStr(DateTime," ",0,1)+1 DateTime:=SubStr(DateTime,Space1) } Space1:=InStr(DateTime," ",0,1) Space2:=InStr(DateTime," ",0,Space1+1) Space3:=InStr(DateTime," ",0,Space2+1) Space4:=InStr(DateTime," ",0,Space3+1) Colon1:=InStr(DateTime,":",0,1) Colon2:=InStr(DateTime,":",0,Colon1+1) _YYYY:=SubStr(DateTime,Space2+1,Space3-Space2-1) _MMMM:=SubStr(DateTime,Space1+1,Space2-Space1-1) _DD:=SubStr(DateTime,1,Space1-1) _HH24:=SubStr(DateTime,Space3+1,Colon1-Space3-1) _MI:=SubStr(DateTime,Colon1+1,Colon2-Colon1-1) _SS:=SubStr(DateTime,Colon2+1,Space4-Colon2-1) if (StrLen(_DD)=1) _DD:= "0" . _DD if (_MMMM="Jan") _MM := "01" else if (_MMMM="Feb") _MM := "02" else if (_MMMM="Mar") _MM := "03" else if (_MMMM="Apr") _MM := "04" else if (_MMMM="May") _MM := "05" else if (_MMMM="Jun") _MM := "06" else if (_MMMM="Jul") _MM := "07" else if (_MMMM="Aug") _MM := "08" else if (_MMMM="Sep") _MM := "09" else if (_MMMM="Oct") _MM := "10" else if (_MMMM="Nov") _MM := "11" else if (_MMMM="Dec") _MM := "12" else { msgbox, GetSendTimeUTC-Error: Month not recognised return,-1 } SendTimeUTC:= _YYYY . _MM . _DD . _HH24 . _MI . _SS DayLightMod= If (DLM:=(InStr(DateTime,"+",0,1))) || (DLM:=(InStr(DateTime,"-",0,1))) { DayLightMod:=SubStr(DateTime,DLM,1) DLM_Hours:=SubStr(DateTime,DLM+1,2) DLM_Mins:=SubStr(DateTime,DLM+3,2) DLM_MinsTotal:=(DLM_Hours*60)+DLM_Mins if (DayLightMod="+") EnvAdd,SendTimeUTC,-%DLM_MinsTotal%,Min else EnvAdd,SendTimeUTC,%DLM_MinsTotal%,Min } return SendTimeUTC } Qdecode(Subject) { IsQcode=0 StringReplace,Subject,Subject,?=%A_Space%=?,?==?,1 loop { Marker1= Marker2= TextStart1= TextStart2= TextEnd1= TextEnd2= TextMid1= TextMid2= Text= LeftOver= Marker1 := InStr(Subject,"=?",0,1) Marker2 := InStr(Subject,"?Q?",0,1) if (Marker1=0)||(Marker2=0)||(Marker1>Marker2) break IsQcode=1 if (Marker1=1) { TextStart1:=Marker2+3 TextEnd1 := InStr(Subject,"?=",0,TextStart1) TextMid1 := SubStr(Subject,TextStart1,TextEnd1-TextStart1) Result:=Result TextMid1 if LeftOver:=InStr(Subject,"=?ISO",0,TextEnd1+2) { Subject:=SubStr(Subject,TextEnd2+2) ;msgbox,Leftover Type 1, Continuing with `n%Subject% continue } else { ;msgbox,No Leftover Type 1, Result is`n%Result% break } } else { TextStart1 = 1 TextEnd1 := Marker1-1 TextMid1 := SubStr(Subject,TextStart1,TextEnd1) TextStart2 := Marker2+3 TextEnd2 := InStr(Subject,"?=",0,TextStart2) TextMid2 := SubStr(Subject,TextStart2,TextEnd2-TextStart2) Result:=Result TextMid1 TextMid2 if LeftOver:=InStr(Subject,"=?ISO",0,TextEnd2+2) { Subject:=SubStr(Subject,TextEnd2+2) ;msgbox,Leftover Type 2, Continuing with `n%Subject% continue } else { ;msgbox,No Leftover Type 2, Result is`n%Result% break } } } if IsQcode=0 return Subject loop { If Encoded:=InStr(Result,"=",0,1) { Qcode:= "0x" . SubStr(Result,Encoded+1,2) Transform,RealCode,Chr,%Qcode% Result := SubStr(Result,1,Encoded-1) . RealCode . SubStr(Result,Encoded+3) } else break } StringReplace,Result,Result,_,%A_Space%,All return Result }
Sample of a tray email notification icon script.
The main script calls it as "Email-Check-Icon.ahk"
; Email-Check-Icon.ahk #SingleInstance,force #NoEnv #Persistent EmailNumber=%1% menu,tray,icon,New_Email.ico if EmailNumber=1 menu,tray,Tip,%EmailNumber% new Mail else menu,tray,Tip,%EmailNumber% new Mails Menu,tray,NoStandard Menu,tray,Add,Open Email Client,Run_Client Menu,tray,Add,Exit,Quit Menu,tray,Default,Open Email Client return Run_Client: Process,Exist,msimn.exe if ! ErrorLevel Run,%Prog_A%\Outlook Express\msimn.exe,%Prog_A%\Outlook Express Exitapp Quit: Exitapp