adding smstrade as provider devel
authorSandro Knauß <knauss@netzguerilla.net>
Sat, 18 Feb 2012 19:50:36 +0100
branchdevel
changeset 172 adfe245c377d
parent 171 5619596a0053
child 173 912ef3e070b0
adding smstrade as provider
iro/anbieter/smstrade.py
iro/offer/__init__.py
iro/offer/smstrade.py
tests/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 <http://www.gnu.org/licenses/>.
-
-
-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
--- 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
--- /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 <http://www.gnu.org/licenses/>.
+
+
+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"]="<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
--- /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")