├── .gitignore ├── README.md ├── mixin_api.py ├── mixin_config.default.py ├── mixin_config.py ├── mixin_msg_test.py ├── mixin_ws_api.py ├── test_api.py └── ws_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mixin Python3 SDK 2 | - All methods implement on https://developers.mixin.one/api 3 | - This SDK support Python3.x 4 | - Tutorial Video in https://www.youtube.com/playlist?list=PLMt8rZaHF-0Yj0w6tHeD1vxBA7f7GU-0- 5 | 6 | ## mixin_api.py 7 | - This SDK base on https://github.com/myrual/mixin_client_demo/blob/master/mixin_api.py 8 | - You can see Demo in test_api.py 9 | 10 | ## mixin_ws_api.py 11 | - This SDK base on https://github.com/myrual/mixin_client_demo/blob/master/home_of_cnb_robot.py 12 | - You can see Demo in ws_test.py -------------------------------------------------------------------------------- /mixin_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mixin API for Python 3.x 4 | This SDK base on 'https://github.com/myrual/mixin_client_demo/blob/master/mixin_api.py' 5 | some method note '?', because can't run right result, may be it will be resolved later. 6 | 7 | env: python 3.x 8 | code by lee.c 9 | update at 2018.12.2 10 | """ 11 | 12 | from Crypto.PublicKey import RSA 13 | import base64 14 | from Crypto.Cipher import PKCS1_OAEP 15 | from Crypto.Signature import PKCS1_v1_5 16 | import Crypto 17 | import time 18 | from Crypto import Random 19 | from Crypto.Cipher import AES 20 | import hashlib 21 | import datetime 22 | import jwt 23 | import uuid 24 | import json 25 | import requests 26 | from urllib.parse import urlencode 27 | 28 | 29 | class MIXIN_API: 30 | def __init__(self, mixin_config): 31 | 32 | # robot's config 33 | self.client_id = mixin_config.client_id 34 | self.client_secret = mixin_config.client_secret 35 | self.pay_session_id = mixin_config.pay_session_id 36 | self.pay_pin = mixin_config.pay_pin 37 | self.pin_token = mixin_config.pin_token 38 | self.private_key = mixin_config.private_key 39 | 40 | 41 | self.keyForAES = "" 42 | # mixin api base url 43 | self.api_base_url = 'https://api.mixin.one' 44 | 45 | """ 46 | BASE METHON 47 | """ 48 | 49 | def generateSig(self, method, uri, body): 50 | hashresult = hashlib.sha256((method + uri+body).encode('utf-8')).hexdigest() 51 | return hashresult 52 | 53 | def genGETPOSTSig(self, methodstring, uristring, bodystring): 54 | jwtSig = self.generateSig(methodstring, uristring, bodystring) 55 | 56 | return jwtSig 57 | 58 | 59 | def genGETSig(self, uristring, bodystring): 60 | return self.genGETPOSTSig("GET", uristring, bodystring) 61 | 62 | def genPOSTSig(self, uristring, bodystring): 63 | return self.genGETPOSTSig("POST", uristring, bodystring) 64 | 65 | def genGETJwtToken(self, uristring, bodystring, jti): 66 | jwtSig = self.genGETSig(uristring, bodystring) 67 | iat = datetime.datetime.utcnow() 68 | exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=200) 69 | encoded = jwt.encode({'uid':self.client_id, 'sid':self.pay_session_id,'iat':iat,'exp': exp, 'jti':jti,'sig':jwtSig}, self.private_key, algorithm='RS512') 70 | 71 | return encoded 72 | 73 | def genGETListenSignedToken(self, uristring, bodystring, jti): 74 | jwtSig = self.genGETSig(uristring, bodystring) 75 | iat = datetime.datetime.utcnow() 76 | exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=200) 77 | encoded = jwt.encode({'uid':self.client_id, 'sid':self.pay_session_id,'iat':iat,'exp': exp, 'jti':jti,'sig':jwtSig}, self.private_key, algorithm='RS512') 78 | privKeyObj = RSA.importKey(self.private_key) 79 | signer = PKCS1_v1_5.new(privKeyObj) 80 | signature = signer.sign(encoded) 81 | return signature 82 | 83 | 84 | def genPOSTJwtToken(self, uristring, bodystring, jti): 85 | jwtSig = self.genPOSTSig(uristring, bodystring) 86 | iat = datetime.datetime.utcnow() 87 | exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=200) 88 | encoded = jwt.encode({'uid':self.client_id, 'sid':self.pay_session_id,'iat':iat,'exp': exp, 'jti':jti,'sig':jwtSig}, self.private_key, algorithm='RS512') 89 | return encoded 90 | 91 | def genEncrypedPin(self, iterString = None): 92 | if self.keyForAES == "": 93 | privKeyObj = RSA.importKey(self.private_key) 94 | 95 | decoded_result = base64.b64decode(self.pin_token) 96 | 97 | cipher = PKCS1_OAEP.new(key=privKeyObj, hashAlgo=Crypto.Hash.SHA256, label=self.pay_session_id.encode("utf-8")) 98 | 99 | decrypted_msg = cipher.decrypt(decoded_result) 100 | 101 | self.keyForAES = decrypted_msg 102 | 103 | ts = int(time.time()) 104 | tszero = ts % 0x100 105 | tsone = (ts % 0x10000) >> 8 106 | tstwo = (ts % 0x1000000) >> 16 107 | tsthree = (ts % 0x100000000) >> 24 108 | 109 | 110 | tszero = chr(tszero).encode('latin1').decode('latin1') 111 | tsone = chr(tsone) 112 | tstwo = chr(tstwo) 113 | tsthree = chr(tsthree) 114 | 115 | tsstring = tszero + tsone + tstwo + tsthree + '\0\0\0\0' 116 | if iterString is None: 117 | ts = int(time.time() * 1000000) 118 | tszero = ts % 0x100 119 | tsone = (ts % 0x10000) >> 8 120 | tstwo = (ts % 0x1000000) >> 16 121 | tsthree = (ts % 0x100000000) >> 24 122 | tsfour = (ts % 0x10000000000) >> 32 123 | tsfive = (ts % 0x1000000000000) >> 40 124 | tssix = (ts % 0x100000000000000) >> 48 125 | tsseven = (ts % 0x10000000000000000) >> 56 126 | 127 | tszero = chr(tszero).encode('latin1').decode('latin1') 128 | tsone = chr(tsone) 129 | tstwo = chr(tstwo) 130 | tsthree = chr(tsthree) 131 | tsfour = chr(tsfour) 132 | tsfive= chr(tsfive) 133 | tssix = chr(tssix) 134 | tsseven = chr(tsseven) 135 | iterStringByTS = tszero + tsone + tstwo + tsthree + tsfour + tsfive + tssix + tsseven 136 | 137 | toEncryptContent = self.pay_pin + tsstring + iterStringByTS 138 | else: 139 | toEncryptContent = self.pay_pin + tsstring + iterString 140 | 141 | lenOfToEncryptContent = len(toEncryptContent) 142 | toPadCount = 16 - lenOfToEncryptContent % 16 143 | if toPadCount > 0: 144 | paddedContent = toEncryptContent + chr(toPadCount) * toPadCount 145 | else: 146 | paddedContent = toEncryptContent 147 | 148 | iv = Random.new().read(AES.block_size) 149 | 150 | 151 | cipher = AES.new(self.keyForAES, AES.MODE_CBC,iv) 152 | encrypted_result = cipher.encrypt(paddedContent.encode('latin1')) 153 | 154 | msg = iv + encrypted_result 155 | encrypted_pin = base64.b64encode(msg) 156 | 157 | return encrypted_pin 158 | 159 | """ 160 | COMMON METHON 161 | """ 162 | 163 | """ 164 | generate API url 165 | """ 166 | def __genUrl(self, path): 167 | return self.api_base_url + path 168 | 169 | """ 170 | generate GET http request 171 | """ 172 | def __genGetRequest(self, path, auth_token=""): 173 | 174 | url = self.__genUrl(path) 175 | 176 | if auth_token == "": 177 | r = requests.get(url) 178 | else: 179 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token}) 180 | 181 | result_obj = r.json() 182 | print(result_obj) 183 | return result_obj['data'] 184 | 185 | """ 186 | generate POST http request 187 | """ 188 | def __genPostRequest(self, path, body, auth_token=""): 189 | 190 | # generate url 191 | url = self.__genUrl(path) 192 | 193 | # transfer obj => json string 194 | body_in_json = json.dumps(body) 195 | 196 | if auth_token == "": 197 | r = requests.post(url, json=body_in_json) 198 | else: 199 | r = requests.post(url, json=body_in_json, headers={"Authorization": "Bearer " + auth_token}) 200 | 201 | result_obj = r.json() 202 | print(result_obj) 203 | return result_obj 204 | 205 | """ 206 | generate Mixin Network GET http request 207 | """ 208 | def __genNetworkGetRequest(self, path, body=None, auth_token=""): 209 | 210 | url = self.__genUrl(path) 211 | 212 | if body is not None: 213 | body = urlencode(body) 214 | else: 215 | body = "" 216 | 217 | if auth_token == "": 218 | token = self.genGETJwtToken(path, body, str(uuid.uuid4())) 219 | auth_token = token.decode('utf8') 220 | 221 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token}) 222 | result_obj = r.json() 223 | return result_obj 224 | 225 | 226 | """ 227 | generate Mixin Network POST http request 228 | """ 229 | # TODO: request 230 | def __genNetworkPostRequest(self, path, body, auth_token=""): 231 | 232 | # transfer obj => json string 233 | body_in_json = json.dumps(body) 234 | 235 | # generate robot's auth token 236 | if auth_token == "": 237 | token = self.genPOSTJwtToken(path, body_in_json, str(uuid.uuid4())) 238 | auth_token = token.decode('utf8') 239 | headers = { 240 | 'Content-Type' : 'application/json', 241 | 'Authorization' : 'Bearer ' + auth_token, 242 | } 243 | # generate url 244 | url = self.__genUrl(path) 245 | 246 | r = requests.post(url, json=body, headers=headers) 247 | # {'error': {'status': 202, 'code': 20118, 'description': 'Invalid PIN format.'}} 248 | 249 | # r = requests.post(url, data=body, headers=headers) 250 | # {'error': {'status': 202, 'code': 401, 'description': 'Unauthorized, maybe invalid token.'}} 251 | result_obj = r.json() 252 | print(result_obj) 253 | return result_obj 254 | 255 | """ 256 | ============ 257 | MESSENGER PRIVATE APIs 258 | ============ 259 | auth token need request 'https://api.mixin.one/me' to get. 260 | """ 261 | 262 | 263 | """ 264 | Read user's all assets. 265 | """ 266 | def getMyAssets(self, auth_token=""): 267 | 268 | return self.__genGetRequest('/assets', auth_token) 269 | 270 | """ 271 | Read self profile. 272 | """ 273 | def getMyProfile(self, auth_token): 274 | return self.__genGetRequest('/me', auth_token) 275 | 276 | """ 277 | ? 278 | Update my preferences. 279 | """ 280 | def updateMyPerference(self,receive_message_source="EVERYBODY",accept_conversation_source="EVERYBODY"): 281 | 282 | body = { 283 | "receive_message_source": receive_message_source, 284 | "accept_conversation_source": accept_conversation_source 285 | } 286 | 287 | return self.__genPostRequest('/me/preferences', body) 288 | 289 | 290 | """ 291 | ? 292 | Update my profile. 293 | """ 294 | def updateMyProfile(self, full_name, auth_token, avatar_base64=""): 295 | 296 | body = { 297 | "full_name": full_name, 298 | "avatar_base64": avatar_base64 299 | } 300 | 301 | return self.__genPostRequest('/me', body, auth_token) 302 | 303 | """ 304 | Get users information by IDs. 305 | """ 306 | def getUsersInfo(self, user_ids, auth_token): 307 | return self.__genPostRequest('/users/fetch', user_ids, auth_token) 308 | 309 | """ 310 | Get user's information by ID. 311 | """ 312 | def getUserInfo(self, user_id, auth_token): 313 | return self.__genGetRequest('/users/' + user_id, auth_token) 314 | 315 | """ 316 | Search user by Mixin ID or Phone Number. 317 | """ 318 | def SearchUser(self, q, auth_token=""): 319 | return self.__genGetRequest('/search/' + q, auth_token) 320 | 321 | """ 322 | Rotate user’s code_id. 323 | """ 324 | def rotateUserQR(self, auth_token): 325 | return self.__genGetRequest('/me/code', auth_token) 326 | 327 | """ 328 | Get my friends. 329 | """ 330 | def getMyFriends(self, auth_token): 331 | return self.__genGetRequest('/friends', auth_token) 332 | 333 | """ 334 | Create a GROUP or CONTACT conversation. 335 | """ 336 | def createConv(self, category, conversation_id, participants, action, role, user_id, auth_token): 337 | 338 | body = { 339 | "category": category, 340 | "conversation_id": conversation_id, 341 | "participants": participants, 342 | "action": action, 343 | "role": role, 344 | "user_id": user_id 345 | } 346 | 347 | return self.__genPostRequest('/conversations', body, auth_token) 348 | 349 | """ 350 | Read conversation by conversation_id. 351 | """ 352 | def getConv(self, conversation_id, auth_token): 353 | return self.__genGetRequest('/conversations/' + conversation_id, auth_token) 354 | 355 | 356 | """ 357 | ============ 358 | NETWORK PRIVATE APIs 359 | ============ 360 | auth token need robot related param to generate. 361 | """ 362 | 363 | """ 364 | PIN is used to manage user’s addresses, assets and etc. There’s no default PIN for a Mixin Network user (except APP). 365 | if auth_token is empty, it create robot' pin. 366 | if auth_token is set, it create messenger user pin. 367 | """ 368 | def updatePin(self, new_pin, old_pin, auth_token=""): 369 | old_inside_pay_pin = self.pay_pin 370 | self.pay_pin = new_pin 371 | newEncrypedPin = self.genEncrypedPin() 372 | if old_pin == "": 373 | body = { 374 | "old_pin": "", 375 | "pin": newEncrypedPin.decode() 376 | } 377 | else: 378 | 379 | self.pay_pin = old_pin 380 | oldEncryptedPin = self.genEncrypedPin() 381 | body = { 382 | "old_pin": oldEncryptedPin.decode(), 383 | "pin": newEncrypedPin.decode() 384 | } 385 | self.pay_pin = old_inside_pay_pin 386 | return self.__genNetworkPostRequest('/pin/update', body, auth_token) 387 | 388 | """ 389 | Verify PIN if is valid or not. For example, you can verify PIN before updating it. 390 | if auth_token is empty, it verify robot' pin. 391 | if auth_token is set, it verify messenger user pin. 392 | """ 393 | def verifyPin(self, auth_token=""): 394 | enPin = self.genEncrypedPin() 395 | body = { 396 | "pin": enPin.decode() 397 | } 398 | 399 | return self.__genNetworkPostRequest('/pin/verify', body, auth_token) 400 | 401 | """ 402 | Grant an asset's deposit address, usually it is public_key, but account_name and account_tag is used for EOS. 403 | """ 404 | def deposit(self, asset_id): 405 | return self.__genNetworkGetRequest(' /assets/' + asset_id) 406 | 407 | 408 | """ 409 | withdrawals robot asset to address_id 410 | Tips:Get assets out of Mixin Network, neet to create an address for withdrawal. 411 | """ 412 | def withdrawals(self, address_id, amount, memo, trace_id=""): 413 | encrypted_pin = self.genEncrypedPin() 414 | 415 | if trace_id == "": 416 | trace_id = str(uuid.uuid1()) 417 | 418 | body = { 419 | "address_id": address_id, 420 | "pin": encrypted_pin, 421 | "amount": amount, 422 | "trace_id": trace_id, 423 | "memo": memo 424 | 425 | } 426 | 427 | return self.__genNetworkPostRequest('/withdrawals/', body) 428 | 429 | 430 | """ 431 | Create an address for withdrawal, you can only withdraw through an existent address. 432 | """ 433 | def createAddress(self, asset_id, public_key = "", label = "", account_name = "", account_tag = ""): 434 | 435 | body = { 436 | "asset_id": asset_id, 437 | "pin": self.genEncrypedPin().decode(), 438 | "public_key": public_key, 439 | "label": label, 440 | "account_name": account_name, 441 | "account_tag": account_tag, 442 | } 443 | print(body) 444 | return self.__genNetworkPostRequest('/addresses', body) 445 | 446 | 447 | """ 448 | Delete an address by ID. 449 | """ 450 | def delAddress(self, address_id): 451 | 452 | encrypted_pin = self.genEncrypedPin() 453 | 454 | body = {"pin": encrypted_pin} 455 | 456 | return self.__genNetworkPostRequest('/addresses/' + address_id + '/delete', body) 457 | 458 | 459 | """ 460 | Read an address by ID. 461 | """ 462 | def getAddress(self, address_id): 463 | return self.__genNetworkGetRequest('/addresses' + address_id) 464 | 465 | """ 466 | Transfer of assets between Mixin Network users. 467 | """ 468 | def transferTo(self, to_user_id, to_asset_id, to_asset_amount, memo, trace_uuid=""): 469 | 470 | # generate encrypted pin 471 | encrypted_pin = self.genEncrypedPin() 472 | 473 | body = {'asset_id': to_asset_id, 'counter_user_id': to_user_id, 'amount': str(to_asset_amount), 474 | 'pin': encrypted_pin.decode('utf8'), 'trace_id': trace_uuid, 'memo': memo} 475 | if trace_uuid == "": 476 | body['trace_id'] = str(uuid.uuid1()) 477 | 478 | return self.__genNetworkPostRequest('/transfers', body) 479 | 480 | """ 481 | Read transfer by trace ID. 482 | """ 483 | def getTransfer(self, trace_id): 484 | return self.__genNetworkGetRequest('/transfers/trace/' + trace_id) 485 | 486 | """ 487 | Verify a transfer, payment status if it is 'paid' or 'pending'. 488 | """ 489 | def verifyPayment(self, asset_id, opponent_id, amount, trace_id): 490 | 491 | body = { 492 | "asset_id": asset_id, 493 | "opponent_id": opponent_id, 494 | "amount": amount, 495 | "trace_id": trace_id 496 | } 497 | 498 | return self.__genNetworkPostRequest('/payments', body) 499 | 500 | """ 501 | Read asset by asset ID. 502 | """ 503 | def getAsset(self, asset_id): 504 | return self.__genNetworkGetRequest('/assets/' + asset_id) 505 | 506 | """ 507 | Read external transactions (pending deposits) by public_key and asset_id, use account_tag for EOS. 508 | """ 509 | def extTrans(self, asset_id, public_key, account_tag, account_name, limit, offset): 510 | 511 | body = { 512 | "asset": asset_id, 513 | "public_key": public_key, 514 | "account_tag": account_tag, 515 | "account_name": account_name, 516 | "limit": limit, 517 | "offset": offset 518 | } 519 | 520 | return self.__genNetworkGetRequest('/external/transactions', body) 521 | 522 | 523 | """ 524 | Create a new Mixin Network user (like a normal Mixin Messenger user). You should keep PrivateKey which is used to sign an AuthenticationToken and encrypted PIN for the user. 525 | """ 526 | def createUser(self, session_secret, full_name): 527 | 528 | body = { 529 | "session_secret": session_secret, 530 | "full_name": full_name 531 | } 532 | 533 | return self.__genNetworkPostRequest('/users', body) 534 | 535 | 536 | """ 537 | =========== 538 | NETWORK PUBLIC APIs 539 | =========== 540 | """ 541 | 542 | """ 543 | Read top valuable assets of Mixin Network. 544 | """ 545 | def topAssets(self): 546 | return self.__genGetRequest('/network') 547 | 548 | """ 549 | Read public snapshots of Mixin Network. 550 | """ 551 | def snapshots(self, offset, asset_id, order='DESC',limit=100): 552 | # TODO: SET offset default(UTC TIME) 553 | body = { 554 | "limit":limit, 555 | "offset":offset, 556 | "asset":asset_id, 557 | "order":order 558 | } 559 | 560 | return self.__genGetRequest('/network/snapshots', body) 561 | 562 | 563 | """ 564 | Read public snapshots of Mixin Network by ID. 565 | """ 566 | def snapshot(self, snapshot_id): 567 | return self.__genGetRequest('/network/snapshots/' + snapshot_id) 568 | -------------------------------------------------------------------------------- /mixin_config.default.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mixin Config 4 | get below config from 'https://developers.mixin.one/dashboard' 5 | code by lee.c 6 | update at 2018.12.2 7 | """ 8 | 9 | client_id= '' 10 | client_secret = '' 11 | 12 | 13 | pay_pin = '' 14 | pay_session_id = '' 15 | pin_token = "" 16 | 17 | 18 | private_key = """-----BEGIN RSA PRIVATE KEY----- 19 | -----END RSA PRIVATE KEY-----""" 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mixin_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mixin Config 4 | get below config from 'https://developers.mixin.one/dashboard' 5 | code by lee.c 6 | update at 2018.12.2 7 | """ 8 | 9 | client_id= '23b554a2-34da-4c43-9e74-1dcbcd09804c' 10 | client_secret = 'a05962e74abf21fe543c4f3ed36b6d3521567eb87f992b96d5988587a1f51952' 11 | 12 | 13 | pay_pin = '397046' 14 | pay_session_id = '3fcd444e-5e45-4cfc-89b1-51cf4b3f7659' 15 | pin_token = "V6E0XHEugsG29h70qoEA4f3dDD2gcJfhLtYZDyz2QH5TibNCRWXaikKC5VoRwgGR2IrBvxdW59QvfQkCXfu94j/2FyWhTA6ICErABGhFcxOTnzQbrvFLMJLnJgLJ5GBPCwmK8sIvEnvwstLNpYkKLYpGO6R7v9eXGS7AI2mvAis=" 16 | 17 | 18 | private_key = """-----BEGIN RSA PRIVATE KEY----- 19 | MIICXAIBAAKBgQCU0YUshjp0CtOZ18qBB/lkP+saQWDOsbawCLHvzmisMmI8j7dj 20 | yRmUxhXVwrDQEieFecOw+1AlTWNyT/FKhqEuVApXEyOQ8lrPKD/QLwptw99SM/bX 21 | LHMrHNW3Jmwun5MmjEogJrHi6NMMUovI6w6LV0sn4ZBp1tZeaEjWZGoSwQIDAQAB 22 | AoGAKSTQI+IscP65R9RQSWIyAhRl5IlkwWCCuKJ+x2USrWD0pfe55R2pM+ecC9Ba 23 | 3/vU72MdxmWE3/tIXkdZ15fnIaB3FOo/bYepio/5vUKTwCIsyAAYelW5huhUohfg 24 | NLVWEE+wfrVPFx0THn04q6Y6YdsajFv/xIfS1pMe6aFt1J0CQQDxorL/rbJ5+35Z 25 | i3IeMcGbEYhtiE+eaLeFwsWILReWRpgiiH7O1TIBxUyBT12nwn3U5HAxUwGHJp+3 26 | dvACkQXPAkEAnapLmMLNtk/iQZwltzLq1fthH+5YyR2H1rd3xzc9gF2knOtnTggB 27 | BlTk31WVkORf1MQ6yC+5KYFXGUuuAOASbwJAK2dKN9r/gCHIpFUD/qB5Yl1X4DTn 28 | +FBfBsvhp4BSCFBN64YRIR3yiZbjEycqb4PkDmWqMXHziE9LySy4F/3syQJADo4E 29 | AIwrNWNWfbwOd0UKDMryAmKca6SAP8AcHJXq5Yi/g4TvunJetdjsb/mUnxWWCyw6 30 | SPSu4TgBdGJaI9aLnQJBAJfVYztNcFFhlFexhEbAb27VnP17v7GOEcArjHhEUipD 31 | zxTLBbyjGnIeU+NH2IbnbRcJuq9CMrUcLkhknyyDDV4= 32 | -----END RSA PRIVATE KEY-----""" 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /mixin_msg_test.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, redirect, request 2 | import requests 3 | import json 4 | 5 | import mixin_config 6 | from mixin_api import MIXIN_API 7 | 8 | mixin_api = MIXIN_API(mixin_config) 9 | 10 | 11 | # 启动 Flask 12 | app = Flask(__name__) 13 | 14 | 15 | @app.route('/') 16 | def index(): 17 | # 1. 获得用户的授权 Request Authorization Code 18 | 19 | scope = 'PROFILE:READ+PHONE:READ+CONTACTS:READ+ASSETS:READ' 20 | 21 | get_auth_code_url = 'https://mixin.one/oauth/authorize?client_id='+ mixin_config.client_id+'&scope='+ scope +'&response_type=code' 22 | return redirect(get_auth_code_url) 23 | 24 | 25 | @app.route('/user') 26 | def user(): 27 | 28 | # 2. 取得 Authorization Token 29 | auth_token = get_auth_token() 30 | 31 | 32 | data = mixin_api.getMyProfile(auth_token) 33 | 34 | data_friends = mixin_api.getMyFriends(auth_token) 35 | 36 | data_asset = mixin_api.getMyAssets(auth_token) 37 | 38 | 39 | return '