# HG changeset patch # User Sandro Knauß # Date 1329591036 -3600 # Node ID adfe245c377d170f8c14b44b002659f38c87e2bf # Parent 5619596a00531407d2597aa4f466474c31ffb433 adding smstrade as provider diff -r 5619596a0053 -r adfe245c377d iro/anbieter/smstrade.py --- a/iro/anbieter/smstrade.py Sat Feb 18 19:48:54 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Georg Bischoff - -#This program is free software; you can redistribute it and/or modify it under the terms -#of the GNU General Public License as published by the Free Software Foundation; -#either version 3 of the License, or any later version. -#This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -#See the GNU General Public License for more details. - -#You should have received a copy of the GNU General Public License -#along with this program; if not, see . - - -from anbieter import anbieter -from sipgate import NoValidStatusCode -from telnumber import telnumber, InvalidTel -import ConfigParser -import urllib, httplib -from httplib import socket - -import logging -logger=logging.getLogger("smstrade") - -class UnknownStatusCode(Exception): - def __init__(self,code): - self.code=code - - def __str__(self): - return "StatusCode %i is unknown"%self.code - - -class StatusCode: - statusCodes = {10 : "Empfaengernummer nicht korrekt", - 20 : "Absenderkennung nicht korrekt", - 30 : "Nachrichtentext nicht korrekt", - 31 : "Messagetyp nicht korrekt", - 40 : "SMS Route nicht korrekt", - 50 : "Identifikation fehlgeschlagen", - 60 : "nicht genuegend Guthaben", - 70 : "Netz wird von Route nicht abgedeckt", - 71 : "Feature nicht ueber diese Route moeglich", - 80 : "Uebergabe an SMS-C fehlgeschlagen", - 90 : "Versand nicht moeglich", - 100 : "SMS wurde versendet", - 999 : "SMS wird zeitversetzt verschickt"} - - def __init__(self,code): - if code in self.statusCodes.keys(): - self.code=code - else: - raise UnknownStatusCode(code) - - def __str__(self): - try: - return self.statusCodes[self.code] - except IndexError: - raise UnknownStatusCode(self.code) - - def __int__(self): - if not self.code in self.statusCodes.keys(): - raise UnknownStatusCode(self.code) - return self.code - - - - -class smstrade(anbieter): - """ - s. auch http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf - """ - section="smstrade" - url="https://gateway.smstrade.de" - def __init__(self): - self.domain = "smstrade.de" # website of the sms service - self.gateway = "gateway.smstrade.de" # gateway where the request will be sent - self.gatewayPort = 80 # port of the gateway - self.script = "/" # full path to the script that will handle the request - self.method = "POST" # method that will be used. Currently only POST is supported - - def read_basic_config(self, filenames): - """Read basic options from the config file""" - cp = ConfigParser.ConfigParser() - cp.read(filenames) - self.key=cp.get(self.section, 'key') - self.route=cp.get(self.section, 'route') - self.from_=cp.get(self.section, 'from') - self.debug=cp.get(self.section, 'debug') - - def sendSMS(self,sms,recipients): - """send SMS with $sms to $recipients""" - logger.debug('smstrade.sendSMS(%s,%s)'%(sms, str(recipients))) - sended = [] - route = unicode(self.route) - message = sms.content - timestamp = None - for recipient in recipients: - try: - tel = telnumber(recipient) - if tel in sended: #only send message once per recipient - continue - sended.append(tel) - to ='00'+tel.land+tel.number - if tel.land == '49': - route=unicode("basic") - else: - route=unicode("economy") - smsSendStatus = self.__send(route, to, message, timestamp) - logger.info('smstrade._send(...)=%i(%s)'%(int(smsSendStatus),str(smsSendStatus))) - if int(smsSendStatus) in(100, 999): - self.updateStatus(arranged=recipient) - else: - self.updateStatus(failed=recipient) - except (InvalidTel,NoValidStatusCode,InternetConnectionError): - self.updateStatus(failed=recipient) - - def __send(self, route, to, message, timestamp=None): - """ This function is the main part of the request to the sms service. - The function has to return a unicode formated string that will represent the answer of the sms service - to the request.""" - logger.debug('smstrade._send(%s,%s,%s,%s)'%( route, to, message, timestamp)) - parameters= {"key": self.key, - "route": route, - "to": to, - "message": message, - "charset":"utf-8", - "debug": self.debug, - } - - if self.from_ is not None: - parameters["from"] = self.from_ - - if timestamp is not None: - parameters["senddate"] = unicode(timestamp) - - parameters["concat_sms"] = "1" if len(message) > 160 else "0" - params = "&".join( ["%s=%s" % (urllib.quote(k),urllib.quote(v.encode("utf-8"))) for (k, v) in parameters.items()]) - logger.debug('smstrade._send-parameters:%s\n\t->%s'%(str(parameters), str(params)) ) - headers = {"Content-type": "application/x-www-form-urlencoded", - "Accept": "text/plain"} - conn = httplib.HTTPConnection("%s:%i" % (self.gateway, self.gatewayPort)) - try: - conn.request(self.method, self.script, params, headers) - response = conn.getresponse() - data = response.read() - except socket.gaierror: - raise InternetConnectionError("%s:%i" % (self.gateway, self.gatewayPort)) - finally: - conn.close() - - try: - return StatusCode(int(data)) - except UnknownStatusCode: - # this happens if the sms will be send delayed - return StatusCode(999) - - def updateStatus(self, arranged=None, failed=None): - """is a function that is called, if a new SMS/FAX was send - -arranged is non None, if SMS/FAX was sended successfully - -failed is non None, if SMS/FAX sending failed - the content will be the recipent""" - pass - -class InternetConnectionError(Exception): - def __init__(self, url): - self.url = url - - def __str__(self): - return "InternetConnectionError: It is not possible to open 'http://%s'. Please check your connection to the Internet!" % self.url diff -r 5619596a0053 -r adfe245c377d iro/offer/__init__.py --- a/iro/offer/__init__.py Sat Feb 18 19:48:54 2012 +0100 +++ b/iro/offer/__init__.py Sat Feb 18 19:50:36 2012 +0100 @@ -12,3 +12,4 @@ raise NoProvider(typ) from .smtp import SMTP +from .smstrade import Smstrade diff -r 5619596a0053 -r adfe245c377d iro/offer/smstrade.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/smstrade.py Sat Feb 18 19:50:36 2012 +0100 @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +#Copyright (C) 2009 Georg Bischoff + +#This program is free software; you can redistribute it and/or modify it under the terms +#of the GNU General Public License as published by the Free Software Foundation; +#either version 3 of the License, or any later version. +#This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#See the GNU General Public License for more details. + +#You should have received a copy of the GNU General Public License +#along with this program; if not, see . + + +import urllib +import copy +from functools import partial + +from ..error import UnknownOption, NeededOption +from ..model.status import Status +from .provider import Provider +from ..offer import providers + +import logging +logger=logging.getLogger("smstrade") + +class UnknownStatusCode(Exception): + def __init__(self,code): + self.code=code + + def __str__(self): + return "StatusCode %i is unknown"%self.code + + +class StatusCode: + statusCodes = {10 : "Empfaengernummer nicht korrekt", + 20 : "Absenderkennung nicht korrekt", + 30 : "Nachrichtentext nicht korrekt", + 31 : "Messagetyp nicht korrekt", + 40 : "SMS Route nicht korrekt", + 50 : "Identifikation fehlgeschlagen", + 60 : "nicht genuegend Guthaben", + 70 : "Netz wird von Route nicht abgedeckt", + 71 : "Feature nicht ueber diese Route moeglich", + 80 : "Uebergabe an SMS-C fehlgeschlagen", + 90 : "Versand nicht moeglich", + 100 : "SMS wurde versendet", + } + + def __init__(self,code, mID=None, cost=None, count=None): + if code in self.statusCodes.keys(): + self.code=code + else: + raise UnknownStatusCode(code) + self.mID=mID + self.cost = cost + self.count = count + + def __str__(self): + try: + return self.statusCodes[self.code] + except IndexError: + raise UnknownStatusCode(self.code) + + def __int__(self): + if not self.code in self.statusCodes.keys(): + raise UnknownStatusCode(self.code) + return self.code + + + + +class Smstrade(Provider): + """ + s. auch http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf + """ + params= {"debug":("boolean",False), + "concat_sms":('boolean',False), + "message_id":('boolean',False), + "count":('boolean',False), + "cost":('boolean',False), + } + def __init__(self, name, config): + self.url = "https://gateway.smstrade.de" + Provider.__init__(self,name,config,{"sms":["basic","economy","gold","direct"]}) + + def loadConfig(self): + """Read options from config""" + needed=["key"] + + for n in needed: + setattr(self,n,None) + + for (n, v) in self.config: + if n in needed: + setattr(self,n,v) + else: + raise UnknownOption(self.name, n) + + for n in needed: + if getattr(self,n) is None: + raise NeededOption(self.name, n) + + + + def send(self, route, sms, recipient): + """send SMS with $sms to $recipients""" + logger.debug('smstrade.sendSMS(%s,%s)'%(sms, recipient)) + route = unicode(route) + + if recipient.land != '49' and route == "basic": + return Exception() + + to ='00'+recipient.land+recipient.number + try: + smsSendStatus = self.__send(route, to, sms) + logger.info('smstrade._send(...)=%i(%s)'%(int(smsSendStatus),str(smsSendStatus))) + if int(smsSendStatus) in (100,): + return Status(self,route) + else: + raise Exception() + except UnknownStatusCode: + raise Exception() + + def __send(self, route, to, sms): + """ This function is the main part of the request to the sms service. + The function has to return a unicode formated string that will represent the answer of the sms service + to the request.""" + logger.debug('smstrade._send(%s,%s,%s)'%( route, to, sms)) + parameters= {"key": self.key, + "route": route, + "to": to, + "message": sms.content.encode("utf-8"), + "charset":"utf-8", + "debug": self.testmode, + "message_id":True, + "count":True, + "cost":True, + } + + doubleChar="€[]{}|\\^~" #these charactar need two GSM Chars + + if sms.from_ is not None: + parameters["from"] = sms.from_ + + length=len(sms.content) + for s in doubleChar: + length += sms.content.count(s) + parameters["concat_sms"] = True if length > 160 else False + + ps={} + for p in parameters: + if p in self.params.keys(): + if self.params[p][0] == "boolean": + if parameters[p] != self.params[p][1]: + ps[p]=int(not self.params[p][1]) + else: + ps[p] = parameters[p] + + params = urllib.urlencode(ps) + dp=copy.deepcopy(ps) + dp["key"]="" + logger.debug('smstrade._send-parameters:%s\n\t->%s'%(str(dp), urllib.urlencode(dp)) ) + + response = urllib.urlopen(self.url, params) + data = response.readlines() + if len(data) == 1: + return StatusCode(int(data[0])) + return StatusCode(int(data[0]),mID=data[1],cost=data[2],count=data[3]) + + def getSendFunc(self, typ, route): + Provider.getSendFunc(self, typ, route) + return partial(self.send,route) + +providers["smstrade"]=Smstrade diff -r 5619596a0053 -r adfe245c377d tests/smstrade.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/smstrade.py Sat Feb 18 19:50:36 2012 +0100 @@ -0,0 +1,64 @@ +from twisted.trial import unittest + +from mock import patch, Mock + +from iro.error import NoRoute, NoTyp, NeededOption, UnknownOption +from iro.telnumber import Telnumber +from iro.model.message import SMS +from iro.offer import Smstrade + +HOST = "localhost" +PORT = 9999 + +class TestSMStradeProvider(unittest.TestCase): + + def getProvider(self, c=None): + ret={"key":"XXXXXX", + } + + if c: + ret.update(c) + + return Smstrade("test",ret.items()) + + @patch("urllib.urlopen") + def testSendSMS(self,mock_urlopen): + f = Mock() + f.readlines.return_value = ["100","12345678","0.55","1"] + mock_urlopen.return_value = f + + params = ["key=XXXXXX","to=00491701234567", "message=Hello+World", "route=gold", "message_id=1", "cost=1","count=1",'charset=utf-8'] + params.sort() + + p=self.getProvider() + content = "Hello World" + status = p.send("gold",SMS(content,None), Telnumber("01701234567")) + + self.assertEqual(status.status, status.GOOD) + + ca = mock_urlopen.call_args[0] + c=ca[1].split("&") + c.sort() + + self.assertEqual(ca[0],"https://gateway.smstrade.de") + self.assertEqual(c,params) + self.assertEqual(f.readlines.call_count,1) + + + def testNeededOption(self): + c={"key":"XXXXXXXX",} + s=Smstrade("test",c.items()) + self.assertEqual(s.key, "XXXXXXXX") + + self.assertRaises(NeededOption,Smstrade,"test",[]) + + c = {"unknown":""} + self.assertRaises(UnknownOption,Smstrade,"test",c.items()) + + def testSendFunc(self): + s = self.getProvider() + p = s.getSendFunc("sms","basic") + self.assertEqual(p.func, s.send) + self.assertEqual(p.args, ("basic",)) + self.assertRaises(NoRoute,s.getSendFunc,"sms","foo") + self.assertRaises(NoTyp,s.getSendFunc,"mail2","basic")