Skip to content

Instantly share code, notes, and snippets.

@riceo
Forked from zspine/deliver_sm.py
Last active April 29, 2025 22:45
Show Gist options
  • Save riceo/80de777ca9209c86037159a7d6e6f27a to your computer and use it in GitHub Desktop.
Save riceo/80de777ca9209c86037159a7d6e6f27a to your computer and use it in GitHub Desktop.
Fork of zspine/deliver_sm.py, updated to work with changes in Jasmin SMS since the original was created + fixes a rabbitmq bug. This script will take a submit_sm PDU, send an OK back to the originator whilst replaying it to jasmin as a deliver_sm, effectively converting a submit_sm to deliver_sm. Also will send back a DELIVRD DLR!
# V0.75
# Bump the above version when modifying this script / pushing to the CLI -
# it's a nice way to confirm that the right code is running on jasmin.
import pickle
import logging
import uuid
import pika
import time
from datetime import datetime
from jasmin.managers.content import DeliverSmContent
from jasmin.routing.Routables import RoutableDeliverSm
from jasmin.routing.jasminApi import *
from jasmin.protocols.smpp.operations import DeliverSM
from jasmin.managers.content import DLRContentForSmpps
from smpp.pdu.pdu_types import RegisteredDeliveryReceipt, RegisteredDelivery
from smpp.pdu.pdu_types import (EsmClass, EsmClassMode, EsmClassType, EsmClassGsmFeatures,
MoreMessagesToSend, MessageState, AddrTon, AddrNpi)
import multiprocessing
RABBITMQ_URL = 'amqp://guest:guest@rabbitmq:5672/%2F'
logger = logging.getLogger('logging-example')
if len(logger.handlers) != 1:
hdlr = logging.FileHandler('/var/log/jasmin/submit2deliver.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
logger.debug("Received a routable from user %s", routable.user.uid)
submit_sm = routable.pdu
# The [rough] time we received the original submit_sm. Used to send back in the
# DLR
sub_date = datetime.now()
logger.debug("Got submit_sm: %s", submit_sm)
deliver_sm = DeliverSM(
submit_sm.seqNum,
service_type=submit_sm.params['service_type'],
source_addr_ton=submit_sm.params['source_addr_ton'],
source_addr_npi=submit_sm.params['source_addr_npi'],
source_addr=submit_sm.params['source_addr'],
dest_addr_ton=submit_sm.params['dest_addr_ton'],
dest_addr_npi=submit_sm.params['dest_addr_npi'],
destination_addr=submit_sm.params['destination_addr'],
esm_class=submit_sm.params['esm_class'],
protocol_id=submit_sm.params['protocol_id'],
priority_flag=submit_sm.params['priority_flag'],
registered_delivery=submit_sm.params['registered_delivery'],
replace_if_present_flag=submit_sm.params['replace_if_present_flag'],
data_coding=submit_sm.params['data_coding'],
short_message=submit_sm.params['short_message'],
sm_default_msg_id=submit_sm.params['sm_default_msg_id'])
logger.debug("Prepared a new deliver_sm: %s", deliver_sm)
# Generate a new routable deliver_sm PDU for injection
_routable = RoutableDeliverSm(deliver_sm, Connector(routable.user.uid))
content = DeliverSmContent(_routable, routable.user.uid, pickleProtocol=pickle.HIGHEST_PROTOCOL)
routing_key = 'deliver.sm.%s' % routable.user.uid
connection = pika.BlockingConnection(pika.URLParameters(RABBITMQ_URL))
channel = connection.channel()
# Inject the deliver_sm version of the submit_sm into rabbit
logger.debug('RabbitMQ channel ready, publishing now msgid %s ...', content.properties['message-id'])
channel.basic_publish(
'messaging',
routing_key,
content.body,
pika.BasicProperties(
message_id=content.properties['message-id'],
headers=content.properties['headers'])
)
logger.debug('Published deliver_sm to %s', routing_key)
# Setting the following status' will stop Jasmin from trying to route this routable
logger.debug("Returning smpp_status %s http_status %s", smpp_status, http_status)
smpp_status = 0
http_status = 200
extra['message_id'] = str(content.properties['message-id'])
# Start DLR hacking - lifted from protocols/smpp/operations.py #232 getReceipt()
# Also see https://smpp.org/SMPP_v3_4_Issue1_2.pdf, `4.6.1 “DELIVER_SM” Syntax`.
# We are crafting a `deliver_sm` PDU here that meets the above specification for a DLR
# with a `DELIVRD` status
def get_enum(enum_type, value):
# not very pythonic, but imported here as I think there's some
# twisted async magic going on when importing this outside of the function
# resulting in the import not being available here.
from enum import Enum
if isinstance(value, Enum):
return value
_value = value.split('.')
if len(_value) == 2:
return getattr(enum_type, _value[1])
else:
return getattr(enum_type, value)
short_message = r"id:%s sub:%03d dlvrd:%03d submit date:%s done date:%s stat:%s err:%s text: " % (
extra['message_id'],
1, # submitted message parts
1, # delivered message parts
sub_date.strftime("%y%m%d%H%M"),
datetime.now().strftime("%y%m%d%H%M"),
"DELIVRD",
0,
)
dlr_deliver_sm = DeliverSM(
source_addr=submit_sm.params['destination_addr'],
destination_addr=submit_sm.params['source_addr'],
esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.SMSC_DELIVERY_RECEIPT),
receipted_message_id=extra['message_id'],
short_message=short_message,
message_state=MessageState.DELIVERED,
source_addr_ton=get_enum(AddrTon, submit_sm.params['dest_addr_ton']),
source_addr_npi=get_enum(AddrNpi, submit_sm.params['dest_addr_npi']),
dest_addr_ton=get_enum(AddrTon, submit_sm.params['source_addr_ton']),
dest_addr_npi=get_enum(AddrNpi, submit_sm.params['source_addr_npi']),
)
# Prepare for deliver_sm injection
dlr_routable = RoutableDeliverSm(dlr_deliver_sm, Connector(routable.user.uid))
# Use this to build your MORouter filter on.
dlr_routable.addTag(666)
dlr_content = DeliverSmContent(dlr_routable, routable.user.uid, pickleProtocol=pickle.HIGHEST_PROTOCOL)
logger.debug("Prepared a new DLR deliver_sm: %s", dlr_deliver_sm)
### Async this part
def raise_dlr(content):
import time
import pika
time.sleep(20)
RABBITMQ_URL = 'amqp://guest:guest@rabbitmq:5672/%2F'
connection = pika.BlockingConnection(pika.URLParameters(RABBITMQ_URL))
channel = connection.channel()
routing_key = 'deliver.sm.%s' % content[1].user.uid
channel.basic_publish(
'messaging',
routing_key,
content[0].body,
pika.BasicProperties(
message_id=content[0].properties['message-id'],
headers=content[0].properties['headers']))
print("published")
connection.close()
p = multiprocessing.Process(target=raise_dlr, args=([dlr_content, routable],), daemon=True)
p.start()
connection.close()
@mateusleguir
Copy link

mateusleguir commented Aug 30, 2023

Hello, I'm testing the script that you were able to modify and run, but I don't know if I'm doing everything right in the configuration. In this case, I configure it as MTInterceptor and create a tag in MORouter. Do I need to configure anything beyond that or modify something in the script?
In this case, is the mtinterceptor type StaticMTInterceptor with tag filter with order 1, 10 , 100? And how is the order, since jasmin seems to only execute the DefaultInterceptor with order 0?

Best regards

@mrsanvicente
Copy link

Hello @riceo ,
It is working great. The only drawback i can see is that they DLR´s conten is in bytes type and it is not undestand by the platform i am testing with. I have tried to decode but no luck. I can see that is the default behavior for jasmin´s DLR. Any suggestion to change to string.
image
image

@riceo
Copy link
Author

riceo commented Apr 29, 2025

Hi @mrsanvicente,

I’m really glad to hear the code is working well for you! I always want to help, but detailed deployment questions can take quite a bit of time to work through.

If you’d like one-on-one support for your setup, you can book a 30-minute consulting session here: https://cal.com/riceo/30min.

Thanks for understanding, and I look forward to helping however I can!

@mrsanvicente
Copy link

mrsanvicente commented Apr 29, 2025 via email

@mrsanvicente
Copy link

mrsanvicente commented Apr 29, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment