Forum Archive

Send SMS from Pythonista

fcrespo82222

Source: https://gist.github.com/4162427
Simple script to open the SMS compose screen (it plays well with Launch Center Pro)

Use it as a custom URL action:

pythonista://simplesms?action=run&args='tel={tel}&body={msg}'
Where {tel}: Phone to send SMS
{body}: Message to send

Warning: As the iOS doesn't allow to send SMS directly, the script will open the compose screen

Warning 2: iOS doesn't support the message to be passed by url scheme only programmaticaly, so when the SMS screen is opeoned you will have to paste the clipboard contents to the SMS itself.

Hey developers, it is possible for a future version include some python binding for the objective-c code below to make it possible prepopulate the text os the SMS

MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = self;

picker.recipients = [NSArray arrayWithObject:@"48151623"];
picker.body = @"Body text.";

[self presentModalViewController:picker animated:YES];
[picker release];

omz

I'm working on something that's kind of related to this, but I'm afraid that's all I can tell you right now. ;)

fcrespo82222

I'm happy with your answer. Seeing that the app is in constant development is awesome. Could you give a time frame for the next version? Just "a few days" or "couple of months" will suffice.

fcrespo82222

I've made a lot of changes.

Now you must run the program with args:

usage: simplesms.py generate_url [-h] telephone message

positional arguments:
telephone phone number to send sms
message message to send

usage: simplesms.py run [-h] pythonista_args

positional arguments:
pythonista_args pythonista args part, spaces replaced with + then quoted

frakman1

How do I do this now in the year 2020? The link to simplesms.py doesn't work.

mikael

@frakman1, this is how far I quickly got, but frustratingly I do not seem to get the sending UI dismissed, so it is unusable right now.


import objc_util
import ui


objc_util.load_framework('MessageUI')


MFMessageComposeViewController = objc_util.ObjCClass(
    'MFMessageComposeViewController')
SUIViewController = objc_util.ObjCClass('SUIViewController')


v = ui.View()
b = ui.Button(
    title='Send SMS',
    background_color='#0e45cd',
    tint_color='white',
    flex='TBLR',
)
b.frame = b.frame.inset(-8,-8)
b.center = v.bounds.center()


RESULTS = ('Cancelled', 'Sent', 'Failed')

def messageComposeViewController_didFinishWithResult_(
    _self, _cmd, _controller, _result):
    """
    Required to be able to dismiss the controller.
    """
    controller = objc_util.ObjCInstance(_controller)
    #result = objc_util.ObjCInstance(_result)
    #print(RESULTS[result])

    controller.dismissModalViewControllerAnimated_completion_(True, None)


MessageSenderDelegate = objc_util.create_objc_class(
    'MessageSenderDelegate',
    methods=[
        messageComposeViewController_didFinishWithResult_,
    ],
    protocols=['MFMessageComposeViewControllerDelegate']
)


def send_sms(sender):
    composer = MFMessageComposeViewController.alloc().init()
    composer.setRecipients_(['12345678'])
    composer.setBody_('Message content')

    delegate = MessageSenderDelegate.alloc().init()
    composer.setDelegate_(delegate)

    vc = SUIViewController.viewControllerForView_(
        sender.superview.objc_instance)
    vc.presentModalViewController_animated_(composer, True)

b.action = send_sms

v.add_subview(b)
v.present('fullscreen')

cvp

@mikael perhaps I forgot something but I get

Traceback (most recent call last):
  File "/private/var/mobile/Containers/Shared/AppGroup/1B829014-77B3-4446-9B65-034BDDC46F49/Pythonista3/Documents/a.py", line 49, in send_sms
    composer.setRecipients_(['12345678'])
AttributeError: 'NoneType' object has no attribute 'setRecipients_'
cvp

@mikael sorry, I'm on an iPad, no sms

mikael

@cvp, thanks for trying.

mikael

@cvp, hmm, but shouldn’t iPads have iMessage, even if no SMS?

Here’s a version that checks for availability:


import objc_util
import ui


objc_util.load_framework('MessageUI')


MFMessageComposeViewController = objc_util.ObjCClass(
    'MFMessageComposeViewController')
SUIViewController = objc_util.ObjCClass('SUIViewController')


v = ui.View()
b = ui.Button(
    tint_color='white',
    flex='TBLR',
)

if MFMessageComposeViewController.canSendText():
    b.title = 'Send Message'
    b.background_color = '#0e45cd'
else:
    b.title = 'No messaging available'
    b.background_color = '#c35757'
    b.enabled = False

b.size_to_fit()
b.frame = b.frame.inset(-8,-8)
b.center = v.bounds.center()

RESULTS = ('Cancelled', 'Sent', 'Failed')

def messageComposeViewController_didFinishWithResult_(
    _self, _cmd, _controller, _result):
    """
    Required to be able to dismiss the controller.
    """
    controller = objc_util.ObjCInstance(_controller)
    #result = objc_util.ObjCInstance(_result)
    #print(RESULTS[result])

    controller.dismissModalViewControllerAnimated_completion_(True, None)


MessageSenderDelegate = objc_util.create_objc_class(
    'MessageSenderDelegate',
    methods=[
        messageComposeViewController_didFinishWithResult_,
    ],
    protocols=['MFMessageComposeViewControllerDelegate']
)


def send_sms(sender):
    composer = MFMessageComposeViewController.alloc().init()
    composer.setRecipients_(['12345678'])
    composer.setBody_('Message content')

    delegate = MessageSenderDelegate.alloc().init()
    composer.setDelegate_(delegate)

    vc = SUIViewController.viewControllerForView_(
        sender.superview.objc_instance)
    vc.presentModalViewController_animated_(composer, True)

b.action = send_sms

v.add_subview(b)
v.present('fullscreen')

mikael

Very annoying that I seem to remember we have done this before, but can’t find the post.

cvp

@mikael said:

hmm, but shouldn’t iPads have iMessage, even if no SMS?

You're right but iMessage is/was off on my iPad...

mikael

Via printing to file I think I have confirmed that the delegate method is not reached at all, but cannot see why.

mikael

Ok, you need to set the messageComposeDelegate instead of delegate, which is for the inherited UINavigationViewController. Will post a working version later today.

mikael

@frakman1, here’s a reusable version, with usage sample in the end. Note the option to provide a callback if you want control the closing of the message composer.


import objc_util


objc_util.load_framework('MessageUI')


MFMessageComposeViewController = objc_util.ObjCClass(
    'MFMessageComposeViewController')
SUIViewController = objc_util.ObjCClass('SUIViewController')


CANCEL, SEND, FAIL = range(3)

def default_callback(result):
    if result == FAIL:
        return True

def messageComposeViewController_didFinishWithResult_(
    _self, _cmd, _controller, _result):
    """
    Required to be able to dismiss the controller.
    """
    controller = objc_util.ObjCInstance(_controller)

    # 0 is returned as None
    result = _result or 0

    # Stay in the composer if callback returns True
    if controller.callback and controller.callback(result):
        return
    else:
        controller.dismissViewControllerAnimated_completion_(True, None)


MessageSenderDelegate = objc_util.create_objc_class(
    'MessageSenderDelegate',
    methods=[
        messageComposeViewController_didFinishWithResult_,
    ],
    protocols=['MFMessageComposeViewControllerDelegate']
)


def send_message(
    superview, to='', body='',
    callback=None
):
    """
    Open a message composer with the
    recipient and message already filled.
    Optional callback gets the result of the
    sending.
    Composer stays open if callback returns True.
    By default, composer stays open if sending fails.
    """
    composer = MFMessageComposeViewController.alloc().init()
    composer.setRecipients_(['12345678'])
    composer.setBody_('Message content')

    composer.callback = callback or default_callback

    delegate = MessageSenderDelegate.alloc().init()
    composer.messageComposeDelegate = delegate

    vc = SUIViewController.viewControllerForView_(
        superview.objc_instance)
    vc.presentModalViewController_animated_(composer, True)


if __name__ == '__main__':

    import ui

    v = ui.View()
    b = ui.Button(
        tint_color='white',
        flex='TBLR',
    )

    if MFMessageComposeViewController.canSendText():
        b.title = 'Send Message'
        b.background_color = '#0e45cd'
    else:
        b.title = 'No messaging available'
        b.background_color = '#c35757'
        b.enabled = False

    b.size_to_fit()
    b.frame = b.frame.inset(-8,-8)
    b.center = v.bounds.center()

    def send_message_action(sender):
        send_message(sender.superview, to='1234566', body='Message')

    b.action = send_message_action

    v.add_subview(b)
    v.present('fullscreen')

cvp

@mikael this app (Pythonista) and you are both amazing