Skip to content

Instantly share code, notes, and snippets.

@rcoup
Last active September 13, 2023 20:22
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rcoup/93460ea39b05e957e884 to your computer and use it in GitHub Desktop.
Save rcoup/93460ea39b05e957e884 to your computer and use it in GitHub Desktop.
Gammu config and script for receiving SMS via a USB modem attached to an OSX computer and forwarding it into iMessage, where it will appear on all your devices.
[gammu]
port = /dev/tty.HUAWEIMobile-Modem
connection = at19200
model = at
synchronizetime = yes
logfile = /Users/me/.gammu/log
logformat = errorsdate
gammucoding = utf8
[smsd]
Service = files
CheckSecurity = 0
LogFile = /Users/me/Library/logs/gammu.log
InboxPath = /Users/me/.gammu/inbox/
OutboxPath = /Users/me/.gammu/outbox/
SentSMSPath = /Users/me/.gammu/sent/
ErrorSMSPath = /Users/me/.gammu/error/
DebugLevel = 1
RunOnReceive = python /Users/me/.gammu/forwardToIMessage.py +447700900000 NZ
CheckBattery = 0

Idea (swap out the countries & numbers):

  • You live in the UK, with a UK mobile number and an iPhone (eg. +447700900000)
  • And have a New Zealand sim card and number (eg. +6421000000), in a USB modem attached to your Mac (permanently roaming)
  • When somebody SMSs your New Zealand number, make it appear in iMessage on your UK phone
  1. Get a USB modem that works on OSX. I use an ancient Huawei E220 I found on eBay. Check it's unlocked for your SIM and can send/receive texts using the vendor's Mac software.
  2. Install Gammu via git or homebrew.
  3. Put forwardToIMessage.py into ~/.gammu, and the .gammurc file into ~. Edit the paths in the files.
  4. Manually start the daemon with gammu-smsd -c .gammurc --pid .gammu/pid, then look at ~Library/logs/gammu.log and ~/Library/logs/gammu-forwardToIMessage.log for debugging help. Send yourself some SMS messages to test.
  5. Once it's working, to start it on boot (so it will pick up messages whenever you connect/disconnect the USB dongle), update the paths in the .plist file and drop it into ~/Library/LaunchAgents, then:
    launchctl load -w ~/Library/LaunchAgents/gammu.IMessageForwarder.plist`
    launchctl start gammu.IMessageForwarder
    

Tips:

  • SMSing an iMessage number via iMessage won't actually send an SMS. Using Skype if you need to test SMS is pretty cheap and easy.
  • If you get AppleScript errors, have a go at sending to your iMessage email address rather than your iMessage number. (change RunOnReceive in ~/.gammurc)
  • Has been working under Sierra, El Capitan, and Mavericks.
#!/usr/bin/env python
from __future__ import print_function
import logging
import os
import re
import subprocess
import sys
USAGE = "USAGE: %s IMESSAGE_RECIPIENT COUNTRY_ISO2 [MESSAGE_FILE ...]"
TEMPLATE = u"%(country)s%(number)s: %(message)s"
APPLESCRIPT = """
on run {targetBuddyPhone, targetMessage}
tell application "Messages"
set targetService to 1st service whose service type = iMessage
set targetBuddy to buddy targetBuddyPhone of targetService
send targetMessage to targetBuddy
end tell
end run
"""
LOG = os.path.expanduser('~/Library/logs/gammu-forwardToIMessage.log')
L = logging.getLogger("forwardToIMessage")
class SMSError(Exception):
pass
class IMessageError(Exception):
pass
def flag(code):
# http://schinckel.net/2015/10/29/unicode-flags-in-python/
OFFSET = 127397
if not code:
return u''
points = [ord(x) + OFFSET for x in code.upper()]
try:
return chr(points[0]) + chr(points[1])
except ValueError:
return ('\\U%08x\\U%08x' % tuple(points)).decode('unicode-escape')
def get_message(files):
if not files:
# get from environment
number = os.environ['SMS_1_NUMBER']
# Are there any decoded parts?
numparts = int(os.environ['DECODED_PARTS'])
if numparts:
# Get all text parts
text = ''
for i in range(0, numparts):
varname = 'DECODED_%d_TEXT' % i
if varname in os.environ:
text = text + os.environ[varname]
else:
text = os.environ['SMS_1_TEXT']
else:
files.sort() # make sure we get the parts in the right order
number = re.match(r'^IN\d+_\d+_\d+_(\+?\d+)_\d+\.txt', os.path.split(files[0])[1]).group(1)
L.debug('Files: parsed sending number as: %s', number)
text = ''
for f in files:
L.debug('Files: parsing: %s', f)
text += open(f, 'r').read()
try:
text = text.decode('UTF-8', 'strict')
except UnicodeDecodeError:
L.exception("Error decoding message as utf-8, falling back to '?' replacement: [%s]", repr(text))
text = text.decode('UTF-8', 'replace')
return number, text
def send_imessage(recipient, text):
args = ['osascript', '-', recipient, text.encode('utf-8')]
L.debug("Invoking AppleScript: %s", args)
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, _ = p.communicate(APPLESCRIPT)
L.debug("AppleScript results: %d: %s", p.returncode, stdout)
if p.returncode:
raise IMessageError("Error %d sending (%r %r): %s" % (p.returncode, recipient, text, stdout))
return stdout
def main():
logging.basicConfig(
filename=os.path.join(os.path.split(__file__)[0], LOG),
filemode="a",
format='%(asctime)-15s %(levelname)s %(funcName)s:%(lineno)d %(message)s',
level=logging.DEBUG
)
L.info("Starting: args=%s", sys.argv[1:])
L.debug("Relevant environment=%s", repr(dict([(k, v) for k, v in os.environ.iteritems() if k.startswith(("SMS_", "DECODED_"))])))
if len(sys.argv) < 3:
print >>sys.stderr, USAGE % sys.argv[0]
sys.exit(2)
if 'SMS_1_NUMBER' in os.environ:
# parse from environment (default)
L.info("Getting message info from environment...")
msg_files = None
else:
# parse from message files
L.info("No data found in environment, parsing from message files...")
msg_files = [os.path.join(os.path.split(__file__)[0], 'inbox', m) for m in sys.argv[3:]]
if not len(msg_files):
print >>sys.stderr, "No message found in environment, and no message paths specified"
print >>sys.stderr, USAGE % sys.argv[0]
sys.exit(2)
L.info("Message file paths: %s", msg_files)
recipient = sys.argv[1]
country = flag(sys.argv[2]) or sys.argv[2].upper()
try:
number, text = get_message(msg_files)
L.info("From %s: %s", number, repr(text))
message = TEMPLATE % {'number': number, 'message': text, 'country': country}
send_imessage(recipient, message)
except:
L.exception("Error processing message")
raise
L.info("Done :)")
if __name__ == "__main__":
main()
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd >
<plist version="1.0">
<dict>
<key>Label</key>
<string>gammu.IMessageForwarder</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/gammu-smsd</string>
<string>-c</string>
<string>/Users/me/.gammurc</string>
<string>--pid</string>
<string>/Users/me/.gammu/pid</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>/Users/me</string>
</dict>
</plist>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment