From a1d83ed5733c5f725a4cd7f7713f04e19c810f14 Mon Sep 17 00:00:00 2001 From: Daniel Dayley Date: Tue, 11 Apr 2023 00:07:04 -0600 Subject: [PATCH] Oops, forgot to commit the work --- bin/bluebubbles_bot | 151 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 bin/bluebubbles_bot diff --git a/bin/bluebubbles_bot b/bin/bluebubbles_bot new file mode 100644 index 0000000..17dde43 --- /dev/null +++ b/bin/bluebubbles_bot @@ -0,0 +1,151 @@ +#!python3 + +import os +import sys +import yaml +import uuid +import logging +import argparse +import persona +from typing import List +from dataclasses import dataclass +import datetime +import requests +from fastapi import FastAPI +import uvicorn + +class LoggingFormatter(logging.Formatter): + def format(self, record): + module_max_width = 30 + datefmt='%Y/%m/%d/ %H:%M:%S' + level = f'[{record.levelname}]'.ljust(9) + if 'log_module' not in dir(record) : + modname = str(record.module)+'.'+str(record.name) + else : + modname = record.log_module + modname = (f'{modname}'[:module_max_width-1] + ']').ljust(module_max_width) + final = "%-7s %s [%s %s" % (self.formatTime(record, self.datefmt), level, modname, record.getMessage()) + return final + +bot = FastAPI() + +if __name__ == '__main__': + + # Command-line client + # Define constants + config_template = {'bluebubbles_bot': {}} + + # Gather Argument options + EXAMPLE_TEXT='Example:\n\tbluebubbles_bot -h' + parser = argparse.ArgumentParser(epilog=EXAMPLE_TEXT,formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('-l', '--log', action='store', help='Specify a file to log to.') + parser.add_argument('-v', '--verbose', action='count', help='Include verbose information in the output. Add \'v\'s for more output.',default=0) + args = parser.parse_args() + + log = logging.getLogger(__name__) + log = logging.LoggerAdapter(log,{'log_module':'bluebubbles_bot'}) + + # Configure logging + log_options = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] + if not args.verbose : + args.verbose = 0 + if args.verbose > 3 : + args.verbose = 3 + if args.log : + logging.basicConfig(level=log_options[args.verbose],filename=args.log) + logging.getLogger().addHandler(logging.StreamHandler(sys.stderr)) + else : + logging.basicConfig(level=log_options[args.verbose]) + logging.getLogger().handlers[0].setFormatter(LoggingFormatter()) + logging.propagate=True + + # Check for missing environment variables + for required_var in ['BB_SERVER_URL','BB_SERVER_PASSWORD'] : + if required_var not in os.environ.keys() : + log.error(f'Missing required ENV variable {required_var}') + exit(1) + + def send_message(message) : + """Send a message to the server, optionally with attachments.""" + # Use the private API instead of applescript + if 'USE_PRIVATE_API' in os.environ.keys() and os.environ['USE_PRIVATE_API'].lower() in ['true', 'yes'] : + method = 'private-api' + else : + method = 'apple-script' + uid = str(uuid.uuid4()).upper() + text = message.text + chat_guid = message.chat_identifier + url = os.environ['BB_SERVER_URL'].rstrip('/') + '/api/v1/message/text' + params = {'password': os.environ['BB_SERVER_PASSWORD']} + effect_id = '' + subject = '' + if 'effectId' in message.meta.keys() : + effect_id = message.meta['effectId'] + if 'subject' in message.meta.keys() : + subject = message.meta['subject'] + payload = { + 'chatGuid': chat_guid, + 'message': text, + 'tempGuid': uid, + 'method': method, + 'effectId': effect_id, + 'subject': subject, + 'selectedMessageGuid': '' + } + if len(message.attachments) > 0 : + payload.update({'name': uid}) + payload.pop('text', None) + attachments = [] + for attachment in message.attachments : + file = {'file': (uid, message.attachments[attachment].data, message.attachments[attachment].mime_type)} + attachments.append(file) + payload.update({'attachments':attachments}) + url = os.environ['BB_SERVER_URL'].rstrip('/') + '/api/v1/message/attachment' + requests.post(url,params=params,json=payload) + # Create persona instance + current_persona = persona.Persona() + + # Create a fastAPI instance + @bot.post('/message') + async def message(content: dict): + # print(content) + if content['type'] == 'new-message' : + message = content['data'] + # Determine sender and receiver + if message['isFromMe'] : + sender = message['handle']['address'] + recipients = [] + else : + sender = None + recipients = [message['handle']['address']] + # Resolve attachments + attachments = [] + for attachment in message['attachments'] : + attachments.append(persona.Attachment(mime_type=message['attachments'][attachment]['mimeType'],data=message['attachments'][attachment]['guid'])) + # Get the date sent + date_sent = datetime.datetime.fromtimestamp(message['dateCreated']/100) + # Get any effects or subjects + subject = '' + effect_id = '' + if 'subject' in message.keys() : + subject = message['subject'] + if 'expressiveSendStyleId' in message.keys() : + effect_id = message['expressiveSendStyleId'] + # Craft the message + persona_message = persona.Message( + text=message['text'], + sender_identifier=sender, + chat_identifier=message['chats'][-1]['guid'], + identifier=message['guid'], + timestamp=date_sent, + recipients=recipients, + attachments=attachments, + meta={'subject': subject, 'effectId': effect_id} + ) + responses = current_persona.receive_message(persona_message) + for response in responses : + prompt = persona_message + send_message(response) + return content + + uvicorn.run(bot,host='0.0.0.0', port=8080)