|
#!/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() |