yowsup-cli 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. #!/usr/bin/env python
  2. __version__ = "2.0.15"
  3. __author__ = "Tarek Galal"
  4. import sys, argparse, yowsup, logging
  5. from yowsup.env import YowsupEnv
  6. HELP_CONFIG = """
  7. ############# Yowsup Configuration Sample ###########
  8. #
  9. # ====================
  10. # The file contains info about your WhatsApp account. This is used during registration and login.
  11. # You can define or override all fields in the command line args as well.
  12. #
  13. # Country code. See http://www.ipipi.com/help/telephone-country-codes.htm. This is now required.
  14. cc=49
  15. #
  16. # Your full phone number including the country code you defined in 'cc', without preceding '+' or '00'
  17. phone=491234567890
  18. #
  19. # You obtain this password when you register using Yowsup.
  20. password=NDkxNTIyNTI1NjAyMkBzLndoYXRzYXBwLm5ldA==
  21. #######################################################
  22. """
  23. CR_TEXT = """yowsup-cli v{cliVersion}
  24. yowsup v{yowsupVersion}
  25. Copyright (c) 2012-2016 Tarek Galal
  26. http://www.openwhatsapp.org
  27. This software is provided free of charge. Copying and redistribution is
  28. encouraged.
  29. If you appreciate this software and you would like to support future
  30. development please consider donating:
  31. http://openwhatsapp.org/yowsup/donate
  32. """
  33. logger = logging.getLogger("yowsup-cli")
  34. class YowArgParser(argparse.ArgumentParser):
  35. def __init__(self, *args, **kwargs):
  36. super(YowArgParser, self).__init__(*args, **kwargs)
  37. self.add_argument("-v", "--version",
  38. action = "store_true",
  39. help = "Print version info and exit"
  40. )
  41. self.add_argument("-d", "--debug",
  42. action = "store_true",
  43. help = "Show debug messages"
  44. )
  45. self.add_argument("--help-config", help = "Prints a config file sample", action = "store_true")
  46. self.args = {}
  47. def getArgs(self):
  48. return self.parse_args()
  49. def getConfig(self, config):
  50. try:
  51. f = open(config)
  52. out = {}
  53. for l in f:
  54. line = l.strip()
  55. if len(line) and line[0] not in ('#',';'):
  56. prep = line.split('#', 1)[0].split(';', 1)[0].split('=', 1)
  57. varname = prep[0].strip()
  58. val = prep[1].strip()
  59. out[varname.replace('-', '_')] = val
  60. return out
  61. except IOError:
  62. print("Invalid config path: %s" % config)
  63. sys.exit(1)
  64. def process(self):
  65. self.args = vars(self.parse_args())
  66. if self.args["debug"]:
  67. logging.basicConfig(level = logging.DEBUG)
  68. else:
  69. logging.basicConfig(level = logging.INFO)
  70. if self.args["version"]:
  71. print("yowsup-cli v%s\nUsing yowsup v%s" % (__version__, yowsup.__version__))
  72. sys.exit(0)
  73. if self.args["help_config"]:
  74. print(HELP_CONFIG)
  75. sys.exit(0)
  76. def printInfoText(self):
  77. print(CR_TEXT.format(cliVersion=__version__, yowsupVersion=yowsup.__version__))
  78. class RegistrationArgParser(YowArgParser):
  79. def __init__(self, *args, **kwargs):
  80. super(RegistrationArgParser, self).__init__(*args, **kwargs)
  81. self.description = "WhatsApp Registration options"
  82. configGroup = self.add_argument_group("Configuration options",
  83. description = "Config file is optional. Other configuration arguments have higher priority if given, and will override those specified in the config file")
  84. configGroup.add_argument("-c", '--config',
  85. action = "store",
  86. help = 'Path to config file. If this is not set then you must set at least --phone and --cc arguments')
  87. configGroup.add_argument('-E', '--env',
  88. action = "store",
  89. help = "Set the environment yowsup simulates",
  90. choices = YowsupEnv.getRegisteredEnvs())
  91. configGroup.add_argument("-m", '--mcc',
  92. action = "store",
  93. help = "Mobile Country Code. Check your mcc here: https://en.wikipedia.org/wiki/Mobile_country_code")
  94. configGroup.add_argument("-n", '--mnc',
  95. action = "store",
  96. help = "Mobile Network Code. Check your mnc from https://en.wikipedia.org/wiki/Mobile_country_code")
  97. # configGroup.add_argument("-M", '--sim-mcc',
  98. # action = "store",
  99. # help = "Mobile Country Code. Check your mcc here: https://en.wikipedia.org/wiki/Mobile_country_code"
  100. # )
  101. # configGroup.add_argument("-N", '--sim-mnc',
  102. # action= "store",
  103. # help = "SIM MNC"
  104. # )
  105. configGroup.add_argument("-p", '--phone',
  106. action= "store",
  107. help = " Your full phone number including the country code you defined in 'cc', without preceeding '+' or '00'")
  108. configGroup.add_argument("-C", '--cc',
  109. action = "store",
  110. help = "Country code. See http://www.ipipi.com/networkList.do. This is now required")
  111. # configGroup.add_argument("-i", '--id',
  112. # action="store",
  113. # help = "Identity"
  114. # )
  115. regSteps = self.add_argument_group("Modes")
  116. regSteps = regSteps.add_mutually_exclusive_group()
  117. regSteps.add_argument("-r", '--requestcode', help='Request the digit registration code from Whatsapp.', action="store", required=False, metavar="(sms|voice)")
  118. regSteps.add_argument("-R", '--register', help='Register account on Whatsapp using the code you previously received', action="store", required=False, metavar="code")
  119. def process(self):
  120. super(RegistrationArgParser, self).process()
  121. config = self.getConfig(self.args["config"]) if self.args["config"] else {}
  122. if self.args["env"]:
  123. YowsupEnv.setEnv(self.args["env"])
  124. if self.args["mcc"] : config["mcc"] = self.args["mcc"]
  125. if self.args["mnc"] : config["mnc"] = self.args["mnc"]
  126. if self.args["phone"] : config["phone"] = self.args["phone"]
  127. if self.args["cc" ] : config["cc"] = self.args["cc"]
  128. #if self.args["sim_mnc"] : config["sim_mnc"] = self.args["sim_mnc"]
  129. #if self.args["sim_mcc"] : config["sim_mcc"] = self.args["sim_mcc"]
  130. if not "mcc" in config: config["mcc"] = "000"
  131. if not "mnc" in config: config["mnc"] = "000"
  132. if not "sim_mcc" in config: config["sim_mcc"] = "000"
  133. if not "sim_mnc" in config: config["sim_mnc"] = "000"
  134. try:
  135. assert self.args["requestcode"] or self.args["register"], "Must specify one of the modes -r/-R"
  136. assert "cc" in config, "Must specify cc (country code)"
  137. assert "phone" in config, "Must specify phone number"
  138. except AssertionError as e:
  139. print(e)
  140. print("\n")
  141. return False
  142. if not config["phone"].startswith(config["cc"]):
  143. print("Error, phone number does not start with the specified country code\n")
  144. return False
  145. config["phone"] = config["phone"][len(config["cc"]):]
  146. if self.args["requestcode"]:
  147. self.handleRequestCode(self.args["requestcode"], config)
  148. elif self.args["register"]:
  149. self.handleRegister(self.args["register"], config)
  150. else:
  151. return False
  152. return True
  153. def handleRequestCode(self, method, config):
  154. from yowsup.registration import WACodeRequest
  155. self.printInfoText()
  156. codeReq = WACodeRequest(config["cc"],
  157. config["phone"],
  158. config["mcc"],
  159. config["mnc"],
  160. config["mcc"],
  161. config["mnc"],
  162. method
  163. )
  164. result = codeReq.send()
  165. print(self.resultToString(result))
  166. def handleRegister(self, code, config):
  167. from yowsup.registration import WARegRequest
  168. self.printInfoText()
  169. code = code.replace('-', '')
  170. req = WARegRequest(config["cc"], config["phone"], code)
  171. result = req.send()
  172. print(self.resultToString(result))
  173. def resultToString(self, result):
  174. unistr = str if sys.version_info >= (3, 0) else unicode
  175. out = []
  176. for k, v in result.items():
  177. if v is None:
  178. continue
  179. out.append("%s: %s" %(k, v.encode("utf-8") if type(v) is unistr else v))
  180. return "\n".join(out)
  181. class DemosArgParser(YowArgParser):
  182. def __init__(self, *args, **kwargs):
  183. super(DemosArgParser, self).__init__(*args, **kwargs)
  184. self.description = "Run a yowsup demo"
  185. configGroup = self.add_argument_group("Configuration options for demos")
  186. credentialsOpts = configGroup.add_mutually_exclusive_group()
  187. credentialsOpts.add_argument("-l", "--login", action="store", metavar="phone:b64password",
  188. help = "WhatsApp login credentials, in the format phonenumber:password, where password is base64 encoded.")
  189. credentialsOpts.add_argument("-c", "--config", action="store",
  190. help = "Path to config file containing authentication info. For more info about config format use --help-config")
  191. configGroup.add_argument('-E', '--env',
  192. action = "store",
  193. help = "Set the environment yowsup simulates",
  194. choices = YowsupEnv.getRegisteredEnvs())
  195. configGroup.add_argument("-M", "--unmoxie", action="store_true", help="Disable E2E Encryption")
  196. cmdopts = self.add_argument_group("Command line interface demo")
  197. cmdopts.add_argument('-y', '--yowsup', action = "store_true", help = "Start the Yowsup command line client")
  198. echoOpts = self.add_argument_group("Echo client demo")
  199. echoOpts.add_argument('-e', '--echo', action = "store_true", help = "Start the Yowsup Echo client")
  200. sendOpts = self.add_argument_group("Send client demo")
  201. sendOpts.add_argument('-s', '--send', action="store", help = "Send a message to specified phone number, "
  202. "wait for server receipt and exit",
  203. metavar=("phone", "message"), nargs = 2)
  204. syncContacts = self.add_argument_group("Sync contacts")
  205. syncContacts.add_argument('-S','--sync', action = "store" , help = "Sync ( check valid ) whatsapp contacts",metavar =("contacts"))
  206. def process(self):
  207. super(DemosArgParser, self).process()
  208. if self.args["env"]:
  209. YowsupEnv.setEnv(self.args["env"])
  210. if self.args["yowsup"]:
  211. self.startCmdline()
  212. elif self.args["echo"]:
  213. self.startEcho()
  214. elif self.args["send"]:
  215. self.startSendClient()
  216. elif self.args["sync"]:
  217. self.startSyncContacts()
  218. else:
  219. return False
  220. return True
  221. def _getCredentials(self):
  222. if self.args["login"]:
  223. return tuple(self.args["login"].split(":"))
  224. elif self.args["config"]:
  225. config = self.getConfig(self.args["config"])
  226. assert "password" in config and "phone" in config, "Must specify at least phone number and password in config file"
  227. return config["phone"], config["password"]
  228. else:
  229. return None
  230. def startCmdline(self):
  231. from yowsup.demos import cli
  232. credentials = self._getCredentials()
  233. if not credentials:
  234. print("Error: You must specify a configuration method")
  235. sys.exit(1)
  236. self.printInfoText()
  237. stack = cli.YowsupCliStack(credentials, not self.args["unmoxie"])
  238. stack.start()
  239. def startEcho(self):
  240. from yowsup.demos import echoclient
  241. credentials = self._getCredentials()
  242. if not credentials:
  243. print("Error: You must specify a configuration method")
  244. sys.exit(1)
  245. try:
  246. self.printInfoText()
  247. stack = echoclient.YowsupEchoStack(credentials, not self.args["unmoxie"])
  248. stack.start()
  249. except KeyboardInterrupt:
  250. print("\nYowsdown")
  251. sys.exit(0)
  252. def startSendClient(self):
  253. from yowsup.demos import sendclient
  254. credentials = self._getCredentials()
  255. if not credentials:
  256. print("Error: You must specify a configuration method")
  257. sys.exit(1)
  258. try:
  259. self.printInfoText()
  260. stack = sendclient.YowsupSendStack(credentials, [([self.args["send"][0], self.args["send"][1]])],
  261. not self.args["unmoxie"])
  262. stack.start()
  263. except KeyboardInterrupt:
  264. print("\nYowsdown")
  265. sys.exit(0)
  266. def startSyncContacts(self):
  267. from yowsup.demos import contacts
  268. credentials = self._getCredentials()
  269. if not credentials:
  270. print("Error: You must specify a configuration method")
  271. sys.exit(1)
  272. try:
  273. self.printInfoText()
  274. stack = contacts.YowsupSyncStack(credentials,self.args["sync"].split(','), not self.args["unmoxie"])
  275. stack.start()
  276. except KeyboardInterrupt:
  277. print("\nYowsdown")
  278. sys.exit(0)
  279. if __name__ == "__main__":
  280. args = sys.argv
  281. if(len(args) > 1):
  282. del args[0]
  283. modeDict = {
  284. "demos": DemosArgParser,
  285. "registration": RegistrationArgParser,
  286. "version": None
  287. }
  288. if(len(args) == 0 or args[0] not in modeDict):
  289. print("Available commands:\n===================")
  290. print(", ".join(modeDict.keys()))
  291. sys.exit(1)
  292. mode = args[0]
  293. if mode == "version":
  294. print("yowsup-cli v%s\nUsing yowsup v%s" % (__version__, yowsup.__version__))
  295. sys.exit(0)
  296. else:
  297. parser = modeDict[mode]()
  298. if not parser.process():
  299. parser.print_help()