├── config.example ├── LICENSE.md ├── README.md ├── Discovery.py ├── ConnectionUtils.py └── client.py /config.example: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | secret = 3 | consumer_key = 4 | app_id = 5 | app_name = My Test Client 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | Copyright 2014, Yahoo 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Yahoo Connected TV’s Device Communication model enables new and exciting TV-viewing experiences driven by the sophisticated controls available on today’s tablets, laptops, and mobile phones. A new generation of TV App capabilities is now available to consumers, including gesture-based, multi-display, and multi-user features for intensive gaming, multimedia, and social applications. 3 | 4 | This Device Communication platform supports two-way message-passing between Internet-enabled devices and connected TVs running the Yahoo Connected TV Platform. Keyboard, navigation, and app-specific messages can be communicated through a new protocol over a local network. 5 | 6 | The Yahoo Connected TV Device Communication Tools repository provides miscellaneous tools and utilities for development on Yahoo Connected TV's Device Communication platform. 7 | 8 | # Documentation 9 | 10 | You can find the Device Communication Java Library at https://github.com/yahoo/ctv-devicecommunication-java 11 | 12 | You can find Device Communication documentation at https://developer.yahoo.com/connectedtv/devicecommunication 13 | 14 | You can find more information about Yahoo Connected TV at https://smarttv.yahoo.com and more developer documentation at https://developer.yahoo.com/connectedtv 15 | 16 | # License 17 | The Yahoo Connected TV Device Communication Tools repository is licensed under the following BSD License. 18 | 19 | Software License Agreement (BSD License) 20 | Copyright (c) 2014, Yahoo. All rights reserved. 21 | 22 | Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 23 | 24 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 25 | 26 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 27 | 28 | * Neither the name of Yahoo. nor the names of Yahoo Connected TV's contributors may be used to endorse or promote products derived from this software without specific prior written permission of Yahoo. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Discovery.py: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | # Copyright (c) 2014, Yahoo. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use of this software in source and binary forms, 6 | # with or without modification, are permitted provided that the following 7 | # conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above 10 | # copyright notice, this list of conditions and the 11 | # following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above 14 | # copyright notice, this list of conditions and the 15 | # following disclaimer in the documentation and/or other 16 | # materials provided with the distribution. 17 | # 18 | # * Neither the name of Yahoo. nor the names of its 19 | # contributors may be used to endorse or promote products 20 | # derived from this software without specific prior 21 | # written permission of Yahoo. 22 | # 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | ########################################################################### 35 | 36 | import select, time, socket 37 | 38 | PYBONJOUR_AVAILABLE = True 39 | try: 40 | import pybonjour 41 | except: 42 | print("pybonjour not installed, no auto discovery available") 43 | PYBONJOUR_AVAILABLE = False 44 | 45 | regtype = "_yctvwidgets._tcp" 46 | timeout = 5 47 | resolved_addrs = [] 48 | 49 | def resolve_callback(sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord): 50 | if errorCode == pybonjour.kDNSServiceErr_NoError: 51 | global resolved_addrs 52 | ip = socket.gethostbyname(hosttarget) 53 | if ip not in [entry.get("host") for entry in resolved_addrs]: 54 | print("resolved %s:%d" % (ip, port)) 55 | resolved_addrs.append({"sdRef":sdRef, "hostname": hosttarget, "host": ip, "port": port}) 56 | 57 | def browse_callback(sdRef, flags, interfaceIndex, errorCode, serviceName, 58 | regtype, replyDomain): 59 | if errorCode != pybonjour.kDNSServiceErr_NoError: 60 | return 61 | 62 | if not (flags & pybonjour.kDNSServiceFlagsAdd): 63 | print 'Service removed' 64 | return 65 | 66 | print 'DC Service found; resolving' 67 | global resolved_addrs 68 | resolve_sdRef = pybonjour.DNSServiceResolve(0, 69 | interfaceIndex, 70 | serviceName, 71 | regtype, 72 | replyDomain, 73 | resolve_callback) 74 | 75 | try: 76 | while True: 77 | ready = select.select([resolve_sdRef], [], [], timeout) 78 | if resolve_sdRef not in ready[0]: 79 | break 80 | pybonjour.DNSServiceProcessResult(resolve_sdRef) 81 | finally: 82 | resolve_sdRef.close() 83 | 84 | def discover(timeout=10): 85 | timeout = float(timeout) 86 | browse_sdRef = pybonjour.DNSServiceBrowse(regtype = regtype, callBack = browse_callback) 87 | cTime = time.time() 88 | try: 89 | try: 90 | while len(resolved_addrs) == 0 and time.time()-cTime < timeout: 91 | ready = select.select([browse_sdRef], [], [], timeout/2) 92 | if browse_sdRef in ready[0]: 93 | pybonjour.DNSServiceProcessResult(browse_sdRef) 94 | except KeyboardInterrupt: 95 | pass 96 | finally: 97 | browse_sdRef.close() 98 | 99 | if len(resolved_addrs) > 0: 100 | if len(resolved_addrs) == 1: 101 | print("Found 1 matching Service, connecting to %s:%d" % (resolved_addrs[0].get("host"), resolved_addrs[0].get("port"))) 102 | return resolved_addrs[0].get("host"), resolved_addrs[0].get("port") 103 | else: 104 | print("Found Services:") 105 | print("#\tHost:Port") 106 | for i in range(0, len(resolved_addrs)): 107 | print("%d:\t%s:%d" % (i, resolved_addrs[i].get("host"), resolved_addrs[i].get("port"))) 108 | user_option = input("Please choose service # to connect:") 109 | return resolved_addrs[user_option].get("host"), resolved_addrs[user_option].get("port") 110 | else: 111 | return None, None 112 | 113 | if __name__ == "__main__": 114 | print discover() 115 | -------------------------------------------------------------------------------- /ConnectionUtils.py: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | # Copyright (c) 2014, Yahoo. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use of this software in source and binary forms, 6 | # with or without modification, are permitted provided that the following 7 | # conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above 10 | # copyright notice, this list of conditions and the 11 | # following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above 14 | # copyright notice, this list of conditions and the 15 | # following disclaimer in the documentation and/or other 16 | # materials provided with the distribution. 17 | # 18 | # * Neither the name of Yahoo. nor the names of its 19 | # contributors may be used to endorse or promote products 20 | # derived from this software without specific prior 21 | # written permission of Yahoo. 22 | # 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | ########################################################################### 35 | 36 | import sys, os, threading, uuid, readline, errno 37 | import ssl, socket, select, asyncore, asynchat 38 | import hmac, hashlib, time 39 | from urllib import urlencode 40 | 41 | class async_chat_ssl(asynchat.async_chat): 42 | """ Asynchronous connection with SSL support. """ 43 | 44 | def __init__(self, addr): 45 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 46 | self.ssl = ssl.wrap_socket(sock) 47 | 48 | #print "connecting to ", addr 49 | self.ssl.connect(addr) 50 | 51 | asynchat.async_chat.__init__(self, sock=self.ssl) 52 | 53 | self.send = self._ssl_send 54 | self.recv = self._ssl_recv 55 | 56 | def _ssl_send(self, data): 57 | """ Replacement for self.send() during SSL connections. """ 58 | try: 59 | result = self.write(data) 60 | return result 61 | except ssl.SSLError, why: 62 | if why[0] in (asyncore.EWOULDBLOCK, errno.ESRCH): 63 | return 0 64 | else: 65 | raise ssl.SSLError, why 66 | return 0 67 | 68 | def _ssl_recv(self, buffer_size): 69 | """ Replacement for self.recv() during SSL connections. """ 70 | try: 71 | data = self.read(buffer_size) 72 | if not data: 73 | self.handle_close() 74 | return '' 75 | return data 76 | except ssl.SSLError, why: 77 | if why[0] in (asyncore.ECONNRESET, asyncore.ENOTCONN, 78 | asyncore.ESHUTDOWN): 79 | self.handle_close() 80 | return '' 81 | elif why[0] == errno.ENOENT: 82 | # Required in order to keep it non-blocking 83 | return '' 84 | else: 85 | raise 86 | 87 | class ConnectionHandler(async_chat_ssl): 88 | def __init__(self, addr, queue=None, onMessageRecieved=None, onMessageRecievedContext=None): 89 | async_chat_ssl.__init__(self, addr) 90 | self.ibuffer = [] 91 | self.set_terminator("|END") 92 | self.onMessageReceived = onMessageRecieved 93 | self.onMessageRecievedContext = onMessageRecievedContext 94 | self.queue = queue 95 | #print("Handler initialized") 96 | 97 | def returnMessage(self, msg): 98 | if self.queue != None: 99 | self.queue.put(msg) 100 | else: 101 | method = self.onMessageReceived 102 | if self.onMessageRecievedContext != None: 103 | context = self.onMessageRecievedContext 104 | context.method(msg) 105 | else: 106 | method(msg) 107 | 108 | def collect_incoming_data(self, data): 109 | self.ibuffer.append(data) 110 | 111 | def found_terminator(self): 112 | response = "".join(self.ibuffer) 113 | self.ibuffer = [] 114 | self.returnMessage(response) 115 | 116 | def createSession(connection, app_id, consumer_key, secret, app_name): 117 | keyopts = { 118 | 'app_id': app_id, 119 | 'consumer_key': consumer_key, 120 | 'secret': hmac.new(secret, consumer_key, hashlib.sha1).hexdigest() 121 | } 122 | 123 | createCommand = "SESSION|CREATE|%s|%s|END" %(urlencode(keyopts), app_name) 124 | print "SENDING:", createCommand 125 | connection.handler.push(createCommand) 126 | 127 | def resetSession(connection, instanceID): 128 | resetCommand = "SESSION|RESET|%s|END" % instanceID 129 | print "SENDING:", resetCommand 130 | connection.handler.push(resetCommand) 131 | 132 | def authSession(connection, code): 133 | cert = str(ssl.DER_cert_to_PEM_cert(connection.handler.socket.getpeercert(True))).rstrip() 134 | 135 | #Some machines already have the \n.. I can't even...wha?? 136 | if '\n-----END CERTIFICATE-----' not in cert: 137 | cert = cert.replace('-----END CERTIFICATE-----', '\n-----END CERTIFICATE-----') 138 | 139 | signature = hmac.new(code, cert, hashlib.sha1).hexdigest() 140 | authCommand = "SESSION|AUTH|%s|END" % signature 141 | 142 | print "SENDING:", authCommand 143 | connection.handler.push(authCommand) 144 | 145 | class Connection(): 146 | def __init__(self, host, port, queue=None, onMessageRecieved=None, onMessageRecievedContext=None): 147 | self.buffer = [] 148 | self.handler = ConnectionHandler((host, port), queue, onMessageRecieved, onMessageRecievedContext) 149 | 150 | def startLoop(self): 151 | asyncore.loop(1) 152 | print "Connection Closed" 153 | 154 | def close(self): 155 | self.handler.close_when_done() 156 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ########################################################################### 4 | # Copyright (c) 2014, Yahoo. 5 | # All rights reserved. 6 | # 7 | # Redistribution and use of this software in source and binary forms, 8 | # with or without modification, are permitted provided that the following 9 | # conditions are met: 10 | # 11 | # * Redistributions of source code must retain the above 12 | # copyright notice, this list of conditions and the 13 | # following disclaimer. 14 | # 15 | # * Redistributions in binary form must reproduce the above 16 | # copyright notice, this list of conditions and the 17 | # following disclaimer in the documentation and/or other 18 | # materials provided with the distribution. 19 | # 20 | # * Neither the name of Yahoo. nor the names of its 21 | # contributors may be used to endorse or promote products 22 | # derived from this software without specific prior 23 | # written permission of Yahoo. 24 | # 25 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 26 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 28 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 31 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | ########################################################################### 37 | 38 | import sys, ConfigParser 39 | try: 40 | import argparse 41 | except: 42 | print("'argparse' python library not found.\n If using Ubuntu, run 'sudo apt-get install python-argparse'") 43 | sys.exit() 44 | from Discovery import * 45 | from ConnectionUtils import * 46 | 47 | DEFAULT_APP_ID = "0xeTgF3c" 48 | DEFAULT_CONSUMER_KEY = "dj0yJmk9T1Y0MmVIWWEzWVc3JmQ9WVdrOU1IaGxWR2RHTTJNbWNHbzlNVEUzTkRFM09ERTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD0yNA--" 49 | DEFAULT_SECRET = "1b8f0feb4d8d468676293caa769e19958bf36843" 50 | DEFAULT_APP_NAME = "Test Client (client.py)" 51 | 52 | IS_RUNNING=False 53 | 54 | class ReceiverThread(threading.Thread): 55 | def __init__(self, connection): 56 | threading.Thread.__init__(self) 57 | self.connection=connection 58 | self.isClosing=False 59 | 60 | def run(self): 61 | while not self.isClosing: 62 | cmnd = getUserInput("SEND: ") 63 | if cmnd == "" or cmnd == -1 : continue 64 | if cmnd == "q": break 65 | 66 | self.connection.handler.push(cmnd) 67 | self.connection.handler.close_when_done() 68 | 69 | userInputState = {"prompt":"", "isWaiting":False} 70 | def printMessage(msg): 71 | if(userInputState["isWaiting"]): 72 | print "\n",msg 73 | sys.stdout.write(userInputState["prompt"]) 74 | else: 75 | print msg 76 | 77 | def getUserInput(prompt): 78 | userInputState["isWaiting"] = True 79 | userInputState["prompt"] = prompt 80 | data = raw_input(prompt) 81 | userInputState["isWaiting"] = False 82 | return data 83 | 84 | def api(args): 85 | def onMessageRecieved(msg): 86 | print "RCVD:", msg 87 | 88 | client = Connection(args.host, args.port, onMessageRecieved=onMessageRecieved) 89 | 90 | if args.instanceId: 91 | resetSession(client, args.instanceId) 92 | elif args.manual_auth == False: 93 | createSession(client, args.app_id, args.consumer_key, args.secret, args.app_name) 94 | 95 | authSession(client, raw_input("Please enter code:")) 96 | 97 | inputReader = ReceiverThread(client) 98 | inputReader.start() 99 | 100 | client.startLoop(); 101 | inputReader.isClosing=True 102 | 103 | def setupReadlineHistory(historyFile): 104 | try: 105 | readline.read_history_file(historyFile) 106 | readline.parse_and_bind("set set editing-mode vi") 107 | readline.parse_and_bind("set horizontal-scroll-mode On") 108 | except IOError, e: 109 | print(e) 110 | pass 111 | import atexit 112 | atexit.register(readline.write_history_file, historyFile) 113 | 114 | def parse_args(): 115 | parser = argparse.ArgumentParser(description='Connect to a Device Communication-enabled TV and send messages') 116 | parser.add_argument('host', nargs='?', help='hostname or IP to connect to, omit for automatic search') 117 | parser.add_argument('port', type=int, nargs='?', default=8099, help='port of device, defaults to 8099') 118 | parser.add_argument('-m', '--manual-auth', action='store_true', help='do not prompt for code, just connect') 119 | parser.add_argument('-i', '--instanceId', help='use an instanceID to connect, will override --manual-auth') 120 | parser.add_argument('-y', '--history', default=os.path.join(os.environ["HOME"], ".client.py.hist"), help='use non-default history file') 121 | parser.add_argument('-c', '--config', default=os.path.join(os.environ["HOME"], ".client.py.config"), help='configuration file that stores authorization keys, leave blank to use default non-production keys. See config.sample for configuration file example. Default location: %s' % os.path.join(os.environ["HOME"], ".client.py.config")) 122 | return parser.parse_args() 123 | 124 | def load_config(args): 125 | config = ConfigParser.RawConfigParser({"app_id": DEFAULT_APP_ID, "consumer_key":DEFAULT_CONSUMER_KEY, "secret": DEFAULT_SECRET, "app_name":DEFAULT_APP_NAME}) 126 | configsRead = config.read(args.config) 127 | 128 | if configsRead is None: 129 | print("WARNING: Using default auth keys. Note these can only be used in a simulator environment. See --help for more information." % args.config) 130 | elif args.config not in configsRead: 131 | print("Unable to load config file %s, using default auth keys. Note these can only be used in a simulator environment." % args.config) 132 | 133 | args.app_id = config.get("DEFAULT", "app_id") 134 | args.consumer_key = config.get("DEFAULT", "consumer_key") 135 | args.secret = config.get("DEFAULT", "secret") 136 | args.app_name = config.get("DEFAULT", "app_name") 137 | 138 | 139 | 140 | def main(): 141 | args = parse_args() 142 | load_config(args) 143 | 144 | if not args.host and PYBONJOUR_AVAILABLE: 145 | print("Starting automatic discovery... For manual usage, see the -h option") 146 | args.host, args.port = discover() 147 | if args.host is None or args.port is None: 148 | print("Unable to automatically resolve host and port. For manual usage, see the -h option") 149 | elif not args.host and not PYBONJOUR_AVAILABLE: 150 | print("Automatic search not available, please install pybonjour") 151 | 152 | if args.host != None and args.port != None: 153 | setupReadlineHistory(args.history) 154 | 155 | api(args) 156 | 157 | if __name__ == "__main__": 158 | main() 159 | --------------------------------------------------------------------------------