1 """SMS Transports by phd, corvin, r_sabitov"""
2 # -*- coding: koi8-r -*-
7 from m_lib.opstring import koi2win
8 from m_lib.rus.rus2lat import rus2lat
14 def debug(level, message):
15 if level <= debug_level:
18 syslog.syslog(message)
20 sys.stderr.write("%s\n" % message)
26 # Base transport classes
29 class Transport(object):
30 def __init__(self, phone, message):
32 self.message = message
34 def calcText(self, maxsize=smsSize, transFunction=None):
35 "Recode text on demand and truncate the result"
39 text = transFunction(text)
40 text = text[:maxsize - 1]
43 def calcExpiration(self):
44 from mx import DateTime
45 return DateTime.now() + DateTime.DateTimeDelta(0, 4)
48 "Mixin that converts input text from koi8 to cp1251"
50 def calcText(self, maxsize=smsSize):
51 text = super(CP1251, self).calcText(maxsize=maxsize)
58 class HTTPTransport(Transport):
59 "Base class for HTTP transport"
62 'User-Agent': 'Mozilla/5.0 (X11; U; Linux 2.2.19 i686; en-US; rv:0.9) Gecko/20010507',
69 import urllib, urlparse
70 _none, host, uri, _none, _none, _none = urlparse.urlparse(self.url)
71 postdata = urllib.urlencode(dict)
73 methodFunc = getattr(self, self.method)
76 reply = methodFunc(postdata, host, uri)
80 html = reply[2].fp.read()
83 if html.find(self.ok_match) >= 0:
89 _debug = 'msg to %s via %s (%s)' % \
90 (self.phone, self.__class__.__name__, ret)
95 def GET(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None):
99 uri = uri + "?" + postdata
103 http = httplib.HTTP(proxy_host, proxy_port)
104 http.set_debuglevel(debug_level)
105 http.putrequest("GET", 'http://%s%s' % (host, uri))
107 http = httplib.HTTP(host, port)
108 http.set_debuglevel(debug_level)
109 http.putrequest("GET", uri)
111 http.putheader("Host", host)
113 http.putheader("Referer", self.referer)
115 for name, val in self.HTTPHeaders.items():
116 http.putheader(name, val)
120 reply = http.getreply()
121 reply[2].fp = http.getfile()
124 def POST(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None):
129 http = httplib.HTTP(proxy_host, proxy_port)
130 http.set_debuglevel(debug_level)
131 http.putrequest("POST", 'http://%s%s' % (host, uri))
133 http = httplib.HTTP(host, port)
134 http.set_debuglevel(debug_level)
135 http.putrequest("POST", uri)
137 http.putheader("Host", host)
139 http.putheader("Referer", self.referer)
140 http.putheader('Content-Type', 'application/x-www-form-urlencoded')
141 http.putheader('Content-Length', str(len(postdata)))
143 for name, val in self.HTTPHeaders.items():
144 http.putheader(name, val)
149 reply = http.getreply()
150 reply[2].fp = http.getfile()
153 class CP1251HTTPTransport(CP1251, HTTPTransport):
160 class SNPPTransport(Transport):
161 "Base class for SNPP transport"
165 ok_match = '250 Message Sent Successfully'
167 def post(self, dict):
168 # raw snpp hack w/o error checking
170 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
171 sock.connect(('www.extel-gsm.com', 4444))
172 sock_file = sock.makefile('r')
174 # 220 Extel Mobile Communications. SNPP Gateway v.0.2.15; Wed May 30 00:43:56 2001
175 # 220 SNPP Gateway Ready
176 debug(2, sock_file.readline())
177 debug(2, sock_file.readline())
179 sock.send('PAGE %s\r\n' % dict['phone'])
180 # 250 Pager ID Accepted
181 debug(2, sock_file.readline())
183 sock.send('DATA\r\n')
184 # 354 Begin Input; End with <CRLF>'.'<CRLF>
185 debug(2, sock_file.readline())
187 sock.send('%s\r\n' % dict['message'])
190 debug(2, sock_file.readline())
192 sock.send('SEND\r\n')
193 # 250 Message Sent Successfully. Msg ID = 692274
194 reply = sock_file.readline()
197 sock.send('QUIT\r\n')
202 if reply.find(self.ok_match) >= 0:
207 _debug = 'msg to %s via %s (%s)' % \
208 (self.phone, self.__class__.__name__, ret)
216 class EMailTransport(Transport):
217 def __init__(self, phone, message, mail_from=None, gate=None):
218 Transport.__init__(self, phone, message)
223 realuser = getpass.getuser()
224 except AttributeError:
225 # Not all systems have os.environ or getpw...
227 thishost = socket.getfqdn()
228 mail_from = "%s@%s" % (realuser, thishost)
230 self.mail_from = mail_from
241 """ % (self.mail_from, self.phone, self.gate, self.message)
246 class SendmailTransport(EMailTransport):
247 def __init__(self, phone, message, mail_from=None, gate=None):
248 EMailTransport.__init__(self, phone, message, mail_from, gate)
250 sendmail = self.find_sendmail()
252 raise ValueError("cannot find sendmail binary")
253 self.sendmail = sendmail
255 def find_sendmail(self):
257 for sendmail in ("/usr/lib/sendmail", "/usr/sbin/sendmail"):
258 if os.path.exists(sendmail):
262 def post(self, message):
263 cmd = "%s -oem -oi '%s@%s'" % (self.sendmail, self.phone, self.gate)
266 sendmail = os.popen(cmd, 'w')
267 sendmail.write(message)
270 class SMTPTransport(EMailTransport):
271 def __init__(self, phone, message, mail_from=None, gate=None, mail_relay="localhost"):
272 EMailTransport.__init__(self, phone, message, mail_from, gate)
273 self.mail_relay = mail_relay
275 def post(self, message):
276 debug(1, self.mail_relay)
278 smtp = smtplib.SMTP(self.mail_relay)
279 smtp.sendmail(self.mail_from, "%s@%s" % (self.phone, self.gate), message)
287 class MTSru(HTTPTransport):
288 "Russian provider MTS.ru"
291 url = "http://www.mts.ru:5051/cgi-bin/cgi.exe"
293 ok_match = 'Ваше сообщение отправлено'
297 text = self.calcText()
298 time = self.calcExpiration()
300 dict['function'] = 'sms_send'
301 #dict['MMObjectType'] = '0'
302 #dict['MMObjectID'] = ''
304 if phone[:4] == "8902":
305 phone = "8916%s" % phone[4:]
308 dict['Hour'] = time.strftime('%k').strip()
309 dict['Min'] = time.strftime('%M')
310 dict['Day'] = time.strftime('%e').strip()
311 dict['Mon'] = str(time.month)
312 dict['Year'] = time.strftime('%Y')
313 dict['count'] = str(len(text))
316 return self.post(dict)
318 def POST(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None):
319 postdata = "MMObjectType=0&MMObjectID=&" + postdata
320 return HTTPTransport.POST(self, postdata, host, uri, port, proxy_host, proxy_port)
323 class MTSGSMcom(HTTPTransport):
324 url = "http://www.mtsgsm.com/sms/sent.html"
325 referer = "http://www.mtsgsm.com/sms/"
327 ok_match = 'Ваше сообщение отправлено'
331 text = self.calcText()
332 time = self.calcExpiration()
335 dict['To'] = self.phone
337 dict['count'] = str(len(text))
338 dict['SMSHour'] = time.strftime('%k').strip()
339 dict['SMSMinute'] = time.strftime('%M')
340 dict['SMSDay'] = time.strftime('%e').strip()
341 dict['SMSMonth'] = str(time.month - 1)
342 dict['SMSYear'] = time.strftime('%Y')
344 return self.post(dict)
347 class BeeOnLine(CP1251HTTPTransport):
348 "Russian provider BeeOnLine.ru"
351 url = "http://www.beeonline.ru/portal/comm/send_sms/simple_send_sms.sms"
352 referer = "http://www.beeonline.ru/portal/comm/send_sms/simple_send_sms.sms"
354 ok_match = koi2win('Ваше сообщение отправлено')
356 def __init__(self, phone, message,
357 mode="GSM", # mode can be either GSM or DAMPS
358 transliterate=1, # turn transliteration of/off
359 reply_to=''): # send reply to this e-mail
360 Transport.__init__(self, phone, message)
361 if mode not in ('GSM', 'DAMPS'):
362 raise ValueError("mode (%s) must be either 'GSM' or 'DAMPS'" % mode)
364 self.transliterate = transliterate
365 self.reply_to = reply_to
369 text = self.calcText(smsSize - len(self.crap))
372 #dict['deferto'] = ''
373 #dict['adv_year'] = ''
374 dict['send'] = 'send'
375 dict['destination_number_from'] = 'ordinary' # number, not a BEEpost
378 if self.mode == "GSM":
379 dict['network_code'] = '3'
381 dict['network_code'] = '2'
383 dict['network_code'] = '1'
385 raise RuntimeError("incorrect combination of mode (%s) and prefix (%s)" % (self.mode, prf))
387 dict['phone'] = self.phone[4:]
388 dict['message'] = text
389 dict['mlength'] = str(smsSize - len(self.crap) - len(text))
391 if self.mode == "GSM" and not self.transliterate:
392 dict['translit'] = '1' # turn transliteration OFF! :)
395 dict['send_email'] = '1'
396 dict['reply_addr'] = self.reply_to
398 return self.post(dict)
400 class BeeOnLineSMS(BeeOnLine):
401 "Russian provider BeeOnLine.ru"
403 url = "http://www.beeonline.ru/servlet/send/sms/"
407 text = self.calcText(smsSize - len(self.crap))
409 dict['prf'] = self.phone[:4]
410 dict['phone'] = self.phone[4:]
411 dict['termtype'] = self.mode[0]
412 dict['message'] = text
414 if self.mode == "GSM" and not self.transliterate:
415 dict['translit'] = '1' # turn transliteration OFF! :)
417 dict['number_sms'] = "number_sms_send"
418 return self.post(dict)
421 class Pcom(HTTPTransport):
422 "Russian provider PCOM.ru"
425 url = "http://www.pcom.ru/online.phtml"
426 referer = "http://www.pcom.ru/online.phtml"
428 ok_match = koi2win('поставлено в очередь')
430 def calcText(self, maxsize=smsSize):
431 "force translitertaion"
433 return HTTPTransport.calcText(self, maxsize=maxsize,
434 transFunction=rus2lat)
438 text = self.calcText(120 - len(self.crap))
439 expiration = self.calcExpiration()
440 from mx import DateTime
443 dict['ACTION'] = 'SENDSMS'
445 dict['SMS_START_HOUR'] = now.strftime('%H')
446 dict['SMS_START_MINUTE'] = now.strftime('%M')
447 dict['SMS_START_DAY'] = now.strftime('%d')
448 dict['SMS_START_MONTH'] = now.strftime('%m')
449 dict['SMS_START_YEAR'] = now.strftime('%Y')
451 dict['SMS_STOP_HOUR'] = expiration.strftime('%H')
452 dict['SMS_STOP_MINUTE'] = expiration.strftime('%M')
453 dict['SMS_STOP_DAY'] = expiration.strftime('%d')
454 dict['SMS_STOP_MONTH'] = expiration.strftime('%m')
455 dict['SMS_STOP_YEAR'] = expiration.strftime('%Y')
457 dict['prefix'] = self.phone[1:4]
458 dict['DN'] = self.phone[4:]
461 return self.post(dict)
464 class ExtelGsmCom(SNPPTransport):
465 "Russian provider Extel-GSM.com"
467 host = 'www.extel-gsm.com'
469 ok_match = '250 Message Sent Successfully'
474 def calcText(self, maxsize=smsSize):
475 "force translitertaion"
477 return SNPPTransport.calcText(self, maxsize=maxsize,
478 transFunction=rus2lat)
482 text = self.calcText(smsSize - len(self.crap))
483 phone = self.phone[5:]
485 # 0112 (city code) must be replaced with (0119)
486 dict['phone'] = '+7%s%s' % (self.prefix, phone)
487 dict['message'] = text
489 return self.post(dict)
492 class MegafoneMoscow(CP1251HTTPTransport):
493 "Rissian provider megafonmoscow.ru"
496 url = "http://www.megafonmoscow.ru/rus/sms.xpml"
497 referer = "http://www.megafonmoscow.ru/rus/sms.xpml"
498 ok_match = koi2win('Сообщение успешно отправлено')
502 text = self.calcText()
504 dict['prefix'] = self.phone[:4]
505 dict['addr'] = self.phone[4:]
506 dict['message'] = text
507 dict['send'] = " Send "
509 if hasattr(self, 'transliterate') and self.transliterate:
510 dict['transliterate'] = '1'
512 return self.post(dict)
515 class SkyLinkMsk(CP1251HTTPTransport):
516 "Russian provider SkyLink.Msk.ru"
519 url = "http://skylink.msk.ru/inc/sendsms.php"
520 ok_match = koi2win('Сообщение было отправлено')
524 text = self.calcText()
528 phone = '8' + phone[1:]
530 dict['smsbody'] = text
532 return self.post(dict)
535 BEELINE = BeeOnLineSMS
542 Megafone = MegafoneMoscow
558 class SMSError(Exception): pass
560 def Phone2Provider(phone):
563 if prefix in ("095", "901"): # 901 is being used by Beeline and SkyLink
564 raise SMSError("unknown provider for phone %s" % phone)
566 if Prefix2Provider.has_key(prefix):
567 return Prefix2Provider[prefix]
569 raise SMSError("bad prefix for phone %s" % phone)