]>
jfr.im git - z_archive/Ophion.git/blob - bot.py
6 ### Copyright 2012 John Runyon
7 ### <https://github.com/Mustis/Ophion>
15 rootdir
= '/home/bots/'
16 logfile
= rootdir
+'/output.log'
17 excfile
= rootdir
+'/exception.log'
19 oidfile
= '/home/bots/.oidentd.conf'
20 identprefix
= 'ophion' # ident will be <identprefix><bot ID#>
22 import socket
, select
, sys
, os
, signal
, time
23 from threading
import *
24 from traceback
import print_exc
26 import MySQLdb
, MySQLdb
.cursors
33 def quit(reason
, restart
=False):
35 cache
.mainBot
.cmsg('fatal', "Shutting down because: %s" % (reason
))
37 for bot
in cache
.bots
.values():
39 for tkey
in cache
.timers
.keys():
40 cache
.timers
[tkey
].cancel()
41 del cache
.timers
[tkey
]
42 for modf
in cache
.modules
:
43 unloadmod(modf
, cache
, True)
46 os
.execv(sys
.argv
[0], sys
.argv
)
48 def sigHandle(signum
, stack
):
49 quit("Signal: %d" % (signum
))
51 return (bid
in cache
.bots
and cache
.bots
[bid
].online
)
52 def toofew(bot
, nick
): # like: if len(params) < NEEDED: return toofew(bot, nick)
53 bot
.msg(nick
, "Too few parameters for that command.")
57 for bot
in cache
.bots
.values():
59 if not cache
.quitting
:
60 t
= Timer(1, sendRaws
)
62 if 'raws' in cache
.timers
: del cache
.timers
['raws']
63 cache
.timers
['raws'] = t
67 curs
= cache
.dbc
.cursor()
68 curs
.execute("SELECT 1")
72 for bot
in cache
.bots
.values():
74 ident
= "%s%d" % (identprefix
, bot
.id)
75 oid
= open(oidfile
, 'w')
76 oid
.write("global {\n\treply \"%s\"\n}" % ident
)
78 if bot
.connect(ident
):
79 bot
.cmsg('info', "Connected")
84 if not cache
.quitting
:
85 if connected
< 2: interval
= 300
87 t
= Timer(interval
, connectBots
)
89 if 'conn' in cache
.timers
: del cache
.timers
['conn']
90 cache
.timers
['conn'] = t
95 curs
= cache
.dbc
.cursor()
96 curs
.execute("SELECT id FROM bots")
98 while row
is not None:
103 cache
.bots
[row
['id']] = bot
104 row
= curs
.fetchone()
107 curs
= cache
.dbc
.cursor()
108 curs
.execute("SELECT username, level FROM admins")
109 row
= curs
.fetchone()
110 while row
is not None:
111 cache
.admins
[row
['username']] = row
['level']
112 row
= curs
.fetchone()
113 curs
.execute("SELECT chname FROM chans ORDER BY id LIMIT 2")
114 rows
= curs
.fetchall()
115 cache
.ctrl
= rows
[0]['chname']
116 cache
.home
= rows
[1]['chname']
120 def parseCmd(bot
, line
, linepieces
, msg
, iscm
):
121 hostmask
= linepieces
[0][1:]
122 fromnick
= hostmask
.split('!')[0]
123 target
= linepieces
[2]
125 cmd
= pieces
[0].upper()
126 if len(pieces
) == 1: params
= ''
127 else: params
= ' '.join(pieces
[1:])
131 if cmd
not in cache
.cmds
:
132 noaccess(bot
, fromnick
)
134 else: ci
= cache
.cmds
[cmd
]
136 if not ci
['isadmin']:
137 if fromnick
not in cache
.users
:
138 cache
.users
[fromnick
] = User(fromnick
)
139 auth
= cache
.users
[fromnick
].auth
142 target
= pieces
.pop(1).lower()
143 params
= ' '.join(pieces
[1:])
146 if target
not in cache
.chans
:
147 bot
.msg(fromnick
, "No such channel.")
149 chan
= cache
.chans
[target
]
150 if auth
not in chan
.alist
or chan
.alist
[auth
] < ci
['level']:
151 noaccess(bot
, fromnick
, True)
153 elif len(pieces
)-1 < cache
.cmds
[cmd
]['params']:
154 toofew(bot
, fromnick
)
157 try: ret
= cache
.cmds
[cmd
]['func'](fromnick
, target
, params
, chan
.bot
, cache
)
158 except Exception as e
:
159 print_exc(None, cache
.excfile
)
160 bot
.msg(fromnick
, "An error occurred, sorry! Try again later.")
161 cache
.mainBot
.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e
, fromnick
, line
))
163 else: # admin/global command
164 if fromnick
in cache
.users
: glblevel
= cache
.users
[fromnick
].access
166 if glblevel
< ci
['level']:
167 noaccess(bot
, fromnick
)
168 elif len(pieces
)-1 < cache
.cmds
[cmd
]['params']:
169 toofew(bot
, fromnick
)
171 try: ret
= cache
.cmds
[cmd
]['func'](fromnick
, target
, params
, bot
, cache
)
172 except Exception as e
:
173 print_exc(None, cache
.excfile
)
174 bot
.msg(fromnick
, "An error occurred, sorry! Try again later.")
175 cache
.mainBot
.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e
, fromnick
, line
))
178 def _parse(bot
, line
):
179 pieces
= line
.split()
180 if pieces
[1] == "PRIVMSG":
182 msg
= ' '.join(pieces
[3:]).lstrip(':')
183 if msg
[0] == cache
.trigger
:
184 return parseCmd(bot
, line
, pieces
, msg
[1:], True)
185 elif target
== bot
.nick
:
186 return parseCmd(bot
, line
, pieces
, msg
, False)
187 elif pieces
[1] == "JOIN":
188 nick
= pieces
[0][1:].split('!')[0]
189 host
= pieces
[0][1:].split('@')[1]
190 chname
= pieces
[2].lower()
191 if chname
in cache
.chans
:
192 bot
.joined(chname
, nick
)
193 elif pieces
[1] == "PART":
194 nick
= pieces
[0][1:].split('!')[0]
195 chname
= pieces
[2].lower()
196 if chname
in cache
.chans
:
197 bot
.parted(chname
, nick
)
198 elif pieces
[1] == "QUIT":
199 nick
= pieces
[0][1:].split('!')[0]
201 elif pieces
[1] == "NICK":
202 fromnick
= pieces
[0][1:].split('!')[0]
203 tonick
= pieces
[2][1:]
204 cache
.users
[tonick
] = cache
.users
[fromnick
]
205 del cache
.users
[fromnick
]
206 cache
.users
[tonick
].nick
= tonick
207 elif pieces
[0] == "PING":
208 bot
.rawnow("PONG %s" % (pieces
[1]))
209 elif pieces
[0] == "ERROR":
210 try: bot
.disconnect()
212 elif pieces
[1] in cache
.nums
:
213 for fn
in cache
.nums
[pieces
[1]]:
215 except Exception as e
:
216 print_exc(None, cache
.excfile
)
217 bot
.msg(fromnick
, "An error occurred, sorry! Try again later.")
218 cache
.mainBot
.cmsg('warn', "EXCEPTION! %r Caused by <%s> %s" % (e
, fromnick
, line
))
221 line
= bot
.get().strip()
222 return _parse(bot
, line
)
227 ready
= cache
.poll
.poll()
230 if fde
[0] == sys
.stdin
.fileno():
231 line
= sys
.stdin
.readline().strip()
232 pieces
= line
.split(None, 2)
233 if len(pieces
) == 3 and pieces
[1].isdigit():
237 if mode
.upper() == "IN": ret
= _parse(cache
.bots
[bid
], line
)
238 elif mode
.upper() == "OUT": cache
.bots
[bid
].rawnow(line
)
239 else: print "ERROR! <'IN'|'OUT'>:<bid>:<line>"
240 else: print "ERROR! <'IN'|'OUT'>:<bid>:<line>"
241 elif fde
[0] in cache
.botsByFD
: # it's a bot
242 bot
= cache
.botsByFD
[fde
[0]]
243 if fde
[1] & select
.POLLERR
or fde
[1] & select
.POLLHUP
or fde
[1] & select
.POLLNVAL
:
247 if ret
== "QUIT": break
249 break # if the "for" was broken, break the while as well.
252 signal
.signal(signal
.SIGHUP
, signal
.SIG_IGN
)
253 signal
.signal(signal
.SIGINT
, sigHandle
)
255 cache
.excfile
= open(excfile
, 'a')
257 sys
.path
.append(rootdir
+'/modules')
258 sys
.path
.append(rootdir
+'/modules/autoload')
259 for modf
in os
.listdir(rootdir
+'/modules/autoload'):
260 if modf
[-3:] == ".py":
261 loadmod(modf
[:-3], cache
)
263 cache
.dbc
= MySQLdb
.connect(host
=dbhost
, user
=dbuser
, passwd
=dbpass
, db
=dbname
, cursorclass
=MySQLdb
.cursors
.DictCursor
)
266 cache
.poll
= select
.poll()
268 cache
.poll
.register(sys
.stdin
.fileno(), select
.POLLIN
)
270 t
= Timer(1, sendRaws
)
272 cache
.timers
['raws'] = t
276 t
= Timer(60, connectBots
)
278 cache
.timers
['conn'] = t