+"SMS Transports by phd, corvin, r_sabitov"
+# -*- coding: koi8-r -*-
+
+
+import socket
+
+from m_lib.opstring import koi2win
+from m_lib.rus.rus2lat import rus2lat
+
+
+debug_level = 0
+use_syslog = 0
+
+def debug(level, message):
+ if level <= debug_level:
+ if use_syslog:
+ import syslog
+ syslog.syslog(message)
+ import sys
+ sys.stderr.write("%s\n" % message)
+
+
+smsSize = 160
+
+#
+# Base transport classes
+#
+
+class Transport(object):
+ def __init__(self, phone, message):
+ self.phone = phone
+ self.message = message
+
+ def calcText(self, maxsize=smsSize, transFunction=None):
+ "Recode text on demand and truncate the result"
+
+ text = self.message
+ if transFunction:
+ text = transFunction(text)
+ text = text[:maxsize - 1]
+ return text
+
+ def calcExpiration(self):
+ from mx import DateTime
+ return DateTime.now() + DateTime.DateTimeDelta(0, 4)
+
+class CP1251(object):
+ "Mixin that converts input text from koi8 to cp1251"
+
+ def calcText(self, maxsize=smsSize):
+ text = super(CP1251, self).calcText(maxsize=maxsize)
+ return koi2win(text)
+
+#
+# HTTP transport
+#
+
+class HTTPTransport(Transport):
+ "Base class for HTTP transport"
+
+ HTTPHeaders = {
+ 'User-Agent': 'Mozilla/5.0 (X11; U; Linux 2.2.19 i686; en-US; rv:0.9) Gecko/20010507',
+ 'Accept': '*/*'
+ }
+
+ referer = ''
+
+ def post(self, dict):
+ import urllib, urlparse
+ _none, host, uri, _none, _none, _none = urlparse.urlparse(self.url)
+ postdata = urllib.urlencode(dict)
+
+ methodFunc = getattr(self, self.method)
+
+ try:
+ reply = methodFunc(postdata, host, uri)
+ except socket.error:
+ ret = 0
+ else:
+ html = reply[2].fp.read()
+ debug(2, html)
+
+ if html.find(self.ok_match) >= 0:
+ ret = 1
+ else:
+ ret = 0
+
+
+ _debug = 'msg to %s via %s (%s)' % \
+ (self.phone, self.__class__.__name__, ret)
+ debug(1, _debug)
+
+ return ret
+
+ def GET(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None):
+ "HTTP method GET"
+
+ if postdata:
+ uri = uri + "?" + postdata
+
+ import httplib
+ if proxy_host:
+ http = httplib.HTTP(proxy_host, proxy_port)
+ http.set_debuglevel(debug_level)
+ http.putrequest("GET", 'http://%s%s' % (host, uri))
+ else:
+ http = httplib.HTTP(host, port)
+ http.set_debuglevel(debug_level)
+ http.putrequest("GET", uri)
+
+ http.putheader("Host", host)
+ if self.referer:
+ http.putheader("Referer", self.referer)
+
+ for name, val in self.HTTPHeaders.items():
+ http.putheader(name, val)
+
+ http.endheaders()
+
+ reply = http.getreply()
+ reply[2].fp = http.getfile()
+ return reply
+
+ def POST(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None):
+ "HTTP method POST"
+
+ import httplib
+ if proxy_host:
+ http = httplib.HTTP(proxy_host, proxy_port)
+ http.set_debuglevel(debug_level)
+ http.putrequest("POST", 'http://%s%s' % (host, uri))
+ else:
+ http = httplib.HTTP(host, port)
+ http.set_debuglevel(debug_level)
+ http.putrequest("POST", uri)
+
+ http.putheader("Host", host)
+ if self.referer:
+ http.putheader("Referer", self.referer)
+ http.putheader('Content-Type', 'application/x-www-form-urlencoded')
+ http.putheader('Content-Length', str(len(postdata)))
+
+ for name, val in self.HTTPHeaders.items():
+ http.putheader(name, val)
+
+ http.endheaders()
+ http.send(postdata)
+
+ reply = http.getreply()
+ reply[2].fp = http.getfile()
+ return reply
+
+class CP1251HTTPTransport(CP1251, HTTPTransport):
+ pass
+
+#
+# SNPP transport
+#
+
+class SNPPTransport(Transport):
+ "Base class for SNPP transport"
+
+ host = 'localhost'
+ port = 444
+ ok_match = '250 Message Sent Successfully'
+
+ def post(self, dict):
+ # raw snpp hack w/o error checking
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(('www.extel-gsm.com', 4444))
+ sock_file = sock.makefile('r')
+
+ # 220 Extel Mobile Communications. SNPP Gateway v.0.2.15; Wed May 30 00:43:56 2001
+ # 220 SNPP Gateway Ready
+ debug(2, sock_file.readline())
+ debug(2, sock_file.readline())
+
+ sock.send('PAGE %s\r\n' % dict['phone'])
+ # 250 Pager ID Accepted
+ debug(2, sock_file.readline())
+
+ sock.send('DATA\r\n')
+ # 354 Begin Input; End with <CRLF>'.'<CRLF>
+ debug(2, sock_file.readline())
+
+ sock.send('%s\r\n' % dict['message'])
+ sock.send('.\r\n')
+ # 250 Message OK
+ debug(2, sock_file.readline())
+
+ sock.send('SEND\r\n')
+ # 250 Message Sent Successfully. Msg ID = 692274
+ reply = sock_file.readline()
+ debug(2, reply)
+
+ sock.send('QUIT\r\n')
+ sock.close()
+ except:
+ ret = 0
+ else:
+ if reply.find(self.ok_match) >= 0:
+ ret = 1
+ else:
+ ret = 0
+
+ _debug = 'msg to %s via %s (%s)' % \
+ (self.phone, self.__class__.__name__, ret)
+ debug(1, _debug)
+ return ret
+
+#
+# E-mail trnsport
+#
+
+class EMailTransport(Transport):
+ def __init__(self, phone, message, mail_from=None, gate=None):
+ Transport.__init__(self, phone, message)
+
+ if not mail_from:
+ import getpass
+ try:
+ realuser = getpass.getuser()
+ except AttributeError:
+ # Not all systems have os.environ or getpw...
+ realuser = 'nobody'
+ thishost = socket.getfqdn()
+ mail_from = "%s@%s" % (realuser, thishost)
+ debug(1, mail_from)
+ self.mail_from = mail_from
+
+ self.gate = gate
+
+ def send(self):
+ message = """\
+From: %s
+To: %s@%s
+Subject: SMS
+
+%s
+""" % (self.mail_from, self.phone, self.gate, self.message)
+ self.post(message)
+ debug(2, message)
+ return 1
+
+class SendmailTransport(EMailTransport):
+ def __init__(self, phone, message, mail_from=None, gate=None):
+ EMailTransport.__init__(self, phone, message, mail_from, gate)
+
+ sendmail = self.find_sendmail()
+ if not sendmail:
+ raise ValueError, "cannot find sendmail binary"
+ self.sendmail = sendmail
+
+ def find_sendmail(self):
+ import os
+ for sendmail in ("/usr/lib/sendmail", "/usr/sbin/sendmail"):
+ if os.path.exists(sendmail):
+ return sendmail
+ return None
+
+ def post(self, message):
+ cmd = "%s -oem -oi '%s@%s'" % (self.sendmail, self.phone, self.gate)
+ debug(1, cmd)
+ import os
+ sendmail = os.popen(cmd, 'w')
+ sendmail.write(message)
+ sendmail.close()
+
+class SMTPTransport(EMailTransport):
+ def __init__(self, phone, message, mail_from=None, gate=None, mail_relay="localhost"):
+ EMailTransport.__init__(self, phone, message, mail_from, gate)
+ self.mail_relay = mail_relay
+
+ def post(self, message):
+ debug(1, self.mail_relay)
+ import smtplib
+ smtp = smtplib.SMTP(self.mail_relay)
+ smtp.sendmail(self.mail_from, "%s@%s" % (self.phone, self.gate), message)
+ smtp.close()
+
+
+#
+# Real transports
+#
+
+class MTSru(HTTPTransport):
+ "Russian provider MTS.ru"
+
+ method = "POST"
+ url = "http://www.mts.ru:5051/cgi-bin/cgi.exe"
+ referer = ""
+ ok_match = 'Ваше сообщение отправлено'
+
+ def send(self):
+ dict = {}
+ text = self.calcText()
+ time = self.calcExpiration()
+
+ dict['function'] = 'sms_send'
+ #dict['MMObjectType'] = '0'
+ #dict['MMObjectID'] = ''
+ phone = self.phone
+ if phone[:4] == "8902":
+ phone = "8916%s" % phone[4:]
+ dict['To'] = phone
+ dict['Msg'] = text
+ dict['Hour'] = time.strftime('%k').strip()
+ dict['Min'] = time.strftime('%M')
+ dict['Day'] = time.strftime('%e').strip()
+ dict['Mon'] = str(time.month)
+ dict['Year'] = time.strftime('%Y')
+ dict['count'] = str(len(text))
+ dict['Lang'] = '2'
+
+ return self.post(dict)
+
+ def POST(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None):
+ postdata = "MMObjectType=0&MMObjectID=&" + postdata
+ return HTTPTransport.POST(self, postdata, host, uri, port, proxy_host, proxy_port)
+
+
+class MTSGSMcom(HTTPTransport):
+ url = "http://www.mtsgsm.com/sms/sent.html"
+ referer = "http://www.mtsgsm.com/sms/"
+ method = "GET"
+ ok_match = 'Ваше сообщение отправлено'
+
+ def send(self):
+ dict = {}
+ text = self.calcText()
+ time = self.calcExpiration()
+
+ dict['Posted'] = '1'
+ dict['To'] = self.phone
+ dict['Msg'] = text
+ dict['count'] = str(len(text))
+ dict['SMSHour'] = time.strftime('%k').strip()
+ dict['SMSMinute'] = time.strftime('%M')
+ dict['SMSDay'] = time.strftime('%e').strip()
+ dict['SMSMonth'] = str(time.month - 1)
+ dict['SMSYear'] = time.strftime('%Y')
+
+ return self.post(dict)
+
+
+class BeeOnLine(CP1251HTTPTransport):
+ "Russian provider BeeOnLine.ru"
+
+ method = "POST"
+ url = "http://www.beeonline.ru/portal/comm/send_sms/simple_send_sms.sms"
+ referer = "http://www.beeonline.ru/portal/comm/send_sms/simple_send_sms.sms"
+ crap = 'BOL '
+ ok_match = koi2win('Ваше сообщение отправлено')
+
+ def __init__(self, phone, message,
+ mode="GSM", # mode can be either GSM or DAMPS
+ transliterate=1, # turn transliteration of/off
+ reply_to=''): # send reply to this e-mail
+ Transport.__init__(self, phone, message)
+ if mode not in ('GSM', 'DAMPS'):
+ raise ValueError, "mode (%s) must be either 'GSM' or 'DAMPS'" % mode
+ self.mode = mode
+ self.transliterate = transliterate
+ self.reply_to = reply_to
+
+ def send(self):
+ dict = {}
+ text = self.calcText(smsSize - len(self.crap))
+
+ # hidden
+ #dict['deferto'] = ''
+ #dict['adv_year'] = ''
+ dict['send'] = 'send'
+ dict['destination_number_from'] = 'ordinary' # number, not a BEEpost
+
+ prf = self.phone[:4]
+ if self.mode == "GSM":
+ dict['network_code'] = '3'
+ elif prf == '7095':
+ dict['network_code'] = '2'
+ elif prf == '7901':
+ dict['network_code'] = '1'
+ else:
+ raise RuntimeError, "incorrect combination of mode (%s) and prefix (%s)" % (self.mode, prf)
+
+ dict['phone'] = self.phone[4:]
+ dict['message'] = text
+ dict['mlength'] = str(smsSize - len(self.crap) - len(text))
+
+ if self.mode == "GSM" and not self.transliterate:
+ dict['translit'] = '1' # turn transliteration OFF! :)
+
+ if self.reply_to:
+ dict['send_email'] = '1'
+ dict['reply_addr'] = self.reply_to
+
+ return self.post(dict)
+
+class BeeOnLineSMS(BeeOnLine):
+ "Russian provider BeeOnLine.ru"
+
+ url = "http://www.beeonline.ru/servlet/send/sms/"
+
+ def send(self):
+ dict = {}
+ text = self.calcText(smsSize - len(self.crap))
+
+ dict['prf'] = self.phone[:4]
+ dict['phone'] = self.phone[4:]
+ dict['termtype'] = self.mode[0]
+ dict['message'] = text
+
+ if self.mode == "GSM" and not self.transliterate:
+ dict['translit'] = '1' # turn transliteration OFF! :)
+
+ dict['number_sms'] = "number_sms_send"
+ return self.post(dict)
+
+
+class Pcom(HTTPTransport):
+ "Russian provider PCOM.ru"
+
+ method = "POST"
+ url = "http://www.pcom.ru/online.phtml"
+ referer = "http://www.pcom.ru/online.phtml"
+ crap = ''
+ ok_match = koi2win('поставлено в очередь')
+
+ def calcText(self, maxsize=smsSize):
+ "force translitertaion"
+
+ return HTTPTransport.calcText(self, maxsize=maxsize,
+ transFunction=rus2lat)
+
+ def send(self):
+ dict = {}
+ text = self.calcText(120 - len(self.crap))
+ expiration = self.calcExpiration()
+ from mx import DateTime
+ now = DateTime.now()
+
+ dict['ACTION'] = 'SENDSMS'
+
+ dict['SMS_START_HOUR'] = now.strftime('%H')
+ dict['SMS_START_MINUTE'] = now.strftime('%M')
+ dict['SMS_START_DAY'] = now.strftime('%d')
+ dict['SMS_START_MONTH'] = now.strftime('%m')
+ dict['SMS_START_YEAR'] = now.strftime('%Y')
+
+ dict['SMS_STOP_HOUR'] = expiration.strftime('%H')
+ dict['SMS_STOP_MINUTE'] = expiration.strftime('%M')
+ dict['SMS_STOP_DAY'] = expiration.strftime('%d')
+ dict['SMS_STOP_MONTH'] = expiration.strftime('%m')
+ dict['SMS_STOP_YEAR'] = expiration.strftime('%Y')
+
+ dict['prefix'] = self.phone[1:4]
+ dict['DN'] = self.phone[4:]
+ dict['MSG'] = text
+
+ return self.post(dict)
+
+
+class ExtelGsmCom(SNPPTransport):
+ "Russian provider Extel-GSM.com"
+
+ host = 'www.extel-gsm.com'
+ port = 4444
+ ok_match = '250 Message Sent Successfully'
+ crap = ''
+
+ prefix = '0119'
+
+ def calcText(self, maxsize=smsSize):
+ "force translitertaion"
+
+ return SNPPTransport.calcText(self, maxsize=maxsize,
+ transFunction=rus2lat)
+
+ def send(self):
+ dict = {}
+ text = self.calcText(smsSize - len(self.crap))
+ phone = self.phone[5:]
+
+ # 0112 (city code) must be replaced with (0119)
+ dict['phone'] = '+7%s%s' % (self.prefix, phone)
+ dict['message'] = text
+
+ return self.post(dict)
+
+
+class MegafoneMoscow(CP1251HTTPTransport):
+ "Rissian provider megafonmoscow.ru"
+
+ method = "POST"
+ url = "http://www.megafonmoscow.ru/rus/sms.xpml"
+ referer = "http://www.megafonmoscow.ru/rus/sms.xpml"
+ ok_match = koi2win('Сообщение успешно отправлено')
+
+ def send(self):
+ dict = {}
+ text = self.calcText()
+
+ dict['prefix'] = self.phone[:4]
+ dict['addr'] = self.phone[4:]
+ dict['message'] = text
+ dict['send'] = " Send "
+
+ if hasattr(self, 'transliterate') and self.transliterate:
+ dict['transliterate'] = '1'
+
+ return self.post(dict)
+
+
+class SkyLinkMsk(CP1251HTTPTransport):
+ "Russian provider SkyLink.Msk.ru"
+
+ method = "POST"
+ url = "http://skylink.msk.ru/inc/sendsms.php"
+ ok_match = koi2win('Сообщение было отправлено')
+
+ def send(self):
+ dict = {}
+ text = self.calcText()
+
+ phone = self.phone
+ if phone[0] == '7':
+ phone = '8' + phone[1:]
+ dict['dest'] = phone
+ dict['smsbody'] = text
+
+ return self.post(dict)
+
+
+BEELINE = BeeOnLineSMS
+#BEELINE = Whizdiary
+MTS = MTSru
+#MTS = MTSGSMcom
+#MTS = Whizdiary
+SONET = Pcom
+EXTEL = ExtelGsmCom
+Megafone = MegafoneMoscow
+SkyLink = SkyLinkMsk
+
+
+Prefix2Provider = {
+ "903": BEELINE,
+ "905": BEELINE,
+ "906": BEELINE,
+
+ "910": MTS,
+ "916": MTS,
+
+ "926": Megafone
+}
+
+
+class SMSError(Exception): pass
+
+def Phone2Provider(phone):
+ prefix = phone[1:4]
+
+ if prefix in ("095", "901"): # 901 is being used by Beeline and SkyLink
+ raise SMSError, "unknown provider for phone %s" % phone
+
+ if Prefix2Provider.has_key(prefix):
+ return Prefix2Provider[prefix]
+
+ raise SMSError, "bad prefix for phone %s" % phone