Spaces:
Sleeping
Sleeping
File size: 92,763 Bytes
0b57eb8 edd00fa 0b57eb8 edd00fa 0b57eb8 edd00fa 0b57eb8 140cf1a 0b57eb8 edd00fa 0b57eb8 edd00fa 0b57eb8 207741a 0c2fccf 207741a 0b57eb8 4dc59b6 cb1e669 f72d601 65eddf7 f72d601 86dc14c f72d601 bfb3f38 98818d4 2b95c4f 98818d4 0c2fccf 850eb27 ca4eb04 a1d3cd4 ca4eb04 1fee37e d46c094 ca4eb04 dba8c30 98818d4 86dc14c 07192c6 f72d601 5c2a467 f72d601 5c2a467 f72d601 1678e8c 0517bea dba8c30 f72d601 1678e8c f101e9d d3839ee 9bc1664 f72d601 8f79838 f72d601 58cae0c 6e5ec17 1d05f9c eddae4a 1258d21 249c69e 14af27b 1258d21 1d05f9c 40c4f6f 68dc3b2 1258d21 1d05f9c 722b2a4 d140f2b 4f85554 e7cc23e ae929a2 b21ec28 4f85554 485c492 4f85554 283f5a6 4f85554 9fd04af d6656be f36fae6 069a32b fbf6b7e 4f85554 b21ec28 8a21ffb b21ec28 1fee37e 966ba02 b21ec28 4f85554 b21ec28 f101e9d 5dddc13 ca4eb04 166e6c3 b21ec28 1fee37e d3839ee 0dda6c9 5dddc13 a1d3cd4 5dddc13 1fee37e 966ba02 5dddc13 b21ec28 5dddc13 166e6c3 5dddc13 166e6c3 fbb92d3 a1d3cd4 07192c6 1fee37e 966ba02 07192c6 d3839ee 5c3ffd5 1fee37e 966ba02 5c3ffd5 a1d3cd4 5c3ffd5 0dda6c9 166e6c3 5dddc13 b21ec28 1fee37e 7ed8c2a b21ec28 f101e9d b21ec28 3dcddfb b57de85 1fee37e b57de85 a1d3cd4 b57de85 4514309 966ba02 b57de85 1fee37e b57de85 1fee37e b57de85 a1d3cd4 b57de85 4514309 966ba02 b57de85 7ed8c2a 52e8fd4 ea6e56a a1d3cd4 ea6e56a 1fee37e 966ba02 ea6e56a 6572a47 ea6e56a 1fee37e 966ba02 ea6e56a a1d3cd4 ea6e56a 3dcddfb 0a95758 4ae6186 fef9f64 b21ec28 d6620de b21ec28 3d8f737 4ae6186 0a95758 c3b46bf fef9f64 afce8ea b21ec28 d6620de 115e1c7 c3b46bf b21ec28 fef9f64 c3b46bf 7d236d5 4ae6186 4cb190b cb1e669 d5377f0 4ef2d29 7b226b1 cb1e669 377d960 4cb190b cb1e669 4cb190b b18bf1e 7b226b1 4cb190b 664ba39 a13abe4 664ba39 e3173e9 664ba39 1ccf99a 664ba39 1ccf99a 664ba39 3475ab9 664ba39 1ccf99a 664ba39 1ccf99a 664ba39 ddde3f9 ae93c3d ddde3f9 664ba39 4cb190b 8ab0f21 a13abe4 88a38c2 eaedfbd 88a38c2 14be26d 96e4ac8 3c89330 1747067 62f3dfd 58b1dae 1747067 3c89330 58b1dae 1747067 58b1dae 1747067 3c89330 1747067 89f4322 58b1dae 1747067 3c89330 58b1dae 1747067 d640d5d 1747067 3c89330 1747067 62f3dfd 58b1dae 1747067 58b1dae 1747067 3c89330 ae929a2 1747067 3c89330 1747067 3c89330 1747067 3c89330 1747067 3c89330 1747067 fbf6b7e 1747067 3c89330 1747067 3c89330 a13abe4 ae929a2 1747067 3c89330 1747067 3c89330 0d7e504 59a0954 3c89330 1747067 3c89330 1747067 3c89330 1747067 42aa29c a13abe4 42aa29c 3c89330 1747067 3c89330 1747067 f85174e 1747067 3c89330 a13abe4 ae929a2 3c89330 ae929a2 3c89330 58b1dae a9b5693 e24a426 214e460 2db0a09 214e460 2db0a09 214e460 77c2c3d 214e460 2db0a09 214e460 d1209d9 214e460 2db0a09 214e460 caee1e3 214e460 2db0a09 214e460 afcd783 caee1e3 15fb9dc caee1e3 15fb9dc a13abe4 15fb9dc a13abe4 caee1e3 af5289c 15fb9dc caee1e3 15fb9dc caee1e3 15fb9dc caee1e3 15fb9dc caee1e3 15fb9dc a66cca9 afcd783 6d903b7 0098ed2 ebd30de 0098ed2 e76b673 0098ed2 e76b673 0098ed2 ebd30de 0098ed2 ebd30de 0098ed2 ebd30de 0098ed2 7daed27 afcd783 7daed27 afcd783 7daed27 afcd783 0c591a4 afcd783 0c591a4 afcd783 7daed27 98818d4 0c591a4 7daed27 98818d4 afcd783 18b0c17 b1c6659 18b0c17 74a1798 646c24f 74a1798 646c24f 74a1798 fd512a4 35ef4da 7fec04d c44ba87 da12f0e c44ba87 da12f0e bc7d848 ae1be39 74a1798 529f9a3 74a1798 ae1be39 5d2ecb7 74a1798 ae1be39 74a1798 ae1be39 74a1798 c44ba87 da12f0e afcd783 f873c75 afcd783 18b0c17 646c24f b4a46b9 cc4bfb3 b4a46b9 7c20dfd b4a46b9 047014e b4a46b9 047014e b4a46b9 c9bf30b a66cca9 d71b420 e24a426 a66cca9 d71b420 a66cca9 d71b420 a66cca9 3985ad4 d71b420 a66cca9 d71b420 a66cca9 86dc14c a2865f4 86dc14c a2865f4 86dc14c a2865f4 86dc14c 2177d18 86dc14c a2865f4 86dc14c 10f9364 4bc9cdd 10f9364 86dc14c 10f9364 4bc9cdd 10f9364 86dc14c 4bc9cdd 2778dc2 e1e212f a467949 e1e212f 89f0d6c 86dc14c a467949 e64e4fa a467949 7d2a30b a467949 5900831 a7ef25d a467949 84beeb9 5ec2795 84beeb9 cb2ea8d a467949 04f73fa 84beeb9 cb2ea8d 7d2a30b cb2ea8d a467949 cb2ea8d 04f73fa 84beeb9 cb2ea8d e1e212f 84beeb9 cb2ea8d 3fdb523 a467949 84beeb9 cb2ea8d 4ef5950 a467949 86dc14c 65f4a89 2778dc2 7c20dfd 3f5f4c6 8fbbaa1 3f5f4c6 8fbbaa1 3f5f4c6 7c05e0e 803e880 13eff4c 3f5f4c6 2778dc2 e21ae09 3f5f4c6 96892be 2778dc2 3f5f4c6 2778dc2 3f5f4c6 2778dc2 92208a8 2778dc2 8083309 2778dc2 3f5f4c6 9bf129d a85dae8 9bf129d a85dae8 971ae28 a85dae8 9bf129d a85dae8 971ae28 a85dae8 9bf129d 9e47530 fc8452e 9e47530 a85dae8 971ae28 a85dae8 9bf129d a85dae8 9bf129d a85dae8 9bf129d a85dae8 9bf129d 2778dc2 86dc14c 8fbbaa1 f71bf59 8d8aca8 02af0ea 8d8aca8 02af0ea 86dc14c 02af0ea 8d8aca8 02af0ea 86dc14c 02af0ea 8d8aca8 02af0ea 002f52f 02af0ea 002f52f 02af0ea 002f52f 86dc14c 15fb9dc 4dc59b6 f71bf59 e24a426 f71bf59 4dc59b6 f71bf59 4dc59b6 f71bf59 4dc59b6 15fb9dc 8ab0f21 df450da 214e460 8ab0f21 df450da efb748b fbf6b7e df450da 9fd04af fbf6b7e df450da 8ab0f21 df450da 8ab0f21 fcc9d8d 95de908 dbdcf4a 1747067 dbdcf4a fbf6b7e dbdcf4a 2eb948a 95de908 13dbf68 3adb4d2 fbf6b7e 3adb4d2 2eb948a 3adb4d2 2eb948a 8ab0f21 13dbf68 fbf6b7e 13dbf68 233aec1 13dbf68 8ab0f21 df450da 214e460 d140f2b fe727bb f1e11eb fe727bb 79ecbe0 454b4e5 79ecbe0 bfef43e 79ecbe0 fe727bb 79ecbe0 fe727bb aa378f4 fe727bb 66f07ae fe727bb 40c4f6f d15dd0e b9df598 d15dd0e 6d5cbcf d15dd0e 40c4f6f d15dd0e 14af27b 40c4f6f 14af27b 2138b6a 6d5cbcf d15dd0e 5fa984e 4e3dd9f 9ede144 b9757a6 5fa984e 4e3dd9f 77bdeeb 4e3dd9f eddae4a 7c075ee eddae4a e2705c5 eddae4a 7c075ee eddae4a 4e3dd9f 1258d21 f162261 1258d21 f162261 1258d21 f162261 1258d21 f162261 1258d21 f162261 1258d21 f162261 1258d21 f162261 1258d21 e976dab 7f021d7 0753094 080f0bc 0e8b5e0 f162261 0e8b5e0 d839796 0e8b5e0 94d02ff 339633c 0e8b5e0 a06db30 5c1f5e0 b020cb6 476d14b 86651b8 476d14b 6e987e4 b020cb6 6e987e4 7c09f2e f2a3d9d f7c783f c1bf40b 0c7f4d2 8cdf664 dce6748 e9355d2 f7c783f dce6748 f7c783f dce6748 4ebc711 f2a3d9d a06db30 339633c 78943ff 339633c a06db30 339633c a06db30 339633c a06db30 339633c a06db30 339633c a06db30 339633c a06db30 f162261 9ba3e78 0f0488a 9ba3e78 f162261 d839796 f162261 0e8b5e0 96393fc 080f0bc eeec488 739886f 8d74358 eeec488 8d74358 eeec488 080f0bc d56557b f18f04b 96393fc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 |
from flask import Flask, request, render_template_string, render_template, jsonify, Response, send_from_directory, render_template
import sqlite3
import os
import random
import requests
import time
import re
import json
import logging
import csv
import io
from urllib.parse import quote, urlparse, parse_qsl, urlencode, parse_qs, unquote, urlencode
import string
from datetime import datetime
import pytz
import socket
from unidecode import unidecode
import uuid
import shutil
import psutil
import hashlib
import hmac
from base64 import b64encode
from collections import OrderedDict
from hashlib import sha256
from hmac import HMAC
from base import replace_null_with_empty_string
from webhook_handler import handle_webhook
# Замените на ваш реальный ключ Системы
api_key_sys = os.getenv('api_key_sys')
ALLOWED_ORIGIN = "https://diamonik7777-up-fail.hf.space"
# Глобальные переменные для хранения настроек
api_key_auth = ''
api_key_apps_vk = ''
vk_api_key = ''
vk_st_alone = ''
key_callback_vk = ''
senler_token = ''
wa_ak = ''
wa_api_key = ''
curators = ''
call_api_key = ''
# Замените на ваш реальный access_token СЕНДЛЕРА
import logging
logging.basicConfig(level=logging.DEBUG)
# Глобальная переменная для управления верификацией
current_curator_index = 0
verifikation_start = "1" # Глобальная переменная для управления верификацией
curator_on_off = "0" # Глобальная переменная для управления назначением куратора
# curators = ["Anna", "Ekaterina", "Ivan", "Maria", "Sergey", "Olga", "Alex", "Natalia", "Dmitry", "Elena"]
# Глобальная переменная для
wa_url = os.getenv('wa_url')
ws_url_mes = "/sendMessage/"
ws_url_ver = "/checkWhatsapp/"
app = Flask(__name__, template_folder="./")
app.config['DEBUG'] = True
UPLOAD_FOLDER = 'static'
UPLOAD_FOLDER_VK = 'static'
HTML_FOLDER = 'html'
HTML_FOLDER_VK = 'html'
# Создание директорий, если они не существуют
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
if not os.path.exists(UPLOAD_FOLDER_VK):
os.makedirs(UPLOAD_FOLDER_VK)
if not os.path.exists(HTML_FOLDER):
os.makedirs(HTML_FOLDER)
if not os.path.exists(HTML_FOLDER_VK):
os.makedirs(HTML_FOLDER_VK)
DATABASES = ['data_gc.db', 'site_data.db', 'ws_data.db', 'vk_data.db', 'tg_data.db', 'gk_data.db']
SETTINGS_DB = 'settings.db'
def init_db(db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
phone TEXT,
email TEXT,
vk_id TEXT,
chat_id TEXT,
ws_st TEXT,
ws_stop TEXT,
web_st INTEGER,
fin_prog INTEGER,
b_city TEXT,
b_fin TEXT,
b_ban TEXT,
b_ign TEXT,
b_baners TEXT,
b_butt TEXT,
b_mess TEXT,
orders TEXT,
curator TEXT,
bonus TEXT,
shop_status TEXT,
answers TEXT,
quiz TEXT,
kassa TEXT,
gc_url TEXT,
key_pr TEXT,
n_con TEXT,
canal TEXT,
data_on TEXT,
data_t TEXT,
utm_source TEXT,
utm_medium TEXT,
utm_campaign TEXT,
utm_term TEXT,
utm_content TEXT,
gcpc TEXT
)
''')
conn.commit()
conn.close()
def init_settings_db():
conn = sqlite3.connect(SETTINGS_DB)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
api_key_auth TEXT,
api_key_apps_vk TEXT,
vk_api_key TEXT,
vk_st_alone TEXT,
key_callback_vk TEXT,
senler_token TEXT,
wa_ak TEXT,
wa_api_key TEXT,
curators TEXT,
call_api_key TEXT
)
''')
conn.commit()
conn.close()
for db in DATABASES:
init_db(db)
init_settings_db()
DATABASE_NEW = 'data_gc.db'
def load_settings():
global api_key_auth, api_key_apps_vk, vk_api_key, vk_st_alone, key_callback_vk
global senler_token, wa_ak, wa_api_key, curators, call_api_key
default_settings = {
'api_key_auth': '',
'api_key_apps_vk': '',
'vk_api_key': '',
'vk_st_alone': '',
'key_callback_vk': '',
'senler_token': '',
'wa_ak': '',
'wa_api_key': '',
'curators': '',
'call_api_key': ''
}
# Загрузка данных из базы
with sqlite3.connect(SETTINGS_DB) as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM settings')
settings = cursor.fetchone()
if settings is None:
settings = default_settings
else:
settings = {key: settings[i + 1] or '' for i, key in enumerate(default_settings)}
# Заполняем глобальные переменные
api_key_auth = settings['api_key_auth']
api_key_apps_vk = settings['api_key_apps_vk']
vk_api_key = settings['vk_api_key']
vk_st_alone = settings['vk_st_alone']
key_callback_vk = settings['key_callback_vk']
senler_token = settings['senler_token']
wa_ak = settings['wa_ak']
wa_api_key = settings['wa_api_key']
curators = settings['curators']
call_api_key = settings['call_api_key']
# Выводим значения в консоль
print(f"wa_api_key: {wa_api_key}")
print(f"wa_ak: {wa_ak}")
print(f"vk_api_key: {vk_api_key}")
print(f"vk_st_alone: {vk_st_alone}")
print(f"key_callback_vk: {key_callback_vk}")
print(f"senler_token: {senler_token}")
print(f"api_key_auth: {api_key_auth}")
print(f"api_key_apps_vk: {api_key_apps_vk}")
print(f"curators: {curators}")
print(f"call_api_key: {call_api_key}")
# Возвращаем настройки, чтобы использовать в API-роуте
return settings
# Запуск функции для инициализации глобальных переменных при старте сервера
load_settings()
def save_settings(settings_dict):
global api_key_auth, api_key_apps_vk, vk_api_key, vk_st_alone, key_callback_vk
global senler_token, wa_ak, wa_api_key, curators, call_api_key
# Удаляем api_key_sys из словаря перед сохранением
if 'api_key_sys' in settings_dict:
del settings_dict['api_key_sys']
conn = sqlite3.connect(SETTINGS_DB)
cursor = conn.cursor()
# Проверка существования записи
cursor.execute('SELECT id FROM settings LIMIT 1')
settings_exist = cursor.fetchone() is not None
if settings_exist:
# Обновляем запись
cursor.execute('''
UPDATE settings SET
api_key_auth = ?, api_key_apps_vk = ?, vk_api_key = ?, vk_st_alone = ?, key_callback_vk = ?,
senler_token = ?, wa_ak = ?, wa_api_key = ?, curators = ?, call_api_key = ?
''', (
settings_dict.get('api_key_auth', ''),
settings_dict.get('api_key_apps_vk', ''),
settings_dict.get('vk_api_key', ''),
settings_dict.get('vk_st_alone', ''),
settings_dict.get('key_callback_vk', ''),
settings_dict.get('senler_token', ''),
settings_dict.get('wa_ak', ''),
settings_dict.get('wa_api_key', ''),
settings_dict.get('curators', ''),
settings_dict.get('call_api_key', '')
))
else:
# Создаем новую запись
cursor.execute('''
INSERT INTO settings (
api_key_auth, api_key_apps_vk, vk_api_key, vk_st_alone, key_callback_vk, senler_token,
wa_ak, wa_api_key, curators, call_api_key
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
settings_dict.get('api_key_auth', ''),
settings_dict.get('api_key_apps_vk', ''),
settings_dict.get('vk_api_key', ''),
settings_dict.get('vk_st_alone', ''),
settings_dict.get('key_callback_vk', ''),
settings_dict.get('senler_token', ''),
settings_dict.get('wa_ak', ''),
settings_dict.get('wa_api_key', ''),
settings_dict.get('curators', ''),
settings_dict.get('call_api_key', '')
))
conn.commit()
conn.close()
# Обновляем глобальные переменные
api_key_auth = settings_dict.get('api_key_auth', '')
api_key_apps_vk = settings_dict.get('api_key_apps_vk', '')
vk_api_key = settings_dict.get('vk_api_key', '')
vk_st_alone = settings_dict.get('vk_st_alone', '')
key_callback_vk = settings_dict.get('key_callback_vk', '')
senler_token = settings_dict.get('senler_token', '')
wa_ak = settings_dict.get('wa_ak', '')
wa_api_key = settings_dict.get('wa_api_key', '')
curators = settings_dict.get('curators', '')
call_api_key = settings_dict.get('call_api_key', '')
# Выводим значения в консоль
print(f"wa_api_key: {wa_api_key}")
print(f"wa_ak: {wa_ak}")
print(f"vk_api_key: {vk_api_key}")
print(f"vk_st_alone: {vk_st_alone}")
print(f"key_callback_vk: {key_callback_vk}")
print(f"senler_token: {senler_token}")
print(f"api_key_auth: {api_key_auth}")
print(f"api_key_apps_vk: {api_key_apps_vk}")
print(f"curators: {curators}")
print(f"call_api_key: {call_api_key}")
@app.route('/load_settings', methods=['POST'])
def get_settings():
# Получаем ключ авторизации из запроса
client_api_key_sys = request.json.get('api_key_sys')
# Проверка ключа авторизации
if client_api_key_sys != os.getenv('api_key_sys'):
return jsonify({"error": "Unauthorized access"}), 403
# Загружаем настройки из базы данных
settings = load_settings()
return jsonify(settings)
@app.route('/save_settings', methods=['POST'])
def save_settings_route():
# Получаем ключ авторизации из запроса
client_api_key_sys = request.json.get('api_key_sys')
# Проверка ключа авторизации
if client_api_key_sys != os.getenv('api_key_sys'):
return jsonify({"error": "Unauthorized access"}), 403
data = request.json.get('data', {})
if data:
# Выводим полученные данные в консоль сервера
print("Received data from page:", data)
save_settings(data)
return jsonify({'status': 'success'})
else:
return jsonify({'status': 'error', 'message': 'No data provided'}), 400
@app.route('/set')
def index_set():
return render_template('settings.html')
# Имя базы данных
db_name = 'data_gc.db'
@app.route('/vk_webhook', methods=['POST'])
def vk_webhook():
data = request.json
# Логируем полученные данные для отладки
print(f"Received data: {data}")
# Обрабатываем вебхук с помощью функции из webhook_handler.py
response = handle_webhook(data, key_callback_vk, db_name, vk_st_alone)
# Возвращаем ответ ВКонтакте
return response
# Пример вызова функции handle_webhook с передачей vk_st_alone
def process_webhook(data):
response = handle_webhook(data, key_callback_vk, 'your_db_name.db', vk_st_alone)
print(response)
# Пример вызова функции process_webhook с данными из вебхука
# Этот код будет выполняться, когда ВКонтакте вызовет ваш вебхук
# data будет содержать данные, пришедшие из вебхука
# process_webhook(data)
mapping_template = {
"username": "name",
"phone": "phone",
"email": "email",
"city": "b_city",
"finished": "b_fin",
"ban": "b_ban",
"ignore": "b_ign",
"banners": "b_baners",
"buttons": "b_butt",
"messages": "b_mess"
}
def get_db_connection_user():
conn = sqlite3.connect(DATABASE_NEW)
conn.row_factory = sqlite3.Row
return conn
@app.route('/user_db_set', methods=['GET'])
def get_user():
# Проверка API-ключа
api_key_sys_control = request.args.get('api_sys')
if api_key_sys_control != api_key_sys:
return jsonify({"error": "Invalid API key"}), 403
email = request.args.get('email')
vk_id = request.args.get('vk_id')
phone = request.args.get('phone')
if not email and not vk_id and not phone:
return jsonify({"error": "Either email, vk_id, or phone must be provided"}), 400
conn = get_db_connection_user()
cursor = conn.cursor()
query = "SELECT * FROM contacts WHERE "
params = []
if email:
query += "email = ?"
params.append(email)
if email and (vk_id or phone):
query += " OR "
if vk_id:
query += "vk_id = ?"
params.append(vk_id)
if vk_id and phone:
query += " OR "
if phone:
query += "phone = ?"
params.append(phone)
cursor.execute(query, params)
user = cursor.fetchone()
conn.close()
if user:
user_dict = dict(user)
# Преобразование строки "orders" в JSON-объект
if 'orders' in user_dict and isinstance(user_dict['orders'], str):
try:
user_dict['orders'] = json.loads(user_dict['orders'])
except json.JSONDecodeError:
user_dict['orders'] = {} # Если не удалось преобразовать, устанавливаем пустой объект
# Преобразование строки "bonus" в JSON-объект
if 'bonus' in user_dict and isinstance(user_dict['bonus'], str):
try:
user_dict['bonus'] = json.loads(user_dict['bonus'])
except json.JSONDecodeError:
user_dict['bonus'] = {} # Если не удалось преобразовать, устанавливаем пустой объект
return jsonify(user_dict)
else:
return jsonify({"error": "User not found"}), 404
# Отдаем дату онлайн
@app.route('/get_current_time', methods=['GET'])
def get_current_time():
utc_now = datetime.utcnow()
msk_tz = pytz.timezone('Europe/Moscow')
msk_now = utc_now.replace(tzinfo=pytz.utc).astimezone(msk_tz)
current_time = msk_now.isoformat(timespec='microseconds')
return jsonify({'current_time': current_time})
# Функция для очистки номера телефона
def clean_phone_number_ss(phone_number):
return re.sub(r'\D', '', phone_number)
# Добавляем пользователя
mt_site = {
'name': 'name',
'phone': 'phone',
'email': 'email',
'utm_campaign': 'utm_campaign',
'utm_content': 'utm_content',
'utm_medium': 'utm_medium',
'utm_source': 'utm_source',
'utm_term': 'utm_term',
'gcpc': 'gcpc'
}
mt_vk = {
'name': 'name',
'phone': 'phone',
'email': 'email',
'vk_id': 'vk_id',
'utm_campaign': 'utm_campaign',
'utm_content': 'utm_content',
'utm_medium': 'utm_medium',
'utm_source': 'utm_source',
'utm_term': 'utm_term',
'gcpc': 'gcpc'
}
mt_tg = {
'name': 'name',
'phone': 'phone',
'email': 'email',
'chat_id': 'chat_id',
'utm_campaign': 'utm_campaign',
'utm_content': 'utm_content',
'utm_medium': 'utm_medium',
'utm_source': 'utm_source',
'utm_term': 'utm_term',
'gcpc': 'gcpc'
}
mt_gc = {
'name': 'name',
'phone': 'phone',
'email': 'email',
'vk_id': 'vk_id',
'gc_url': 'gc_url',
'utm_campaign': 'utm_campaign',
'utm_content': 'utm_content',
'utm_medium': 'utm_medium',
'utm_source': 'utm_source',
'utm_term': 'utm_term',
'gcpc': 'gcpc'
}
mt_pass = {
'name': 'name',
'phone': 'phone',
'email': 'email',
'kol': 'pr1',
'pr2': 'pr2',
'gen_pass': 'pr5',
'utm_campaign': 'utm_campaign',
'utm_content': 'utm_content',
'utm_medium': 'utm_medium',
'utm_source': 'utm_source',
'utm_term': 'utm_term',
'gcpc': 'gcpc'
}
tl_quest = {
'name': 'name',
'phone': 'phone',
'email': 'email',
'pr2': 'pr2',
'utm_campaign': 'utm_campaign',
'utm_content': 'utm_content',
'utm_medium': 'utm_medium',
'utm_source': 'utm_source',
'utm_term': 'utm_term',
'gcpc': 'gcpc'
}
mapp_templates = {
'site': mt_site,
'vk': mt_vk,
'tg': mt_tg,
'gc': mt_gc,
'tilda': mt_pass,
'quest': tl_quest
}
def verify_phone_number(phone_number):
full_url_ver = f"{wa_url}{wa_ak}{ws_url_ver}{wa_api_key}"
payload = {"phoneNumber": phone_number}
headers = {'Content-Type': 'application/json'}
response = requests.post(full_url_ver, headers=headers, json=payload)
if response.status_code == 200:
response_body = response.json()
return response_body.get('existsWhatsapp', 'false')
else:
return "false"
def generate_password(length=8):
letters_and_digits = string.ascii_letters + string.digits
return ''.join(random.choice(letters_and_digits) for i in range(length))
def add_or_update_contact(contact_data, db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
email = contact_data.get('email')
if not email:
logging.error(f"Missing email in contact data: {contact_data}")
return
utc_now = datetime.utcnow()
msk_tz = pytz.timezone('Europe/Moscow')
msk_now = utc_now.replace(tzinfo=pytz.utc).astimezone(msk_tz)
contact_data['data_t'] = msk_now.strftime('%Y-%m-%d %H:%M:%S')
fields = [
'name', 'phone', 'email', 'vk_id', 'chat_id', 'ws_st', 'ws_stop', 'web_st', 'fin_prog',
'b_city', 'b_fin', 'b_ban', 'b_ign', 'b_baners', 'b_butt', 'b_mess', 'orders', 'curator',
'bonus', 'shop_status', 'answers', 'quiz', 'kassa', 'gc_url', 'key_pr', 'n_con', 'canal', 'data_on', 'data_t', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gcpc'
]
for field in fields:
if field not in contact_data:
contact_data[field] = ''
cursor.execute("SELECT id FROM contacts WHERE email = ?", (email,))
contact = cursor.fetchone()
if contact:
update_fields = [f"{field} = ?" for field in fields if contact_data[field] != '']
update_values = [contact_data[field] for field in fields if contact_data[field] != '']
update_values.append(contact[0])
update_query = f"UPDATE contacts SET {', '.join(update_fields)} WHERE id = ?"
cursor.execute(update_query, update_values)
else:
insert_query = f"INSERT INTO contacts ({', '.join(fields)}) VALUES ({', '.join(['?' for _ in fields])})"
cursor.execute(insert_query, tuple(contact_data[field] for field in fields))
conn.commit()
conn.close()
@app.route('/add_user_home', methods=['GET'])
def add_user_home():
global current_curator_index
veref_on_off = request.args.get('ver', '0')
curator_on_off = request.args.get('cur', '0')
db_name = request.args.get('db', 'data_gc.db') # Получаем имя базы данных из запроса
template_key = request.args.get('player', 'site')
mapping_template_cur = mapp_templates.get(template_key, mt_site)
user_data = {mapping_template_cur[key]: request.args.get(key, "") for key in mapping_template_cur}
logging.debug(f"Received data: {user_data}")
if curator_on_off == "1":
user_data['curator'] = curators[current_curator_index]
current_curator_index = (current_curator_index + 1) % len(curators)
else:
user_data['curator'] = user_data.get('curator', '')
if veref_on_off == "1":
phone_number = user_data.get('phone', '')
if not phone_number:
logging.error("Phone number is empty")
return jsonify({'status': 'error', 'message': 'Phone number is empty'}), 400
phone_verification_response = verify_phone_number(phone_number)
if phone_verification_response is not None:
user_data['ws_st'] = '1' if phone_verification_response else '0'
else:
user_data['ws_st'] = user_data.get('ws_st', '')
try:
add_or_update_contact(user_data, db_name)
return jsonify({'status': 'success', 'message': f'User added {user_data.get("curator", "not assigned")}'})
except Exception as e:
logging.error(f"Error adding user: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/user', methods=['GET'])
def user():
global current_curator_index
veref_on_off = request.args.get('ver', '0')
curator_on_off = request.args.get('cur', '0')
db_name = request.args.get('db', 'data_gc.db') # Получаем имя базы данных из запроса
template_key = request.args.get('player', 'site')
mapping_template_cur = mapp_templates.get(template_key, mt_site)
user_data = {mapping_template_cur[key]: request.args.get(key, "") for key in mapping_template_cur}
logging.debug(f"Received data: {user_data}")
if curator_on_off == "1":
user_data['curator'] = curators[current_curator_index]
current_curator_index = (current_curator_index + 1) % len(curators)
else:
user_data['curator'] = user_data.get('curator', '')
if veref_on_off == "1":
phone_number = user_data.get('phone', '')
if not phone_number:
logging.error("Phone number is empty")
return jsonify({'status': 'error', 'message': 'Phone number is empty'}), 400
phone_verification_response = verify_phone_number(phone_number)
if phone_verification_response is not None:
user_data['ws_st'] = '1' if phone_verification_response else '0'
else:
user_data['ws_st'] = user_data.get('ws_st', '')
try:
add_or_update_contact(user_data, db_name)
return jsonify({'status': 'success', 'message': f'User added {user_data.get("curator", "not assigned")}'})
except Exception as e:
logging.error(f"Error adding user: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500
@app.route('/add_user_mess', methods=['GET'])
def add_user_mess():
global current_curator_index
veref_on_off = request.args.get('ver', '0')
curator_on_off = request.args.get('cur', '0')
db_name = request.args.get('db', 'site_data.db') # Получаем имя базы данных из запроса
template_key = request.args.get('player', 'site')
mapping_template_cur = mapp_templates.get(template_key, mt_site)
user_data = {mapping_template_cur[key]: request.args.get(key, "") for key in mapping_template_cur}
logging.debug(f"Received data: {user_data}")
if curator_on_off == "1":
user_data['curator'] = curators[current_curator_index]
current_curator_index = (current_curator_index + 1) % len(curators)
else:
user_data['curator'] = user_data.get('curator', '')
if veref_on_off == "1":
phone_number = user_data.get('phone', '')
if not phone_number:
logging.error("Phone number is empty")
return jsonify({'status': 'error', 'message': 'Phone number is empty'}), 400
phone_verification_response = verify_phone_number(phone_number)
if phone_verification_response is not None:
user_data['ws_st'] = '1' if phone_verification_response else '0'
else:
user_data['ws_st'] = user_data.get('ws_st', '')
try:
add_or_update_contact(user_data, db_name)
return jsonify({'status': 'success', 'message': f'User added {user_data.get("curator", "not assigned")}'})
except Exception as e:
logging.error(f"Error adding user: {e}")
return jsonify({'status': 'error', 'message': str(e)}), 500
# Работа с ордером из сайта
@app.route('/wr_order', methods=['GET','POST'])
def shop_order_new():
try:
logging.debug("Starting shop_order_new")
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
logging.warning("Unauthorized access attempt")
return json.dumps({"error": "Unauthorized access"}), 403
name = request.args.get('name', '')
email = request.args.get('email', '')
phone = request.args.get('phone', '').lstrip('+')
order = request.args.get('order', '')
status = request.args.get('status', '')
del_flag = request.args.get('del', '')
if not email or not phone:
logging.error("Email and phone are required")
return json.dumps({"error": "Email and phone are required"}), 400
phone = clean_phone_number_ss(phone)
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
cursor.execute("SELECT * FROM contacts WHERE email = ? OR phone = ?", (email, phone))
result = cursor.fetchone()
if result:
shop_st = result[17] if result[17] else '{}'
shop_st_data = json.loads(shop_st)
logging.debug(f"Existing record found. Loaded JSON: {shop_st_data}")
else:
shop_st_data = {}
if del_flag == '1':
if order in shop_st_data:
del shop_st_data[order]
elif order and status:
shop_st_data[order] = status
shop_st_json = json.dumps(shop_st_data)
utc_now = datetime.utcnow()
msk_tz = pytz.timezone('Europe/Moscow')
msk_now = utc_now.replace(tzinfo=pytz.utc).astimezone(msk_tz)
data_on = msk_now.strftime('%Y-%m-%d %H:%M:%S')
columns_to_update = ['name', 'phone', 'email', 'orders', 'data_on']
values_to_update = [name, phone, email, shop_st_json, data_on]
if result:
set_clause = ', '.join([f"{col} = ?" for col in columns_to_update])
query = f"UPDATE contacts SET {set_clause} WHERE email = ? OR phone = ?"
cursor.execute(query, values_to_update + [email, phone])
else:
query = f"INSERT INTO contacts ({', '.join(columns_to_update)}) VALUES ({', '.join(['?' for _ in columns_to_update])})"
cursor.execute(query, values_to_update)
conn.commit()
replace_null_with_empty_string(conn)
conn.close()
return json.dumps(shop_st_data), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Данные формы из ВК
@app.route('/wr_form_vk', methods=['POST'])
def wr_form_vk():
try:
logging.debug("Starting wr_form_vk")
# Читаем параметры из POST-запроса (формы)
api_sys_control = request.form.get('api_sys')
if api_sys_control != api_key_sys:
logging.warning("Unauthorized access attempt")
return json.dumps({"error": "Unauthorized access"}), 403
name = request.form.get('name', '')
email = request.form.get('email', '')
phone = request.form.get('phone', '').lstrip('+')
vkid = request.form.get('vk_id', '')
if not email or not phone:
logging.error("Email and phone are required")
return json.dumps({"error": "Email and phone are required"}), 400
phone = clean_phone_number_ss(phone)
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
# Ищем по email, phone или vk_id
cursor.execute("SELECT * FROM contacts WHERE email = ? OR phone = ? OR vk_id = ?", (email, phone, vkid))
result = cursor.fetchone()
columns_to_update = ['name', 'phone', 'email', 'vk_id']
values_to_update = [name, phone, email, vkid]
if result:
set_clause = ', '.join([f"{col} = ?" for col in columns_to_update])
query = f"UPDATE contacts SET {set_clause} WHERE email = ? OR phone = ? OR vk_id = ?"
cursor.execute(query, values_to_update + [email, phone, vkid])
else:
query = f"INSERT INTO contacts ({', '.join(columns_to_update)}) VALUES ({', '.join(['?' for _ in columns_to_update])})"
cursor.execute(query, values_to_update)
conn.commit()
replace_null_with_empty_string(conn)
conn.close()
return json.dumps({"success": True}), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Запись ордера по ключу и ВК ИД
@app.route('/set_order_vk', methods=['POST'])
def set_order_vk():
try:
logging.debug("Starting wr_order_vk")
# Читаем параметры из POST-запроса
api_sys_control = request.form.get('api_sys')
if api_sys_control != api_key_sys:
logging.warning("Unauthorized access attempt")
return json.dumps({"error": "Unauthorized access"}), 403
vkid = request.form.get('vk_id', '')
order = request.form.get('order', '')
status = request.form.get('status', '')
del_flag = request.form.get('del', '')
n_con_flag = request.form.get('n_con', '')
if not vkid:
logging.error("VK ID is required")
return json.dumps({"error": "VK ID is required"}), 400
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
cursor.execute("SELECT * FROM contacts WHERE vk_id = ?", (vkid,))
result = cursor.fetchone()
if result:
shop_st = result[17] if result[17] else '{}'
shop_st_data = json.loads(shop_st)
logging.debug(f"Existing record found. Loaded JSON: {shop_st_data}")
else:
shop_st_data = {}
if del_flag == '1':
if order in shop_st_data:
del shop_st_data[order]
elif order and status:
shop_st_data[order] = status
shop_st_json = json.dumps(shop_st_data)
utc_now = datetime.utcnow()
msk_tz = pytz.timezone('Europe/Moscow')
msk_now = utc_now.replace(tzinfo=pytz.utc).astimezone(msk_tz)
data_on = msk_now.strftime('%Y-%m-%d %H:%M:%S')
columns_to_update = ['vk_id', 'orders', 'n_con', 'data_on']
values_to_update = [vkid, shop_st_json, n_con_flag, data_on]
if result:
set_clause = ', '.join([f"{col} = ?" for col in columns_to_update])
query = f"UPDATE contacts SET {set_clause} WHERE vk_id = ?"
cursor.execute(query, values_to_update + [vkid])
else:
query = f"INSERT INTO contacts ({', '.join(columns_to_update)}) VALUES ({', '.join(['?' for _ in columns_to_update])})"
cursor.execute(query, values_to_update)
conn.commit()
replace_null_with_empty_string(conn)
conn.close()
return json.dumps(shop_st_data), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Чтение ордера
@app.route('/get_order_monitop', methods=['GET'])
def get_order_monitop():
try:
logging.debug("Starting set_order_monitop")
# Читаем параметры из GET-запроса
vkid = request.args.get('vk_id', '')
order = request.args.get('order', '')
if not vkid or not order:
logging.error("VK ID and order are required")
return json.dumps({"error": "VK ID and order are required"}), 400
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
# Ищем запись по vk_id
cursor.execute("SELECT orders FROM contacts WHERE vk_id = ?", (vkid,))
result = cursor.fetchone()
# Получаем текущую дату и время на сервере
utc_now = datetime.utcnow()
msk_tz = pytz.timezone('Europe/Moscow')
msk_now = utc_now.replace(tzinfo=pytz.utc).astimezone(msk_tz)
current_time = msk_now.isoformat(timespec='microseconds')
# Если запись по vk_id не найдена, возвращаем значение "not" для ордера
if not result:
logging.error(f"VK ID {vkid} not found")
response = {order: 'not', 'online_date': current_time}
return jsonify(response), 200
shop_st = result[0] if result[0] else '{}'
shop_st_data = json.loads(shop_st)
logging.debug(f"Existing record found. Loaded JSON: {shop_st_data}")
# Ищем значение по ключу order
value = shop_st_data.get(order, 'not')
# Возвращаем данные из столбца и текущую дату и время
response = {order: value, 'online_date': current_time}
return jsonify(response), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Функция для валидации подписи ВК приложения ОБЩАЯ
def is_valid(*, query: dict, secret: str) -> bool:
"""Check VK Apps signature"""
# Отфильтровываем параметры, начинающиеся с "vk_"
vk_subset = OrderedDict(sorted((k, v) for k, v in query.items() if k.startswith("vk_")))
logging.debug(f"Filtered VK params: {vk_subset}")
# Объединяем параметры в строку
encoded_params = urlencode(vk_subset, doseq=True)
logging.debug(f"Encoded params: {encoded_params}")
# Вычисляем хеш-код с использованием HMAC и SHA256
hash_code = b64encode(HMAC(secret.encode(), encoded_params.encode(), sha256).digest())
decoded_hash_code = hash_code.decode('utf-8')[:-1].replace('+', '-').replace('/', '_')
logging.debug(f"Calculated signature: {decoded_hash_code}")
# Сравниваем с переданной подписью
return query.get("sign") == decoded_hash_code
# Функция для работы с базой данных
def get_order_from_db(vkid):
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
# Ищем запись по vk_id
cursor.execute("SELECT orders FROM contacts WHERE vk_id = ?", (vkid,))
result = cursor.fetchone()
logging.debug(f"Database result: {result}")
# Если запись по vk_id не найдена, возвращаем значение "not" для ордера
if not result:
logging.error(f"VK ID {vkid} not found")
return None
shop_st = result[0] if result[0] else '{}'
logging.debug(f"Shop_st: {shop_st}")
shop_st_data = json.loads(shop_st)
logging.debug(f"Existing record found. Loaded JSON: {shop_st_data}")
return shop_st_data
# Чтение ордера по ключу и ВК ИД для приложения
@app.route('/get_order', methods=['POST'])
def get_order():
try:
logging.debug("Starting get_order")
# Читаем параметры из POST-запроса
vkid = request.form.get('vk_id', '')
order = request.form.get('order', '')
apps_id = request.form.get('apps_id', '') # Сюда придёт ИД ВК приложения
fullUrl = request.form.get('fullUrl', '') # Полный URL, который выдаёт ВКонтакте
logging.debug(f"Received data: vk_id={vkid}, order={order}, apps_id={apps_id}, fullUrl={fullUrl}")
# Преобразуем строку в JSON
try:
api_key_apps_vk_dict = json.loads(api_key_apps_vk)
except json.JSONDecodeError as e:
logging.error(f"Error decoding JSON: {e}")
return jsonify({"status": "invalid"}), 200
# Проверка подписи для приложения
if str(apps_id) not in api_key_apps_vk_dict: # Приводим apps_id к строке
logging.error("Invalid apps_id")
return json.dumps({"error": "Invalid apps_id"}), 400
secret = api_key_apps_vk_dict[str(apps_id)] # Приводим apps_id к строке
logging.debug(f"Using secret: {secret}")
# Парсим полный URL для получения параметров запроса
query_params = dict(parse_qsl(urlparse(fullUrl).query, keep_blank_values=True))
logging.debug(f"Query params for signature check: {query_params}")
# Проверяем подпись
if not is_valid(query=query_params, secret=secret):
logging.error("Invalid signature")
return json.dumps({"error": "Invalid signature"}), 400
# Получаем данные из базы данных
shop_st_data = get_order_from_db(vkid)
if not shop_st_data:
response = {order: 'not'}
return jsonify(response), 200
# Ищем значение по ключу order
value = shop_st_data.get(order, 'not')
logging.debug(f"Value for order {order}: {value}")
# Возвращаем данные из столбца
response = {order: value}
return jsonify(response), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Функция для работы с базой данных - Чтение значения разрешена ли рассылка ВК
def get_lo_mess_from_db(vkid):
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
# Ищем запись по vk_id
cursor.execute("SELECT canal FROM contacts WHERE vk_id = ?", (vkid,))
result = cursor.fetchone()
logging.debug(f"Database result: {result}")
# Если запись по vk_id не найдена, возвращаем None
if not result:
logging.error(f"VK ID {vkid} not found")
return None
canal_data = result[0] if result[0] else '{}'
logging.debug(f"Canal data: {canal_data}")
canal_data_json = json.loads(canal_data)
logging.debug(f"Existing record found. Loaded JSON: {canal_data_json}")
return canal_data_json
# Чтение значения разрешена ли рассылка ВК из базы
@app.route('/get_lo_mess', methods=['POST'])
def getlo_mess():
try:
logging.debug("Starting get_lo_mess")
# Читаем параметры из POST-запроса
vkid = request.form.get('vk_id', '')
grup_id = request.form.get('grup_id', '') # Сюда придёт номер сообщества, который является ключом для поиска
apps_id = request.form.get('apps_id', '') # Сюда придёт ИД ВК приложения
fullUrl = request.form.get('fullUrl', '') # Полный URL, который выдаёт ВКонтакте
logging.debug(f"Received data: vk_id={vkid}, grup_id={grup_id}, apps_id={apps_id}, fullUrl={fullUrl}")
# Преобразуем строку в JSON
try:
api_key_apps_vk_dict = json.loads(api_key_apps_vk)
except json.JSONDecodeError as e:
logging.error(f"Error decoding JSON: {e}")
return jsonify({"status": "invalid"}), 200
# Проверка подписи для приложения
if str(apps_id) not in api_key_apps_vk_dict: # Приводим apps_id к строке
logging.error("Invalid apps_id")
return json.dumps({"error": "Invalid apps_id"}), 400
secret = api_key_apps_vk_dict[str(apps_id)] # Приводим apps_id к строке
logging.debug(f"Using secret: {secret}")
# Парсим полный URL для получения параметров запроса
query_params = dict(parse_qsl(urlparse(fullUrl).query, keep_blank_values=True))
logging.debug(f"Query params for signature check: {query_params}")
# Проверяем подпись
if not is_valid(query=query_params, secret=secret):
logging.error("Invalid signature")
return json.dumps({"error": "Invalid signature"}), 400
# Получаем данные из базы данных
canal_data_json = get_lo_mess_from_db(vkid)
if not canal_data_json:
response = {"status": "not"}
return jsonify(response), 200
# Ищем значение по ключу grup_id
value = canal_data_json.get(grup_id, 'not')
logging.debug(f"Value for grup_id {grup_id}: {value}")
# Возвращаем данные из столбца
response = {"status": value}
return jsonify(response), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Работа с бонусами из сайта без VK_ID
@app.route('/wr_bonus', methods=['GET','POST'])
def shop_bonus_new():
try:
logging.debug("Starting shop_bonus_new")
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
logging.warning("Unauthorized access attempt")
return json.dumps({"error": "Unauthorized access"}), 403
name = request.args.get('name', '')
email = request.args.get('email', '')
phone = request.args.get('phone', '').lstrip('+')
bonus = request.args.get('bonus', '')
status = request.args.get('status', '')
del_flag = request.args.get('del', '')
if not email or not phone:
logging.error("Email and phone are required")
return json.dumps({"error": "Email and phone are required"}), 400
phone = clean_phone_number_ss(phone)
conn = sqlite3.connect(DATABASE_NEW)
cursor = conn.cursor()
cursor.execute("SELECT * FROM contacts WHERE email = ? OR phone = ?", (email, phone))
result = cursor.fetchone()
if result:
bonus_st = result[19] if result[19] else '{}'
bonus_st_data = json.loads(bonus_st)
logging.debug(f"Existing record found. Loaded JSON: {bonus_st_data}")
else:
bonus_st_data = {}
if del_flag == '1':
if bonus in bonus_st_data:
del bonus_st_data[bonus]
elif bonus and status:
bonus_st_data[bonus] = status
bonus_st_json = json.dumps(bonus_st_data)
utc_now = datetime.utcnow()
msk_tz = pytz.timezone('Europe/Moscow')
msk_now = utc_now.replace(tzinfo=pytz.utc).astimezone(msk_tz)
data_on = msk_now.strftime('%Y-%m-%d %H:%M:%S')
columns_to_update = ['name', 'phone', 'email', 'bonus', 'data_on']
values_to_update = [name, phone, email, bonus_st_json, data_on]
if result:
set_clause = ', '.join([f"{col} = ?" for col in columns_to_update])
query = f"UPDATE contacts SET {set_clause} WHERE email = ? OR phone = ?"
cursor.execute(query, values_to_update + [email, phone])
else:
query = f"INSERT INTO contacts ({', '.join(columns_to_update)}) VALUES ({', '.join(['?' for _ in columns_to_update])})"
cursor.execute(query, values_to_update)
conn.commit()
replace_null_with_empty_string(conn)
conn.close()
return json.dumps(bonus_st_data), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return json.dumps({"error": str(e)}), 500
# Добавление пользователя в группу СЕНДЛЕРА
@app.route('/add_user_senler', methods=['POST'])
def add_user_senler():
# Получаем параметры из POST-запроса, если параметр отсутствует, устанавливаем значение пустой строки
vk_user_id = request.form.get('vk_user_id', "")
vk_group_id = request.form.get('vk_group_id', "")
subscription_id = request.form.get('sub_id', "")
utm_source = request.form.get('utm_source', "")
utm_medium = request.form.get('utm_medium', "")
utm_campaign = request.form.get('utm_campaign', "")
utm_content = request.form.get('utm_content', "")
utm_term = request.form.get('utm_term', "")
# Формируем данные для запроса к API Senler
senler_data = {
'vk_user_id': vk_user_id,
'vk_group_id': vk_group_id,
'subscription_id': subscription_id,
'utm_source': utm_source,
'utm_medium': utm_medium,
'utm_campaign': utm_campaign,
'utm_content': utm_content,
'utm_term': utm_term,
'access_token': senler_token,
'v': 2
}
# Отправляем запрос к API Senler
response = requests.post('https://senler.ru/api/subscribers/add', data=senler_data)
# Проверяем успешность запроса
success = response.status_code == 200 and response.json().get('success', False)
# Возвращаем результат
return jsonify({
'success': success
})
# Добавление пользователя в группу СЕНДЛЕРА с данными
@app.route('/add_user_senler_full', methods=['POST'])
def add_user_senler_full():
# Получаем параметры из POST-запроса, если параметр отсутствует, устанавливаем значение пустой строки
vk_user_id = request.form.get('vk_user_id', "")
vk_group_id = request.form.get('vk_group_id', "")
subscription_id = request.form.get('sub_id', "")
utm_source = request.form.get('utm_source', "")
utm_medium = request.form.get('utm_medium', "")
utm_campaign = request.form.get('utm_campaign', "")
utm_content = request.form.get('utm_content', "")
utm_term = request.form.get('utm_term', "")
name = request.form.get('name', "")
email = request.form.get('email', "")
phone = request.form.get('phone', "")
utms = request.form.get('utms', "")
# Формируем данные для запроса к API Senler для добавления пользователя
add_data = {
'vk_user_id': vk_user_id,
'vk_group_id': vk_group_id,
'subscription_id': subscription_id,
'utm_source': utm_source,
'utm_medium': utm_medium,
'utm_campaign': utm_campaign,
'utm_content': utm_content,
'utm_term': utm_term,
'access_token': senler_token,
'v': 2
}
# Отправляем запрос к API Senler для добавления пользователя
add_response = requests.post('https://senler.ru/api/subscribers/add', data=add_data)
print("Add Data:", add_data)
print("Add User Error Response:", add_response.json())
# Проверяем успешность добавления пользователя
if add_response.json().get('success'):
# Формируем данные для запроса к API Senler для установки name
name_data = {
'vk_user_id': vk_user_id,
'vk_group_id': vk_group_id,
'name': 'gb_name',
'value': name,
'access_token': senler_token,
'v': 2
}
# Формируем данные для запроса к API Senler для установки email
email_data = {
'vk_user_id': vk_user_id,
'vk_group_id': vk_group_id,
'name': 'gb_email',
'value': email,
'access_token': senler_token,
'v': 2
}
# Формируем данные для запроса к API Senler для установки телефона
phone_data = {
'vk_user_id': vk_user_id,
'vk_group_id': vk_group_id,
'name': 'gb_phone',
'value': phone,
'access_token': senler_token,
'v': 2
}
# Отправляем запрос к API Senler для установки name
name_response = requests.post('https://senler.ru/api/vars/set', data=name_data)
# Отправляем запрос к API Senler для установки email
email_response = requests.post('https://senler.ru/api/vars/set', data=email_data)
# Отправляем запрос к API Senler для установки телефона
phone_response = requests.post('https://senler.ru/api/vars/set', data=phone_data)
# Возвращаем результат
return jsonify({
'add_response': add_response.json(),
'name_response': name_response.json(),
'email_response': email_response.json(),
'phone_response': phone_response.json()
})
else:
# Возвращаем ошибку добавления пользователя
return jsonify(add_response.json())
# Проверка групп СЕНДЛЕРА на рассылку
@app.route('/get_Lo_Mess_senler', methods=['POST'])
def get_Lo_Mess_senler():
try:
# Получаем параметры из POST-запроса, если параметр отсутствует, устанавливаем значение пустой строки
vk_user_id = request.form.get('vk_user_id', "")
vk_group_id = request.form.get('vk_group_id', "") # Добавляем параметр vk_group_id
subscription_id = request.form.get('sub_id', "")
# Проверяем, что все необходимые параметры переданы
if not vk_user_id or not vk_group_id or not subscription_id:
return jsonify({"status": "error", "message": "Missing required parameters"}), 400
payload = {
"vk_user_id": [vk_user_id],
'vk_group_id': vk_group_id,
"access_token": senler_token,
"v": 2
}
# Выводим данные запроса для отладки
logging.debug(f"Request payload: {payload}")
# Выполняем запрос к API Senler
response = requests.post('https://senler.ru/api/subscribers/get', data=payload)
# Проверяем статус ответа
if response.status_code != 200:
logging.error(f"Failed to fetch data from Senler API: {response.status_code} - {response.text}")
return jsonify({"status": "error", "message": "Failed to fetch data from Senler API"}), 500
# Парсим ответ
data = response.json()
# Выводим полный ответ от сервера Senler в консоль
logging.debug(f"Senler API response: {data}")
# Проверяем, что ответ содержит данные
if not data.get('success'):
return jsonify({"status": "error", "message": "Failed to fetch data from Senler API"}), 500
# Проверяем, что пользователь подписан на указанную группу
user_subscriptions = data.get('items', [])
if not user_subscriptions:
return jsonify({"status": "not"}), 200
for user in user_subscriptions:
subscriptions = user.get('subscriptions', [])
for sub in subscriptions:
if sub.get('subscription_id') == int(subscription_id):
return jsonify({"status": "1"}), 200
# Если группа не найдена
return jsonify({"status": "not"}), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return jsonify({"status": "error", "message": str(e)}), 500
# Отписка пользователя от группы СЕНДЛЕРА
@app.route('/del_user_senler', methods=['POST'])
def del_user_senler():
try:
# Получаем параметры из POST-запроса, если параметр отсутствует, устанавливаем значение пустой строки
vk_user_id = request.form.get('vk_user_id', "")
vk_group_id = request.form.get('vk_group_id', "") # Добавляем параметр vk_group_id
subscription_id = request.form.get('sub_id', "")
# Проверяем, что все необходимые параметры переданы
if not vk_user_id or not vk_group_id or not subscription_id:
return jsonify({"status": "error", "message": "Missing required parameters"}), 400
# Преобразуем параметры в строки, если это необходимо
vk_user_id = int(vk_user_id)
vk_group_id = int(vk_group_id)
subscription_id = int(subscription_id)
payload = {
"vk_user_id": vk_user_id,
'vk_group_id': vk_group_id,
"subscription_id": subscription_id,
"access_token": senler_token,
"v": 2
}
# Выводим данные запроса для отладки
logging.debug(f"Request payload: {payload}")
# Выполняем запрос к API Senler для удаления подписки
response = requests.post('https://senler.ru/api/subscribers/del', data=payload)
# Проверяем статус ответа
if response.status_code != 200:
logging.error(f"Failed to delete subscription from Senler API: {response.status_code} - {response.text}")
return jsonify({"status": "error", "message": "Failed to delete subscription from Senler API"}), 500
# Парсим ответ
data = response.json()
# Выводим полный ответ от сервера Senler в консоль
logging.debug(f"Senler API response: {data}")
# Проверяем, что ответ содержит данные
if not data.get('success'):
return jsonify({"status": "error", "message": "Failed to delete subscription from Senler API"}), 500
# Возвращаем успешный ответ
return jsonify({"status": "success", "message": "Subscription deleted successfully"}), 200
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return jsonify({"status": "error", "message": str(e)}), 500
# ЗАПИСЫВАЕМ ДАННЫЕ В ВК СТОРИДЖ
# Функция для обработки ошибок
def error_finish(message):
return jsonify({'success': False, 'error_type': 'Setup error', 'error_msg': message}), 400
# Функция для отправки запроса
def sky_request(url, params):
response = requests.post(url, data=params)
return response.json()
@app.route('/vk_s_set', methods=['POST'])
def vk_s_set():
# Читаем контрольную переменную
api_sys_control = request.form.get('api_sys')
if api_sys_control != api_key_sys:
return "EUR 22", 200
# Читаем параметры
vk_id = request.form.get('vk_id', '')
vk_key = request.form.get('vk_key', '')
vk_value = request.form.get('vk_value', '')
# Проверка на наличие обязательных параметров
if not vk_id or not vk_key:
return "Missing required parameters", 400
# Формируем URL для вызова метода storage.set
url = f"https://api.vk.com/method/storage.set"
params = {
'access_token': vk_api_key,
'v': '5.131', # Версия API
'key': vk_key,
'value': vk_value,
'user_id': vk_id
}
# Выполняем запрос к API ВКонтакте
data = sky_request(url, params)
# Обрабатываем результат
if 'response' in data and data['response'] == 1:
return jsonify({vk_key: vk_value}), 200
elif 'error' in data:
error_code = data['error']['error_code']
error_msg = data['error']['error_msg']
return jsonify({"error": f"Error {error_code}: {error_msg}"}), 400
else:
return jsonify({"error": "Unknown error"}), 500
# ЗАПИСЫВАЕМ ДАННЫЕ В ВК СТОРИДЖ
@app.route('/vk_s_get', methods=['GET','POST'])
def vk_s_get():
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
return "EUR 22", 200
# Читаем параметры
vk_id = request.args.get('vk_id', '')
vk_key = request.args.get('vk_key', '')
# Проверка на наличие обязательных параметров
if not vk_id or not vk_key:
return "Missing required parameters", 400
# Формируем URL для вызова метода storage.get
url = f"https://api.vk.com/method/storage.get"
params = {
'access_token': vk_api_key,
'v': '5.131', # Версия API
'key': vk_key,
'user_id': vk_id
}
# Выполняем запрос к API ВКонтакте
response = requests.get(url, params=params)
data = response.json()
# Обрабатываем результат
if 'response' in data and data['response']:
value = data['response'][0]['value']
return jsonify({vk_key: value}), 200
elif 'error' in data:
error_code = data['error']['error_code']
error_msg = data['error']['error_msg']
return f"Error {error_code}: {error_msg}", 400
else:
return "Unknown error", 500
# Поднятие страницы с таблицей
@app.route('/data_gc_tab', methods=['GET'])
def data_gc_tab():
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
return "EUR 22", 200
return render_template('data_gc_tab.html')
# Данные в таблицу
@app.route('/data_gc_tab_out', methods=['GET'])
def data_gc_tab_out():
try:
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
return "EUR 22", 200
conn = sqlite3.connect('data_gc.db')
cursor = conn.cursor()
cursor.execute('''
SELECT id, name, phone, email, vk_id, chat_id, ws_st, ws_stop, web_st, fin_prog,
b_city, b_fin, b_ban, b_ign, b_baners, b_butt, b_mess, orders, curator,
bonus, shop_status, answers, quiz, kassa, gc_url, key_pr, n_con, canal, data_on, data_t, utm_source, utm_medium, utm_campaign, utm_term, utm_content, gcpc
FROM contacts
''')
contacts = cursor.fetchall()
conn.close()
contacts_json = [{
'id': contact[0], 'name': contact[1], 'phone': contact[2], 'email': contact[3],
'vk_id': contact[4], 'chat_id': contact[5], 'ws_st': contact[6], 'ws_stop': contact[7],
'web_st': contact[8], 'fin_prog': contact[9], 'b_city': contact[10], 'b_fin': contact[11],
'b_ban': contact[12], 'b_ign': contact[13], 'b_baners': contact[14], 'b_butt': contact[15],
'b_mess': contact[16], 'orders': contact[17], 'curator': contact[18], 'bonus': contact[19],
'shop_status': contact[20], 'answers': contact[21], 'quiz': contact[22], 'kassa': contact[23],
'gc_url': contact[24], 'key_pr': contact[25], 'n_con': contact[26], 'canal': contact[27],'data_on': contact[28],
'data_t': contact[29],'utm_source': contact[30], 'utm_medium': contact[31], 'utm_campaign': contact[32],
'utm_term': contact[33], 'utm_content': contact[34], 'gcpc': contact[34]
} for contact in contacts]
return jsonify(contacts_json), 200
except Exception as e:
error_message = f"Error getting data from data_gc: {e}"
print(error_message)
return error_message, 500
# Поднимаем страницу обновления базы
@app.route('/biz_v', methods=['GET'])
def biz_v():
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
return "EUR 22", 200
return render_template('biz_v.html')
# ОБНОВЛЯЕМ CSV-файла
DATABASE2 = 'data_gc.db'
def parse_csv_data(data):
parsed_data = []
for item in data:
for key, value in item.items():
headers = key.split(';')
row = value.split(';')
parsed_data.append(dict(zip(headers, row)))
return parsed_data
def insert_data(data, verify_phone, add_curator):
global current_curator_index
with sqlite3.connect(DATABASE2) as conn:
cursor = conn.cursor()
for row in data:
name = row.get('Name', '')
phone = row.get('Phone', '').lstrip('+')
email = row.get('Email', '')
data_t = row.get('Date', '').strip('"')
cursor.execute("SELECT 1 FROM contacts WHERE email = ? OR phone = ?", (email, phone))
user_exists = cursor.fetchone()
if user_exists:
print(f"User with email {email} or phone {phone} already exists. Skipping insert.")
continue
if add_curator == "1":
curator = curators[current_curator_index]
current_curator_index = (current_curator_index + 1) % len(curators)
else:
curator = row.get('curator', '')
if verify_phone == "1":
ws_st = verify_phone_number(phone)
else:
ws_st = row.get('ws_st', '')
columns = ['name', 'phone', 'email', 'vk_id', 'chat_id', 'ws_st', 'ws_stop', 'web_st', 'fin_prog', 'b_city', 'b_fin', 'b_ban', 'b_ign', 'b_baners', 'b_butt', 'b_mess', 'orders', 'curator', 'bonus', 'shop_status', 'answers', 'quiz', 'kassa', 'gc_url', 'key_pr', 'n_con', 'canal', 'data_on', 'data_t', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gcpc']
values = [name, phone, email, row.get('vk_id', ''), row.get('chat_id', ''), ws_st, row.get('ws_stop', ''), row.get('web_st', 0), row.get('fin_prog', 0), row.get('b_city', ''), row.get('b_fin', ''), row.get('b_ban', ''), row.get('b_ign', ''), row.get('b_baners', ''), row.get('b_butt', ''), row.get('b_mess', ''), row.get('orders', ''), curator, row.get('bonus', ''), row.get('shop_status', ''), row.get('answers', ''), row.get('quiz', ''), row.get('kassa', ''), row.get('gc_url', ''), row.get('key_pr', ''), row.get('n_con', ''), row.get('canal', ''), row.get('data_on', ''), row.get('data_t', ''), row.get('utm_source', ''), row.get('utm_medium', ''), row.get('utm_campaign', ''), row.get('utm_term', ''), row.get('utm_content', ''), row.get('gcpc', '')]
placeholders = ', '.join(['?' for _ in columns])
columns_str = ', '.join(columns)
query = f'''
INSERT INTO contacts ({columns_str})
VALUES ({placeholders})
'''
try:
cursor.execute(query, values)
except Exception as e:
print(f"Error inserting row: {row}")
print(f"Error message: {str(e)}")
conn.rollback()
raise
conn.commit()
@app.route('/upload_csv', methods=['POST'])
def upload_csv():
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if file and file.filename.endswith('.csv'):
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
csv_input = csv.DictReader(stream)
data = [row for row in csv_input]
parsed_data = parse_csv_data(data)
verify_phone = request.form.get('verify_phone', '0')
add_curator = request.form.get('add_curator', '0')
print(f"Verify Phone: {verify_phone}")
print(f"Add Curator: {add_curator}")
insert_data(parsed_data, verify_phone, add_curator)
return jsonify({"message": "Data uploaded and inserted successfully"})
return jsonify({"error": "Invalid file format"}), 400
# ОБНОВЛЯЕМ JSON-файла
DATABASE = 'data_gc.db'
# Функция для очистки номера телефона
def clean_phone_number_j(phone_number):
return re.sub(r'\D', '', phone_number)
# Функция для вставки данных в базу данных
def insert_data_j(data):
conn = sqlite3.connect(DATABASE) # Подключаемся к базе данных
cursor = conn.cursor()
for row in data:
name = row.get('name', '')
phone = row.get('phone', '').lstrip('+')
email = row.get('email', '')
data_t = row.get('data_t', '').strip('"')
# Очистка номера телефона
phone = clean_phone_number_j(phone)
cursor.execute("SELECT 1 FROM contacts WHERE email = ? OR phone = ?", (email, phone))
user_exists = cursor.fetchone()
if user_exists:
print(f"User with email {email} or phone {phone} already exists. Skipping insert.")
continue
columns = ['name', 'phone', 'email', 'vk_id', 'chat_id', 'ws_st', 'ws_stop', 'web_st', 'fin_prog', 'b_city', 'b_fin', 'b_ban', 'b_ign', 'b_baners', 'b_butt', 'b_mess', 'orders', 'curator', 'bonus', 'shop_status', 'answers', 'quiz', 'kassa', 'gc_url', 'key_pr', 'n_con', 'canal', 'data_on', 'data_t', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gcpc']
values = [name, phone, email, row.get('vk_id', ''), row.get('chat_id', ''), row.get('ws_st', ''), row.get('ws_stop', ''), row.get('web_st', 0), row.get('fin_prog', 0), row.get('b_city', ''), row.get('b_fin', ''), row.get('b_ban', ''), row.get('b_ign', ''), row.get('b_baners', ''), row.get('b_butt', ''), row.get('b_mess', ''), row.get('orders', ''), row.get('curator', ''), row.get('bonus', ''), row.get('shop_status', ''), row.get('answers', ''), row.get('quiz', ''), row.get('kassa', ''), row.get('gc_url', ''), row.get('key_pr', ''), row.get('n_con', ''), row.get('canal', ''), row.get('data_on', ''), row.get('data_t', ''), row.get('utm_source', ''), row.get('utm_medium', ''), row.get('utm_campaign', ''), row.get('utm_term', ''), row.get('utm_content', ''), row.get('gcpc', '')]
placeholders = ', '.join(['?' for _ in columns])
columns_str = ', '.join(columns)
query = f'''
INSERT INTO contacts ({columns_str})
VALUES ({placeholders})
'''
try:
cursor.execute(query, values)
except Exception as e:
print(f"Error inserting row: {row}")
print(f"Error message: {str(e)}")
conn.rollback()
continue
conn.commit()
conn.close()
# Маршрут для загрузки JSON-файла
@app.route('/upload_json', methods=['POST'])
def upload_json():
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if file and file.filename.endswith('.json'):
data = json.load(file)
insert_data_j(data)
return jsonify({"message": "Data uploaded and inserted successfully"})
return jsonify({"error": "Invalid file format"}), 400
# ОБНОВЛЯЕМ Бизон 365
DATABASE_NAME = 'data_gc.db'
def update_or_insert_user(db_name, user_data, mapping_template):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
# Получение email пользователя из данных
email = user_data.get('email')
if not email:
logging.error(f"User data missing email: {user_data}")
return
logging.debug(f"Processing user with email: {email}")
# Проверка существования пользователя в базе данных по email
cursor.execute("SELECT web_st, ws_st, b_mess FROM contacts WHERE email = ?", (email,))
user = cursor.fetchone()
logging.debug(f"User found: {user}")
# Вынесение увеличения значения web_st в отдельный блок
web_st_value = 1 # Инициализация значения web_st
if user:
# Проверка текущего значения web_st и его инкрементация
current_web_st = user[0] if user[0] is not None and user[0] != "" else 0
web_st_value = int(current_web_st) + 1
logging.debug(f"Calculated web_st_value: {web_st_value}")
# Обновление значения web_st
cursor.execute("UPDATE contacts SET web_st = ? WHERE email = ?", (web_st_value, email))
conn.commit()
conn.close()
logging.debug(f"User {email} web_st updated to {web_st_value}")
else:
conn.close()
logging.debug(f"User {email} not found, proceeding with insert")
# Открываем соединение снова для остальных операций
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
# Преобразование данных пользователя на основе шаблона сопоставления
transformed_data = {}
for json_key, db_column in mapping_template.items():
value = user_data.get(json_key, "")
if isinstance(value, list):
# Проверяем тип элементов списка
if all(isinstance(item, str) for item in value):
transformed_data[db_column] = "; ".join(value) # Сохраняем сообщения в строку
else:
logging.error(f"Expected list of strings for key {json_key}, but got: {value}")
transformed_data[db_column] = ""
else:
transformed_data[db_column] = str(value)
logging.debug(f"Transformed data: {transformed_data}")
# Заполнение обязательных полей значениями по умолчанию
required_fields = [
"vk_id", "chat_id", "ws_st", "ws_stop", "web_st", "fin_prog",
"b_city", "b_fin", "b_ban", "b_ign", "b_baners", "b_butt", "b_mess",
"orders", "curator", "bonus", "shop_status", "answers", "quiz", "kassa", "gc_url",
"key_pr", "n_con", "canal", "data_on", "data_t", 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gcpc'
]
for field in required_fields:
if field not in transformed_data:
transformed_data[field] = ""
logging.debug(f"Transformed data after adding required fields: {transformed_data}")
# Обработка номера телефона, если он есть
if 'phone' in user_data:
phone = user_data['phone']
if phone.startswith('+'):
phone = phone[1:]
transformed_data['phone'] = phone
logging.debug(f"Transformed data after phone processing: {transformed_data}")
# Добавление значения web_st в данные для вставки
transformed_data['web_st'] = web_st_value
# Обновление данных пользователя в базе данных
if user:
# Объединение новых сообщений с существующими
if 'b_mess' in transformed_data and user[2]:
transformed_data['b_mess'] = user[2] + "; " + transformed_data['b_mess']
update_query = "UPDATE contacts SET "
update_values = []
for column, value in transformed_data.items():
if column != 'ws_st' or not user[1]: # Проверка на наличие существующего ws_st
update_query += f"{column} = ?, "
update_values.append(value)
update_query = update_query.rstrip(", ") + " WHERE email = ?"
update_values.append(email)
logging.debug(f"Update query: {update_query} with values: {update_values}")
cursor.execute(update_query, update_values)
else:
columns = ', '.join(transformed_data.keys())
placeholders = ', '.join('?' for _ in transformed_data)
insert_query = f"INSERT INTO contacts ({columns}) VALUES ({placeholders})"
insert_values = list(transformed_data.values())
logging.debug(f"Insert query: {insert_query} with values: {insert_values}")
cursor.execute(insert_query, insert_values)
# Подтверждение изменений и закрытие соединения
conn.commit()
conn.close()
logging.debug(f"User with email {email} processed successfully")
@app.route('/send_request', methods=['POST'])
def send_request():
token = request.form.get('token')
min_date = request.form.get('minDate')
type = request.form.get('type')
url = f'https://online.bizon365.ru/api/v1/webinars/reports/getlist?minDate={min_date}&limit=100&type={type}'
response = requests.get(url, headers={'X-Token': token})
if response.status_code == 200:
data = response.json()
webinar_ids = [item['webinarId'] for item in data['list']]
return jsonify(webinar_ids)
else:
return jsonify({'error': 'Failed to fetch data from the API'}), response.status_code
@app.route('/send_get_request', methods=['GET'])
def send_get_request():
token = request.args.get('token')
webinarId = request.args.get('webinarId')
url = f'https://online.bizon365.ru/api/v1/webinars/reports/get?webinarId={webinarId}'
try:
response = requests.get(url, headers={'X-Token': token})
response.raise_for_status() # Проверка на ошибки HTTP
data = response.json()
# Убедитесь, что report существует в данных
if data is None or 'report' not in data:
return jsonify({'error': 'No report data found'}), 500
report = data.get('report', {})
messages = data.get('messages', {})
# Проверка на None перед использованием
if report is None:
return jsonify({'error': 'No report data found in the response'}), 500
report_json_str = report.get('report', '{}')
try:
report_json = json.loads(report_json_str)
except json.JSONDecodeError:
report_json = {}
messages_json_str = report.get('messages', '{}')
try:
messages_json = json.loads(messages_json_str)
except json.JSONDecodeError:
messages_json = {}
users_meta = report_json.get('usersMeta', {})
processed_emails = set()
for user_id, user_data in users_meta.items():
user_messages = messages_json.get(user_id, [])
user_data['messages'] = user_messages
email = user_data.get('email')
if email and email not in processed_emails:
update_or_insert_user(DATABASE_NAME, user_data, mapping_template)
processed_emails.add(email)
return jsonify({'status': 'User data saved successfully'})
except requests.exceptions.RequestException as e:
return jsonify({'error': f'API request failed: {str(e)}'}), 500
@app.route('/webhookbz', methods=['POST'])
def webhookbz():
api_sys_control = request.args.get('api_sys')
if api_sys_control != api_key_sys:
return "EUR 22", 200
data = request.json
webinar_id = data.get('webinarId')
if not webinar_id:
return jsonify({'error': 'webinarId is required'}), 400
url = f'https://online.bizon365.ru/api/v1/webinars/reports/get?webinarId={webinar_id}'
response = requests.get(url, headers={'X-Token': api_key_sys})
if response.status_code == 200:
data = response.json()
report = data.get('report', {})
messages = data.get('messages', {})
report_json_str = report.get('report', '{}')
try:
report_json = json.loads(report_json_str)
except json.JSONDecodeError:
report_json = {}
messages_json_str = report.get('messages', '{}')
try:
messages_json = json.loads(messages_json_str)
except json.JSONDecodeError:
messages_json = {}
users_meta = report_json.get('usersMeta', {})
processed_emails = set()
for user_id, user_data in users_meta.items():
user_messages = messages_json.get(user_id, [])
user_data['messages'] = user_messages
email = user_data.get('email')
if email and email not in processed_emails:
update_or_insert_user(DATABASE_NAME, user_data, mapping_template)
processed_emails.add(email)
return jsonify({'status': 'User data saved successfully'})
else:
return jsonify({'error': 'Failed to fetch data from the API'}), response.status_code
# Отправка в НС1 в раб. дни нужно поправить нас групп
@app.route('/add_ns', methods=['GET'])
def handle_in1():
name = request.args.get('name')
email = request.args.get('email')
phone = request.args.get('phone')
base_url = 'https://api.notisend.ru/v1'
token = request.args.get('token')
list_id = request.args.get('list_id')
phone_id = request.args.get('phone_id')
name_id = request.args.get('name_id')
# Проверка наличия всех необходимых параметров
if not all([name, email, phone, token, list_id, phone_id, name_id]):
return jsonify({'error': 'Missing required parameters'}), 400
# Отправляем запросы в три разных места
response_ns = send_ns(base_url, token, list_id, email, phone, name, phone_id, name_id)
# Возвращаем список ответов
return jsonify({'responses': [response_ns]})
@app.route('/ns_info', methods=['GET'])
def ns_info():
return render_template('ns_info.html')
@app.route('/api/group/<int:group_id>/parameters', methods=['GET'])
def get_group_parameters(group_id):
api_token = request.args.get('apiToken')
if not api_token:
return jsonify({'error': 'API Token is required'}), 400
url = f'https://api.notisend.ru/v1/email/lists/{group_id}/parameters'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_token}'
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
return jsonify(data)
except requests.RequestException as e:
return jsonify({'error': str(e)}), 500
@app.route('/upload', methods=['POST'])
def upload_file():
# Получаем ключ авторизации из запроса
api_sys_control = request.form.get('api_key_sys')
# Проверка ключа авторизации
if api_sys_control != api_key_sys:
return jsonify({"error": "Unauthorized access"}), 403
# Проверяем, что файл был отправлен
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
# Если пользователь не выбрал файл, браузер может отправить пустой файл без имени
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
# Генерация уникального имени файла
unique_filename = str(uuid.uuid4()) + os.path.splitext(file.filename)[1]
save_path = os.path.join(UPLOAD_FOLDER, unique_filename)
file.save(save_path)
# Возвращаем полный URL загруженного файла с протоколом https
full_url = request.url_root.replace('http://', 'https://') + 'uploads/' + unique_filename
return jsonify({"message": "File uploaded successfully", "url": full_url}), 200
@app.route('/uploads/<filename>', methods=['GET'])
def uploaded_file(filename):
return send_from_directory(UPLOAD_FOLDER, filename)
@app.route('/up_fa', methods=['GET'])
def up_fa():
return render_template('up_fa.html')
@app.route('/upload_vk', methods=['POST'])
def upload_file_vk():
# Получаем ключ авторизации из запроса
api_sys_control = request.form.get('api_key_sys')
# Проверка ключа авторизации
if api_sys_control != api_key_sys:
return jsonify({"error": "Unauthorized access"}), 403
# Проверяем, что файл был отправлен
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
# Если пользователь не выбрал файл, браузер может отправить пустой файл без имени
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
# Генерация уникального имени файла
unique_filename = str(uuid.uuid4()) + os.path.splitext(file.filename)[1]
save_path = os.path.join('static', unique_filename)
file.save(save_path)
# Возвращаем полный URL загруженного файла с протоколом https
full_url = request.url_root.replace('http://', 'https://') + 'uploads_vk/' + unique_filename
return jsonify({"message": "File uploaded successfully", "url": full_url}), 200
@app.route('/uploads_vk/<filename>', methods=['GET'])
def uploaded_file_vk(filename):
return send_from_directory('static', filename)
@app.route('/up_fa_vk', methods=['GET'])
def up_fa_vk():
return render_template('up_fa_vk.html')
@app.route('/up_page', methods=['POST'])
def upload_page():
# Получаем ключ авторизации из запроса
api_sys_control = request.form.get('api_key_sys')
# Проверка ключа авторизации
if api_sys_control != api_key_sys:
return jsonify({"error": "Unauthorized access"}), 403
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
filename = request.form.get('filename')
if not filename:
return jsonify({"error": "Filename is required"}), 400
save_path = os.path.join(HTML_FOLDER, filename + '.html')
file.save(save_path)
# Возвращаем полный URL загруженного файла с протоколом https
full_url = request.url_root.replace('http://', 'https://') + filename
return jsonify({"message": "Page uploaded successfully", "url": full_url}), 200
@app.route('/<path:filename>', methods=['GET'])
def serve_html(filename):
if not filename.endswith('.html'):
filename += '.html'
return send_from_directory(HTML_FOLDER, filename)
@app.route('/up_page', methods=['GET'])
def up_page():
return render_template('up_page.html')
# Дублированный маршрут для загрузки страницы через POST-запрос
@app.route('/up_page_vk', methods=['POST'])
def upload_page_vk():
# Получаем ключ авторизации из запроса
api_sys_control = request.form.get('api_key_sys')
# Проверка ключа авторизации
if api_sys_control != api_key_sys:
return jsonify({"error": "Unauthorized access"}), 403
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
filename = request.form.get('filename')
if not filename:
return jsonify({"error": "Filename is required"}), 400
save_path = os.path.join(HTML_FOLDER_VK, filename + '.html')
file.save(save_path)
# Возвращаем полный URL загруженного файла с протоколом https
full_url = request.url_root.replace('http://', 'https://') + 'page_vk/' + filename
return jsonify({"message": "Page uploaded successfully", "url": full_url}), 200
# Дублированный маршрут для обслуживания загруженных страниц
@app.route('/page_vk/<path:filename>', methods=['GET'])
def serve_html_vk(filename):
try:
# Получаем параметры из GET-запроса
apps_id = request.args.get('apps_id')
in_url = request.args.get('fullUrl')
# Проверяем, есть ли параметры
if not apps_id or not in_url:
return jsonify({"error": "Access denied"}), 400
# Декодируем URL перед парсингом
decoded_url = unquote(in_url)
# Получаем базовую часть URL (до знака вопроса)
parsed_url = urlparse(decoded_url)
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
logging.debug(f"Decoded URL: {decoded_url}")
logging.debug(f"Parsed base URL: {base_url}")
# Парсим параметры из декодированного URL
query_params = dict(parse_qsl(parsed_url.query, keep_blank_values=True))
# Добавляем все возможные параметры, даже если они пустые
all_params = {
'vk_access_token_settings': query_params.get('vk_access_token_settings', ''),
'vk_app_id': query_params.get('vk_app_id', ''),
'vk_are_notifications_enabled': query_params.get('vk_are_notifications_enabled', ''),
'vk_is_app_user': query_params.get('vk_is_app_user', ''),
'vk_is_favorite': query_params.get('vk_is_favorite', ''),
'vk_language': query_params.get('vk_language', ''),
'vk_platform': query_params.get('vk_platform', ''),
'vk_ref': query_params.get('vk_ref', ''),
'vk_ts': query_params.get('vk_ts', ''),
'vk_user_id': query_params.get('vk_user_id', ''),
'vk_are_notifications_enabled': query_params.get('vk_are_notifications_enabled', ''),
'vk_chat_id': query_params.get('vk_chat_id', ''),
'vk_group_id': query_params.get('vk_group_id', ''),
'vk_has_profile_button': query_params.get('vk_has_profile_button', ''),
'vk_is_favorite': query_params.get('vk_is_favorite', ''),
'vk_is_recommended': query_params.get('vk_is_recommended', ''),
'vk_is_play_machine': query_params.get('vk_is_play_machine', ''),
'vk_is_widescreen': query_params.get('vk_is_widescreen', ''),
'vk_profile_id': query_params.get('vk_profile_id', ''),
'vk_testing_group_id': query_params.get('vk_testing_group_id', ''),
'vk_viewer_group_role': query_params.get('vk_viewer_group_role', ''),
'sign': query_params.get('sign', '')
}
# Очищаем параметры от пустых значений, кроме vk_access_token_settings
cleaned_params = {key: value for key, value in all_params.items() if value or key == 'vk_access_token_settings'}
# Формирование URL с использованием f-строк
fullUrl = f"{base_url}?{urlencode(cleaned_params)}"
logging.debug(f"Received params: fullUrl={fullUrl}")
# Преобразуем строку в JSON
try:
api_key_apps_vk_dict = json.loads(api_key_apps_vk)
except json.JSONDecodeError as e:
logging.error(f"Error decoding JSON: {e}")
return jsonify({"status": "invalid"}), 200
logging.debug(f"api_key_apps_vk_dict: {api_key_apps_vk_dict}")
# Проверка подписи для приложения
if str(apps_id) not in api_key_apps_vk_dict: # Приводим apps_id к строке
logging.error("Invalid apps_id")
return jsonify({"error": "Invalid apps_id"}), 400
secret = api_key_apps_vk_dict[str(apps_id)] # Приводим apps_id к строке
logging.debug(f"Using secret: {secret}")
# Парсим полный URL для получения параметров запроса
query_params = dict(parse_qsl(urlparse(fullUrl).query, keep_blank_values=True))
logging.debug(f"Query params for signature check: {query_params}")
# Проверяем подпись
if not is_valid(query=query_params, secret=secret):
logging.error("Invalid signature")
return jsonify({"error": "Invalid signature"}), 400
# Если верификация прошла успешно, отдаём файл
if not filename.endswith('.html'):
filename += '.html'
return send_from_directory(HTML_FOLDER_VK, filename)
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return jsonify({"error": str(e)}), 500
# Дублированный маршрут для отображения страницы загрузки
@app.route('/up_page_vk', methods=['GET'])
def up_page_vk():
return render_template('up_page_vk.html')
@app.route('/monitor', methods=['GET'])
def monitor():
# Получаем информацию о загруженных файлах
files = os.listdir(UPLOAD_FOLDER)
html_files = os.listdir(HTML_FOLDER)
# Получаем информацию о дисковом пространстве
total, used, free = shutil.disk_usage("/")
# Преобразуем байты в гигабайты для удобства чтения
total_gb = total // (2**30)
used_gb = used // (2**30)
free_gb = free // (2**30)
# Получаем информацию об использовании оперативной памяти
memory = psutil.virtual_memory()
memory_total_gb = memory.total // (2**30)
memory_used_gb = memory.used // (2**30)
memory_free_gb = memory.free // (2**30)
# Получаем информацию о количестве процессоров
cpu_count = psutil.cpu_count(logical=True)
return render_template('monitor.html',
uploaded_files=files,
uploaded_html_files=html_files,
disk_space={
'total': f"{total_gb} GB",
'used': f"{used_gb} GB",
'free': f"{free_gb} GB"
},
memory_usage={
'total': f"{memory_total_gb} GB",
'used': f"{memory_used_gb} GB",
'free': f"{memory_free_gb} GB"
},
cpu_count=cpu_count)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860))) |