├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── codeql │ └── codeql-config.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── codeql.yml ├── .gitignore ├── .npmrc ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── docs ├── LocalSession.html ├── fonts │ ├── Montserrat │ │ ├── Montserrat-Bold.eot │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-Bold.woff │ │ ├── Montserrat-Bold.woff2 │ │ ├── Montserrat-Regular.eot │ │ ├── Montserrat-Regular.ttf │ │ ├── Montserrat-Regular.woff │ │ └── Montserrat-Regular.woff2 │ └── Source-Sans-Pro │ │ ├── sourcesanspro-light-webfont.eot │ │ ├── sourcesanspro-light-webfont.svg │ │ ├── sourcesanspro-light-webfont.ttf │ │ ├── sourcesanspro-light-webfont.woff │ │ ├── sourcesanspro-light-webfont.woff2 │ │ ├── sourcesanspro-regular-webfont.eot │ │ ├── sourcesanspro-regular-webfont.svg │ │ ├── sourcesanspro-regular-webfont.ttf │ │ ├── sourcesanspro-regular-webfont.woff │ │ └── sourcesanspro-regular-webfont.woff2 ├── global.html ├── index.html ├── module-telegraf-session-local.html ├── scripts │ ├── collapse.js │ ├── commonNav.js │ ├── linenumber.js │ ├── nav.js │ ├── polyfill.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js │ └── search.js ├── session.js.html └── styles │ ├── jsdoc.css │ └── prettify.css ├── examples ├── extra.js └── simple.js ├── jsdoc.json ├── lib ├── lodash-id.js ├── session.d.ts └── session.js ├── package.json └── tests ├── general.js ├── lodash-id.js ├── sessionKeyUpdateTypes.js ├── storageCustom.js ├── storageFileAsync.js ├── storageFileSync.js └── storageMemory.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.txt] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docs/ 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "extends": "standard", 8 | "plugins": ["mocha"], 9 | "parserOptions": { 10 | "ecmaVersion": 2017 11 | }, 12 | "rules": { 13 | "one-var": 0, 14 | "comma-dangle": 0, 15 | "new-cap": 0, 16 | "padded-blocks": 0, 17 | "brace-style": [0, "1tbs", { "allowSingleLine": true }], 18 | "object-shorthand": ["warn", "consistent"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL config" 2 | 3 | paths-ignore: 4 | - docs 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 30 8 | assignees: 9 | - TemaSM 10 | labels: 11 | - "Type: Maintenance" 12 | - dependencies 13 | versioning-strategy: increase 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [14, 16, 18] 11 | name: Node.js ${{ matrix.node-version }} 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | 21 | - name: Install pnpm 22 | uses: pnpm/action-setup@v2 23 | id: pnpm-install 24 | with: 25 | version: 7 26 | run_install: false 27 | 28 | - name: Get pnpm store directory 29 | id: pnpm-cache 30 | shell: bash 31 | run: | 32 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 33 | 34 | - name: Setup pnpm cache 35 | uses: actions/cache@v3 36 | with: 37 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 38 | key: ${{ runner.os }}-pnpm-store-node-${{ matrix.node-version }} 39 | restore-keys: | 40 | ${{ runner.os }}-pnpm-store-node-${{ matrix.node-version }} 41 | 42 | - name: Install dependencies 43 | run: pnpm install 44 | 45 | # Here we will use pnpx to run executables, instead of running `npm run 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 43 | 44 |
45 | 46 |

LocalSession

47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 |

59 | 60 | LocalSession 61 | 62 |

63 | 64 |

Represents a wrapper around locally stored session, it's middleware & lowdb

65 | 66 | 67 |
68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 | 76 |

Constructor

77 | 78 | 79 |

new LocalSession(optionsopt)

80 | 81 | 82 | 83 | 84 | 85 | 86 |
87 | 88 | 89 | 90 |
Source:
91 |
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 |
Parameters:
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 | 178 | 179 | 180 | 189 | 190 | 191 | 192 | 193 | 618 | 619 | 620 | 621 | 622 |
NameTypeAttributesDescription
options 170 | 171 | 172 | Object 173 | 174 | 175 | 176 | 177 | 181 | 182 | <optional>
183 | 184 | 185 | 186 | 187 | 188 |

Options for LocalSession & lowdb

194 |
Properties
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 | 233 | 234 | 235 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 267 | 268 | 269 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 301 | 302 | 303 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 335 | 336 | 337 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 369 | 370 | 371 | 380 | 381 | 382 | 383 | 384 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 499 | 500 | 501 | 510 | 511 | 512 | 513 | 514 | 611 | 612 | 613 | 614 | 615 |
NameTypeAttributesDescription
database 225 | 226 | 227 | String 228 | 229 | 230 | 231 | 232 | 236 | 237 | <optional>
238 | 239 | 240 | 241 | 242 | 243 |

Name or path to database file default: 'sessions.json'

property 259 | 260 | 261 | String 262 | 263 | 264 | 265 | 266 | 270 | 271 | <optional>
272 | 273 | 274 | 275 | 276 | 277 |

Name of property in Telegraf Context where session object will be located default: 'session'

state 293 | 294 | 295 | Object 296 | 297 | 298 | 299 | 300 | 304 | 305 | <optional>
306 | 307 | 308 | 309 | 310 | 311 |

Initial state of database. You can use it to pre-init database Arrays/Objects to store your own data default: {}

getSessionKey 327 | 328 | 329 | function 330 | 331 | 332 | 333 | 334 | 338 | 339 | <optional>
340 | 341 | 342 | 343 | 344 | 345 |

Function to get identifier for session from Telegraf Context (may implement it on your own)

storage 361 | 362 | 363 | Object 364 | 365 | 366 | 367 | 368 | 372 | 373 | <optional>
374 | 375 | 376 | 377 | 378 | 379 |

lowdb storage option for implementing your own storage read/write operations default: LocalSession.storageFileSync

385 |
Properties
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 | 424 | 425 | 426 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 458 | 459 | 460 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 |
NameTypeAttributesDescription
read 416 | 417 | 418 | function 419 | 420 | 421 | 422 | 423 | 427 | 428 | <optional>
429 | 430 | 431 | 432 | 433 | 434 |

lowdb storage read function, must return an object or a Promise

write 450 | 451 | 452 | function 453 | 454 | 455 | 456 | 457 | 461 | 462 | <optional>
463 | 464 | 465 | 466 | 467 | 468 |

lowdb storage write function, must return undefined or a Promise

479 | 480 |
format 491 | 492 | 493 | Object 494 | 495 | 496 | 497 | 498 | 502 | 503 | <optional>
504 | 505 | 506 | 507 | 508 | 509 |

lowdb storage format option for implementing your own database format for read/write operations

515 |
Properties
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 | 554 | 555 | 556 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 588 | 589 | 590 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 |
NameTypeAttributesDescription
serialize 546 | 547 | 548 | function 549 | 550 | 551 | 552 | 553 | 557 | 558 | <optional>
559 | 560 | 561 | 562 | 563 | 564 |

lowdb storage serialize function, must return data (usually string) default: JSON.stringify()

deserialize 580 | 581 | 582 | function 583 | 584 | 585 | 586 | 587 | 591 | 592 | <optional>
593 | 594 | 595 | 596 | 597 | 598 |

lowdb storage deserialize function, must return an object default: JSON.parse()

609 | 610 |
616 | 617 |
623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 |
Returns:
640 | 641 | 642 |
643 |

Instance of LocalSession

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 |

Members

670 | 671 | 672 | 673 |

(readonly) .storagefileAsync

674 | 675 | 676 | 677 | 678 | 679 |
680 | 681 |
Description:
682 |
683 | 684 | 685 | 686 |
Source:
687 |
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 |

lowdb storage named fileAsync before lowdb@0.17.0

729 |
730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 |

(readonly) .storagefileSync

741 | 742 | 743 | 744 | 745 | 746 |
747 | 748 |
Description:
749 |
750 | 751 | 752 | 753 |
Source:
754 |
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 |

lowdb storage named fileSync before lowdb@0.17.0

796 |
797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 |

Methods

810 | 811 | 812 | 813 | 814 | 815 | 816 |

getSession(key) → {Object}

817 | 818 | 819 | 820 | 821 | 822 | 823 |
824 | 825 |
Description:
826 |
  • Get session by it's key in database

827 | 828 | 829 | 830 |
Source:
831 |
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 |
Parameters:
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 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 |
NameTypeDescription
key 908 | 909 | 910 | String 911 | 912 | 913 | 914 | 915 |

Key which will be used to find associated session object

927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 |
Returns:
944 | 945 | 946 |
947 |

Session data object or empty object if there's no session in database with this key

948 |
949 | 950 | 951 | 952 |
953 |
954 | Type 955 |
956 |
957 | 958 | Object 959 | 960 | 961 | 962 |
963 |
964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 |

getSessionKey(ctx) → {String}

975 | 976 | 977 | 978 | 979 | 980 | 981 |
982 | 983 |
Description:
984 |
985 | 986 | 987 | 988 |
Source:
989 |
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 |
Parameters:
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 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 |
NameTypeDescription
ctx 1066 | 1067 | 1068 | Object 1069 | 1070 | 1071 | 1072 | 1073 |

Telegraf Context

1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 |
Returns:
1102 | 1103 | 1104 |
1105 |

Session key in format number:number (chat.id:from.id)

1106 |
1107 | 1108 | 1109 | 1110 |
1111 |
1112 | Type 1113 |
1114 |
1115 | 1116 | String 1117 | 1118 | 1119 | 1120 |
1121 |
1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 |

middleware(propertyopt) → {Promise}

1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 |
1140 | 1141 |
Description:
1142 |
  • Session middleware for use in Telegraf

1143 | 1144 | 1145 | 1146 |
Source:
1147 |
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 |
Parameters:
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 | 1234 | 1235 | 1236 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 |
NameTypeAttributesDescription
property 1226 | 1227 | 1228 | String 1229 | 1230 | 1231 | 1232 | 1233 | 1237 | 1238 | <optional>
1239 | 1240 | 1241 | 1242 | 1243 | 1244 |

Name of property in Telegraf Context where session object will be located (overrides property at LocalSession constructor)

1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 |
Returns:
1272 | 1273 | 1274 | 1275 | 1276 |
1277 |
1278 | Type 1279 |
1280 |
1281 | 1282 | Promise 1283 | 1284 | 1285 | 1286 |
1287 |
1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 |

(async) saveSession(key, data) → {Promise|function}

1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 |
1306 | 1307 |
Description:
1308 |
  • Save session to database

1309 | 1310 | 1311 | 1312 |
Source:
1313 |
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 |
Parameters:
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 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1413 | 1422 | 1423 | 1424 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | 1431 | 1432 |
NameTypeDescription
key 1390 | 1391 | 1392 | String 1393 | 1394 | 1395 | 1396 | 1397 |

Unique Key which will be used to store session object

data 1414 | 1415 | 1416 | Object 1417 | 1418 | 1419 | 1420 | 1421 |

Session data object (if empty, session will be removed from database)

1433 | 1434 | 1435 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1442 | 1443 | 1444 | 1445 | 1446 | 1447 | 1448 | 1449 |
Returns:
1450 | 1451 | 1452 |
1453 |
    1454 |
  • Promise or Promise-like .then() function, with session object at 1-st argument
  • 1455 |
1456 |
1457 | 1458 | 1459 | 1460 |
1461 |
1462 | Type 1463 |
1464 |
1465 | 1466 | Promise 1467 | | 1468 | 1469 | function 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 | 1502 | 1503 | 1504 | 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Bold.eot -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Bold.woff -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Bold.woff2 -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Regular.eot -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Regular.woff -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Montserrat/Montserrat-Regular.woff2 -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealSpeaker/telegraf-session-local/27c5ff4abd036c979bf89c113318492d101316c6/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 -------------------------------------------------------------------------------- /docs/global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Global - Telegraf Session local Documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 43 | 44 |
45 | 46 |

Global

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 |

Members

133 | 134 | 135 | 136 |

(readonly) LocalSession.storageBase

137 | 138 | 139 | 140 | 141 | 142 |
143 | 144 |
Description:
145 |
146 | 147 | 148 | 149 |
Source:
150 |
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 |

lowdb storage/adapter base constructor (to extend it in your custom storage/adapter)

192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |

(readonly) LocalSession.storageFileAsync

204 | 205 | 206 | 207 | 208 | 209 |
210 | 211 |
Description:
212 |
213 | 214 | 215 | 216 |
Source:
217 |
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 |

lowdb storage/adapter named FileAsync

259 |
260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 |

(readonly) LocalSession.storageFileSync

271 | 272 | 273 | 274 | 275 | 276 |
277 | 278 |
Description:
279 |
280 | 281 | 282 | 283 |
Source:
284 |
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 |

lowdb storage/adapter named FileSync

326 |
327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 |

(readonly) LocalSession.storageMemory

338 | 339 | 340 | 341 | 342 | 343 |
344 | 345 |
Description:
346 |
  • lowdb storage/adapter named Memory

347 | 348 | 349 | 350 |
Source:
351 |
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 |

lowdb storage/adapter named Memory

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 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home - Telegraf Session local Documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 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 |

Telegraf Session local

72 |

NPM Version
73 | Nodejs
74 | NPM downloads/month
75 | GitHub Actions Status
76 | Coveralls
77 | Codacy grade
78 | GitHub last commit

79 |
80 |

Middleware for locally stored sessions & database

81 |
82 |

⚡️ Features

83 |
    84 |
  • 85 |

    Any type of storage: Memory, FileSync, FileAsync, ... (implement your own)

    86 |
  • 87 |
  • 88 |

    Any format you want: JSON, BSON, YAML, XML, ... (implement your own)

    89 |
  • 90 |
  • 91 |

    Shipped together with power of lodash

    92 |
  • 93 |
  • 94 |

    Supports basic DB-like operations (thanks to lodash-id):

    95 |

    getById, insert, upsert, updateById, updateWhere, replaceById, removeById, removeWhere, createId,

    96 |
  • 97 |
98 |

🚀 Installation

99 |
$ npm install -S telegraf-session-local
100 | 
101 |
102 |

💡 TIP: We recommend pnpm package manager: npm i -g pnpm and then pnpm i -S telegraf-session-local.
103 | It's in-place replacement for npm, faster and better than npm/yarn, and saves your disk space.

104 |
105 |
106 |

📚 Documentation & API

107 |
108 |

👀 Quick-start example

109 |
const { Telegraf } = require('telegraf')
110 | const LocalSession = require('telegraf-session-local')
111 | 
112 | const bot = new Telegraf(process.env.BOT_TOKEN) // Your Bot token here
113 | 
114 | bot.use((new LocalSession({ database: 'example_db.json' })).middleware())
115 | 
116 | bot.on('text', (ctx, next) => {
117 |   ctx.session.counter = ctx.session.counter || 0
118 |   ctx.session.counter++
119 |   ctx.replyWithMarkdownV2(`Counter updated, new value: \`${ctx.session.counter}\``)
120 |   return next()
121 | })
122 | 
123 | bot.command('/stats', (ctx) => {
124 |   ctx.replyWithMarkdownV2(`Database has \`${ctx.session.counter}\` messages from @${ctx.from.username || ctx.from.id}`)
125 | })
126 | 
127 | bot.command('/remove', (ctx) => {
128 |   ctx.replyWithMarkdownV2(`Removing session from database: \`${JSON.stringify(ctx.session)}\``)
129 |   // Setting session to null, undefined or empty object/array will trigger removing it from database
130 |   ctx.session = null
131 | })
132 | 
133 | bot.launch()
134 | 
135 |

📄 Full example

136 |
const { Telegraf } = require('telegraf')
137 | const LocalSession = require('telegraf-session-local')
138 | 
139 | const bot = new Telegraf(process.env.BOT_TOKEN) // Your Bot token here
140 | 
141 | const localSession = new LocalSession({
142 |   // Database name/path, where sessions will be located (default: 'sessions.json')
143 |   database: 'example_db.json',
144 |   // Name of session property object in Telegraf Context (default: 'session')
145 |   property: 'session',
146 |   // Type of lowdb storage (default: 'storageFileSync')
147 |   storage: LocalSession.storageFileAsync,
148 |   // Format of storage/database (default: JSON.stringify / JSON.parse)
149 |   format: {
150 |     serialize: (obj) => JSON.stringify(obj, null, 2), // null & 2 for pretty-formatted JSON
151 |     deserialize: (str) => JSON.parse(str),
152 |   },
153 |   // We will use `messages` array in our database to store user messages using exported lowdb instance from LocalSession via Telegraf Context
154 |   state: { messages: [] }
155 | })
156 | 
157 | // Wait for database async initialization finished (storageFileAsync or your own asynchronous storage adapter)
158 | localSession.DB.then(DB => {
159 |   // Database now initialized, so now you can retrieve anything you want from it
160 |   console.log('Current LocalSession DB:', DB.value())
161 |   // console.log(DB.get('sessions').getById('1:1').value())
162 | })
163 | 
164 | // Telegraf will use `telegraf-session-local` configured above middleware
165 | bot.use(localSession.middleware())
166 | 
167 | bot.on('text', (ctx, next) => {
168 |   ctx.session.counter = ctx.session.counter || 0
169 |   ctx.session.counter++
170 |   ctx.replyWithMarkdownV2(`Counter updated, new value: \`${ctx.session.counter}\``)
171 |   // Writing message to Array `messages` into database which already has sessions Array
172 |   ctx.sessionDB.get('messages').push([ctx.message]).write()
173 |   // `property`+'DB' is a name of ctx property which contains lowdb instance, default = `sessionDB`
174 | 
175 |   return next()
176 | })
177 | 
178 | bot.command('/stats', (ctx) => {
179 |   ctx.replyWithMarkdownV2(`Session has \`${ctx.session.counter}\` messages from @${ctx.from.username || ctx.from.id}`)
180 | })
181 | 
182 | bot.command('/remove', (ctx) => {
183 |   ctx.replyWithMarkdownV2(`Removing session from lowdb database: \`${JSON.stringify(ctx.session)}\``)
184 |   // Setting session to null, undefined or empty object/array will trigger removing it from database
185 |   ctx.session = null
186 | })
187 | 
188 | bot.launch()
189 | 
190 |

Another examples located in /examples folder (PRs welcome)

191 |

Also, you may read comments in /lib/session.js

192 |

193 |

Tema Smirnov and contributors / github.tema@smirnov.one / Telegram

194 |
195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
204 | 205 |
206 | 207 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /docs/module-telegraf-session-local.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | telegraf-session-local - Telegraf Session local Documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 43 | 44 |
45 | 46 |

telegraf-session-local

47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 | 65 | 66 |
67 | 68 |
Description:
69 |
  • Telegraf Session middleware for storing sessions locally (Memory/FileSync/FileAsync/...)

70 | 71 | 72 | 73 |
Source:
74 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
Author:
97 |
98 | 101 |
102 | 103 | 104 | 105 | 106 | 107 |
License:
108 |
  • MIT
109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
See:
117 |
118 | 121 |
122 | 123 | 124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 |

Telegraf Session middleware for storing sessions locally (Memory/FileSync/FileAsync/...)

132 | 133 | 134 | 135 | 136 |
137 | 138 | 139 | 140 | 141 | 142 |

Requires

143 | 144 | 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 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /docs/scripts/collapse.js: -------------------------------------------------------------------------------- 1 | function hideAllButCurrent(){ 2 | //by default all submenut items are hidden 3 | //but we need to rehide them for search 4 | document.querySelectorAll("nav > ul").forEach(function(parent) { 5 | if (parent.className.indexOf("collapse_top") !== -1) { 6 | parent.style.display = "none"; 7 | } 8 | }); 9 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) { 10 | parent.style.display = "none"; 11 | }); 12 | document.querySelectorAll("nav > h3").forEach(function(section) { 13 | if (section.className.indexOf("collapsed_header") !== -1) { 14 | section.addEventListener("click", function(){ 15 | if (section.nextSibling.style.display === "none") { 16 | section.nextSibling.style.display = "block"; 17 | } else { 18 | section.nextSibling.style.display = "none"; 19 | } 20 | }); 21 | } 22 | }); 23 | 24 | //only current page (if it exists) should be opened 25 | var file = window.location.pathname.split("/").pop().replace(/\.html/, ''); 26 | document.querySelectorAll("nav > ul > li > a").forEach(function(parent) { 27 | var href = parent.attributes.href.value.replace(/\.html/, ''); 28 | if (file === href) { 29 | if (parent.parentNode.parentNode.className.indexOf("collapse_top") !== -1) { 30 | parent.parentNode.parentNode.style.display = "block"; 31 | } 32 | parent.parentNode.querySelectorAll("ul li").forEach(function(elem) { 33 | elem.style.display = "block"; 34 | }); 35 | } 36 | }); 37 | } 38 | 39 | hideAllButCurrent(); -------------------------------------------------------------------------------- /docs/scripts/commonNav.js: -------------------------------------------------------------------------------- 1 | if (typeof fetch === 'function') { 2 | const init = () => { 3 | if (typeof scrollToNavItem !== 'function') return false 4 | scrollToNavItem() 5 | // hideAllButCurrent not always loaded 6 | if (typeof hideAllButCurrent === 'function') hideAllButCurrent() 7 | return true 8 | } 9 | fetch('./nav.inc.html') 10 | .then(response => response.ok ? response.text() : `${response.url} => ${response.status} ${response.statusText}`) 11 | .then(body => { 12 | document.querySelector('nav').innerHTML += body 13 | // nav.js should be quicker to load than nav.inc.html, a fallback just in case 14 | return init() 15 | }) 16 | .then(done => { 17 | if (done) return 18 | let i = 0 19 | ;(function waitUntilNavJs () { 20 | if (init()) return 21 | if (i++ < 100) return setTimeout(waitUntilNavJs, 300) 22 | console.error(Error('nav.js not loaded after 30s waiting for it')) 23 | })() 24 | }) 25 | .catch(error => console.error(error)) 26 | } else { 27 | console.error(Error('Browser too old to display commonNav (remove commonNav docdash option)')) 28 | } 29 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/nav.js: -------------------------------------------------------------------------------- 1 | function scrollToNavItem() { 2 | var path = window.location.href.split('/').pop().replace(/\.html/, ''); 3 | document.querySelectorAll('nav a').forEach(function(link) { 4 | var href = link.attributes.href.value.replace(/\.html/, ''); 5 | if (path === href) { 6 | link.scrollIntoView({block: 'center'}); 7 | return; 8 | } 9 | }) 10 | } 11 | 12 | scrollToNavItem(); 13 | -------------------------------------------------------------------------------- /docs/scripts/polyfill.js: -------------------------------------------------------------------------------- 1 | //IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/ 2 | if (typeof(NodeList.prototype.forEach)!==typeof(alert)){ 3 | NodeList.prototype.forEach=Array.prototype.forEach; 4 | } -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p ul > li:not(.level-hide)").forEach(function(elem) { 16 | elem.style.display = "block"; 17 | }); 18 | 19 | if (typeof hideAllButCurrent === "function"){ 20 | //let's do what ever collapse wants to do 21 | hideAllButCurrent(); 22 | } else { 23 | //menu by default should be opened 24 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 25 | elem.style.display = "block"; 26 | }); 27 | } 28 | } else { 29 | //we are searching 30 | document.documentElement.setAttribute(searchAttr, ''); 31 | 32 | //show all parents 33 | document.querySelectorAll("nav > ul > li").forEach(function(elem) { 34 | elem.style.display = "block"; 35 | }); 36 | document.querySelectorAll("nav > ul").forEach(function(elem) { 37 | elem.style.display = "block"; 38 | }); 39 | //hide all results 40 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 41 | elem.style.display = "none"; 42 | }); 43 | //show results matching filter 44 | document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) { 45 | if (!contains(elem.parentNode, search)) { 46 | return; 47 | } 48 | elem.parentNode.style.display = "block"; 49 | }); 50 | //hide parents without children 51 | document.querySelectorAll("nav > ul > li").forEach(function(parent) { 52 | var countSearchA = 0; 53 | parent.querySelectorAll("a").forEach(function(elem) { 54 | if (contains(elem, search)) { 55 | countSearchA++; 56 | } 57 | }); 58 | 59 | var countUl = 0; 60 | var countUlVisible = 0; 61 | parent.querySelectorAll("ul").forEach(function(ulP) { 62 | // count all elements that match the search 63 | if (contains(ulP, search)) { 64 | countUl++; 65 | } 66 | 67 | // count all visible elements 68 | var children = ulP.children 69 | for (i=0; i ul.collapse_top").forEach(function(parent) { 86 | var countVisible = 0; 87 | parent.querySelectorAll("li").forEach(function(elem) { 88 | if (elem.style.display !== "none") { 89 | countVisible++; 90 | } 91 | }); 92 | 93 | if (countVisible == 0) { 94 | //has no child at all and does not contain text 95 | parent.style.display = "none"; 96 | } 97 | }); 98 | } 99 | }); -------------------------------------------------------------------------------- /docs/session.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | session.js - Telegraf Session local Documentation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 43 | 44 |
45 | 46 |

session.js

47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 |
56 |
const Debug = require('debug')
 57 | const lowdb = require('lowdb')
 58 | const storageFileAsync = require('lowdb/adapters/FileAsync')
 59 | const storageFileSync = require('lowdb/adapters/FileSync')
 60 | const storageMemory = require('lowdb/adapters/Memory')
 61 | 
 62 | const lodashId = require('./lodash-id.js')
 63 | 
 64 | const debug = Debug('telegraf:session-local')
 65 | 
 66 | /**
 67 |  * Represents a wrapper around locally stored session, it's {@link LocalSession#middleware|middleware} & lowdb
 68 |  *
 69 |  * @param {Object} [options] - Options for {@link LocalSession|LocalSession} & {@link https://github.com/typicode/lowdb|lowdb}
 70 |  * @param {String} [options.database] - Name or path to database file `default:` `'sessions.json'`
 71 |  * @param {String} [options.property] - Name of property in {@link https://telegraf.js.org/#/?id=context|Telegraf Context} where session object will be located `default:` `'session'`
 72 |  * @param {Object} [options.state] - Initial state of database. You can use it to pre-init database Arrays/Objects to store your own data `default:` `{}`
 73 |  * @param {Function} [options.getSessionKey] - Function to get identifier for session from {@link https://telegraf.js.org/#/?id=context|Telegraf Context} (may implement it on your own)
 74 |  * @param {Object} [options.storage] - lowdb storage option for implementing your own storage read/write operations `default:` {@link LocalSession.storageFileSync|LocalSession.storageFileSync}
 75 |  * @param {Function} [options.storage.read] - lowdb storage read function, must return an object or a Promise
 76 |  * @param {Function} [options.storage.write] - lowdb storage write function, must return undefined or a Promise
 77 |  * @param {Object} [options.format] - lowdb storage format option for implementing your own database format for read/write operations
 78 |  * @param {Function} [options.format.serialize] - lowdb storage serialize function, must return data (usually string) `default:` {@link https://goo.gl/dmGpZd|JSON.stringify()}
 79 |  * @param {Function} [options.format.deserialize] - lowdb storage deserialize function, must return an object `default:` {@link https://goo.gl/wNy3ar|JSON.parse()}
 80 |  * @returns Instance of {@link LocalSession|LocalSession}
 81 |  */
 82 | class LocalSession {
 83 |   constructor (options = {}) {
 84 |     this.options = Object.assign({
 85 |       // TODO: Use storageFileAsync as default with support of Promise or Promise-like initialization, see: https://git.io/fA3ZN
 86 |       storage: LocalSession.storageFileSync,
 87 |       database: 'sessions.json',
 88 |       property: 'session',
 89 |       state: { },
 90 |       format: { },
 91 |       getSessionKey: (ctx) => {
 92 |         if (!ctx.from) return // should never happen
 93 | 
 94 |         let chatInstance
 95 |         if (ctx.chat) {
 96 |           chatInstance = ctx.chat.id
 97 |         } else if (ctx.updateType === 'callback_query') {
 98 |           chatInstance = ctx.callbackQuery.chat_instance
 99 |         } else { // if (ctx.updateType === 'inline_query') {
100 |           chatInstance = ctx.from.id
101 |         }
102 |         return chatInstance + ':' + ctx.from.id
103 |       }
104 |     }, options)
105 |     this.DB = undefined
106 |     this._adapter = undefined
107 |     // DISABLED: this.options.defaultValue = this.options.state // Backward compatability with old lowdb
108 |     const defaultAdaptersOptions = Object.assign(
109 |       { defaultValue: this.options.state },
110 |       this.options.format.serialize ? { serialize: this.options.format.serialize } : {},
111 |       this.options.format.deserialize ? { deserialize: this.options.format.deserialize } : {}
112 |     )
113 |     if (this.options.storage === LocalSession.storageMemory) {
114 |       debug('Initiating: lowdb adapter: storageMemory')
115 |       this._adapter = new LocalSession.storageMemory(this.options.database, defaultAdaptersOptions)
116 |     } else if (this.options.storage === LocalSession.storageFileAsync) {
117 |       debug('Initiating: lowdb adapter: storageFileAsync')
118 |       this._adapter = new LocalSession.storageFileAsync(this.options.database, defaultAdaptersOptions)
119 |     } else if (this.options.storage === LocalSession.storageFileSync) {
120 |       debug('Initiating: lowdb adapter: storageFileSync')
121 |       this._adapter = new LocalSession.storageFileSync(this.options.database, defaultAdaptersOptions)
122 |     } else {
123 |       debug('Initiating: lowdb adapter: custom storage/adapter')
124 |       this._adapter = new this.options.storage(this.options.database, defaultAdaptersOptions)
125 |     }
126 |     debug('Initiating: lowdb instance')
127 |     const DbInstance = lowdb(this._adapter)
128 |     // If lowdb initiated with async (Promise) adapter
129 |     if (isPromise(DbInstance)) {
130 |       debug('DbInstance is Promise like')
131 |       // TODO: Split it from constructor, because this code will produce glitches if async initiating may take too long time
132 |       this.DB = DbInstance
133 |       this.DB.then((DB) => {
134 |         debug('DbInstance Promise resolved')
135 |         this.DB = DB
136 |         _initDB.call(this)
137 |       })
138 |     }
139 |     // If lowdb initiated with sync adapter
140 |     else {
141 |       this.DB = DbInstance
142 |       _initDB.call(this)
143 |     }
144 |   }
145 | 
146 |   /**
147 |    * Get session key from {@link https://telegraf.js.org/#/?id=context|Telegraf Context}
148 |    *
149 |    * @param {Object} ctx - {@link https://telegraf.js.org/#/?id=context|Telegraf Context}
150 |    * @returns {String} Session key in format `number:number` (chat.id:from.id)
151 |    */
152 |   getSessionKey (ctx) {
153 |     this._called()
154 |     return this.options.getSessionKey(ctx)
155 |   }
156 | 
157 |   /**
158 |    * Get session by it's key in database
159 |    *
160 |    * @param {String} key - Key which will be used to find associated session object
161 |    * @returns {Object} Session data object or empty object if there's no session in database with this key
162 |    */
163 |   getSession (key) {
164 |     this._called(arguments)
165 |     const session = this.DB.get('sessions').getById(key).value() || {}
166 |     debug('Session state', session)
167 |     return session.data || {}
168 |   }
169 | 
170 |   /**
171 |    * Save session to database
172 |    *
173 |    * @param {String} key - Unique Key which will be used to store session object
174 |    * @param {Object} data - Session data object (if empty, session will be removed from database)
175 |    * @returns {Promise|Function} - Promise or Promise-like `.then()` function, with session object at 1-st argument
176 |    */
177 |   async saveSession (key, data) {
178 |     this._called(arguments)
179 |     if (!key) return
180 |     // If there's no data provided or it's empty, we should remove session record from database
181 |     if (this.DB._.isEmpty(data)) {
182 |       debug('Removing session #', key)
183 |       return this.DB.get('sessions').removeById(key).write()
184 |     }
185 |     debug('Saving session: %s = %o', key, data)
186 |     /* eslint-disable brace-style */
187 |     // If database has record, then just update it
188 |     if (this.DB.get('sessions').getById(key).value()) {
189 |       debug(' -> Updating')
190 |       const session = await this.DB.get('sessions').updateById(key, { data: data }).write()
191 |       // Check if lowdb Storage returned var type is Promise-like or just sync-driven data
192 |       return session
193 |     }
194 |     // If no, so we should push new record into it
195 |     else {
196 |       debug(' -> Inserting')
197 |       const session = await this.DB.get('sessions').push({ id: key, data: data }).write()
198 |       // Check if lowdb Storage returned var type is Promise-like or just sync-driven data
199 |       return session[0]
200 |     }
201 |     /* eslint-enable brace-style */
202 |   }
203 | 
204 |   /**
205 |    * Session middleware for use in Telegraf
206 |    *
207 |    * @param {String} [property] - Name of property in {@link https://telegraf.js.org/#/?id=context|Telegraf Context} where session object will be located (overrides `property` at {@link LocalSession} constructor)
208 |    * @returns {Promise}
209 |    */
210 |   middleware (property = this.options.property) {
211 |     const that = this
212 |     return async (ctx, next) => {
213 |       const key = that.getSessionKey(ctx)
214 |       if (!key) return next()
215 |       debug('Session key: %s', key)
216 |       let session = that.getSession(key)
217 |       debug('Session data: %o', session)
218 |       // Assigning session object to the Telegraf Context using `property` as a variable name
219 |       Object.defineProperty(ctx, property, {
220 |         get: function () { return session },
221 |         set: function (newValue) { session = Object.assign({}, newValue) }
222 |       })
223 |       // Make lowdb available in the Telegraf Context
224 |       Object.defineProperty(ctx, `${property}DB`, {
225 |         get: function () { return that.DB },
226 |         set: function () { }
227 |       })
228 |       // Saving session object on the next middleware
229 |       await next()
230 | 
231 |       this._called(arguments)
232 |       debug('Next Middleware -> Key: %s | Session: %o', key, session)
233 |       return that.saveSession(key, session)
234 |     }
235 |   }
236 | 
237 |   /**
238 |    * lowdb storage named {@link https://git.io/vhM3Y|fileSync} before {@link https://git.io/vhM3Z|lowdb@0.17.0}
239 |    *
240 |    * @memberof! LocalSession
241 |    * @name LocalSession.storagefileSync
242 |    * @alias LocalSession.storageFileSync
243 |    * @readonly
244 |    */
245 |   static get storagefileSync () {
246 |     return storageFileSync
247 |   }
248 | 
249 |   /**
250 |    * lowdb storage/adapter named {@link https://git.io/vhMqc|FileSync}
251 |    *
252 |    * @memberof! LocalSession
253 |    * @name LocalSession.storageFileSync
254 |    * @global
255 |    * @readonly
256 |    */
257 |   static get storageFileSync () {
258 |     return storageFileSync
259 |   }
260 | 
261 |   /**
262 |    * lowdb storage named {@link https://git.io/vhM3m|fileAsync} before {@link https://git.io/vhM3Z|lowdb@0.17.0}
263 |    *
264 |    * @memberof! LocalSession
265 |    * @name LocalSession.storagefileAsync
266 |    * @alias LocalSession.storageFileAsync
267 |    * @readonly
268 |    */
269 |   static get storagefileAsync () {
270 |     return storageFileAsync
271 |   }
272 | 
273 |   /**
274 |    * lowdb storage/adapter named {@link https://git.io/vhMqm|FileAsync}
275 |    *
276 |    * @memberof! LocalSession
277 |    * @name LocalSession.storageFileAsync
278 |    * @global
279 |    * @readonly
280 |    */
281 |   static get storageFileAsync () {
282 |     return storageFileAsync
283 |   }
284 | 
285 |   /**
286 |    * lowdb storage/adapter named {@link https://git.io/vhMqs|Memory}
287 |    *
288 |    * @memberof! LocalSession
289 |    * @name LocalSession.storageMemory
290 |    * @global
291 |    * @readonly
292 |    */
293 |   static get storageMemory () {
294 |     return storageMemory
295 |   }
296 | 
297 |   /**
298 |    * lowdb {@link https://git.io/vhMOK|storage/adapter base} constructor (to extend it in your custom storage/adapter)
299 |    *
300 |    * @memberof! LocalSession
301 |    * @name LocalSession.storageBase
302 |    * @global
303 |    * @readonly
304 |    */
305 |   static get storageBase () {
306 |     return require('lowdb/adapters/Base')
307 |   }
308 | 
309 |   /**
310 |    * For Debugging purposes only - shows info about what, where & with what args was called
311 |    *
312 |    * @param {Object} args - Called function's arguments
313 |    * @private
314 |    */
315 |   _called (args) {
316 |     debug('Called function: \n\t-> %s \n\t-> Arguments: \n\t-> %o', ((new Error().stack).split('at ')[2]).trim(), this.DB._.values(args))
317 |   }
318 | }
319 | 
320 | function _initDB () {
321 |   // Use ID based resources, so we can query records by ID (ex.: getById(), removeById(), ...)
322 |   this.DB._.mixin(lodashId)
323 |   // If database is empty, fill it with empty Array of sessions and optionally with initial state
324 |   this.DB.defaults(Object.assign({ sessions: [] }, this.options.state)).write()
325 |   debug('Initiating finished')
326 |   return true
327 | }
328 | 
329 | // Credits to `is-promise` package
330 | function isPromise (obj) {
331 |   return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
332 | }
333 | 
334 | /**
335 |  * @overview {@link http://telegraf.js.org/|Telegraf} Session middleware for storing sessions locally (Memory/FileSync/FileAsync/...)
336 |  * @module telegraf-session-local
337 |  * @license MIT
338 |  * @author Tema Smirnov <git.tema@smirnov.one>
339 |  * @requires {@link https://www.npmjs.com/package/telegraf|npm: telegraf}
340 |  * @requires {@link https://www.npmjs.com/package/lowdb|npm: lowdb}
341 |  * @see {@link http://telegraf.js.org/|Telegraf} | {@link https://github.com/typicode/lowdb|lowdb}
342 |  * @exports LocalSession
343 |  */
344 | module.exports = LocalSession
345 | module.exports.isPromise = isPromise
346 | 
347 |
348 |
349 | 350 | 351 | 352 | 353 | 354 | 355 |
356 | 357 |
358 | 359 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /docs/styles/jsdoc.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box 3 | } 4 | 5 | html, body { 6 | height: 100%; 7 | width: 100%; 8 | } 9 | 10 | body { 11 | color: #4d4e53; 12 | background-color: white; 13 | margin: 0 auto; 14 | padding: 0 20px; 15 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 16 | font-size: 16px; 17 | } 18 | 19 | img { 20 | max-width: 100%; 21 | } 22 | 23 | a, 24 | a:active { 25 | color: #606; 26 | text-decoration: none; 27 | } 28 | 29 | a:hover { 30 | text-decoration: none; 31 | } 32 | 33 | article a { 34 | border-bottom: 1px solid #ddd; 35 | } 36 | 37 | article a:hover, article a:active { 38 | border-bottom-color: #222; 39 | } 40 | 41 | article .description a { 42 | word-break: break-word; 43 | } 44 | 45 | p, ul, ol, blockquote { 46 | margin-bottom: 1em; 47 | line-height: 160%; 48 | } 49 | 50 | h1, h2, h3, h4, h5, h6 { 51 | font-family: 'Montserrat', sans-serif; 52 | } 53 | 54 | h1, h2, h3, h4, h5, h6 { 55 | color: #000; 56 | font-weight: 400; 57 | margin: 0; 58 | } 59 | 60 | h1 { 61 | font-weight: 300; 62 | font-size: 48px; 63 | margin: 1em 0 .5em; 64 | } 65 | 66 | h1.page-title { 67 | font-size: 48px; 68 | margin: 1em 30px; 69 | line-height: 100%; 70 | word-wrap: break-word; 71 | } 72 | 73 | h2 { 74 | font-size: 24px; 75 | margin: 1.5em 0 .3em; 76 | } 77 | 78 | h3 { 79 | font-size: 24px; 80 | margin: 1.2em 0 .3em; 81 | } 82 | 83 | h4 { 84 | font-size: 18px; 85 | margin: 1em 0 .2em; 86 | color: #4d4e53; 87 | } 88 | 89 | h4.name { 90 | color: #fff; 91 | background: #6d426d; 92 | box-shadow: 0 .25em .5em #d3d3d3; 93 | border-top: 1px solid #d3d3d3; 94 | border-bottom: 1px solid #d3d3d3; 95 | margin: 1.5em 0 0.5em; 96 | padding: .75em 0 .75em 10px; 97 | } 98 | 99 | h4.name a { 100 | color: #fc83ff; 101 | } 102 | 103 | h4.name a:hover { 104 | border-bottom-color: #fc83ff; 105 | } 106 | 107 | h5, .container-overview .subsection-title { 108 | font-size: 120%; 109 | letter-spacing: -0.01em; 110 | margin: 8px 0 3px 0; 111 | } 112 | 113 | h6 { 114 | font-size: 100%; 115 | letter-spacing: -0.01em; 116 | margin: 6px 0 3px 0; 117 | font-style: italic; 118 | } 119 | 120 | .usertext h1 { 121 | font-family: "Source Sans Pro"; 122 | font-size: 24px; 123 | margin: 2.5em 0 1em; 124 | font-weight: 400; 125 | } 126 | 127 | .usertext h2 { 128 | font-family: "Source Sans Pro"; 129 | font-size: 18px; 130 | margin: 2em 0 0.5em; 131 | font-weight: 400; 132 | 133 | } 134 | 135 | .usertext h3 { 136 | font-family: "Source Sans Pro"; 137 | font-size: 15px; 138 | margin: 1.5em 0 0; 139 | font-weight: 400; 140 | } 141 | 142 | .usertext h4 { 143 | font-family: "Source Sans Pro"; 144 | font-size: 14px; 145 | margin: 0 0 0; 146 | font-weight: 400; 147 | } 148 | 149 | .usertext h5 { 150 | font-size: 12px; 151 | margin: 1em 0 0; 152 | font-weight: normal; 153 | color: #666; 154 | } 155 | 156 | .usertext h6 { 157 | font-size: 11px; 158 | margin: 1em 0 0; 159 | font-weight: normal; 160 | font-style: normal; 161 | color: #666; 162 | } 163 | 164 | 165 | tt, code, kbd, samp, pre { 166 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 167 | background: #f4f4f4; 168 | } 169 | 170 | tt, code, kbd, samp{ 171 | padding: 1px 5px; 172 | } 173 | 174 | pre { 175 | padding-bottom: 1em; 176 | } 177 | 178 | .class-description { 179 | font-size: 130%; 180 | line-height: 140%; 181 | margin-bottom: 1em; 182 | margin-top: 1em; 183 | } 184 | 185 | .class-description:empty { 186 | margin: 0 187 | } 188 | 189 | #main { 190 | float: right; 191 | width: calc(100% - 240px); 192 | } 193 | 194 | header { 195 | display: block 196 | } 197 | 198 | section { 199 | display: block; 200 | background-color: #fff; 201 | padding: 0 0 0 30px; 202 | } 203 | 204 | .variation { 205 | display: none 206 | } 207 | 208 | .signature-attributes { 209 | font-size: 60%; 210 | color: #eee; 211 | font-style: italic; 212 | font-weight: lighter; 213 | } 214 | 215 | nav { 216 | float: left; 217 | display: block; 218 | width: 250px; 219 | background: #fff; 220 | overflow: auto; 221 | position: fixed; 222 | height: 100%; 223 | } 224 | 225 | nav #nav-search{ 226 | width: 210px; 227 | height: 30px; 228 | padding: 5px 10px; 229 | font-size: 12px; 230 | line-height: 1.5; 231 | border-radius: 3px; 232 | margin-right: 20px; 233 | margin-top: 20px; 234 | } 235 | 236 | nav.wrap a{ 237 | word-wrap: break-word; 238 | } 239 | 240 | nav h3 { 241 | margin-top: 12px; 242 | font-size: 13px; 243 | text-transform: uppercase; 244 | letter-spacing: 1px; 245 | font-weight: 700; 246 | line-height: 24px; 247 | margin: 15px 0 10px; 248 | padding: 0; 249 | color: #000; 250 | } 251 | 252 | nav h3.collapsed_header { 253 | cursor: pointer; 254 | } 255 | 256 | nav ul { 257 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 258 | font-size: 100%; 259 | line-height: 17px; 260 | padding: 0; 261 | margin: 0; 262 | list-style-type: none; 263 | } 264 | 265 | nav ul a, 266 | nav ul a:active { 267 | font-family: 'Montserrat', sans-serif; 268 | line-height: 18px; 269 | padding: 0; 270 | display: block; 271 | font-size: 12px; 272 | } 273 | 274 | nav a:hover, 275 | nav a:active { 276 | color: #606; 277 | } 278 | 279 | nav > ul { 280 | padding: 0 10px; 281 | } 282 | 283 | nav > ul > li > a { 284 | color: #606; 285 | margin-top: 10px; 286 | } 287 | 288 | nav ul ul a { 289 | color: hsl(207, 1%, 60%); 290 | border-left: 1px solid hsl(207, 10%, 86%); 291 | } 292 | 293 | nav ul ul a, 294 | nav ul ul a:active { 295 | padding-left: 20px 296 | } 297 | 298 | nav h2 { 299 | font-size: 13px; 300 | margin: 10px 0 0 0; 301 | padding: 0; 302 | } 303 | 304 | nav > h2 > a { 305 | margin: 10px 0 -10px; 306 | color: #606 !important; 307 | } 308 | 309 | footer { 310 | color: hsl(0, 0%, 28%); 311 | margin-left: 250px; 312 | display: block; 313 | padding: 15px; 314 | font-style: italic; 315 | font-size: 90%; 316 | } 317 | 318 | .ancestors { 319 | color: #999 320 | } 321 | 322 | .ancestors a { 323 | color: #999 !important; 324 | } 325 | 326 | .clear { 327 | clear: both 328 | } 329 | 330 | .important { 331 | font-weight: bold; 332 | color: #950B02; 333 | } 334 | 335 | .yes-def { 336 | text-indent: -1000px 337 | } 338 | 339 | .type-signature { 340 | color: #CA79CA 341 | } 342 | 343 | .type-signature:last-child { 344 | color: #eee; 345 | } 346 | 347 | .name, .signature { 348 | font-family: Consolas, Monaco, 'Andale Mono', monospace 349 | } 350 | 351 | .signature { 352 | color: #fc83ff; 353 | } 354 | 355 | .details { 356 | margin-top: 6px; 357 | border-left: 2px solid #DDD; 358 | line-height: 20px; 359 | font-size: 14px; 360 | } 361 | 362 | .details dt { 363 | width: auto; 364 | float: left; 365 | padding-left: 10px; 366 | } 367 | 368 | .details dd { 369 | margin-left: 70px; 370 | margin-top: 6px; 371 | margin-bottom: 6px; 372 | } 373 | 374 | .details ul { 375 | margin: 0 376 | } 377 | 378 | .details ul { 379 | list-style-type: none 380 | } 381 | 382 | .details pre.prettyprint { 383 | margin: 0 384 | } 385 | 386 | .details .object-value { 387 | padding-top: 0 388 | } 389 | 390 | .description { 391 | margin-bottom: 1em; 392 | margin-top: 1em; 393 | } 394 | 395 | .code-caption { 396 | font-style: italic; 397 | font-size: 107%; 398 | margin: 0; 399 | } 400 | 401 | .prettyprint { 402 | font-size: 14px; 403 | overflow: auto; 404 | } 405 | 406 | .prettyprint.source { 407 | width: inherit; 408 | line-height: 18px; 409 | display: block; 410 | background-color: #0d152a; 411 | color: #aeaeae; 412 | } 413 | 414 | .prettyprint code { 415 | line-height: 18px; 416 | display: block; 417 | background-color: #0d152a; 418 | color: #4D4E53; 419 | } 420 | 421 | .prettyprint > code { 422 | padding: 15px; 423 | } 424 | 425 | .prettyprint .linenums code { 426 | padding: 0 15px 427 | } 428 | 429 | .prettyprint .linenums li:first-of-type code { 430 | padding-top: 15px 431 | } 432 | 433 | .prettyprint code span.line { 434 | display: inline-block 435 | } 436 | 437 | .prettyprint.linenums { 438 | padding-left: 70px; 439 | -webkit-user-select: none; 440 | -moz-user-select: none; 441 | -ms-user-select: none; 442 | user-select: none; 443 | } 444 | 445 | .prettyprint.linenums ol { 446 | padding-left: 0 447 | } 448 | 449 | .prettyprint.linenums li { 450 | border-left: 3px #34446B solid; 451 | } 452 | 453 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 454 | background-color: #34446B; 455 | } 456 | 457 | .prettyprint.linenums li * { 458 | -webkit-user-select: text; 459 | -moz-user-select: text; 460 | -ms-user-select: text; 461 | user-select: text; 462 | } 463 | 464 | .prettyprint.linenums li code:empty:after { 465 | content:""; 466 | display:inline-block; 467 | width:0px; 468 | } 469 | 470 | table { 471 | border-spacing: 0; 472 | border: 1px solid #ddd; 473 | border-collapse: collapse; 474 | border-radius: 3px; 475 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 476 | width: 100%; 477 | font-size: 14px; 478 | margin: 1em 0; 479 | } 480 | 481 | td, th { 482 | margin: 0px; 483 | text-align: left; 484 | vertical-align: top; 485 | padding: 10px; 486 | display: table-cell; 487 | } 488 | 489 | thead tr, thead tr { 490 | background-color: #fff; 491 | font-weight: bold; 492 | border-bottom: 1px solid #ddd; 493 | } 494 | 495 | .params .type { 496 | white-space: nowrap; 497 | } 498 | 499 | .params code { 500 | white-space: pre; 501 | } 502 | 503 | .params td, .params .name, .props .name, .name code { 504 | color: #4D4E53; 505 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 506 | font-size: 100%; 507 | } 508 | 509 | .params td { 510 | border-top: 1px solid #eee 511 | } 512 | 513 | .params td.description > p:first-child, .props td.description > p:first-child { 514 | margin-top: 0; 515 | padding-top: 0; 516 | } 517 | 518 | .params td.description > p:last-child, .props td.description > p:last-child { 519 | margin-bottom: 0; 520 | padding-bottom: 0; 521 | } 522 | 523 | span.param-type, .params td .param-type, .param-type dd { 524 | color: #606; 525 | font-family: Consolas, Monaco, 'Andale Mono', monospace 526 | } 527 | 528 | .param-type dt, .param-type dd { 529 | display: inline-block 530 | } 531 | 532 | .param-type { 533 | margin: 14px 0; 534 | } 535 | 536 | .disabled { 537 | color: #454545 538 | } 539 | 540 | /* navicon button */ 541 | .navicon-button { 542 | display: none; 543 | position: relative; 544 | padding: 2.0625rem 1.5rem; 545 | transition: 0.25s; 546 | cursor: pointer; 547 | -webkit-user-select: none; 548 | -moz-user-select: none; 549 | -ms-user-select: none; 550 | user-select: none; 551 | opacity: .8; 552 | } 553 | .navicon-button .navicon:before, .navicon-button .navicon:after { 554 | transition: 0.25s; 555 | } 556 | .navicon-button:hover { 557 | transition: 0.5s; 558 | opacity: 1; 559 | } 560 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 561 | transition: 0.25s; 562 | } 563 | .navicon-button:hover .navicon:before { 564 | top: .825rem; 565 | } 566 | .navicon-button:hover .navicon:after { 567 | top: -.825rem; 568 | } 569 | 570 | /* navicon */ 571 | .navicon { 572 | position: relative; 573 | width: 2.5em; 574 | height: .3125rem; 575 | background: #000; 576 | transition: 0.3s; 577 | border-radius: 2.5rem; 578 | } 579 | .navicon:before, .navicon:after { 580 | display: block; 581 | content: ""; 582 | height: .3125rem; 583 | width: 2.5rem; 584 | background: #000; 585 | position: absolute; 586 | z-index: -1; 587 | transition: 0.3s 0.25s; 588 | border-radius: 1rem; 589 | } 590 | .navicon:before { 591 | top: .625rem; 592 | } 593 | .navicon:after { 594 | top: -.625rem; 595 | } 596 | 597 | /* open */ 598 | .nav-trigger:checked + label:not(.steps) .navicon:before, 599 | .nav-trigger:checked + label:not(.steps) .navicon:after { 600 | top: 0 !important; 601 | } 602 | 603 | .nav-trigger:checked + label .navicon:before, 604 | .nav-trigger:checked + label .navicon:after { 605 | transition: 0.5s; 606 | } 607 | 608 | /* Minus */ 609 | .nav-trigger:checked + label { 610 | -webkit-transform: scale(0.75); 611 | transform: scale(0.75); 612 | } 613 | 614 | /* × and + */ 615 | .nav-trigger:checked + label.plus .navicon, 616 | .nav-trigger:checked + label.x .navicon { 617 | background: transparent; 618 | } 619 | 620 | .nav-trigger:checked + label.plus .navicon:before, 621 | .nav-trigger:checked + label.x .navicon:before { 622 | -webkit-transform: rotate(-45deg); 623 | transform: rotate(-45deg); 624 | background: #FFF; 625 | } 626 | 627 | .nav-trigger:checked + label.plus .navicon:after, 628 | .nav-trigger:checked + label.x .navicon:after { 629 | -webkit-transform: rotate(45deg); 630 | transform: rotate(45deg); 631 | background: #FFF; 632 | } 633 | 634 | .nav-trigger:checked + label.plus { 635 | -webkit-transform: scale(0.75) rotate(45deg); 636 | transform: scale(0.75) rotate(45deg); 637 | } 638 | 639 | .nav-trigger:checked ~ nav { 640 | left: 0 !important; 641 | } 642 | 643 | .nav-trigger:checked ~ .overlay { 644 | display: block; 645 | } 646 | 647 | .nav-trigger { 648 | position: fixed; 649 | top: 0; 650 | clip: rect(0, 0, 0, 0); 651 | } 652 | 653 | .overlay { 654 | display: none; 655 | position: fixed; 656 | top: 0; 657 | bottom: 0; 658 | left: 0; 659 | right: 0; 660 | width: 100%; 661 | height: 100%; 662 | background: hsla(0, 0%, 0%, 0.5); 663 | z-index: 1; 664 | } 665 | 666 | /* nav level */ 667 | .level-hide { 668 | display: none; 669 | } 670 | html[data-search-mode] .level-hide { 671 | display: block; 672 | } 673 | 674 | 675 | @media only screen and (max-width: 680px) { 676 | body { 677 | overflow-x: hidden; 678 | } 679 | 680 | nav { 681 | background: #FFF; 682 | width: 250px; 683 | height: 100%; 684 | position: fixed; 685 | top: 0; 686 | right: 0; 687 | bottom: 0; 688 | left: -250px; 689 | z-index: 3; 690 | padding: 0 10px; 691 | transition: left 0.2s; 692 | } 693 | 694 | .navicon-button { 695 | display: inline-block; 696 | position: fixed; 697 | top: 1.5em; 698 | right: 0; 699 | z-index: 2; 700 | } 701 | 702 | #main { 703 | width: 100%; 704 | } 705 | 706 | #main h1.page-title { 707 | margin: 1em 0; 708 | } 709 | 710 | #main section { 711 | padding: 0; 712 | } 713 | 714 | footer { 715 | margin-left: 0; 716 | } 717 | } 718 | 719 | /** Add a '#' to static members */ 720 | [data-type="member"] a::before { 721 | content: '#'; 722 | display: inline-block; 723 | margin-left: -14px; 724 | margin-right: 5px; 725 | } 726 | 727 | #disqus_thread{ 728 | margin-left: 30px; 729 | } 730 | 731 | @font-face { 732 | font-family: 'Montserrat'; 733 | font-style: normal; 734 | font-weight: 400; 735 | src: url('../fonts/Montserrat/Montserrat-Regular.eot'); /* IE9 Compat Modes */ 736 | src: url('../fonts/Montserrat/Montserrat-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 737 | url('../fonts/Montserrat/Montserrat-Regular.woff2') format('woff2'), /* Super Modern Browsers */ 738 | url('../fonts/Montserrat/Montserrat-Regular.woff') format('woff'), /* Pretty Modern Browsers */ 739 | url('../fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); /* Safari, Android, iOS */ 740 | } 741 | 742 | @font-face { 743 | font-family: 'Montserrat'; 744 | font-style: normal; 745 | font-weight: 700; 746 | src: url('../fonts/Montserrat/Montserrat-Bold.eot'); /* IE9 Compat Modes */ 747 | src: url('../fonts/Montserrat/Montserrat-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 748 | url('../fonts/Montserrat/Montserrat-Bold.woff2') format('woff2'), /* Super Modern Browsers */ 749 | url('../fonts/Montserrat/Montserrat-Bold.woff') format('woff'), /* Pretty Modern Browsers */ 750 | url('../fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); /* Safari, Android, iOS */ 751 | } 752 | 753 | @font-face { 754 | font-family: 'Source Sans Pro'; 755 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot'); 756 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot?#iefix') format('embedded-opentype'), 757 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2') format('woff2'), 758 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff') format('woff'), 759 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf') format('truetype'), 760 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg#source_sans_proregular') format('svg'); 761 | font-weight: 400; 762 | font-style: normal; 763 | } 764 | 765 | @font-face { 766 | font-family: 'Source Sans Pro'; 767 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot'); 768 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot?#iefix') format('embedded-opentype'), 769 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2') format('woff2'), 770 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff') format('woff'), 771 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf') format('truetype'), 772 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg'); 773 | font-weight: 300; 774 | font-style: normal; 775 | 776 | } 777 | -------------------------------------------------------------------------------- /docs/styles/prettify.css: -------------------------------------------------------------------------------- 1 | .pln { 2 | color: #ddd; 3 | } 4 | 5 | /* string content */ 6 | .str { 7 | color: #61ce3c; 8 | } 9 | 10 | /* a keyword */ 11 | .kwd { 12 | color: #fbde2d; 13 | } 14 | 15 | /* a comment */ 16 | .com { 17 | color: #aeaeae; 18 | } 19 | 20 | /* a type name */ 21 | .typ { 22 | color: #8da6ce; 23 | } 24 | 25 | /* a literal value */ 26 | .lit { 27 | color: #fbde2d; 28 | } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #ddd; 33 | } 34 | 35 | /* lisp open bracket */ 36 | .opn { 37 | color: #000000; 38 | } 39 | 40 | /* lisp close bracket */ 41 | .clo { 42 | color: #000000; 43 | } 44 | 45 | /* a markup tag name */ 46 | .tag { 47 | color: #8da6ce; 48 | } 49 | 50 | /* a markup attribute name */ 51 | .atn { 52 | color: #fbde2d; 53 | } 54 | 55 | /* a markup attribute value */ 56 | .atv { 57 | color: #ddd; 58 | } 59 | 60 | /* a declaration */ 61 | .dec { 62 | color: #EF5050; 63 | } 64 | 65 | /* a variable name */ 66 | .var { 67 | color: #c82829; 68 | } 69 | 70 | /* a function name */ 71 | .fun { 72 | color: #4271ae; 73 | } 74 | 75 | /* Specify class=linenums on a pre to get line numbering */ 76 | ol.linenums { 77 | margin-top: 0; 78 | margin-bottom: 0; 79 | padding-bottom: 2px; 80 | } 81 | -------------------------------------------------------------------------------- /examples/extra.js: -------------------------------------------------------------------------------- 1 | const { Telegraf } = require('telegraf') 2 | const LocalSession = require('../lib/session') // require('telegraf-session-local') 3 | 4 | const bot = new Telegraf(process.env.BOT_TOKEN) // Your Bot token here 5 | 6 | const localSession = new LocalSession({ 7 | // Database name/path, where sessions will be located (default: 'sessions.json') 8 | database: 'example_db.json', 9 | // Name of session property object in Telegraf Context (default: 'session') 10 | property: 'session', 11 | // Type of lowdb storage (default: 'storageFileSync') 12 | storage: LocalSession.storageFileAsync, 13 | // Format of storage/database (default: JSON.stringify / JSON.parse) 14 | format: { 15 | serialize: (obj) => JSON.stringify(obj, null, 2), // null & 2 for pretty-formatted JSON 16 | deserialize: (str) => JSON.parse(str), 17 | }, 18 | // We will use `messages` array in our database to store user messages using exported lowdb instance from LocalSession via Telegraf Context 19 | state: { messages: [] } 20 | }) 21 | 22 | // Wait for database async initialization finished (storageFileAsync or your own asynchronous storage adapter) 23 | localSession.DB.then(DB => { 24 | // Database now initialized, so now you can retrieve anything you want from it 25 | console.log('Current LocalSession DB:', DB.value()) 26 | // console.log(DB.get('sessions').getById('1:1').value()) 27 | }) 28 | 29 | // Telegraf will use `telegraf-session-local` configured above middleware 30 | bot.use(localSession.middleware()) 31 | 32 | bot.on('text', (ctx, next) => { 33 | ctx.session.counter = ctx.session.counter || 0 34 | ctx.session.counter++ 35 | ctx.replyWithMarkdownV2(`Counter updated, new value: \`${ctx.session.counter}\``) 36 | // Writing message to Array `messages` into database which already has sessions Array 37 | ctx.sessionDB.get('messages').push([ctx.message]).write() 38 | // `property`+'DB' is a name of ctx property which contains lowdb instance, default = `sessionDB` 39 | 40 | return next() 41 | }) 42 | 43 | bot.command('/stats', (ctx) => { 44 | ctx.replyWithMarkdownV2(`Session has \`${ctx.session.counter}\` messages from @${ctx.from.username || ctx.from.id}`) 45 | }) 46 | 47 | bot.command('/remove', (ctx) => { 48 | ctx.replyWithMarkdownV2(`Removing session from lowdb database: \`${JSON.stringify(ctx.session)}\``) 49 | // Setting session to null, undefined or empty object/array will trigger removing it from database 50 | ctx.session = null 51 | }) 52 | 53 | bot.launch() 54 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | const { Telegraf } = require('telegraf') 2 | const LocalSession = require('../lib/session') // require('telegraf-session-local') 3 | 4 | const bot = new Telegraf(process.env.BOT_TOKEN) // Your Bot token here 5 | 6 | bot.use((new LocalSession({ database: 'example_db.json' })).middleware()) 7 | 8 | bot.on('text', (ctx, next) => { 9 | ctx.session.counter = ctx.session.counter || 0 10 | ctx.session.counter++ 11 | ctx.replyWithMarkdownV2(`Counter updated, new value: \`${ctx.session.counter}\``) 12 | return next() 13 | }) 14 | 15 | bot.command('/stats', (ctx) => { 16 | ctx.replyWithMarkdownV2(`Database has \`${ctx.session.counter}\` messages from @${ctx.from.username || ctx.from.id}`) 17 | }) 18 | 19 | bot.command('/remove', (ctx) => { 20 | ctx.replyWithMarkdownV2(`Removing session from database: \`${JSON.stringify(ctx.session)}\``) 21 | // Setting session to null, undefined or empty object/array will trigger removing it from database 22 | ctx.session = null 23 | }) 24 | 25 | bot.launch() 26 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "telegraf-session-local - Documentation", 3 | "tags": { 4 | "allowUnknownTags": true 5 | }, 6 | "source": { 7 | "include": [ 8 | "lib/", 9 | "./README.md" 10 | ], 11 | "includePattern": ".js$", 12 | "excludePattern": "(node_modules/|docs)" 13 | }, 14 | "plugins": ["plugins/markdown"], 15 | "opts": { 16 | "encoding": "utf8", 17 | "template": "node_modules/docdash", 18 | "destination": "docs/", 19 | "recurse": true, 20 | "verbose": true 21 | }, 22 | "markdown": { 23 | "parser": "gfm", 24 | "hardwrap": true 25 | }, 26 | "templates": { 27 | "cleverLinks": true, 28 | "monospaceLinks": true, 29 | "default": { 30 | "outputSourceFiles": true, 31 | "includeDate": false 32 | } 33 | }, 34 | "docdash": { 35 | "static": true, 36 | "sort": true, 37 | "sectionOrder": [ 38 | "Modules", 39 | "Namespaces", 40 | "Classes", 41 | "Externals", 42 | "Events", 43 | "Mixins", 44 | "Tutorials", 45 | "Interfaces" 46 | ], 47 | "search": true, 48 | "collapse": false, 49 | "typedefs": true, 50 | "meta": { 51 | "title": "Telegraf Session local Documentation", 52 | "keyword": "Telegram Telegraf Session Local Database JSON XML", 53 | "description": "Documentation for Telegraf Session local" 54 | }, 55 | "menu": { 56 | "GitHub Repository ⧉": { 57 | "href": "https://github.com/RealSpeaker/telegraf-session-local", 58 | "target": "_blank", 59 | "class": "menu-item", 60 | "id": "link_repo" 61 | }, 62 | "NPM Package ⧉": { 63 | "href": "https://www.npmjs.com/package/telegraf-session-local", 64 | "target": "_blank", 65 | "class": "menu-item", 66 | "id": "link_npm" 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/lodash-id.js: -------------------------------------------------------------------------------- 1 | // UUID 2 | // https://gist.github.com/LeverOne/1308368 3 | /* jshint ignore:start */ 4 | function uuid (a, b) { for (b = a = ''; a++ < 36; b += a * 51 & 52 ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16) : '-');return b } 5 | /* jshint ignore:end */ 6 | 7 | // Credits to typicode/lodash-id 8 | // Based on: https://github.com/typicode/lodash-id/blob/adf8c8e99e598c448e6db6ec5705de9d9edb96ed/src/index.js 9 | module.exports = { 10 | // Empties properties 11 | __empty: function (doc) { 12 | this.forEach(doc, function (value, key) { 13 | delete doc[key] 14 | }) 15 | }, 16 | 17 | // Copies properties from an object to another 18 | __update: function (dest, src) { 19 | this.forEach(src, function (value, key) { 20 | dest[key] = value 21 | }) 22 | }, 23 | 24 | // Removes an item from an array 25 | __remove: function (array, item) { 26 | const index = this.indexOf(array, item) 27 | if (index !== -1) array.splice(index, 1) 28 | }, 29 | 30 | __id: function () { 31 | const id = this.id || 'id' 32 | return id 33 | }, 34 | 35 | // TODO: Handle situation when collection has docs with id === null 36 | getById: function (collection, id) { 37 | const self = this 38 | return this.find(collection, function (doc) { 39 | if (self.has(doc, self.__id())) { 40 | return doc[self.__id()].toString() === id.toString() 41 | } 42 | }) 43 | }, 44 | 45 | createId: function (collection, doc) { 46 | return uuid() 47 | }, 48 | 49 | insert: function (collection, doc) { 50 | doc[this.__id()] = doc[this.__id()] || this.createId(collection, doc) 51 | const d = this.getById(collection, doc[this.__id()]) 52 | if (d) throw new Error('Insert failed, duplicate id') 53 | collection.push(doc) 54 | return doc 55 | }, 56 | 57 | upsert: function (collection, doc) { 58 | if (doc[this.__id()]) { 59 | // id is set 60 | const d = this.getById(collection, doc[this.__id()]) 61 | if (d) { 62 | // replace properties of existing object 63 | this.__empty(d) 64 | this.assign(d, doc) 65 | } else { 66 | // push new object 67 | collection.push(doc) 68 | } 69 | } else { 70 | // create id and push new object 71 | doc[this.__id()] = this.createId(collection, doc) 72 | collection.push(doc) 73 | } 74 | 75 | return doc 76 | }, 77 | 78 | updateById: function (collection, id, attrs) { 79 | const doc = this.getById(collection, id) 80 | 81 | if (doc) { 82 | this.assign(doc, attrs, { id: doc.id }) 83 | } 84 | 85 | return doc 86 | }, 87 | 88 | updateWhere: function (collection, predicate, attrs) { 89 | const self = this 90 | const docs = this.filter(collection, predicate) 91 | 92 | docs.forEach(function (doc) { 93 | self.assign(doc, attrs, { id: doc.id }) 94 | }) 95 | 96 | return docs 97 | }, 98 | 99 | replaceById: function (collection, id, attrs) { 100 | const doc = this.getById(collection, id) 101 | 102 | if (doc) { 103 | const docId = doc.id 104 | this.__empty(doc) 105 | this.assign(doc, attrs, { id: docId }) 106 | } 107 | 108 | return doc 109 | }, 110 | 111 | removeById: function (collection, id) { 112 | const doc = this.getById(collection, id) 113 | 114 | this.__remove(collection, doc) 115 | 116 | return doc 117 | }, 118 | 119 | removeWhere: function (collection, predicate) { 120 | const self = this 121 | const docs = this.filter(collection, predicate) 122 | 123 | docs.forEach(function (doc) { 124 | self.__remove(collection, doc) 125 | }) 126 | 127 | return docs 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/session.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'telegraf-session-local' { 2 | import { AdapterSync, AdapterAsync, BaseAdapter } from 'lowdb' 3 | import { Context } from 'telegraf' 4 | 5 | type MiddlewareFn = (ctx: C, next: () => Promise) => Promise 6 | 7 | export interface LocalSessionOptions { 8 | storage?: AdapterSync | AdapterAsync 9 | database?: string 10 | property?: string 11 | state?: TSession 12 | format?: { 13 | serialize?: (value: TSession) => string 14 | deserialize?: (value: string) => TSession 15 | } 16 | getSessionKey?: (ctx: Context) => string 17 | } 18 | 19 | class LocalSession { 20 | public DB: unknown 21 | 22 | constructor(options?: LocalSessionOptions) 23 | 24 | getSessionKey(ctx: Context): string 25 | getSession(key: string): TSession 26 | saveSession(key: string, data: TSession): Promise 27 | middleware(property?: string): MiddlewareFn 28 | static get storageFileSync(): AdapterSync 29 | static get storageFileAsync(): AdapterAsync 30 | static get storageMemory(): AdapterSync 31 | static get storageBase(): BaseAdapter 32 | } 33 | 34 | export = LocalSession 35 | } 36 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | const Debug = require('debug') 2 | const lowdb = require('lowdb') 3 | const storageFileAsync = require('lowdb/adapters/FileAsync') 4 | const storageFileSync = require('lowdb/adapters/FileSync') 5 | const storageMemory = require('lowdb/adapters/Memory') 6 | 7 | const lodashId = require('./lodash-id.js') 8 | 9 | const debug = Debug('telegraf:session-local') 10 | 11 | /** 12 | * Represents a wrapper around locally stored session, it's {@link LocalSession#middleware|middleware} & lowdb 13 | * 14 | * @param {Object} [options] - Options for {@link LocalSession|LocalSession} & {@link https://github.com/typicode/lowdb|lowdb} 15 | * @param {String} [options.database] - Name or path to database file `default:` `'sessions.json'` 16 | * @param {String} [options.property] - Name of property in {@link https://telegraf.js.org/#/?id=context|Telegraf Context} where session object will be located `default:` `'session'` 17 | * @param {Object} [options.state] - Initial state of database. You can use it to pre-init database Arrays/Objects to store your own data `default:` `{}` 18 | * @param {Function} [options.getSessionKey] - Function to get identifier for session from {@link https://telegraf.js.org/#/?id=context|Telegraf Context} (may implement it on your own) 19 | * @param {Object} [options.storage] - lowdb storage option for implementing your own storage read/write operations `default:` {@link LocalSession.storageFileSync|LocalSession.storageFileSync} 20 | * @param {Function} [options.storage.read] - lowdb storage read function, must return an object or a Promise 21 | * @param {Function} [options.storage.write] - lowdb storage write function, must return undefined or a Promise 22 | * @param {Object} [options.format] - lowdb storage format option for implementing your own database format for read/write operations 23 | * @param {Function} [options.format.serialize] - lowdb storage serialize function, must return data (usually string) `default:` {@link https://goo.gl/dmGpZd|JSON.stringify()} 24 | * @param {Function} [options.format.deserialize] - lowdb storage deserialize function, must return an object `default:` {@link https://goo.gl/wNy3ar|JSON.parse()} 25 | * @returns Instance of {@link LocalSession|LocalSession} 26 | */ 27 | class LocalSession { 28 | constructor (options = {}) { 29 | this.options = Object.assign({ 30 | // TODO: Use storageFileAsync as default with support of Promise or Promise-like initialization, see: https://git.io/fA3ZN 31 | storage: LocalSession.storageFileSync, 32 | database: 'sessions.json', 33 | property: 'session', 34 | state: { }, 35 | format: { }, 36 | getSessionKey: (ctx) => { 37 | if (!ctx.from) return // should never happen 38 | 39 | let chatInstance 40 | if (ctx.chat) { 41 | chatInstance = ctx.chat.id 42 | } else if (ctx.updateType === 'callback_query') { 43 | chatInstance = ctx.callbackQuery.chat_instance 44 | } else { // if (ctx.updateType === 'inline_query') { 45 | chatInstance = ctx.from.id 46 | } 47 | return chatInstance + ':' + ctx.from.id 48 | } 49 | }, options) 50 | this.DB = undefined 51 | this._adapter = undefined 52 | // DISABLED: this.options.defaultValue = this.options.state // Backward compatability with old lowdb 53 | const defaultAdaptersOptions = Object.assign( 54 | { defaultValue: this.options.state }, 55 | this.options.format.serialize ? { serialize: this.options.format.serialize } : {}, 56 | this.options.format.deserialize ? { deserialize: this.options.format.deserialize } : {} 57 | ) 58 | if (this.options.storage === LocalSession.storageMemory) { 59 | debug('Initiating: lowdb adapter: storageMemory') 60 | this._adapter = new LocalSession.storageMemory(this.options.database, defaultAdaptersOptions) 61 | } else if (this.options.storage === LocalSession.storageFileAsync) { 62 | debug('Initiating: lowdb adapter: storageFileAsync') 63 | this._adapter = new LocalSession.storageFileAsync(this.options.database, defaultAdaptersOptions) 64 | } else if (this.options.storage === LocalSession.storageFileSync) { 65 | debug('Initiating: lowdb adapter: storageFileSync') 66 | this._adapter = new LocalSession.storageFileSync(this.options.database, defaultAdaptersOptions) 67 | } else { 68 | debug('Initiating: lowdb adapter: custom storage/adapter') 69 | this._adapter = new this.options.storage(this.options.database, defaultAdaptersOptions) 70 | } 71 | debug('Initiating: lowdb instance') 72 | const DbInstance = lowdb(this._adapter) 73 | // If lowdb initiated with async (Promise) adapter 74 | if (isPromise(DbInstance)) { 75 | debug('DbInstance is Promise like') 76 | // TODO: Split it from constructor, because this code will produce glitches if async initiating may take too long time 77 | this.DB = DbInstance 78 | this.DB.then((DB) => { 79 | debug('DbInstance Promise resolved') 80 | this.DB = DB 81 | _initDB.call(this) 82 | }) 83 | } 84 | // If lowdb initiated with sync adapter 85 | else { 86 | this.DB = DbInstance 87 | _initDB.call(this) 88 | } 89 | } 90 | 91 | /** 92 | * Get session key from {@link https://telegraf.js.org/#/?id=context|Telegraf Context} 93 | * 94 | * @param {Object} ctx - {@link https://telegraf.js.org/#/?id=context|Telegraf Context} 95 | * @returns {String} Session key in format `number:number` (chat.id:from.id) 96 | */ 97 | getSessionKey (ctx) { 98 | this._called() 99 | return this.options.getSessionKey(ctx) 100 | } 101 | 102 | /** 103 | * Get session by it's key in database 104 | * 105 | * @param {String} key - Key which will be used to find associated session object 106 | * @returns {Object} Session data object or empty object if there's no session in database with this key 107 | */ 108 | getSession (key) { 109 | this._called(arguments) 110 | const session = this.DB.get('sessions').getById(key).value() || {} 111 | debug('Session state', session) 112 | return session.data || {} 113 | } 114 | 115 | /** 116 | * Save session to database 117 | * 118 | * @param {String} key - Unique Key which will be used to store session object 119 | * @param {Object} data - Session data object (if empty, session will be removed from database) 120 | * @returns {Promise|Function} - Promise or Promise-like `.then()` function, with session object at 1-st argument 121 | */ 122 | async saveSession (key, data) { 123 | this._called(arguments) 124 | if (!key) return 125 | // If there's no data provided or it's empty, we should remove session record from database 126 | if (this.DB._.isEmpty(data)) { 127 | debug('Removing session #', key) 128 | return this.DB.get('sessions').removeById(key).write() 129 | } 130 | debug('Saving session: %s = %o', key, data) 131 | /* eslint-disable brace-style */ 132 | // If database has record, then just update it 133 | if (this.DB.get('sessions').getById(key).value()) { 134 | debug(' -> Updating') 135 | const session = await this.DB.get('sessions').updateById(key, { data: data }).write() 136 | // Check if lowdb Storage returned var type is Promise-like or just sync-driven data 137 | return session 138 | } 139 | // If no, so we should push new record into it 140 | else { 141 | debug(' -> Inserting') 142 | const session = await this.DB.get('sessions').push({ id: key, data: data }).write() 143 | // Check if lowdb Storage returned var type is Promise-like or just sync-driven data 144 | return session[0] 145 | } 146 | /* eslint-enable brace-style */ 147 | } 148 | 149 | /** 150 | * Session middleware for use in Telegraf 151 | * 152 | * @param {String} [property] - Name of property in {@link https://telegraf.js.org/#/?id=context|Telegraf Context} where session object will be located (overrides `property` at {@link LocalSession} constructor) 153 | * @returns {Promise} 154 | */ 155 | middleware (property = this.options.property) { 156 | const that = this 157 | return async (ctx, next) => { 158 | const key = that.getSessionKey(ctx) 159 | if (!key) return next() 160 | debug('Session key: %s', key) 161 | let session = that.getSession(key) 162 | debug('Session data: %o', session) 163 | // Assigning session object to the Telegraf Context using `property` as a variable name 164 | Object.defineProperty(ctx, property, { 165 | get: function () { return session }, 166 | set: function (newValue) { session = Object.assign({}, newValue) } 167 | }) 168 | // Make lowdb available in the Telegraf Context 169 | Object.defineProperty(ctx, `${property}DB`, { 170 | get: function () { return that.DB }, 171 | set: function () { } 172 | }) 173 | // Saving session object on the next middleware 174 | await next() 175 | 176 | this._called(arguments) 177 | debug('Next Middleware -> Key: %s | Session: %o', key, session) 178 | return that.saveSession(key, session) 179 | } 180 | } 181 | 182 | /** 183 | * lowdb storage named {@link https://git.io/vhM3Y|fileSync} before {@link https://git.io/vhM3Z|lowdb@0.17.0} 184 | * 185 | * @memberof! LocalSession 186 | * @name LocalSession.storagefileSync 187 | * @alias LocalSession.storageFileSync 188 | * @readonly 189 | */ 190 | static get storagefileSync () { 191 | return storageFileSync 192 | } 193 | 194 | /** 195 | * lowdb storage/adapter named {@link https://git.io/vhMqc|FileSync} 196 | * 197 | * @memberof! LocalSession 198 | * @name LocalSession.storageFileSync 199 | * @global 200 | * @readonly 201 | */ 202 | static get storageFileSync () { 203 | return storageFileSync 204 | } 205 | 206 | /** 207 | * lowdb storage named {@link https://git.io/vhM3m|fileAsync} before {@link https://git.io/vhM3Z|lowdb@0.17.0} 208 | * 209 | * @memberof! LocalSession 210 | * @name LocalSession.storagefileAsync 211 | * @alias LocalSession.storageFileAsync 212 | * @readonly 213 | */ 214 | static get storagefileAsync () { 215 | return storageFileAsync 216 | } 217 | 218 | /** 219 | * lowdb storage/adapter named {@link https://git.io/vhMqm|FileAsync} 220 | * 221 | * @memberof! LocalSession 222 | * @name LocalSession.storageFileAsync 223 | * @global 224 | * @readonly 225 | */ 226 | static get storageFileAsync () { 227 | return storageFileAsync 228 | } 229 | 230 | /** 231 | * lowdb storage/adapter named {@link https://git.io/vhMqs|Memory} 232 | * 233 | * @memberof! LocalSession 234 | * @name LocalSession.storageMemory 235 | * @global 236 | * @readonly 237 | */ 238 | static get storageMemory () { 239 | return storageMemory 240 | } 241 | 242 | /** 243 | * lowdb {@link https://git.io/vhMOK|storage/adapter base} constructor (to extend it in your custom storage/adapter) 244 | * 245 | * @memberof! LocalSession 246 | * @name LocalSession.storageBase 247 | * @global 248 | * @readonly 249 | */ 250 | static get storageBase () { 251 | return require('lowdb/adapters/Base') 252 | } 253 | 254 | /** 255 | * For Debugging purposes only - shows info about what, where & with what args was called 256 | * 257 | * @param {Object} args - Called function's arguments 258 | * @private 259 | */ 260 | _called (args) { 261 | debug('Called function: \n\t-> %s \n\t-> Arguments: \n\t-> %o', ((new Error().stack).split('at ')[2]).trim(), this.DB._.values(args)) 262 | } 263 | } 264 | 265 | function _initDB () { 266 | // Use ID based resources, so we can query records by ID (ex.: getById(), removeById(), ...) 267 | this.DB._.mixin(lodashId) 268 | // If database is empty, fill it with empty Array of sessions and optionally with initial state 269 | this.DB.defaults(Object.assign({ sessions: [] }, this.options.state)).write() 270 | debug('Initiating finished') 271 | return true 272 | } 273 | 274 | // Credits to `is-promise` package 275 | function isPromise (obj) { 276 | return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' 277 | } 278 | 279 | /** 280 | * @overview {@link http://telegraf.js.org/|Telegraf} Session middleware for storing sessions locally (Memory/FileSync/FileAsync/...) 281 | * @module telegraf-session-local 282 | * @license MIT 283 | * @author Tema Smirnov 284 | * @requires {@link https://www.npmjs.com/package/telegraf|npm: telegraf} 285 | * @requires {@link https://www.npmjs.com/package/lowdb|npm: lowdb} 286 | * @see {@link http://telegraf.js.org/|Telegraf} | {@link https://github.com/typicode/lowdb|lowdb} 287 | * @exports LocalSession 288 | */ 289 | module.exports = LocalSession 290 | module.exports.isPromise = isPromise 291 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "telegraf-session-local", 3 | "version": "2.1.1", 4 | "description": "Telegraf local sessions middleware with multiple supported storage types (Memory/FileSync/FileAsync/...) using lowdb", 5 | "main": "lib/session.js", 6 | "types": "lib/session.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/RealSpeaker/telegraf-session-local.git" 10 | }, 11 | "keywords": [ 12 | "telegram", 13 | "telegram bot", 14 | "telegraf", 15 | "telegraf session", 16 | "telegraf session local", 17 | "session", 18 | "middleware", 19 | "database", 20 | "local database", 21 | "json", 22 | "lowdb", 23 | "db" 24 | ], 25 | "author": "Tema Smirnov ", 26 | "license": "MIT", 27 | "contributors": [ 28 | "EdJoPaTo (https://edjopato.de/)" 29 | ], 30 | "bugs": { 31 | "url": "https://github.com/RealSpeaker/telegraf-session-local/issues" 32 | }, 33 | "homepage": "https://realspeaker.github.io/telegraf-session-local/", 34 | "engines": { 35 | "node": ">=12" 36 | }, 37 | "files": [ 38 | "lib" 39 | ], 40 | "scripts": { 41 | "lint": "npx eslint . --fix", 42 | "test": "npx nyc mocha tests --require should", 43 | "coverage": "npx nyc report --reporter=lcovonly", 44 | "jsdoc": "npx jsdoc -c jsdoc.json", 45 | "version": "npm run jsdoc && git add docs", 46 | "release": "npx np" 47 | }, 48 | "dependencies": { 49 | "@types/lowdb": "^1.0.9", 50 | "lowdb": "^1.0.0" 51 | }, 52 | "peerDependencies": { 53 | "debug": "^2.0.0 || ^3.0.0 || ^4.0.0", 54 | "telegraf": "^3.38.0 || ^4.0.0" 55 | }, 56 | "devDependencies": { 57 | "docdash": "^2.0.1", 58 | "eslint": "^8.32.0", 59 | "eslint-config-standard": "^17.0.0", 60 | "eslint-plugin-import": "^2.27.5", 61 | "eslint-plugin-mocha": "^10.1.0", 62 | "eslint-plugin-n": "^15.6.1", 63 | "eslint-plugin-promise": "^6.1.1", 64 | "jsdoc": "^4.0.0", 65 | "mocha": "^10.2.0", 66 | "np": "*", 67 | "nyc": "^15.1.0", 68 | "should": "^13.2.3", 69 | "telegraf": "^4.11.2" 70 | }, 71 | "np": { 72 | "yarn": false 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/general.js: -------------------------------------------------------------------------------- 1 | /* eslint object-curly-spacing: ["error", "always"] */ 2 | const 3 | { Telegraf } = require('telegraf'), 4 | LocalSession = require('../lib/session'), 5 | should = require('should'), 6 | debug = require('debug')('telegraf:session-local:test'), 7 | options = { storage: LocalSession.storageMemory } 8 | 9 | describe('Telegraf Session local : General', () => { 10 | let bot = {} 11 | const localSession = new LocalSession(options) 12 | 13 | it('Should works without specifying any options for LocalSession', (done) => { 14 | bot = new Telegraf() 15 | bot.botInfo = {} 16 | const session = new LocalSession() 17 | bot.on('text', session.middleware(), (ctx) => { 18 | should.exist(ctx.session) 19 | done() 20 | }) 21 | // Temporary using setTimeout() because `telegraf-local-session` doesn't handle async adapters correctly yet 22 | setTimeout(() => { 23 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 24 | }, 25) 25 | }) 26 | 27 | it('Should use custom `format.serialize` and `format.deserialize` functions', (done) => { 28 | bot = new Telegraf() 29 | bot.botInfo = {} 30 | const session = new LocalSession({ 31 | database: 'test_sync_db.json', 32 | storage: LocalSession.storageFileSync, 33 | format: { 34 | // By default lowdb uses pretty-printed JSON string: JSON.stringify(obj, null, 2) 35 | // We will override that behaviour calling it `oneline`, making one-lined JSON string 36 | serialize: function oneline (obj) { 37 | return JSON.stringify(obj) 38 | }, 39 | deserialize: JSON.parse 40 | } 41 | }) 42 | bot.on('text', session.middleware(), (ctx) => { 43 | should.exist(ctx.session) 44 | ctx.session.wow = true 45 | // ctx.session.should.have.property('wow') 46 | // ctx.session.foo.should.be.equal(true) 47 | done() 48 | }) 49 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 50 | }) 51 | 52 | it('Should have access to lowdb instance via ctx.sessionDB', (done) => { 53 | bot = new Telegraf() 54 | bot.botInfo = {} 55 | bot.on('text', localSession.middleware(), (ctx) => { 56 | debug('lowdb instance via `ctx.sessionDB` %o', ctx.sessionDB) 57 | should.exist(ctx.sessionDB) 58 | done() 59 | }) 60 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 61 | }) 62 | 63 | it('Should override default `session` property to `data` at middleware() call', (done) => { 64 | bot = new Telegraf() 65 | bot.botInfo = {} 66 | bot.on('text', localSession.middleware('data'), (ctx) => { 67 | debug('Overrided session property %o', ctx.data) 68 | should.exist(ctx.data) 69 | done() 70 | }) 71 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 72 | }) 73 | 74 | it('Should have access to lowdb instance via overrided property in ctx', (done) => { 75 | bot = new Telegraf() 76 | bot.botInfo = {} 77 | bot.on('text', localSession.middleware('data'), (ctx) => { 78 | debug('lowdb instance via `ctx.dataDB` %o', ctx.dataDB) 79 | should.exist(ctx.dataDB) 80 | done() 81 | }) 82 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 83 | }) 84 | 85 | it('Should return `undefined` when context has no `from` field', (done) => { 86 | bot = new Telegraf() 87 | bot.botInfo = {} 88 | bot.on('text', localSession.middleware(), (ctx) => { 89 | debug('Telegraf context `from` field: %o', ctx.from) 90 | should.not.exists(localSession.getSessionKey(ctx)) 91 | done() 92 | }) 93 | bot.handleUpdate({ message: { chat: { id: 1 }, text: 'hey' } }) 94 | }) 95 | 96 | it('Should return `undefined` when no key provided for session to be saved', (done) => { 97 | bot = new Telegraf() 98 | bot.botInfo = {} 99 | bot.on('text', localSession.middleware(), async (ctx) => { 100 | const sessionKey = localSession.getSessionKey(ctx) 101 | debug('Real session key calculated by LocalSession: %s', sessionKey) 102 | should.not.exists(await localSession.saveSession(undefined, { authenticated: false })) 103 | done() 104 | }) 105 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 106 | }) 107 | 108 | it('Should detect if object is Promise/like or not', (done) => { 109 | const isPromise = require('../lib/session').isPromise 110 | function notPromise () { return null } 111 | function promise () { return new Promise((resolve, reject) => resolve(null)) } 112 | function promiseLike () { return { then: cb => cb(null) } } 113 | isPromise(undefined).should.be.equal(false) 114 | isPromise(true).should.be.equal(false) 115 | isPromise(notPromise()).should.be.equal(false) 116 | isPromise(promise()).should.be.equal(true) 117 | isPromise(promiseLike()).should.be.equal(true) 118 | done() 119 | }) 120 | }) 121 | -------------------------------------------------------------------------------- /tests/lodash-id.js: -------------------------------------------------------------------------------- 1 | const 2 | should = require('should'), 3 | // debug = require('debug')('telegraf:session-local:test'), 4 | _db = require('../lib/lodash-id.js') 5 | 6 | describe('lodash + lodash-id', () => { 7 | let db 8 | const _ = require('lodash') 9 | 10 | beforeEach(() => { 11 | _.mixin(_db) 12 | db = { 13 | posts: [ 14 | { id: 1, body: 'one', published: true }, 15 | { id: 2, body: 'two', published: false }, 16 | { id: 3, body: 'three', published: false } 17 | ], 18 | comments: [ 19 | { id: 1, body: 'foo', postId: 1 }, 20 | { id: 2, body: 'bar', postId: 2 } 21 | ], 22 | authors: [ 23 | { id: '1', name: 'foo' }, 24 | { id: '2', name: 'bar' } 25 | ], 26 | tags: [ 27 | { id: 1, text: '#reddit' }, 28 | { id: '1337', text: '#9gag' }, 29 | { text: '#medium' } 30 | ] 31 | } 32 | }) 33 | 34 | describe('id', () => { 35 | beforeEach(() => { _.id = 'body' }) 36 | afterEach(() => { delete _.id }) 37 | 38 | it('is the property used by get to find document', () => { 39 | const expect = db.posts[0] 40 | const doc = _.getById(db.posts, 'one') 41 | 42 | should.deepEqual(doc, expect) 43 | }) 44 | }) 45 | 46 | describe('createId', () => { 47 | it('returns an id', () => { 48 | should(_.createId()) 49 | }) 50 | }) 51 | 52 | describe('getById', () => { 53 | it('returns doc by id', () => { 54 | const expect = db.posts[0] 55 | const doc = _.getById(db.posts, 1) 56 | 57 | should.deepEqual(doc, expect) 58 | }) 59 | 60 | it('returns doc by id with string param', () => { 61 | const expect = db.posts[0] 62 | const doc = _.getById(db.posts, '1') 63 | 64 | should.deepEqual(doc, expect) 65 | }) 66 | 67 | it('returns doc by id with string id', () => { 68 | const expect = db.authors[0] 69 | const doc = _.getById(db.authors, 1) 70 | 71 | should.deepEqual(doc, expect) 72 | }) 73 | 74 | it('returns undefined if doc is not found', () => { 75 | const doc = _.getById(db.posts, 9999) 76 | 77 | should.equal(doc, undefined) 78 | }) 79 | 80 | it('returns undefined if doc is not found and some docs in collection without id', () => { 81 | const doc = _.getById(db.tags, 9999) 82 | 83 | should.equal(doc, undefined) 84 | }) 85 | }) 86 | 87 | describe('insert', () => { 88 | describe('and id is set', () => { 89 | it('inserts and returns inserted doc', () => { 90 | const doc = _.insert(db.posts, { id: 'foo', body: 'one' }) 91 | 92 | should.equal(db.posts.length, 4) 93 | should.deepEqual(doc, { id: 'foo', body: 'one' }) 94 | should.deepEqual(_.getById(db.posts, doc.id), { id: 'foo', body: 'one' }) 95 | }) 96 | 97 | it('does not replace in place and throws an error', () => { 98 | const length = db.posts.length 99 | 100 | should.throws(() => { 101 | _.insert(db.posts, { id: 2, title: 'one' }) 102 | }, /duplicate/) 103 | should.equal(db.posts.length, length) 104 | should.deepEqual(_.getById(db.posts, 2), { id: 2, body: 'two', published: false }) 105 | should.deepEqual(_.map(db.posts, 'id'), [1, 2, 3]) 106 | }) 107 | }) 108 | 109 | describe('and id is not set', () => { 110 | it('inserts, sets an id and returns inserted doc', () => { 111 | const doc = _.insert(db.posts, { body: 'one' }) 112 | 113 | should.equal(db.posts.length, 4) 114 | should(doc.id) 115 | should.equal(doc.body, 'one') 116 | }) 117 | }) 118 | }) 119 | 120 | describe('upsert', () => { 121 | describe('and id is set', () => { 122 | it('inserts and returns inserted doc', () => { 123 | const doc = _.upsert(db.posts, { id: 'foo', body: 'one' }) 124 | 125 | should.equal(db.posts.length, 4) 126 | should.deepEqual(doc, { id: 'foo', body: 'one' }) 127 | should.deepEqual(_.getById(db.posts, doc.id), { id: 'foo', body: 'one' }) 128 | }) 129 | 130 | it('replaces in place and returns inserted doc', () => { 131 | const length = db.posts.length 132 | const doc = _.upsert(db.posts, { id: 2, title: 'one' }) 133 | 134 | should.equal(db.posts.length, length) 135 | should.deepEqual(doc, { id: 2, title: 'one' }) 136 | should.deepEqual(_.getById(db.posts, doc.id), { id: 2, title: 'one' }) 137 | should.deepEqual(_.map(db.posts, 'id'), [1, 2, 3]) 138 | }) 139 | }) 140 | 141 | describe('and id is not set', () => { 142 | it('inserts, sets an id and returns inserted doc', () => { 143 | const doc = _.upsert(db.posts, { body: 'one' }) 144 | 145 | should.equal(db.posts.length, 4) 146 | should(doc.id) 147 | should.equal(doc.body, 'one') 148 | }) 149 | }) 150 | }) 151 | 152 | describe('updateById', () => { 153 | it('updates doc and returns updated doc', () => { 154 | const doc = _.updateById(db.posts, 1, { published: false }) 155 | 156 | should(!db.posts[0].published) 157 | should(!doc.published) 158 | }) 159 | 160 | it('keeps initial id type', () => { 161 | const doc = _.updateById(db.posts, '1', { published: false }) 162 | 163 | should.strictEqual(doc.id, 1) 164 | }) 165 | 166 | it('returns undefined if doc is not found', () => { 167 | const doc = _.updateById(db.posts, 9999, { published: false }) 168 | 169 | should.equal(doc, undefined) 170 | }) 171 | }) 172 | 173 | describe('updateWhere', () => { 174 | it('updates docs and returns updated docs', () => { 175 | const docs = _.updateWhere(db.posts, { published: false }, { published: true }) 176 | 177 | should.equal(docs.length, 2) 178 | should(db.posts[1].published) 179 | should(db.posts[2].published) 180 | }) 181 | 182 | it('returns an empty array if no docs match', () => { 183 | const docs = _.updateWhere(db.posts, { published: 'draft' }, { published: true }) 184 | 185 | should.equal(docs.length, 0) 186 | }) 187 | }) 188 | 189 | describe('__update', () => { 190 | it('copies properties from an object to another', () => { 191 | const src = { 192 | 1: 'one', 193 | test: true, 194 | hello: 'world', 195 | leet: 1337 196 | } 197 | const dst = {} 198 | _.__update(dst, src) 199 | 200 | dst.should.containDeep(src) 201 | }) 202 | }) 203 | 204 | describe('replaceById', () => { 205 | it('replaces doc and returns it', () => { 206 | const doc = _.replaceById(db.posts, 1, { foo: 'bar' }) 207 | 208 | should.deepEqual(doc, db.posts[0]) 209 | should.deepEqual(db.posts[0], { id: 1, foo: 'bar' }) 210 | }) 211 | 212 | it('keeps initial id type', () => { 213 | const doc = _.replaceById(db.posts, '1', { published: false }) 214 | 215 | should.strictEqual(doc.id, 1) 216 | }) 217 | 218 | it('returns undefined if doc is not found', () => { 219 | const doc = _.replaceById(db.posts, 9999, {}) 220 | 221 | should.equal(doc, undefined) 222 | }) 223 | }) 224 | 225 | describe('removeById', () => { 226 | it('removes and returns doc ', () => { 227 | const expected = db.posts[0] 228 | const doc = _.removeById(db.posts, 1) 229 | 230 | should.equal(db.posts.length, 2) 231 | should.deepEqual(doc, expected) 232 | }) 233 | 234 | it('returns undefined if doc is not found', () => { 235 | const doc = _.removeById(db.posts, 9999) 236 | 237 | should.equal(doc, undefined) 238 | }) 239 | }) 240 | 241 | describe('removeWhere', () => { 242 | it('removes docs', () => { 243 | const expected = [db.comments[0]] 244 | const docs = _.removeWhere(db.comments, { postId: 1 }) 245 | 246 | should.equal(db.comments.length, 1) 247 | should.deepEqual(docs, expected) 248 | }) 249 | 250 | it('returns an empty array if no docs match', () => { 251 | const docs = _.removeWhere(db.comments, { postId: 9999 }) 252 | 253 | should.equal(docs.length, 0) 254 | }) 255 | }) 256 | }) 257 | -------------------------------------------------------------------------------- /tests/sessionKeyUpdateTypes.js: -------------------------------------------------------------------------------- 1 | /* eslint object-curly-spacing: ["error", "always"] */ 2 | const 3 | { Telegraf } = require('telegraf'), 4 | LocalSession = require('../lib/session'), 5 | should = require('should'), 6 | debug = require('debug')('telegraf:session-local:test'), 7 | options = { storage: LocalSession.storageMemory } 8 | 9 | describe('Telegraf Session local : Session Key Update Types', () => { 10 | let bot = {} 11 | const localSession = new LocalSession(options) 12 | 13 | it('Should handle message', (done) => { 14 | bot = new Telegraf() 15 | bot.botInfo = {} 16 | bot.on('text', (ctx) => { 17 | const sessionKey = localSession.getSessionKey(ctx) 18 | debug('Session key', sessionKey) 19 | sessionKey.should.be.equal('2:1') 20 | done() 21 | }) 22 | bot.handleUpdate({ message: { chat: { id: 2 }, from: { id: 1 }, text: 'hey' } }) 23 | }) 24 | 25 | it('Should handle inline_query', (done) => { 26 | bot = new Telegraf() 27 | bot.botInfo = {} 28 | bot.on('inline_query', (ctx) => { 29 | const sessionKey = localSession.getSessionKey(ctx) 30 | debug('Session key', sessionKey) 31 | should.exist(sessionKey) 32 | sessionKey.should.be.equal('1:1') 33 | done() 34 | }) 35 | bot.handleUpdate({ inline_query: { from: { id: 1 }, query: '' } }) 36 | }) 37 | 38 | it('Should handle callback_query from chat', (done) => { 39 | bot = new Telegraf() 40 | bot.botInfo = {} 41 | bot.action('c:b', (ctx) => { 42 | const sessionKey = localSession.getSessionKey(ctx) 43 | debug('Session key', sessionKey) 44 | should.exist(sessionKey) 45 | sessionKey.should.be.equal('2:1') 46 | done() 47 | }) 48 | bot.handleUpdate({ callback_query: { from: { id: 1 }, message: { from: { id: 3 }, chat: { id: 2 }, text: 'hey' }, chat_instance: '-123', data: 'c:b' } }) 49 | }) 50 | 51 | it('Should handle callback_query from inline_query message', (done) => { 52 | bot = new Telegraf() 53 | bot.botInfo = {} 54 | bot.action('c:b', (ctx) => { 55 | const sessionKey = localSession.getSessionKey(ctx) 56 | debug('Session key', sessionKey) 57 | should.exist(sessionKey) 58 | sessionKey.should.be.equal('-123:1') 59 | done() 60 | }) 61 | bot.handleUpdate({ callback_query: { from: { id: 1 }, inline_message_id: 'BAA', chat_instance: '-123', data: 'c:b' } }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/storageCustom.js: -------------------------------------------------------------------------------- 1 | /* eslint object-curly-spacing: ["error", "always"] */ 2 | const 3 | { Telegraf } = require('telegraf'), 4 | LocalSession = require('../lib/session'), 5 | should = require('should'), 6 | debug = require('debug')('telegraf:session-local:test') 7 | 8 | // Custom adapter, it's like storageMemory 9 | // We even no need to extend storageBase, it's just for example purposes here 10 | class storageCustom extends LocalSession.storageBase { 11 | constructor () { 12 | super() 13 | this.serialize = require('lowdb/adapters/_stringify') 14 | } 15 | 16 | read () { return this.defaultValue } 17 | 18 | write () {} 19 | } 20 | 21 | const options = { storage: storageCustom } 22 | 23 | describe('Telegraf Session local : storageCustom', () => { 24 | let bot = {} 25 | const localSession = new LocalSession(options) 26 | 27 | it('storageCustom: Should retrieve and save session', async () => { 28 | const key = '1:1' // ChatID:FromID 29 | const session = localSession.getSession(key) 30 | debug('getSession %O', session) 31 | should.exist(session) 32 | session.foo = 42 33 | const _session = await localSession.saveSession(key, session) 34 | debug('Saved session %O', _session) 35 | should.exist(_session) 36 | _session.data.should.be.deepEqual({ foo: 42 }) 37 | }) 38 | 39 | it('storageCustom: Should has session', (done) => { 40 | bot = new Telegraf() 41 | bot.botInfo = {} 42 | bot.on('text', localSession.middleware(), (ctx) => { 43 | debug('Middleware session %O', ctx.session) 44 | should.exist(ctx.session) 45 | ctx.session.foo = 42 46 | debug('Updated Middleware session %O', ctx.session) 47 | done() 48 | }) 49 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 50 | }) 51 | 52 | it('storageCustom: Should handle existing session', (done) => { 53 | bot = new Telegraf() 54 | bot.botInfo = {} 55 | bot.on('text', localSession.middleware(), (ctx) => { 56 | debug('Existing Middleware session %O', ctx.session) 57 | should.exist(ctx.session) 58 | ctx.session.should.have.property('foo') 59 | ctx.session.foo.should.be.equal(42) 60 | done() 61 | }) 62 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 63 | }) 64 | 65 | it('storageCustom: Should handle not existing session', (done) => { 66 | bot = new Telegraf() 67 | bot.botInfo = {} 68 | bot.on('text', localSession.middleware(), (ctx) => { 69 | debug('Not Existing Middleware session %O', ctx.session) 70 | should.exist(ctx.session) 71 | ctx.session.should.not.have.property('foo') 72 | done() 73 | }) 74 | bot.handleUpdate({ message: { chat: { id: 1337 }, from: { id: 1337 }, text: 'hey' } }) 75 | }) 76 | 77 | it('storageCustom: Should handle session reset', (done) => { 78 | bot = new Telegraf() 79 | bot.botInfo = {} 80 | bot.on('text', localSession.middleware(), (ctx) => { 81 | debug('Middleware session reset - before %O', ctx.session) 82 | ctx.session = null 83 | should.exist(ctx.session) 84 | ctx.session.should.not.have.property('foo') 85 | debug('Middleware session reset - after %O', ctx.session) 86 | done() 87 | }) 88 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /tests/storageFileAsync.js: -------------------------------------------------------------------------------- 1 | /* eslint object-curly-spacing: ["error", "always"] */ 2 | const 3 | { Telegraf } = require('telegraf'), 4 | LocalSession = require('../lib/session'), 5 | should = require('should'), 6 | debug = require('debug')('telegraf:session-local:test'), 7 | options = { database: 'test_async_db.json', storage: LocalSession.storageFileAsync } 8 | 9 | let bot = {} 10 | const localSession = new LocalSession(options) 11 | 12 | // Wait for database async initialization finished 13 | before((done) => { 14 | localSession.DB.then((DB) => { done() }) 15 | }) 16 | 17 | describe('Telegraf Session local : storageFileAsync', () => { 18 | 19 | it('storageFileAsync: Should retrieve and save session', async () => { 20 | const key = '1:1' // ChatID:FromID 21 | const session = localSession.getSession(key) 22 | debug('getSession %O', session) 23 | should.exist(session) 24 | session.foo = 42 25 | const _session = await localSession.saveSession(key, session) 26 | debug('Saved session %O', _session) 27 | should.exist(_session) 28 | _session.data.should.be.deepEqual({ foo: 42 }) 29 | }) 30 | 31 | it('storageFileAsync: Should has session', (done) => { 32 | bot = new Telegraf() 33 | bot.botInfo = {} 34 | bot.on('text', localSession.middleware(), (ctx) => { 35 | debug('Middleware session %O', ctx.session) 36 | should.exist(ctx.session) 37 | ctx.session.foo = 42 38 | debug('Updated Middleware session %O', ctx.session) 39 | done() 40 | }) 41 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 42 | }) 43 | 44 | it('storageFileAsync: Should handle existing session', (done) => { 45 | bot = new Telegraf() 46 | bot.botInfo = {} 47 | bot.on('text', localSession.middleware(), (ctx) => { 48 | debug('Existing Middleware session %O', ctx.session) 49 | should.exist(ctx.session) 50 | ctx.session.should.have.property('foo') 51 | ctx.session.foo.should.be.equal(42) 52 | done() 53 | }) 54 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 55 | }) 56 | 57 | it('storageFileAsync: Should handle not existing session', (done) => { 58 | bot = new Telegraf() 59 | bot.botInfo = {} 60 | bot.on('text', localSession.middleware(), (ctx) => { 61 | debug('Not Existing Middleware session %O', ctx.session) 62 | should.exist(ctx.session) 63 | ctx.session.should.not.have.property('foo') 64 | done() 65 | }) 66 | bot.handleUpdate({ message: { chat: { id: 1337 }, from: { id: 1337 }, text: 'hey' } }) 67 | }) 68 | 69 | it('storageFileAsync: Should handle session reset', (done) => { 70 | bot = new Telegraf() 71 | bot.botInfo = {} 72 | bot.on('text', localSession.middleware(), (ctx) => { 73 | debug('Middleware session reset - before %O', ctx.session) 74 | ctx.session = null 75 | should.exist(ctx.session) 76 | ctx.session.should.not.have.property('foo') 77 | debug('Middleware session reset - after %O', ctx.session) 78 | done() 79 | }) 80 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 81 | }) 82 | 83 | it('storageFileAsync: Should work properly with deprecated stoarge name - storagefileAsync', (done) => { 84 | const _options = Object.assign({ storage: LocalSession.storagefileAsync }, options) 85 | const _localSession = new LocalSession(_options) 86 | // Wait for database async initialization finished 87 | _localSession.DB.then(async (DB) => { 88 | // console.log(DB.get('sessions').getById('1:1').value()) 89 | const key = '1:1' // ChatID:FromID 90 | const session = _localSession.getSession(key) 91 | debug('getSession %O', session) 92 | should.exist(session) 93 | session.foo = 42 94 | const _session = await _localSession.saveSession(key, session) 95 | debug('Saved session %O', _session) 96 | should.exist(_session) 97 | _session.data.should.be.deepEqual({ foo: 42 }) 98 | done() 99 | }) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /tests/storageFileSync.js: -------------------------------------------------------------------------------- 1 | /* eslint object-curly-spacing: ["error", "always"] */ 2 | const 3 | { Telegraf } = require('telegraf'), 4 | LocalSession = require('../lib/session'), 5 | should = require('should'), 6 | debug = require('debug')('telegraf:session-local:test'), 7 | options = { database: 'test_sync_db.json', storage: LocalSession.storageFileSync } 8 | 9 | let bot = {} 10 | const localSession = new LocalSession(options) 11 | 12 | describe('Telegraf Session local : storageFileSync', () => { 13 | 14 | it('storageFileSync: Should retrieve and save session', (done) => { 15 | const key = '1:1' // ChatID:FromID 16 | const session = localSession.getSession(key) 17 | debug('getSession %O', session) 18 | should.exist(session) 19 | session.foo = 42 20 | localSession.saveSession(key, session).then((_session) => { 21 | debug('Saved session %O', _session) 22 | should.exist(_session) 23 | _session.data.should.be.deepEqual({ foo: 42 }) 24 | done() 25 | }) 26 | }) 27 | 28 | it('storageFileSync: Should has session', (done) => { 29 | bot = new Telegraf() 30 | bot.botInfo = {} 31 | bot.on('text', localSession.middleware(), (ctx) => { 32 | debug('Middleware session %O', ctx.session) 33 | should.exist(ctx.session) 34 | ctx.session.foo = 42 35 | debug('Updated Middleware session %O', ctx.session) 36 | done() 37 | }) 38 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 39 | }) 40 | 41 | it('storageFileSync: Should handle existing session', (done) => { 42 | bot = new Telegraf() 43 | bot.botInfo = {} 44 | bot.on('text', localSession.middleware(), (ctx) => { 45 | debug('Existing Middleware session %O', ctx.session) 46 | should.exist(ctx.session) 47 | ctx.session.should.have.property('foo') 48 | ctx.session.foo.should.be.equal(42) 49 | done() 50 | }) 51 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 52 | }) 53 | 54 | it('storageFileSync: Should handle not existing session', (done) => { 55 | bot = new Telegraf() 56 | bot.botInfo = {} 57 | bot.on('text', localSession.middleware(), (ctx) => { 58 | debug('Not Existing Middleware session %O', ctx.session) 59 | should.exist(ctx.session) 60 | ctx.session.should.not.have.property('foo') 61 | done() 62 | }) 63 | bot.handleUpdate({ message: { chat: { id: 1337 }, from: { id: 1337 }, text: 'hey' } }) 64 | }) 65 | 66 | it('storageFileSync: Should handle session reset', (done) => { 67 | bot = new Telegraf() 68 | bot.botInfo = {} 69 | bot.on('text', localSession.middleware(), (ctx) => { 70 | debug('Middleware session reset - before %O', ctx.session) 71 | ctx.session = null 72 | should.exist(ctx.session) 73 | ctx.session.should.not.have.property('foo') 74 | debug('Middleware session reset - after %O', ctx.session) 75 | done() 76 | }) 77 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 78 | }) 79 | 80 | it('storageFileSync: Should work properly with deprecated stoarge name - storagefileSync', (done) => { 81 | const _options = Object.assign({ storage: LocalSession.storagefileSync }, options) 82 | const _localSession = new LocalSession(_options) 83 | const key = '1:1' // ChatID:FromID 84 | const session = _localSession.getSession(key) 85 | debug('getSession %O', session) 86 | should.exist(session) 87 | session.foo = 42 88 | _localSession.saveSession(key, session).then((_session) => { 89 | debug('Saved session %O', _session) 90 | should.exist(_session) 91 | _session.data.should.be.deepEqual({ foo: 42 }) 92 | done() 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /tests/storageMemory.js: -------------------------------------------------------------------------------- 1 | /* eslint object-curly-spacing: ["error", "always"] */ 2 | const 3 | { Telegraf } = require('telegraf'), 4 | LocalSession = require('../lib/session'), 5 | should = require('should'), 6 | debug = require('debug')('telegraf:session-local:test'), 7 | options = { storage: LocalSession.storageMemory } 8 | 9 | describe('Telegraf Session local : storageMemory', () => { 10 | let bot = {} 11 | const localSession = new LocalSession(options) 12 | 13 | it('storageMemory: Should retrieve and save session', (done) => { 14 | const key = '1:1' // ChatID:FromID 15 | const session = localSession.getSession(key) 16 | debug('getSession %O', session) 17 | should.exist(session) 18 | session.foo = 42 19 | localSession.saveSession(key, session).then(_session => { 20 | debug('Saved session %O', _session) 21 | should.exist(_session) 22 | _session.data.should.be.deepEqual({ foo: 42 }) 23 | done() 24 | }) 25 | }) 26 | 27 | it('storageMemory: Should has session', (done) => { 28 | bot = new Telegraf() 29 | bot.botInfo = {} 30 | bot.on('text', localSession.middleware(), (ctx) => { 31 | debug('Middleware session %O', ctx.session) 32 | should.exist(ctx.session) 33 | ctx.session.foo = 42 34 | debug('Updated Middleware session %O', ctx.session) 35 | done() 36 | }) 37 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 38 | }) 39 | 40 | it('storageMemory: Should handle existing session', (done) => { 41 | bot = new Telegraf() 42 | bot.botInfo = {} 43 | bot.on('text', localSession.middleware(), (ctx) => { 44 | debug('Existing Middleware session %O', ctx.session) 45 | should.exist(ctx.session) 46 | ctx.session.should.have.property('foo') 47 | ctx.session.foo.should.be.equal(42) 48 | done() 49 | }) 50 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 51 | }) 52 | 53 | it('storageMemory: Should handle not existing session', (done) => { 54 | bot = new Telegraf() 55 | bot.botInfo = {} 56 | bot.on('text', localSession.middleware(), (ctx) => { 57 | debug('Not Existing Middleware session %O', ctx.session) 58 | should.exist(ctx.session) 59 | ctx.session.should.not.have.property('foo') 60 | done() 61 | }) 62 | bot.handleUpdate({ message: { chat: { id: 1337 }, from: { id: 1337 }, text: 'hey' } }) 63 | }) 64 | 65 | it('storageMemory: Should handle session reset', (done) => { 66 | bot = new Telegraf() 67 | bot.botInfo = {} 68 | bot.on('text', localSession.middleware(), (ctx) => { 69 | debug('Middleware session reset - before %O', ctx.session) 70 | ctx.session = null 71 | should.exist(ctx.session) 72 | ctx.session.should.not.have.property('foo') 73 | debug('Middleware session reset - after %O', ctx.session) 74 | done() 75 | }) 76 | bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) 77 | }) 78 | }) 79 | --------------------------------------------------------------------------------