├── .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 | <![CDATA[公众平台官网链接]]> 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 | <![CDATA[{title}]]> 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 | --------------------------------------------------------------------------------