Thanks to DerRaphael and all the others who made the winsock library!
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.
Code:
; 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"
FilterFrom=mailings@gmx.net|AndSoOn@bla.net
FilterTo=anotherAddress@t-online.com
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"
Code:
; 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