├── .coverage
├── .gitignore
├── README.rst
├── demo
├── handlers
│ ├── __init__.py
│ └── main.py
├── requirements.txt
├── run.py
├── settings.py
├── urls.py
├── utils.py
└── wechat
├── dist
└── wechat2-1.0.0.0.tar.gz
├── docs
└── index.rst
├── requirements.txt
├── setup.py
├── tests
├── __init__.py
├── settings.py
├── test_core_base.py
├── test_menu_client.py
├── test_message_handler.py
└── test_message_response.py
├── wechat
├── __init__.py
├── base.py
├── base_client.py
├── constants.py
├── core.py
├── menu
│ ├── __init__.py
│ └── client.py
├── message
│ ├── __init__.py
│ ├── hander.py
│ └── response.py
├── settings.py
└── utils.py
└── wechat2.egg-info
├── PKG-INFO
├── SOURCES.txt
├── dependency_links.txt
├── top_level.txt
└── zip-safe
/.coverage:
--------------------------------------------------------------------------------
1 | !coverage.py: This is a private format, don't read it directly!{"lines": {"/Library/Python/2.7/site-packages/requests/adapters.py": [9, 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 35, 36, 38, 39, 41, 44, 48, 67, 68, 69, 71, 72, 73, 74, 75, 76, 78, 80, 81, 82, 84, 86, 90, 97, 107, 108, 109, 111, 112, 114, 124, 126, 129, 132, 133, 135, 138, 139, 144, 151, 160, 163, 166, 169, 170, 171, 173, 174, 179, 182, 183, 185, 187, 195, 196, 198, 209, 211, 213, 221, 234, 235, 236, 238, 241, 243, 245, 257, 259, 285, 296, 298, 299, 300, 302, 304, 307, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 369, 371, 372, 374], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/proxy.py": [30, 163, 164, 165, 168, 169, 170, 43, 45, 46, 47, 176, 177, 178, 57, 58, 59, 60, 62, 64, 78, 80, 81, 82, 83, 102, 103, 104, 110, 111, 112, 116, 117, 118], "/Users/songzhengang/wechat_sdk/wechat/base.py": [3, 6, 9, 11, 12, 14, 15, 18, 19, 20, 22, 23], "/Users/songzhengang/wechat_sdk/wechat/menu/client.py": [3, 6, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/isolate.py": [61, 62], "/Users/songzhengang/wechat_sdk/wechat/utils.py": [], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/base.py": [98, 100, 101, 102], "/Library/Python/2.7/site-packages/simplejson/scanner.py": [32, 48, 2, 3, 4, 5, 6, 7, 44, 10, 12, 14, 15, 16, 18, 57, 133, 30, 68], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/multiprocess.py": [224, 225, 226, 227, 231, 233, 234, 235, 238, 223], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/case.py": [128, 129, 130, 131, 132, 133, 140, 147, 148, 149, 151, 29, 33, 34, 36, 37, 38, 39, 40, 41, 42, 45, 59, 60, 64, 69, 70, 74, 99, 100, 101, 102, 103, 104], "/Users/songzhengang/wechat_sdk/wechat/message/__init__.py": [3, 5, 6, 7, 8, 9], "/Library/Python/2.7/site-packages/requests/certs.py": [18, 23, 21, 13, 15], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/importer.py": [128, 129, 130, 131, 132, 143, 144, 146, 147, 148, 149, 150, 151, 155, 156, 157, 158, 32, 161, 40, 41, 42, 44, 45, 47, 30, 54, 59, 62, 53, 65, 66, 67, 68, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 85, 86, 89, 94, 96, 97, 98, 99, 100, 101, 102, 103, 104, 110, 111, 116, 117, 118, 119, 63, 126, 127], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/capture.py": [64, 96, 98, 69, 102, 97, 58, 59, 101], "/Library/Python/2.7/site-packages/requests/packages/urllib3/util.py": [512, 8, 9, 10, 11, 12, 13, 526, 15, 16, 17, 18, 19, 20, 535, 24, 25, 26, 539, 28, 29, 30, 32, 33, 35, 36, 39, 44, 48, 51, 523, 587, 594, 597, 602, 527, 528, 530, 114, 117, 630, 631, 632, 121, 122, 123, 125, 130, 141, 144, 145, 164, 540, 178, 180, 192, 193, 195, 201, 119, 204, 206, 120, 633, 219, 634, 635, 229, 230, 551, 237, 254, 263, 266, 269, 273, 274, 276, 277, 279, 284, 294, 302, 318, 319, 320, 321, 322, 323, 325, 326, 327, 329, 332, 335, 357, 358, 359, 360, 361, 362, 363, 366, 367, 371, 373, 375, 378, 383, 388, 403, 404, 406, 410, 414, 415, 417, 420, 428, 429, 203, 480], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/deprecated.py": [40, 42, 43, 44], "/Library/Python/2.7/site-packages/xmltodict.py": [2, 4, 5, 6, 7, 8, 14, 15, 22, 23, 26, 27, 31, 32, 33, 36, 37, 40, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 84, 85, 87, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 101, 102, 104, 105, 106, 113, 114, 115, 116, 117, 118, 120, 121, 123, 125, 128, 130, 131, 132, 134, 136, 137, 142, 143, 144, 145, 150, 151, 152, 155, 156, 224, 225, 226, 230, 231, 232, 233, 234, 236, 237, 241, 242, 243, 244, 245, 246, 247, 248, 249, 253, 254, 255, 256, 257, 258, 259, 260, 306, 344], "/Library/Python/2.7/site-packages/simplejson/compat.py": [2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 46], "/Users/songzhengang/wechat_sdk/tests/test_core_base.py": [3, 5, 6, 7, 9, 12, 14, 17, 19, 22, 23, 24, 26, 29, 31, 32, 34], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/pyversion.py": [70, 136, 49, 50, 51, 52, 53, 54, 56, 58], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/testid.py": [137, 138, 142, 143, 144, 145, 148, 149, 150, 151, 154, 155], "/Users/songzhengang/wechat_sdk/wechat/__init__.py": [7, 9, 11, 14], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/prof.py": [71, 74, 75, 76, 80, 81, 82, 83, 84, 57], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/manager.py": [128, 262, 263, 264, 265, 272, 273, 274, 149, 167, 166, 295, 168, 301, 302, 177, 178, 184, 249, 88, 89, 93, 94, 95, 96, 99, 252, 106, 107, 111, 114, 123, 118, 105, 120, 121, 250, 251, 124, 253, 254], "/Library/Python/2.7/site-packages/requests/exceptions.py": [9, 12, 14, 17, 18, 20, 26, 27, 30, 31, 34, 35, 38, 39, 42, 43, 46, 47, 50, 51, 54, 55, 58, 59, 62, 63], "/Library/Python/2.7/site-packages/requests/packages/urllib3/poolmanager.py": [258, 132, 133, 7, 9, 10, 11, 12, 14, 15, 16, 17, 18, 21, 24, 25, 26, 29, 32, 35, 135, 174, 115, 61, 63, 65, 66, 67, 68, 69, 71, 201, 202, 79, 80, 81, 86, 88, 220, 199, 97, 228, 105, 107, 109, 111, 114, 243, 119, 120, 121, 123], "/Library/Python/2.7/site-packages/requests/packages/charade/__init__.py": [51, 18, 19, 34, 22], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/skip.py": [57, 59, 60, 61], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/errorclass.py": [148, 150, 151, 152, 153, 154], "/Users/songzhengang/wechat_sdk/tests/test_menu_client.py": [3, 4, 6, 7, 9, 12, 14, 16, 17, 19, 20, 21, 23, 24, 26, 27, 28, 29, 31, 32, 34, 35, 36, 37, 39, 40, 41, 42, 44, 45, 46, 47, 53, 54, 55, 57, 60, 61, 63, 66, 67], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/xunit.py": [192, 193, 191], "/Library/Python/2.7/site-packages/requests/cookies.py": [258, 277, 406, 7, 9, 10, 11, 13, 14, 143, 16, 145, 274, 403, 148, 21, 333, 407, 408, 282, 265, 31, 33, 34, 163, 36, 165, 38, 295, 41, 44, 174, 47, 50, 53, 215, 56, 313, 59, 190, 63, 320, 66, 395, 326, 69, 198, 73, 77, 206, 269, 82, 35, 87, 89, 412, 94, 223, 96, 97, 67, 99, 276, 103, 231, 110, 111, 368, 241, 114, 123, 116, 117, 120, 404, 122, 251, 124, 127], "/Library/Python/2.7/site-packages/requests/models.py": [514, 519, 8, 521, 10, 11, 12, 14, 15, 16, 18, 19, 20, 21, 22, 23, 537, 26, 541, 30, 545, 34, 35, 37, 40, 41, 45, 559, 49, 50, 53, 566, 55, 56, 57, 58, 571, 60, 573, 62, 575, 576, 71, 72, 73, 75, 76, 77, 591, 594, 596, 85, 89, 528, 530, 622, 626, 628, 629, 633, 636, 533, 641, 644, 646, 149, 150, 153, 156, 158, 159, 161, 679, 173, 690, 691, 692, 695, 195, 197, 198, 199, 200, 713, 202, 203, 204, 205, 208, 209, 210, 211, 212, 214, 727, 218, 219, 220, 221, 222, 223, 224, 225, 587, 227, 553, 230, 247, 264, 266, 215, 268, 270, 272, 274, 276, 278, 279, 282, 283, 284, 285, 286, 287, 292, 294, 297, 306, 308, 309, 310, 312, 315, 316, 324, 326, 329, 333, 334, 339, 340, 342, 343, 347, 570, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 362, 363, 369, 370, 372, 574, 375, 376, 380, 524, 685, 387, 388, 389, 391, 392, 393, 394, 395, 398, 399, 403, 201, 415, 418, 419, 420, 421, 425, 428, 47, 431, 433, 434, 438, 439, 440, 441, 442, 445, 449, 450, 451, 453, 589, 467, 470, 471, 475, 476, 477, 480, 482, 483, 486, 489, 491, 492, 494, 495, 498, 503, 508, 511], "/Library/Python/2.7/site-packages/requests/packages/urllib3/fields.py": [161, 67, 68, 7, 8, 10, 76, 13, 142, 109, 55, 120, 27], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/util.py": [520, 521, 522, 524, 526, 527, 529, 530, 129, 642, 643, 644, 645, 646, 140, 660, 662, 663, 163, 164, 184, 187, 188, 189, 190, 191, 192, 195, 263, 264, 265, 266, 267, 130, 270, 271, 272, 273, 274, 276, 277, 278, 279, 132, 306, 307, 308, 309, 310, 311, 312, 313, 318, 319, 320, 321, 322, 323, 337, 338, 339, 340, 342, 343, 393, 397, 398, 399, 403, 404, 405, 406, 407, 408, 409, 410, 411, 446, 447, 448, 449, 470, 471, 479, 481, 483, 484, 485, 486, 502, 503, 504, 505, 506], "/Library/Python/2.7/site-packages/requests/compat.py": [5, 7, 9, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 58, 60, 61, 62, 65, 68, 71, 72, 73, 74, 76, 77, 85, 86, 87, 88, 89, 90, 91, 92, 93, 95, 96, 97, 98, 99], "/Library/Python/2.7/site-packages/requests/packages/urllib3/connectionpool.py": [7, 8, 10, 11, 524, 13, 14, 15, 16, 17, 530, 20, 540, 541, 31, 32, 33, 38, 39, 40, 553, 48, 50, 52, 54, 55, 56, 569, 571, 572, 62, 575, 576, 577, 66, 579, 580, 69, 582, 583, 584, 585, 586, 75, 588, 589, 78, 592, 83, 85, 598, 599, 600, 601, 602, 603, 604, 605, 607, 618, 620, 624, 625, 626, 628, 633, 634, 635, 639, 640, 641, 643, 644, 645, 647, 136, 138, 139, 141, 142, 143, 144, 145, 147, 151, 152, 154, 156, 157, 159, 160, 163, 164, 167, 168, 170, 186, 198, 199, 200, 213, 217, 219, 550, 233, 234, 235, 248, 250, 253, 254, 469, 260, 276, 278, 280, 281, 282, 285, 292, 295, 301, 305, 308, 311, 312, 313, 650, 341, 342, 343, 344, 345, 347, 363, 574, 380, 381, 382, 578, 68, 71, 73, 449, 587, 452, 455, 456, 76, 459, 462, 464, 466, 590, 470, 471, 477, 480, 481, 482, 483], "/Library/Python/2.7/site-packages/simplejson/encoder.py": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 396, 386, 14, 16, 216, 387, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 214, 33, 35, 36, 37, 39, 389, 41, 391, 392, 388, 219, 393, 56, 394, 395, 390, 204, 205, 206, 397, 208, 248, 210, 211, 212, 213, 86, 215, 88, 217, 207, 220, 222, 224, 353, 226, 228, 209, 360, 362, 371, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 277], "/Library/Python/2.7/site-packages/requests/utils.py": [513, 514, 515, 518, 10, 523, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 27, 28, 29, 31, 33, 35, 38, 556, 47, 48, 49, 530, 566, 572, 522, 574, 575, 66, 69, 70, 71, 73, 74, 78, 79, 527, 102, 529, 109, 531, 132, 145, 148, 151, 152, 154, 158, 189, 547, 223, 247, 261, 273, 559, 288, 560, 294, 561, 296, 299, 301, 304, 308, 326, 334, 368, 369, 370, 373, 377, 378, 392, 395, 404, 407, 410, 525, 414, 415, 417, 582, 430, 436, 439, 441, 443, 444, 458, 459, 460, 465, 466, 467, 470, 471, 472, 473, 474, 478], "/Library/Python/2.7/site-packages/requests/packages/urllib3/exceptions.py": [10, 11, 12, 15, 16, 17, 21, 26, 27, 28, 32, 37, 38, 39, 42, 43, 44, 47, 48, 49, 54, 55, 57, 69, 70, 72, 78, 79, 80, 83, 88, 89, 92, 93, 94, 99, 100, 101, 104, 105, 106, 109, 110, 111, 114, 115, 117], "/Library/Python/2.7/site-packages/requests/packages/urllib3/packages/__init__.py": [1, 3], "/Users/songzhengang/wechat_sdk/tests/test_message_handler.py": [3, 5, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 34, 36, 39, 41, 44, 46, 49, 51, 54, 58, 61, 63, 76, 77, 78, 80, 94, 95, 96, 98, 112, 113, 114, 116, 130, 131, 132, 134, 148, 149, 150, 152, 168, 169, 170, 172, 187, 188, 189, 191, 203, 204, 205, 207, 219, 220, 221, 223, 237, 238, 239, 241, 256, 257, 258, 260, 273, 274, 275], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/result.py": [38, 39, 40, 41, 106, 43, 44, 109, 110, 104, 105, 103], "/Library/Python/2.7/site-packages/simplejson/__init__.py": [385, 386, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 276, 150, 151, 152, 153, 154, 155, 389, 390, 497, 391, 556, 498, 499, 521, 500, 273, 447, 448, 449, 275, 501, 271, 272, 99, 100, 101, 103, 104, 105, 108, 274, 110, 112, 113, 114, 115, 116, 117, 118, 122, 124], "/Users/songzhengang/wechat_sdk/wechat/constants.py": [4, 5, 6, 7], "/Users/songzhengang/wechat_sdk/tests/__init__.py": [1], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/loader.py": [512, 513, 514, 515, 516, 517, 519, 522, 523, 540, 541, 542, 547, 550, 551, 559, 564, 566, 567, 568, 569, 570, 572, 79, 81, 82, 83, 84, 85, 86, 89, 90, 91, 92, 93, 94, 95, 97, 99, 105, 108, 109, 110, 112, 113, 114, 116, 119, 121, 122, 123, 128, 131, 134, 135, 143, 144, 145, 146, 147, 149, 150, 151, 154, 155, 156, 157, 158, 159, 160, 161, 163, 164, 167, 169, 170, 177, 178, 179, 180, 181, 182, 183, 186, 187, 190, 191, 196, 197, 200, 201, 209, 210, 212, 314, 315, 316, 317, 321, 322, 323, 325, 326, 327, 328, 330, 331, 332, 333, 338, 340, 341, 343, 344, 345, 346, 347, 348, 349, 350, 353, 354, 356, 359, 369, 371, 374, 375, 378, 379, 404, 405, 406, 409, 410, 416, 417, 418, 420, 421, 428, 431, 432, 433, 434, 435, 436, 447, 454, 455, 473, 474, 475, 476, 481, 486, 487, 488, 493, 494, 508, 509, 510], "/Library/Python/2.7/site-packages/requests/packages/urllib3/_collections.py": [7, 8, 10, 11, 16, 19, 22, 34, 36, 38, 39, 40, 42, 43, 45, 47, 48, 52, 53, 54, 56, 57, 61, 64, 67, 74, 78, 81, 92], "/Users/songzhengang/wechat_sdk/tests/test_message_response.py": [3, 5, 7, 10, 12, 14, 15, 16, 17, 21, 22, 26, 27, 31, 32, 33, 37, 39, 43, 45, 47, 48, 50, 52, 54, 56, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, 75, 77, 79, 81, 83, 84, 86], "/Library/Python/2.7/site-packages/requests/packages/urllib3/__init__.py": [32, 38, 40, 9, 11, 12, 13, 16, 22, 23, 24, 25, 26, 58, 30, 31], "/Users/songzhengang/wechat_sdk/wechat/message/hander.py": [3, 5, 7, 10, 12, 13, 15, 16, 19, 22, 23, 25, 26, 28, 30, 32, 35, 38, 41, 44, 47, 50, 53, 56, 58, 59, 61, 63, 68, 73, 78, 83], "/Users/songzhengang/wechat_sdk/wechat/core.py": [3, 4, 5, 7, 9, 12, 16, 19, 22, 26, 28, 29, 30, 33, 34, 35, 38, 39, 41, 44, 47, 50, 51], "/Users/songzhengang/wechat_sdk/wechat/settings.py": [], "/Library/Python/2.7/site-packages/requests/packages/__init__.py": [1, 3], "/Library/Python/2.7/site-packages/requests/packages/urllib3/response.py": [8, 9, 10, 12, 13, 14, 17, 20, 22, 27, 30, 46, 53, 74, 76, 77, 79, 80, 81, 82, 83, 84, 85, 86, 87, 89, 90, 91, 92, 94, 95, 97, 98, 100, 103, 116, 117, 120, 121, 123, 132, 155, 156, 157, 159, 162, 165, 167, 168, 173, 174, 175, 186, 187, 195, 199, 202, 205, 206, 208, 224, 225, 227, 228, 231, 242, 243, 245, 247, 248, 251, 254, 255, 256, 257, 258, 259, 260, 261, 262, 265, 268, 272, 276, 278, 280, 282, 283, 287, 296, 300], "/Library/Python/2.7/site-packages/simplejson/decoder.py": [2, 3, 4, 5, 6, 7, 8, 137, 10, 11, 12, 13, 398, 143, 16, 387, 20, 22, 24, 25, 303, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 42, 43, 44, 302, 47, 304, 49, 50, 51, 300, 400, 394, 139, 353, 140, 395, 348, 349, 350, 351, 352, 144, 354, 355, 356, 357, 358, 359, 360, 361, 363, 236, 272, 368, 370, 371, 372, 391, 374, 376, 396], "/Library/Python/2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py": [21, 23], "/Library/Python/2.7/site-packages/requests/packages/urllib3/packages/six.py": [1, 2, 4, 6, 8, 10, 12, 13, 20, 22, 23, 24, 26, 27, 28, 31, 33, 35, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 53, 54, 55, 56, 57, 63, 64, 67, 69, 72, 74, 75, 78, 80, 81, 83, 84, 85, 87, 88, 91, 93, 94, 95, 100, 102, 106, 108, 109, 110, 121, 122, 123, 124, 126, 127, 128, 132, 133, 137, 138, 139, 140, 141, 142, 143, 144, 145, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 182, 183, 184, 186, 189, 194, 205, 216, 217, 219, 220, 222, 223, 224, 227, 228, 232, 235, 244, 247, 249, 252, 253, 254, 257, 258, 259, 260, 263, 267, 271, 276, 291, 293, 295, 296, 297, 298, 299, 302, 317, 319, 320, 321, 322, 323, 324, 327, 330, 332, 335, 380, 383], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/core.py": [65, 34, 36, 37, 41, 42, 43, 44, 50, 51, 55, 56, 59, 188, 61, 62, 193, 66, 199, 200, 201, 202, 203, 204, 205, 207, 187, 60], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/doctests.py": [192, 193, 194, 195, 188, 189, 190, 191], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/suite.py": [539, 540, 543, 544, 545, 546, 547, 548, 551, 552, 554, 563, 52, 53, 567, 68, 72, 73, 75, 76, 79, 80, 81, 82, 83, 94, 95, 96, 97, 98, 99, 100, 103, 104, 105, 106, 107, 113, 114, 148, 149, 150, 151, 153, 154, 155, 156, 157, 158, 173, 177, 201, 204, 205, 208, 209, 216, 217, 218, 224, 226, 227, 228, 269, 270, 272, 273, 274, 277, 278, 279, 282, 283, 285, 286, 287, 288, 289, 290, 291, 292, 293, 297, 298, 301, 302, 303, 304, 308, 309, 310, 312, 564, 314, 315, 323, 324, 325, 326, 327, 328, 329, 330, 331, 337, 338, 339, 340, 341, 342, 313, 345, 346, 347, 348, 349, 350, 351, 356, 357, 358, 360, 361, 362, 364, 365, 366, 367, 368, 372, 373, 374, 394, 396, 397, 401, 402, 403, 404, 405, 406, 407, 418, 419, 420, 421, 422, 423, 424, 427, 435, 436, 441, 443, 445, 446, 447, 448, 451, 452, 453, 454, 457, 459, 460, 462, 463, 464, 465, 466, 467, 471, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486], "/Library/Python/2.7/site-packages/requests/status_codes.py": [3, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 82, 84, 85, 86, 87, 88], "/Users/songzhengang/wechat_sdk/wechat/message/response.py": [4, 6, 9, 12, 14, 15, 16, 17, 19, 20, 21, 24, 26, 28, 38, 41, 43, 45, 57, 60, 62, 64, 76, 79, 81, 83, 97, 100, 102, 104, 115, 124, 126, 127, 128, 130, 131, 132, 134, 135, 136, 137, 138, 139, 141], "/Library/Python/2.7/site-packages/requests/packages/urllib3/request.py": [50, 81, 7, 8, 9, 10, 12, 45, 91, 15, 48, 59, 18, 51, 53, 54, 90, 47], "/Library/Python/2.7/site-packages/requests/auth.py": [8, 10, 11, 12, 13, 14, 16, 145, 18, 19, 21, 23, 24, 27, 33, 34, 36, 40, 41, 42, 46, 175, 51, 52, 53, 58, 59, 60, 67], "/Library/Python/2.7/site-packages/requests/sessions.py": [515, 516, 517, 520, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 23, 25, 27, 29, 30, 31, 32, 34, 37, 44, 45, 47, 48, 52, 53, 57, 58, 61, 62, 65, 523, 68, 69, 70, 73, 76, 528, 154, 165, 168, 169, 170, 172, 177, 181, 186, 189, 194, 197, 200, 203, 207, 210, 216, 219, 220, 221, 223, 226, 229, 238, 241, 242, 245, 246, 247, 251, 252, 253, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 267, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 324, 327, 329, 332, 334, 335, 339, 340, 343, 344, 347, 348, 349, 350, 353, 354, 355, 356, 357, 358, 359, 361, 363, 365, 375, 385, 395, 405, 415, 425, 434, 438, 439, 440, 441, 445, 450, 451, 452, 453, 454, 455, 456, 459, 462, 464, 466, 469, 472, 476, 479, 480, 481, 484, 487, 494, 496, 498, 500, 501, 506, 511], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/cover.py": [164, 263, 173, 271, 182, 183], "/Users/songzhengang/wechat_sdk/wechat/menu/__init__.py": [3], "/Library/Python/2.7/site-packages/requests/packages/urllib3/contrib/__init__.py": [1], "/Users/songzhengang/wechat_sdk/wechat/base_client.py": [5, 7, 8, 11, 13, 14, 16, 17, 18, 19, 21, 22, 23, 24], "/Users/songzhengang/wechat_sdk/tests/settings.py": [3, 5, 7, 8, 9, 13, 14], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/debug.py": [40, 41, 42, 43], "/Library/Python/2.7/site-packages/requests/packages/urllib3/packages/ordered_dict.py": [129, 258, 171, 133, 6, 7, 138, 11, 12, 143, 17, 18, 67, 152, 155, 29, 159, 160, 161, 162, 35, 37, 38, 39, 40, 41, 42, 43, 45, 174, 157, 176, 49, 178, 51, 52, 53, 55, 70, 191, 64, 66, 245, 68, 69, 198, 72, 80, 211, 170, 92, 221, 165, 225, 226, 50, 236, 117, 169, 121, 250, 125, 254, 127], "/Library/Python/2.7/site-packages/requests/hooks.py": [32, 34, 35, 37, 40, 45, 14, 17, 20, 21, 22, 23, 24, 29], "/Library/Python/2.7/site-packages/requests/structures.py": [9, 11, 12, 13, 16, 17, 18, 22, 25, 33, 37, 64, 65, 66, 67, 68, 69, 71, 74, 76, 77, 79, 82, 83, 85, 86, 88, 96, 105, 108, 112, 113, 115, 116, 117, 119, 122, 127], "/Library/Python/2.7/site-packages/requests/api.py": [69, 102, 113, 44, 43, 12, 14, 47, 80, 17, 54, 55, 88, 58, 91], "/Library/Python/2.7/site-packages/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py": [1, 3, 5, 7, 8, 10, 15, 16, 19, 20, 22, 23, 32, 33, 60, 68, 70, 71, 72, 73, 74, 75, 76], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/failuredetail.py": [33, 35, 36], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/plugins/logcapture.py": [65, 89, 34, 38, 39, 40, 41, 71, 44, 49, 178, 179, 52, 58, 59, 60, 64, 193, 194, 195, 196, 69, 198, 199, 76, 204, 77, 78, 207, 80, 209, 82, 86, 88, 217, 79, 222, 208], "/Library/Python/2.7/site-packages/nose-1.3.7-py2.7.egg/nose/selector.py": [35, 37, 40, 41, 42, 43, 44, 45, 53, 54, 57, 68, 69, 72, 73, 74, 76, 77, 80, 81, 89, 90, 91, 96, 97, 98, 99, 100, 104, 105, 116, 117, 118, 119, 120, 121, 122, 123, 126, 127, 129, 130, 131, 134, 135, 140, 141, 144, 148, 149, 152, 153, 154, 156, 157, 162, 163, 167, 169, 170, 171, 174, 175, 176, 178, 179, 187, 188, 191, 192, 193, 194, 196, 197, 222, 224, 225, 226, 227, 228, 229, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241], "/Library/Python/2.7/site-packages/requests/packages/urllib3/connection.py": [7, 8, 10, 11, 12, 13, 15, 16, 17, 19, 20, 21, 23, 24, 26, 27, 28, 29, 31, 32, 37, 40, 41, 48, 52, 53, 54, 55, 57, 58, 59, 61, 62, 63, 64, 65, 66, 68, 70, 71, 72, 73, 80, 81, 83, 91, 92, 93, 94, 95, 97, 98, 101, 102, 103, 106, 107], "/Library/Python/2.7/site-packages/requests/__init__.py": [77, 69, 70, 71, 42, 44, 45, 46, 47, 48, 49, 52, 53, 55, 56, 58, 59, 60, 61, 62, 63], "/Library/Python/2.7/site-packages/requests/packages/urllib3/filepost.py": [66, 7, 8, 47, 10, 11, 13, 14, 15, 17, 20, 27]}}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.log
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | 微信公众平台SDK
2 | =================
3 |
4 | 项目背景
5 | --------
6 | 从2014年开始玩微信公众平台,试用过其中大多数的功能,如:消息回复、自定义菜单、公众号中的支付,页面授权等。之前的程序中都是直接调用公众平台的接口,这样复用功能无法实现。现将功能独立出单独模块
7 |
8 | 目前完成
9 | -----------
10 | * 获取access_token方法
11 | * 获取微信服务器IP地址
12 | * 自定义菜单中的查询、创建、删除(不包括个性化菜单接口)
13 | * 消息管理中的接收普通消息、接收事件推送
14 | * 消息管理中的被动回复用户消息
15 | * 添加tornado代码的demo实例
16 |
17 | 安装说明
18 | -----------
19 |
20 | ::
21 |
22 | pip install wechat2
23 |
24 |
25 | 使用示例
26 | -----------
27 |
28 | 获取access_token方法
29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 |
31 | ::
32 |
33 | from wechat.base import get_access_token_dict
34 |
35 | get_access_token_dict(APPID, APPSECRET)
36 |
37 |
38 | 消息处理基类
39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40 |
41 | 继承基础的消息处理类BaseHandler, 重写对应方法即可。 如文本、图片、视频等对应的处理方法分别问on_text、on_image、on_video。
42 |
43 | ::
44 |
45 | from wechat.message import *
46 |
47 | class MessageHandler(BaseHandler):
48 |
49 | def on_text(self, xml_dict):
50 |
51 | from_user = xml_dict['FromUserName']
52 | to_user = xml_dict['ToUserName']
53 | create_time = xml_dict['CreateTime']
54 | content = xml_dict['Content']
55 |
56 | text_response = TextResponse(from_user=to_user, to_user=from_user, create_time=create_time, content=content)
57 | return text_response
58 |
59 |
60 | 自定义菜单接口
61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 |
63 | ::
64 |
65 | from wechat.menu.client import Client
66 |
67 | client = Client(access_token['access_token'])
68 |
69 | # 创建菜单
70 | client.create_menu(data)
71 |
72 | # 获取菜单
73 | client.get_menu()
74 |
75 | # 删除菜单
76 | client.delete_menu()
77 |
78 |
79 | 跑单元测试
80 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
81 |
82 | ::
83 |
84 | nosetests --with-coverage --cover-package=wechat
85 |
86 |
87 | 下一步计划
88 | -------------
89 | 1. 继续补充其他常用接口
90 |
91 | **感兴趣的同学可以加入到项目中一起完善**
92 |
--------------------------------------------------------------------------------
/demo/handlers/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
--------------------------------------------------------------------------------
/demo/handlers/main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import tornado.ioloop
4 | import tornado.web
5 |
6 | from wechat.message import *
7 |
8 |
9 | class MessageHandler(BaseHandler):
10 | def on_text(self, xml_dict):
11 | from_user = xml_dict['FromUserName']
12 | to_user = xml_dict['ToUserName']
13 | create_time = xml_dict['CreateTime']
14 | content = xml_dict['Content']
15 |
16 | text_response = TextResponse(from_user=from_user, to_user=to_user, create_time=create_time, content=content)
17 | return text_response
18 |
19 | # 统一处理消息的类实例
20 | hd = MessageHandler()
21 |
22 | # 测试公众号
23 | appid = 'wx863e67751855b610'
24 | appsecret = 'd4624c36b6795d1d99dcf0547af5443d'
25 |
26 |
27 | class MainHandler(tornado.web.RequestHandler):
28 |
29 | def get(self):
30 |
31 | # s = self.get_argument('s', '')
32 | # d = get_access_token_dict(appid, appsecret)
33 |
34 | s = 'ok'
35 | self.write(s)
36 |
37 | def post(self):
38 | # 处理消息
39 | body = self.request.body
40 | text = str(hd(body))
41 |
42 | self.write(text)
43 |
--------------------------------------------------------------------------------
/demo/requirements.txt:
--------------------------------------------------------------------------------
1 | tornado==4.4.1
2 |
--------------------------------------------------------------------------------
/demo/run.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import tornado.ioloop
4 | import tornado.web
5 |
6 | from tornado.options import define, options
7 |
8 | from settings import app_settings
9 | from urls import url_patterns
10 |
11 | # 定义默认的程序端口号
12 | define("port", default=9999, type=int, help="port", metavar='server port')
13 |
14 | tornado.options.parse_command_line()
15 |
16 | application = tornado.web.Application(url_patterns, **app_settings)
17 |
18 | if __name__ == "__main__":
19 | application.listen(options.port)
20 | tornado.ioloop.IOLoop.instance().start()
21 |
--------------------------------------------------------------------------------
/demo/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | app_settings = dict()
4 | app_settings['debug'] = True
5 | app_settings['a'] = 1
6 |
--------------------------------------------------------------------------------
/demo/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from handlers.main import MainHandler
4 |
5 | url_patterns = [
6 | (r"/", MainHandler),
7 | ]
8 |
--------------------------------------------------------------------------------
/demo/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
--------------------------------------------------------------------------------
/demo/wechat:
--------------------------------------------------------------------------------
1 | ../wechat
--------------------------------------------------------------------------------
/dist/wechat2-1.0.0.0.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyroge/wechat_sdk/9e8026d052e96b3f3fa14c3121fc9ec87312c9f0/dist/wechat2-1.0.0.0.tar.gz
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyroge/wechat_sdk/9e8026d052e96b3f3fa14c3121fc9ec87312c9f0/docs/index.rst
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.0.1
2 | xmltodict==0.9.2
3 | nose==1.3.7
4 | coverage==4.2
5 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import codecs
4 | import os
5 |
6 | try:
7 | from setuptools import setup
8 | except:
9 | from distutils.core import setup
10 |
11 | """
12 | 打包的用的setup必须引入,
13 | """
14 |
15 |
16 | def read(fname):
17 | """
18 | 定义一个read方法,用来读取目录下的长描述
19 | 我们一般是将README文件中的内容读取出来作为长描述,这个会在PyPI中你这个包的页面上展现出来,
20 | 你也可以不用这个方法,自己手动写内容即可,
21 | PyPI上支持.rst格式的文件。暂不支持.md格式的文件,
.rst文件PyPI会自动把它转为HTML形式显示在你包的信息页面上。
22 | """
23 | return codecs.open(os.path.join(os.path.dirname(__file__), fname)).read()
24 |
25 |
26 | NAME = "wechat2"
27 |
28 | PACKAGES = ["wechat", "wechat.message", "wechat.menu"]
29 |
30 | DESCRIPTION = "Wechat Python SDK"
31 |
32 | LONG_DESCRIPTION = read("README.rst")
33 |
34 | KEYWORDS = "wechat"
35 |
36 | AUTHOR = "songzhengang"
37 |
38 | AUTHOR_EMAIL = "lyroge@foxmail.com"
39 |
40 | URL = "https://github.com/lyroge/wechat_sdk"
41 |
42 | VERSION = "1.0.0"
43 |
44 | LICENSE = "MIT"
45 |
46 | setup(
47 | name=NAME,
48 | version=VERSION,
49 | description=DESCRIPTION,
50 | long_description=LONG_DESCRIPTION,
51 | classifiers=[
52 | 'License :: OSI Approved :: MIT License',
53 | 'Programming Language :: Python',
54 | 'Intended Audience :: Developers',
55 | 'Operating System :: OS Independent',
56 | ],
57 | keywords=KEYWORDS,
58 | author=AUTHOR,
59 | author_email=AUTHOR_EMAIL,
60 | url=URL,
61 | license=LICENSE,
62 | packages=PACKAGES,
63 | include_package_data=True,
64 | zip_safe=True,
65 | )
66 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lyroge/wechat_sdk/9e8026d052e96b3f3fa14c3121fc9ec87312c9f0/tests/__init__.py
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import logging
4 |
5 | stream_hd = logging.StreamHandler()
6 |
7 | logger = logging.getLogger('wechat')
8 | logger.setLevel(logging.INFO)
9 | logger.addHandler(stream_hd)
10 |
11 |
12 | # 微信公众号key
13 | APPID = 'wx863e67751855b610'
14 | APPSECRET = 'd4624c36b6795d1d99dcf0547af5443d'
15 |
--------------------------------------------------------------------------------
/tests/test_core_base.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from unittest import TestCase
4 |
5 | from wechat.core import wechat_api_domain
6 | from wechat.base import get_access_token_dict
7 | from wechat.base import get_wechat_serverip_list
8 |
9 | from settings import APPID, APPSECRET
10 |
11 |
12 | class CoreTestCase(TestCase):
13 |
14 | def test_wechat_api_domain(self):
15 | """ 测试获取微信服务器域名
16 | """
17 | self.assertEqual(wechat_api_domain, 'api.weixin.qq.com')
18 |
19 | def test_get_access_token_dict(self):
20 | """ 测试获取access_token方法
21 | """
22 | d = get_access_token_dict(APPID, APPSECRET)
23 | key_true = 'access_token' in d and 'expires_in'
24 | self.assertTrue(key_true)
25 |
26 | def test_get_wechat_serverip_list(self):
27 | """ 获取微信服务器ip列表
28 | """
29 | access_token = get_access_token_dict(APPID, APPSECRET)
30 |
31 | d = get_wechat_serverip_list(access_token['access_token'])
32 | key_true = 'ip_list' in d
33 |
34 | self.assertTrue(key_true)
35 |
--------------------------------------------------------------------------------
/tests/test_menu_client.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import json
4 | from unittest import TestCase
5 |
6 | from wechat.base import get_access_token_dict
7 | from wechat.menu.client import Client
8 |
9 | from settings import APPID, APPSECRET
10 |
11 |
12 | class MenuClientTestCase(TestCase):
13 |
14 | @classmethod
15 | def setUpClass(cls):
16 | access_token = get_access_token_dict(APPID, APPSECRET)
17 | cls.client = Client(access_token['access_token'])
18 |
19 | def _check_json_result(self, d):
20 | errcode = d.get('errcode', 0)
21 | self.assertTrue(not errcode)
22 |
23 | def test_0_create_menu(self):
24 | data = {
25 | "button": [
26 | {
27 | "type": "click",
28 | "name": "abc",
29 | "key": "V1001_TODAY_MUSIC"
30 | },
31 | {
32 | "name": "def",
33 | "sub_button": [
34 | {
35 | "type": "view",
36 | "name": "a",
37 | "url": "http://www.soso.com/"
38 | },
39 | {
40 | "type": "view",
41 | "name": "b",
42 | "url": "http://v.qq.com/"
43 | },
44 | {
45 | "type": "click",
46 | "name": "c",
47 | "key": "V1001_GOOD"
48 | }
49 | ]
50 | }
51 | ]
52 | }
53 | data = json.dumps(data)
54 | d = self.client.create_menu(data)
55 | self._check_json_result(d)
56 |
57 | def test_1_get_menu(self):
58 | """ 测试获取菜单功能
59 | """
60 | d = self.client.get_menu()
61 | self._check_json_result(d)
62 |
63 | def test_2_delete_menu(self):
64 | """ 测试删除菜单功能
65 | """
66 | d = self.client.delete_menu()
67 | self._check_json_result(d)
68 |
--------------------------------------------------------------------------------
/tests/test_message_handler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from unittest import TestCase
4 |
5 | from wechat.message import BaseHandler
6 |
7 |
8 | class TestBaseHandler(BaseHandler):
9 |
10 | def on_text(self, xml_dict):
11 | return 'text' == xml_dict['MsgType'] and 'Content' in xml_dict
12 |
13 | def on_image(self, xml_dict):
14 | return 'image' == xml_dict['MsgType'] and 'PicUrl' in xml_dict
15 |
16 | def on_voice(self, xml_dict):
17 | return 'voice' == xml_dict['MsgType'] and 'Format' in xml_dict
18 |
19 | def on_video(self, xml_dict):
20 | return 'video' == xml_dict['MsgType'] and 'ThumbMediaId' in xml_dict
21 |
22 | def on_shortvideo(self, xml_dict):
23 | return 'shortvideo' == xml_dict['MsgType'] and 'ThumbMediaId' in xml_dict
24 |
25 | def on_location(self, xml_dict):
26 | return 'location' == xml_dict['MsgType'] and 'Location_X' in xml_dict
27 |
28 | def on_link(self, xml_dict):
29 | return 'link' == xml_dict['MsgType'] and 'Title' in xml_dict
30 |
31 | def on_event_subscribe(self, xml_dict):
32 | """ 用户关注、包括扫码关注
33 | """
34 | return 'subscribe' == xml_dict['Event']
35 |
36 | def on_event_unsubscribe(self, xml_dict):
37 | """ 用户取消关注
38 | """
39 | return 'unsubscribe' == xml_dict['Event']
40 |
41 | def on_event_scan(self, xml_dict):
42 | """ 用户扫码进入场景
43 | """
44 | return 'SCAN' == xml_dict['Event']
45 |
46 | def on_event_location(self, xml_dict):
47 | """ 用户上报位置
48 | """
49 | return 'LOCATION' == xml_dict['Event'] and 'Longitude' in xml_dict
50 |
51 | def on_event_click(self, xml_dict):
52 | """ 用户点击菜单事件
53 | """
54 | return 'CLICK' == xml_dict['Event']
55 |
56 |
57 | # 定义统一处理消息的类
58 | test_hd = TestBaseHandler()
59 |
60 |
61 | class BaseHandlerTestCase(TestCase):
62 |
63 | def test_on_text(self):
64 | """ 测试处理文本消息的方法
65 | """
66 |
67 | xml = """
68 |
69 |
70 |
71 | 1348831860
72 |
73 |
74 | 1234567890123456
75 |
76 | """
77 | r = test_hd(xml)
78 | self.assertTrue(r)
79 |
80 | def test_on_image(self):
81 | """ 测试处理图片的消息的方法
82 | """
83 |
84 | xml = """
85 |
86 |
87 |
88 | 1348831860
89 |
90 |
91 |
92 | 1234567890123456
93 |
94 | """
95 | r = test_hd(xml)
96 | self.assertTrue(r)
97 |
98 | def test_on_voice(self):
99 | """ 测试处理语音的消息的方法
100 | """
101 |
102 | xml = """
103 |
104 |
105 |
106 | 1357290913
107 |
108 |
109 |
110 | 1234567890123456
111 |
112 | """
113 | r = test_hd(xml)
114 | self.assertTrue(r)
115 |
116 | def test_on_video(self):
117 | """ 测试处理视频的消息的方法
118 | """
119 |
120 | xml = """
121 |
122 |
123 |
124 | 1357290913
125 |
126 |
127 |
128 | 1234567890123456
129 |
130 | """
131 | r = test_hd(xml)
132 | self.assertTrue(r)
133 |
134 | def test_on_shortvideo(self):
135 | """ 测试处理小视频的消息的方法
136 | """
137 |
138 | xml = """
139 |
140 |
141 |
142 | 1357290913
143 |
144 |
145 |
146 | 1234567890123456
147 |
148 | """
149 | r = test_hd(xml)
150 | self.assertTrue(r)
151 |
152 | def test_on_location(self):
153 | """ 测试处理位置消息的方法
154 | """
155 |
156 | xml = """
157 |
158 |
159 |
160 | 1351776360
161 |
162 | 23.134521
163 | 113.358803
164 | 20
165 |
166 | 1234567890123456
167 |
168 | """
169 | r = test_hd(xml)
170 | self.assertTrue(r)
171 |
172 | def test_on_link(self):
173 | """ 测试处理链接消息的方法
174 | """
175 |
176 | xml = """
177 |
178 |
179 |
180 | 1351776360
181 |
182 |
183 |
184 |
185 | 1234567890123456
186 |
187 | """
188 | r = test_hd(xml)
189 | self.assertTrue(r)
190 |
191 | def test_on_event_subscribe(self):
192 | """ 测试处理关注消息的方法
193 | """
194 |
195 | xml = """
196 |
197 |
198 |
199 | 123456789
200 |
201 |
202 |
203 | """
204 | r = test_hd(xml)
205 | self.assertTrue(r)
206 |
207 | def test_on_event_unsubscribe(self):
208 | """ 测试处理关注消息的方法
209 | """
210 |
211 | xml = """
212 |
213 |
214 |
215 | 123456789
216 |
217 |
218 |
219 | """
220 | r = test_hd(xml)
221 | self.assertTrue(r)
222 |
223 | def test_on_event_scan(self):
224 | """ 测试处理扫码的方法
225 | """
226 |
227 | xml = """
228 |
229 |
230 |
231 | 123456789
232 |
233 |
234 |
235 |
236 |
237 | """
238 | r = test_hd(xml)
239 | self.assertTrue(r)
240 |
241 | def test_on_event_location(self):
242 | """ 测试上报位置的方法(服务号的位置信息)
243 | """
244 |
245 | xml = """
246 |
247 |
248 |
249 | 123456789
250 |
251 |
252 | 23.137466
253 | 113.352425
254 | 119.385040
255 |
256 | """
257 | r = test_hd(xml)
258 | self.assertTrue(r)
259 |
260 | def test_on_event_click(self):
261 | """ 测试点击自定义菜单的事件
262 | """
263 |
264 | xml = """
265 |
266 |
267 |
268 | 123456789
269 |
270 |
271 |
272 |
273 | """
274 | r = test_hd(xml)
275 | self.assertTrue(r)
276 |
--------------------------------------------------------------------------------
/tests/test_message_response.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from unittest import TestCase
4 |
5 | import xmltodict
6 |
7 | from wechat.message import *
8 |
9 |
10 | class TestResponse(TestCase):
11 |
12 | @classmethod
13 | def setUpClass(cls):
14 | cls.d = {
15 | 'from_user': 'from_user',
16 | 'to_user': 'to_user',
17 | 'create_time': 'create_time',
18 | }
19 |
20 | # 音频、图片
21 | cls.d.update({
22 | 'media_id': 'media_id'
23 | })
24 |
25 | # 文本消息
26 | cls.d.update({
27 | 'content': 'content'
28 | })
29 |
30 | # 视频
31 | cls.d.update({
32 | 'title': 'title',
33 | 'description': 'description'
34 | })
35 |
36 | # 多图文
37 | cls.d.update({
38 | 'item_list': [
39 | {'title': 'title1', 'description': 'description1', 'picurl': 'picurl1', 'url': 'url1'}
40 | ]
41 | })
42 |
43 | def test_text_response(self):
44 |
45 | r = TextResponse(**self.d)
46 |
47 | d = xmltodict.parse(str(r))
48 | root = d['xml']
49 |
50 | self.assertEqual('text', root['MsgType'])
51 |
52 | def test_image_response(self):
53 |
54 | r = ImageResponse(**self.d)
55 |
56 | d = xmltodict.parse(str(r))
57 | root = d['xml']
58 |
59 | self.assertEqual('image', root['MsgType'])
60 |
61 | def test_voice_response(self):
62 |
63 | r = VoiceResponse(**self.d)
64 |
65 | d = xmltodict.parse(str(r))
66 | root = d['xml']
67 |
68 | self.assertEqual('voice', root['MsgType'])
69 |
70 | def test_video_response(self):
71 |
72 | r = VideoResponse(**self.d)
73 |
74 | d = xmltodict.parse(str(r))
75 | root = d['xml']
76 |
77 | self.assertEqual('video', root['MsgType'])
78 |
79 | def test_news_response(self):
80 |
81 | r = NewsResponse(**self.d)
82 |
83 | d = xmltodict.parse(str(r))
84 | root = d['xml']
85 |
86 | self.assertEqual('news', root['MsgType'])
87 |
--------------------------------------------------------------------------------
/wechat/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 | """
3 | 使用说明:
4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 | 微信公众平台开发官方文档
6 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432&token=&lang=zh_CN
7 | """
8 |
9 | __version__ = '1.0.0'
10 |
11 | __author__ = 'songzhengang'
12 |
13 |
14 | def get_version():
15 | return __version__
16 |
--------------------------------------------------------------------------------
/wechat/base.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from core import get_request_wechat_api
4 |
5 |
6 | def get_access_token_dict(appid, appsecret):
7 | """ 获取access_token对象字典
8 | """
9 | path = 'cgi-bin/token'
10 |
11 | args = (appid, appsecret)
12 | query_string = 'grant_type=client_credential&appid=%s&secret=%s' % args
13 |
14 | d = get_request_wechat_api(path, query_string)
15 | return d
16 |
17 |
18 | def get_wechat_serverip_list(access_token):
19 | path = 'cgi-bin/getcallbackip'
20 | query_string = 'access_token=%s' % access_token
21 |
22 | d = get_request_wechat_api(path, query_string)
23 | return d
24 |
--------------------------------------------------------------------------------
/wechat/base_client.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 | """
3 | 提供其他类别调用的基础
4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 | """
6 |
7 | from core import get_request_wechat_api
8 | from core import post_request_wechat_api
9 |
10 |
11 | class BaseClient(object):
12 |
13 | def __init__(self, access_token):
14 | self.access_token = access_token
15 |
16 | def get(self, path):
17 | query_string = 'access_token=%s' % self.access_token
18 | d = get_request_wechat_api(path, query_string)
19 | return d
20 |
21 | def post(self, path, data):
22 | query_string = 'access_token=%s' % self.access_token
23 | d = post_request_wechat_api(path, query_string, data)
24 | return d
25 |
--------------------------------------------------------------------------------
/wechat/constants.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | WECHAT_API_DOMAIN_LIST = [
4 | 'api.weixin.qq.com',
5 | 'sh.api.weixin.qq.com',
6 | 'sz.api.weixin.qq.com',
7 | 'hk.api.weixin.qq.com'
8 | ]
9 |
--------------------------------------------------------------------------------
/wechat/core.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import logging
4 | from functools import partial
5 | import requests
6 |
7 | from wechat.constants import WECHAT_API_DOMAIN_LIST
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | def get_api_domain():
13 | """ 获取第一个主域名
14 | 说明:内部获取微信域名方法
15 | """
16 | return WECHAT_API_DOMAIN_LIST[0]
17 |
18 | # 全局调用的微信服务域名
19 | wechat_api_domain = get_api_domain()
20 |
21 |
22 | def request_wechat_api(path, query_string='', data='', method='get'):
23 | """ 请求微信服务基础接口
24 | """
25 |
26 | url = 'https://%s/%s?%s' % (wechat_api_domain, path, query_string)
27 |
28 | args = [url]
29 | if data and method == 'post':
30 | args.append(data)
31 |
32 | # 日志输出url及参数信息
33 | logger.info('START REQUEST[%s]: %s' % (method.upper(), url))
34 | if data:
35 | logger.info('***params***:%s', data)
36 |
37 | # 获取方法
38 | meth = getattr(requests, method)
39 | r = meth(*args)
40 |
41 | d = r.json()
42 |
43 | # 如果查询结果出现问题,打印
44 | if d.get('errcode', 0):
45 | logger.error(d)
46 |
47 | return d
48 |
49 | # 请求微信服务的get、post方法
50 | get_request_wechat_api = partial(request_wechat_api)
51 | post_request_wechat_api = partial(request_wechat_api, method='post')
52 |
--------------------------------------------------------------------------------
/wechat/menu/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from client import Client
4 |
--------------------------------------------------------------------------------
/wechat/menu/client.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from wechat.base_client import BaseClient
4 |
5 |
6 | class Client(BaseClient):
7 | """ 操作菜单创建、产出、查询的客户端
8 | """
9 | base_path = 'cgi-bin/menu/'
10 |
11 | def get_menu(self):
12 | path = self.base_path + 'get'
13 | d = self.get(path)
14 | return d
15 |
16 | def create_menu(self, data):
17 | path = self.base_path + 'create'
18 | d = self.post(path, data)
19 | return d
20 |
21 | def delete_menu(self):
22 | path = self.base_path + 'delete'
23 | d = self.get(path)
24 | return d
25 |
--------------------------------------------------------------------------------
/wechat/message/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | from hander import BaseHandler
4 |
5 | from response import TextResponse
6 | from response import ImageResponse
7 | from response import VideoResponse
8 | from response import VoiceResponse
9 | from response import NewsResponse
10 |
--------------------------------------------------------------------------------
/wechat/message/hander.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import logging
4 |
5 | import xmltodict
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | class BaseHandler(object):
11 |
12 | def __call__(self, xml):
13 | return self._dispatch(xml)
14 |
15 | def _dispatch(self, xml):
16 | logger.info('post xml:%s', xml)
17 |
18 | # 解析提交过来的数据
19 | d = xmltodict.parse(xml)
20 |
21 | # 获取提交信息的类别,分配到对应方法
22 | root = d['xml']
23 | msg_type = root['MsgType']
24 |
25 | meth = getattr(self, 'on_%s' % msg_type, None)
26 | r = meth(root)
27 |
28 | logger.info('return: %s', r)
29 |
30 | return r
31 |
32 | def on_text(self, xml_dict):
33 | pass
34 |
35 | def on_image(self, xml_dict):
36 | pass
37 |
38 | def on_voice(self, xml_dict):
39 | pass
40 |
41 | def on_video(self, xml_dict):
42 | pass
43 |
44 | def on_shortvideo(self, xml_dict):
45 | pass
46 |
47 | def on_location(self, xml_dict):
48 | pass
49 |
50 | def on_link(self, xml_dict):
51 | pass
52 |
53 | def on_event(self, xml_dict):
54 | """ 事件信息分发
55 | """
56 | event = xml_dict['Event'].lower()
57 |
58 | meth = getattr(self, 'on_event_%s' % event, None)
59 | r = meth(xml_dict)
60 |
61 | return r
62 |
63 | def on_event_subscribe(self, xml_dict):
64 | """ 用户关注、包括扫码关注
65 | """
66 | pass
67 |
68 | def on_event_unsubscribe(self, xml_dict):
69 | """ 用户取消关注
70 | """
71 | pass
72 |
73 | def on_event_scan(self, xml_dict):
74 | """ 用户扫码进入场景
75 | """
76 | pass
77 |
78 | def on_event_location(self, xml_dict):
79 | """ 用户上报位置(服务号的位置信息)
80 | """
81 | pass
82 |
83 | def on_event_click(self, xml_dict):
84 | """ 用户点击菜单事件
85 | """
86 | pass
87 |
--------------------------------------------------------------------------------
/wechat/message/response.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 |
4 | class BaseResponse(object):
5 | """ 公众号回复消息基类
6 | """
7 |
8 | # 模板中有哪些key
9 | allow_key_list = []
10 |
11 | # 回复的消息模板字符串
12 | xml_str_template = ''
13 |
14 | def __init__(self, **kwargs):
15 | for k in self.allow_key_list:
16 | v = kwargs.pop(k, '')
17 | setattr(self, k, v)
18 |
19 | def __str__(self):
20 | d = dict((k, getattr(self, k)) for k in self.allow_key_list)
21 | return self.xml_str_template.format(**d)
22 |
23 |
24 | class TextResponse(BaseResponse):
25 | """ 公众号回复文本消息
26 | """
27 |
28 | allow_key_list = ['to_user', 'from_user', 'create_time', 'content']
29 |
30 | xml_str_template = """
31 |
32 |
33 |
34 | {create_time}
35 |
36 |
37 |
38 | """
39 |
40 |
41 | class ImageResponse(BaseResponse):
42 | """ 公众号回复图片消息
43 | """
44 |
45 | allow_key_list = ['to_user', 'from_user', 'create_time', 'media_id']
46 |
47 | xml_str_template = """
48 |
49 |
50 |
51 | {create_time}
52 |
53 |
54 |
55 |
56 |
57 | """
58 |
59 |
60 | class VoiceResponse(BaseResponse):
61 | """ 公众号回复语音消息
62 | """
63 |
64 | allow_key_list = ['to_user', 'from_user', 'create_time', 'media_id']
65 |
66 | xml_str_template = """
67 |
68 |
69 |
70 | {create_time}
71 |
72 |
73 |
74 |
75 |
76 | """
77 |
78 |
79 | class VideoResponse(BaseResponse):
80 | """ 公众号回复视频消息
81 | """
82 |
83 | allow_key_list = ['to_user', 'from_user', 'create_time', 'media_id', 'title', 'description']
84 |
85 | xml_str_template = """
86 |
87 |
88 |
89 | {create_time}
90 |
91 |
96 |
97 | """
98 |
99 |
100 | class NewsResponse(BaseResponse):
101 | """ 公众号回复图文消息
102 | """
103 |
104 | allow_key_list = ['to_user', 'from_user', 'create_time', 'item_list']
105 |
106 | xml_str_template = """
107 |
108 |
109 |
110 | {create_time}
111 |
112 | {article_count}
113 | {articles}
114 |
115 | """
116 |
117 | item_str_template = """
118 | -
119 |
120 |
121 |
122 |
123 |
124 | """
125 |
126 | def __str__(self):
127 | item_list = self.item_list
128 | article_count = len(item_list)
129 |
130 | articles = ''
131 | for item in item_list:
132 | articles += self.item_str_template.format(**item)
133 |
134 | d = {
135 | 'from_user': self.from_user,
136 | 'to_user': self.to_user,
137 | 'create_time': self.create_time,
138 | 'article_count': article_count,
139 | 'articles': articles
140 | }
141 | return self.xml_str_template.format(**d)
142 |
--------------------------------------------------------------------------------
/wechat/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 |
--------------------------------------------------------------------------------
/wechat/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: UTF-8 -*-
2 |
3 | import logging
4 |
5 | logger = logging.getLogger('wechat')
6 |
--------------------------------------------------------------------------------
/wechat2.egg-info/PKG-INFO:
--------------------------------------------------------------------------------
1 | Metadata-Version: 1.1
2 | Name: wechat2
3 | Version: 1.0.0
4 | Summary: Wechat Python SDK
5 | Home-page: https://github.com/lyroge/wechat_sdk
6 | Author: songzhengang
7 | Author-email: lyroge@foxmail.com
8 | License: MIT
9 | Description: 微信公众平台SDK
10 | =================
11 |
12 | 项目背景
13 | --------
14 | 从2014年开始玩微信公众平台,试用过其中大多数的功能,如:消息回复、自定义菜单、公众号中的支付,页面授权等。之前的程序中都是直接调用公众平台的接口,这样复用功能无法实现。现将功能独立出单独模块
15 |
16 | 目前完成
17 | -----------
18 | * 获取access_token方法
19 | * 获取微信服务器IP地址
20 | * 自定义菜单中的查询、创建、删除(不包括个性化菜单接口)
21 | * 消息管理中的接收普通消息、接收事件推送
22 | * 消息管理中的被动回复用户消息
23 | * 添加tornado代码的demo实例
24 |
25 | 使用示例
26 | -----------
27 |
28 | 获取access_token方法
29 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 |
31 | ::
32 |
33 | from wechat.base import get_access_token_dict
34 |
35 | get_access_token_dict(APPID, APPSECRET)
36 |
37 |
38 | 消息处理基类
39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40 |
41 | 继承基础的消息处理类BaseHandler, 重写对应方法即可。 如文本、图片、视频等对应的处理方法分别问on_text、on_image、on_video。
42 |
43 | ::
44 |
45 | from wechat.message import *
46 |
47 | class MessageHandler(BaseHandler):
48 |
49 | def on_text(self, xml_dict):
50 |
51 | from_user = xml_dict['FromUserName']
52 | to_user = xml_dict['ToUserName']
53 | create_time = xml_dict['CreateTime']
54 | content = xml_dict['Content']
55 |
56 | text_response = TextResponse(from_user=to_user, to_user=from_user, create_time=create_time, content=content)
57 | return text_response
58 |
59 |
60 | 自定义菜单接口
61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 |
63 | ::
64 |
65 | from wechat.menu.client import Client
66 |
67 | client = Client(access_token['access_token'])
68 |
69 | # 创建菜单
70 | client.create_menu(data)
71 |
72 | # 获取菜单
73 | client.get_menu()
74 |
75 | # 删除菜单
76 | client.delete_menu()
77 |
78 |
79 | 跑单元测试
80 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
81 |
82 | ::
83 |
84 | nosetests --with-coverage --cover-package=wechat
85 |
86 |
87 | 下一步计划
88 | -------------
89 | 1. 继续补充其他常用接口
90 |
91 | **感兴趣的同学可以加入到项目中一起完善**
92 |
93 | Keywords: wechat
94 | Platform: UNKNOWN
95 | Classifier: License :: OSI Approved :: MIT License
96 | Classifier: Programming Language :: Python
97 | Classifier: Intended Audience :: Developers
98 | Classifier: Operating System :: OS Independent
99 |
--------------------------------------------------------------------------------
/wechat2.egg-info/SOURCES.txt:
--------------------------------------------------------------------------------
1 | README.rst
2 | setup.py
3 | wechat/__init__.py
4 | wechat/base.py
5 | wechat/base_client.py
6 | wechat/constants.py
7 | wechat/core.py
8 | wechat/settings.py
9 | wechat/utils.py
10 | wechat/menu/__init__.py
11 | wechat/menu/client.py
12 | wechat/message/__init__.py
13 | wechat/message/hander.py
14 | wechat/message/response.py
15 | wechat2.egg-info/PKG-INFO
16 | wechat2.egg-info/SOURCES.txt
17 | wechat2.egg-info/dependency_links.txt
18 | wechat2.egg-info/top_level.txt
19 | wechat2.egg-info/zip-safe
--------------------------------------------------------------------------------
/wechat2.egg-info/dependency_links.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/wechat2.egg-info/top_level.txt:
--------------------------------------------------------------------------------
1 | wechat
2 |
--------------------------------------------------------------------------------
/wechat2.egg-info/zip-safe:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------