2016-11-10 17:29:50 +00:00
""" The NNTP module """
# PYTHON STUFFS #######################################################
import email
from email . utils import mktime_tz , parseaddr , parsedate_tz
from nntplib import NNTP , decode_header
import re
import time
from datetime import datetime
from zlib import adler32
from nemubot import context
from nemubot . event import ModuleEvent
from nemubot . exception import IMException
from nemubot . hooks import hook
from more import Response
2017-07-14 10:20:18 +00:00
# LOADING #############################################################
def load ( context ) :
for wn in context . data . getNodes ( " watched_newsgroup " ) :
watch ( * * wn )
2016-11-10 17:29:50 +00:00
# MODULE CORE #########################################################
def list_groups ( group_pattern = " * " , * * server ) :
with NNTP ( * * server ) as srv :
response , l = srv . list ( group_pattern )
for i in l :
yield i . group , srv . description ( i . group ) , i . flag
def read_group ( group , * * server ) :
with NNTP ( * * server ) as srv :
response , count , first , last , name = srv . group ( group )
resp , overviews = srv . over ( ( first , last ) )
for art_num , over in reversed ( overviews ) :
yield over
def read_article ( msg_id , * * server ) :
with NNTP ( * * server ) as srv :
response , info = srv . article ( msg_id )
return email . message_from_bytes ( b " \r \n " . join ( info . lines ) )
def whatsnew ( date_last_check , group = " * " , * * server ) :
with NNTP ( * * server ) as srv :
response , groups = srv . newgroups ( date_last_check )
for g in groups :
yield g
response , articles = srv . newnews ( group , date_last_check )
for msg_id in articles :
response , info = srv . article ( msg_id )
yield email . message_from_bytes ( b " \r \n " . join ( info . lines ) )
def format_article ( art , * * response_args ) :
art [ " X-FromName " ] , art [ " X-FromEmail " ] = parseaddr ( art [ " From " ] if " From " in art else " " )
if art [ " X-FromName " ] == ' ' : art [ " X-FromName " ] = art [ " X-FromEmail " ]
date = mktime_tz ( parsedate_tz ( art [ " Date " ] ) )
if date < time . time ( ) - 120 :
title = " \x03 14In \x0F \x03 {0:02d} {Newsgroups} \x0F \x03 14: on \x0F {Date} \x03 14 by \x0F \x03 {0:02d} { X-FromName} \x0F \x02 {Subject} \x0F "
else :
title = " \x03 14In \x0F \x03 {0:02d} {Newsgroups} \x0F \x03 14: by \x0F \x03 {0:02d} { X-FromName} \x0F \x02 {Subject} \x0F "
return Response ( art . get_payload ( ) . replace ( ' \n ' , ' ' ) ,
title = title . format ( adler32 ( art [ " Newsgroups " ] . encode ( ) ) & 0xf , adler32 ( art [ " X-FromEmail " ] . encode ( ) ) & 0xf , * * { h : decode_header ( i ) for h , i in art . items ( ) } ) ,
* * response_args )
2017-07-14 10:20:18 +00:00
watches = dict ( )
def _indexServer ( * * kwargs ) :
return " {user} : {password} @ {host} : {port} " . format ( * * kwargs )
def _newevt ( * args ) :
context . add_event ( ModuleEvent ( call = _fini , call_data = args , interval = 42 ) )
def _fini ( lastcheck , server ) :
_newevt ( datetime . now ( ) , server )
n = 0
for art in whatsnew ( lastcheck , group , * * server ) :
n + = 1
2016-11-10 17:29:50 +00:00
if n > 10 :
2017-07-14 10:20:18 +00:00
continue
context . send_response ( to_server , format_article ( art , channel = to_channel ) )
if n > 10 :
context . send_response ( to_server , Response ( " ... and %s others news " % ( n - 10 ) , channel = to_channel ) )
def watch ( to_server , to_channel , group = " * " , lastcheck = None , * * server ) :
idsrv = _indexServer ( * * server )
if lastcheck is None :
lastcheck = datetime . now ( )
if idsrv not in watches :
wnnode = ModuleState ( " watched_newsgroup " )
wnnode . setIndex ( " group " )
wnnode [ " id " ] = idsrv
wnnode . update ( server )
context . data . addChild ( wnnode )
_newevt ( lastcheck , server )
else :
wnnode = context . data . index [ idsrv ]
if group not in wnnode :
ngnode = ModuleState ( " notify_group " )
ngnode [ " group " ] = group
wnnode . addChild ( ngnode )
else :
ngnode = wnnode . index [ group ]
2016-11-10 17:29:50 +00:00
2017-07-14 10:20:18 +00:00
# Ensure this watch is not already registered
watches [ idsrv ] [ group ] . append ( ( to_server , to_channel ) )
2016-11-10 17:29:50 +00:00
# MODULE INTERFACE ####################################################
keywords_server = {
" host=HOST " : " hostname or IP of the NNTP server " ,
" port=PORT " : " port of the NNTP server " ,
" user=USERNAME " : " username to use to connect to the server " ,
" password=PASSWORD " : " password to use to connect to the server " ,
}
@hook.command ( " nntp_groups " ,
help = " Show list of existing groups " ,
help_usage = {
None : " Display all groups " ,
" PATTERN " : " Filter on group matching the PATTERN "
} ,
keywords = keywords_server )
def cmd_groups ( msg ) :
if " host " not in msg . kwargs :
raise IMException ( " please give a hostname in keywords " )
return Response ( [ " \x02 \x03 {0:02d} {1} \x0F : {2} " . format ( adler32 ( g [ 0 ] . encode ( ) ) & 0xf , * g ) for g in list_groups ( msg . args [ 0 ] if len ( msg . args ) > 0 else " * " , * * msg . kwargs ) ] ,
channel = msg . channel ,
title = " Matching groups on %s " % msg . kwargs [ " host " ] )
@hook.command ( " nntp_overview " ,
help = " Show an overview of articles in given group(s) " ,
help_usage = {
" GROUP " : " Filter on group matching the PATTERN "
} ,
keywords = keywords_server )
def cmd_overview ( msg ) :
if " host " not in msg . kwargs :
raise IMException ( " please give a hostname in keywords " )
if not len ( msg . args ) :
raise IMException ( " which group would you overview? " )
for g in msg . args :
arts = [ ]
for grp in read_group ( g , * * msg . kwargs ) :
grp [ " X-FromName " ] , grp [ " X-FromEmail " ] = parseaddr ( grp [ " from " ] if " from " in grp else " " )
if grp [ " X-FromName " ] == ' ' : grp [ " X-FromName " ] = grp [ " X-FromEmail " ]
arts . append ( " On {date} , from \x03 {0:02d} { X-FromName} \x0F \x02 {subject} \x0F : \x03 14 { message-id} \x0F " . format ( adler32 ( grp [ " X-FromEmail " ] . encode ( ) ) & 0xf , * * { h : decode_header ( i ) for h , i in grp . items ( ) } ) )
if len ( arts ) :
yield Response ( arts ,
channel = msg . channel ,
title = " In \x03 {0:02d} {1} \x0F " . format ( adler32 ( g [ 0 ] . encode ( ) ) & 0xf , g ) )
@hook.command ( " nntp_read " ,
help = " Read an article from a server " ,
help_usage = {
" MSG_ID " : " Read the given message "
} ,
keywords = keywords_server )
def cmd_read ( msg ) :
if " host " not in msg . kwargs :
raise IMException ( " please give a hostname in keywords " )
for msgid in msg . args :
if not re . match ( " <.*> " , msgid ) :
msgid = " < " + msgid + " > "
art = read_article ( msgid , * * msg . kwargs )
yield format_article ( art , channel = msg . channel )
@hook.command ( " nntp_watch " ,
help = " Launch an event looking for new groups and articles on a server " ,
help_usage = {
None : " Watch all groups " ,
" PATTERN " : " Limit the watch on group matching this PATTERN "
} ,
keywords = keywords_server )
def cmd_watch ( msg ) :
if " host " not in msg . kwargs :
raise IMException ( " please give a hostname in keywords " )
print ( type ( msg ) )
if not msg . frm_owner :
raise IMException ( " sorry, this command is currently limited to the owner " )
2017-07-14 10:20:18 +00:00
wnnode = ModuleState ( " watched_newsgroup " )
context . data . addChild ( wnnode )
2016-11-10 17:29:50 +00:00
watch ( msg . server , msg . channel , msg . args [ 0 ] if len ( msg . args ) > 0 else " * " , * * msg . kwargs )
return Response ( " Ok ok, I watch this newsgroup! " , channel = msg . channel )