Source code for my personal quote bot project.

Compare changes

Choose any two refs to compare.

Changed files
+1007 -1527
quotes
deltarune
xenoblade-chronicles-3
src
+265 -17
Cargo.lock
··· 33 33 checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 34 35 35 [[package]] 36 - name = "android-tzdata" 37 - version = "0.1.1" 38 - source = "registry+https://github.com/rust-lang/crates.io-index" 39 - checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 40 - 41 - [[package]] 42 36 name = "android_system_properties" 43 37 version = "0.1.5" 44 38 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 148 142 version = "0.1.0" 149 143 dependencies = [ 150 144 "bsky-sdk", 145 + "chrono", 146 + "cron-lite", 147 + "futures", 151 148 "glob", 152 149 "grep", 150 + "kameo", 153 151 "rand", 154 152 "redis", 155 153 "tokio", ··· 269 267 270 268 [[package]] 271 269 name = "chrono" 272 - version = "0.4.40" 270 + version = "0.4.42" 273 271 source = "registry+https://github.com/rust-lang/crates.io-index" 274 - checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 272 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 275 273 dependencies = [ 276 - "android-tzdata", 277 274 "iana-time-zone", 278 275 "js-sys", 279 276 "num-traits", ··· 283 280 ] 284 281 285 282 [[package]] 283 + name = "chrono-tz" 284 + version = "0.10.4" 285 + source = "registry+https://github.com/rust-lang/crates.io-index" 286 + checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" 287 + dependencies = [ 288 + "chrono", 289 + "phf", 290 + ] 291 + 292 + [[package]] 286 293 name = "cid" 287 294 version = "0.11.1" 288 295 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 351 358 checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 352 359 dependencies = [ 353 360 "cfg-if", 361 + ] 362 + 363 + [[package]] 364 + name = "cron-lite" 365 + version = "0.3.0" 366 + source = "registry+https://github.com/rust-lang/crates.io-index" 367 + checksum = "7b1c9e28df18340148b754969b7b66ed3c7f1242d10f4a4840391624333b589c" 368 + dependencies = [ 369 + "chrono", 370 + "futures", 371 + "pin-project", 354 372 ] 355 373 356 374 [[package]] 357 375 name = "croner" 358 - version = "2.1.0" 376 + version = "3.0.1" 359 377 source = "registry+https://github.com/rust-lang/crates.io-index" 360 - checksum = "38fd53511eaf0b00a185613875fee58b208dfce016577d0ad4bb548e1c4fb3ee" 378 + checksum = "4aa42bcd3d846ebf66e15bd528d1087f75d1c6c1c66ebff626178a106353c576" 361 379 dependencies = [ 362 380 "chrono", 381 + "derive_builder", 382 + "strum", 363 383 ] 364 384 365 385 [[package]] ··· 387 407 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 388 408 389 409 [[package]] 410 + name = "darling" 411 + version = "0.20.11" 412 + source = "registry+https://github.com/rust-lang/crates.io-index" 413 + checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 414 + dependencies = [ 415 + "darling_core", 416 + "darling_macro", 417 + ] 418 + 419 + [[package]] 420 + name = "darling_core" 421 + version = "0.20.11" 422 + source = "registry+https://github.com/rust-lang/crates.io-index" 423 + checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 424 + dependencies = [ 425 + "fnv", 426 + "ident_case", 427 + "proc-macro2", 428 + "quote", 429 + "strsim", 430 + "syn", 431 + ] 432 + 433 + [[package]] 434 + name = "darling_macro" 435 + version = "0.20.11" 436 + source = "registry+https://github.com/rust-lang/crates.io-index" 437 + checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 438 + dependencies = [ 439 + "darling_core", 440 + "quote", 441 + "syn", 442 + ] 443 + 444 + [[package]] 390 445 name = "dashmap" 391 446 version = "6.1.0" 392 447 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 427 482 ] 428 483 429 484 [[package]] 485 + name = "derive_builder" 486 + version = "0.20.2" 487 + source = "registry+https://github.com/rust-lang/crates.io-index" 488 + checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 489 + dependencies = [ 490 + "derive_builder_macro", 491 + ] 492 + 493 + [[package]] 494 + name = "derive_builder_core" 495 + version = "0.20.2" 496 + source = "registry+https://github.com/rust-lang/crates.io-index" 497 + checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 498 + dependencies = [ 499 + "darling", 500 + "proc-macro2", 501 + "quote", 502 + "syn", 503 + ] 504 + 505 + [[package]] 506 + name = "derive_builder_macro" 507 + version = "0.20.2" 508 + source = "registry+https://github.com/rust-lang/crates.io-index" 509 + checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 510 + dependencies = [ 511 + "derive_builder_core", 512 + "syn", 513 + ] 514 + 515 + [[package]] 430 516 name = "displaydoc" 431 517 version = "0.2.5" 432 518 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 436 522 "quote", 437 523 "syn", 438 524 ] 525 + 526 + [[package]] 527 + name = "downcast-rs" 528 + version = "2.0.2" 529 + source = "registry+https://github.com/rust-lang/crates.io-index" 530 + checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" 531 + 532 + [[package]] 533 + name = "dyn-clone" 534 + version = "1.0.20" 535 + source = "registry+https://github.com/rust-lang/crates.io-index" 536 + checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 439 537 440 538 [[package]] 441 539 name = "encoding_rs" ··· 545 643 ] 546 644 547 645 [[package]] 646 + name = "futures" 647 + version = "0.3.31" 648 + source = "registry+https://github.com/rust-lang/crates.io-index" 649 + checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 650 + dependencies = [ 651 + "futures-channel", 652 + "futures-core", 653 + "futures-executor", 654 + "futures-io", 655 + "futures-sink", 656 + "futures-task", 657 + "futures-util", 658 + ] 659 + 660 + [[package]] 548 661 name = "futures-channel" 549 662 version = "0.3.31" 550 663 source = "registry+https://github.com/rust-lang/crates.io-index" 551 664 checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 552 665 dependencies = [ 553 666 "futures-core", 667 + "futures-sink", 554 668 ] 555 669 556 670 [[package]] ··· 560 674 checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 561 675 562 676 [[package]] 677 + name = "futures-executor" 678 + version = "0.3.31" 679 + source = "registry+https://github.com/rust-lang/crates.io-index" 680 + checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 681 + dependencies = [ 682 + "futures-core", 683 + "futures-task", 684 + "futures-util", 685 + ] 686 + 687 + [[package]] 688 + name = "futures-io" 689 + version = "0.3.31" 690 + source = "registry+https://github.com/rust-lang/crates.io-index" 691 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 692 + 693 + [[package]] 563 694 name = "futures-macro" 564 695 version = "0.3.31" 565 696 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 588 719 source = "registry+https://github.com/rust-lang/crates.io-index" 589 720 checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 590 721 dependencies = [ 722 + "futures-channel", 591 723 "futures-core", 724 + "futures-io", 592 725 "futures-macro", 593 726 "futures-sink", 594 727 "futures-task", 728 + "memchr", 595 729 "pin-project-lite", 596 730 "pin-utils", 597 731 "slab", ··· 744 878 ] 745 879 746 880 [[package]] 881 + name = "heck" 882 + version = "0.5.0" 883 + source = "registry+https://github.com/rust-lang/crates.io-index" 884 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 885 + 886 + [[package]] 747 887 name = "http" 748 888 version = "1.2.0" 749 889 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 979 1119 ] 980 1120 981 1121 [[package]] 1122 + name = "ident_case" 1123 + version = "1.0.1" 1124 + source = "registry+https://github.com/rust-lang/crates.io-index" 1125 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1126 + 1127 + [[package]] 982 1128 name = "idna" 983 1129 version = "1.0.3" 984 1130 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1043 1189 ] 1044 1190 1045 1191 [[package]] 1192 + name = "kameo" 1193 + version = "0.17.2" 1194 + source = "registry+https://github.com/rust-lang/crates.io-index" 1195 + checksum = "41a73be96f616ca2784f597b5b6635582f5a7b3ba73b1dbe7afa5d9667955d39" 1196 + dependencies = [ 1197 + "downcast-rs", 1198 + "dyn-clone", 1199 + "futures", 1200 + "kameo_macros", 1201 + "once_cell", 1202 + "serde", 1203 + "tokio", 1204 + "tracing", 1205 + ] 1206 + 1207 + [[package]] 1208 + name = "kameo_macros" 1209 + version = "0.17.0" 1210 + source = "registry+https://github.com/rust-lang/crates.io-index" 1211 + checksum = "b3f384b32bf6426ae93a8b37da62c85073b676a31a82a86d608ad86453878de0" 1212 + dependencies = [ 1213 + "heck", 1214 + "proc-macro2", 1215 + "quote", 1216 + "syn", 1217 + "uuid", 1218 + ] 1219 + 1220 + [[package]] 1046 1221 name = "langtag" 1047 1222 version = "0.3.4" 1048 1223 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1374 1549 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1375 1550 1376 1551 [[package]] 1552 + name = "phf" 1553 + version = "0.12.1" 1554 + source = "registry+https://github.com/rust-lang/crates.io-index" 1555 + checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" 1556 + dependencies = [ 1557 + "phf_shared", 1558 + ] 1559 + 1560 + [[package]] 1561 + name = "phf_shared" 1562 + version = "0.12.1" 1563 + source = "registry+https://github.com/rust-lang/crates.io-index" 1564 + checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" 1565 + dependencies = [ 1566 + "siphasher", 1567 + ] 1568 + 1569 + [[package]] 1570 + name = "pin-project" 1571 + version = "1.1.10" 1572 + source = "registry+https://github.com/rust-lang/crates.io-index" 1573 + checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 1574 + dependencies = [ 1575 + "pin-project-internal", 1576 + ] 1577 + 1578 + [[package]] 1579 + name = "pin-project-internal" 1580 + version = "1.1.10" 1581 + source = "registry+https://github.com/rust-lang/crates.io-index" 1582 + checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 1583 + dependencies = [ 1584 + "proc-macro2", 1585 + "quote", 1586 + "syn", 1587 + ] 1588 + 1589 + [[package]] 1377 1590 name = "pin-project-lite" 1378 1591 version = "0.2.16" 1379 1592 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1790 2003 ] 1791 2004 1792 2005 [[package]] 2006 + name = "siphasher" 2007 + version = "1.0.1" 2008 + source = "registry+https://github.com/rust-lang/crates.io-index" 2009 + checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 2010 + 2011 + [[package]] 1793 2012 name = "slab" 1794 2013 version = "0.4.9" 1795 2014 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1821 2040 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1822 2041 1823 2042 [[package]] 2043 + name = "strsim" 2044 + version = "0.11.1" 2045 + source = "registry+https://github.com/rust-lang/crates.io-index" 2046 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2047 + 2048 + [[package]] 2049 + name = "strum" 2050 + version = "0.27.2" 2051 + source = "registry+https://github.com/rust-lang/crates.io-index" 2052 + checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" 2053 + dependencies = [ 2054 + "strum_macros", 2055 + ] 2056 + 2057 + [[package]] 2058 + name = "strum_macros" 2059 + version = "0.27.2" 2060 + source = "registry+https://github.com/rust-lang/crates.io-index" 2061 + checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" 2062 + dependencies = [ 2063 + "heck", 2064 + "proc-macro2", 2065 + "quote", 2066 + "syn", 2067 + ] 2068 + 2069 + [[package]] 1824 2070 name = "syn" 1825 2071 version = "2.0.99" 1826 2072 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1935 2181 "signal-hook-registry", 1936 2182 "socket2", 1937 2183 "tokio-macros", 2184 + "tracing", 1938 2185 "windows-sys 0.52.0", 1939 2186 ] 1940 2187 1941 2188 [[package]] 1942 2189 name = "tokio-cron-scheduler" 1943 - version = "0.13.0" 2190 + version = "0.15.1" 1944 2191 source = "registry+https://github.com/rust-lang/crates.io-index" 1945 - checksum = "6a5597b569b4712cf78aa0c9ae29742461b7bda1e49c2a5fdad1d79bf022f8f0" 2192 + checksum = "1f50e41f200fd8ed426489bd356910ede4f053e30cebfbd59ef0f856f0d7432a" 1946 2193 dependencies = [ 1947 2194 "chrono", 2195 + "chrono-tz", 1948 2196 "croner", 1949 2197 "num-derive", 1950 2198 "num-traits", ··· 2027 2275 2028 2276 [[package]] 2029 2277 name = "tracing-attributes" 2030 - version = "0.1.28" 2278 + version = "0.1.31" 2031 2279 source = "registry+https://github.com/rust-lang/crates.io-index" 2032 - checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2280 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 2033 2281 dependencies = [ 2034 2282 "proc-macro2", 2035 2283 "quote", ··· 2356 2604 2357 2605 [[package]] 2358 2606 name = "windows-link" 2359 - version = "0.1.0" 2607 + version = "0.2.1" 2360 2608 source = "registry+https://github.com/rust-lang/crates.io-index" 2361 - checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" 2609 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2362 2610 2363 2611 [[package]] 2364 2612 name = "windows-registry"
+5 -3
Cargo.toml
··· 1 - cargo-features = ["edition2024"] # For rust-analyzer to work 2 - 3 1 [package] 4 2 name = "audquotes" 5 3 version = "0.1.0" ··· 8 6 9 7 [dependencies] 10 8 bsky-sdk = "0.1.16" 9 + chrono = "0.4.42" 10 + cron-lite = { version = "0.3.0", features = ["async"] } 11 + futures = "0.3.31" 11 12 glob = "0.3.2" 12 13 grep = "0.3.2" 14 + kameo = "0.17.2" 13 15 rand = "0.9.0" 14 16 redis = { version = "0.29.1", features = ["aio", "connection-manager", "tokio-comp"] } 15 17 tokio = { version = "1.44.0", features = ["full"] } 16 - tokio-cron-scheduler = "0.13.0" 18 + tokio-cron-scheduler = "0.15.1"
-11
quotes/deltarune/1225_you_couldnt_find.txt
··· 1 - * You used the machine. 2 - * ... The capsule came out. 3 - * Inside was something hard like lacquer. 4 - * It was a small, dark, triangle. 5 - * You tried to take it... 6 - * But, it slipped through your hand 7 - * And you couldn't find it anymore. 8 - 9 - * You couldn't find 10 - 11 - your hand.
-16
quotes/deltarune/THE_HALFWAY_MARK.txt
··· 1 - WELL DONE. 2 - 3 - THE HALFWAY 4 - MARK HAS 5 - BEEN ATTAINED. 6 - 7 - BUT 8 - IT 9 - IS STILL 10 - WAITING. 11 - 12 - HOW MUCH 13 - LONGER NOW...? 14 - 15 - MY 16 - DELTARUNE.
-4
quotes/deltarune/alvin_asgore_pray.txt
··· 1 - * Ah, your father? He typically comes here at night. 2 - * He prays much for you, Kris... for Asriel, Toriel, Rudy... and his flowers, too. 3 - * You should join your mother in praying for him, as well. 4 - * The poor man. It seems the Angel has made a difficult path for him...
-1
quotes/deltarune/alvin_shelter.txt
··· 1 - * Kris... stay away from the shelter.
-4
quotes/deltarune/alvin_tarnish_his_legacy.txt
··· 1 - * Thank you, Kris. Ha ha. Although I did see that it put you to sleep. 2 - * I know. I do not exactly have a "flair" for entertainment. 3 - * That's why I don't write my own sermons. Or... anything, anymore. 4 - * I don't think my father could rest well knowing I was... tarnishing his legacy.
-6
quotes/deltarune/asgore_they_will_all_1.txt
··· 1 - * We're almost there, aren't we, old friend? 2 - * This time for sure... Tori will finally see. 3 - * ... see what really happened. 4 - * ... that I just wanted to... protect everyone... 5 - * And this time, she'll have to believe me. 6 - * ... they all will.
-5
quotes/deltarune/asgore_they_will_all_2.txt
··· 1 - * Then... 2 - * We'll all be a happy family again... won't we? 3 - * ... 4 - * It sure is beautiful, isn't it...? 5 - * ... this black shard.
-1
quotes/deltarune/bell_of_justice.txt
··· 1 - * The bell of justice is ringing... it's for you.
-6
quotes/deltarune/carol_kris_the_festival.txt
··· 1 - * Kris... are you there? 2 - * ... Then, I'll just leave a message. 3 - * You are going to the festival tomorrow, aren't you? 4 - * ... I just wanted to confirm. 5 - * Noelle said... 6 - * She's really looking forward to seeing you.
-3
quotes/deltarune/concert_just_for_me_1.txt
··· 1 - It's funny... there was a time when they were coming over almost every day. 2 - We'd play, and we'd play... then after a while, they would suddenly get very still, like they were remembering something. 3 - They'd go into the dining room to "get a snack," then after a few moments, I'd hear the piano.
-2
quotes/deltarune/concert_just_for_me_2.txt
··· 1 - The first few times, I went into watch them play, but when they realized I was looking, they'd always shut the piano and come back. 2 - So over time, I just started staying on the couch in the living room. I'd lie there, listening to them play, sometimes for hours, sometimes even until I fell asleep.
-3
quotes/deltarune/concert_just_for_me_3.txt
··· 1 - Even then, what were they thinking about me? 2 - Maybe they weren't thinking about me at all. 3 - They didn't have a piano at their house, so they probably just came over to use mine.
-4
quotes/deltarune/concert_just_for_me_4.txt
··· 1 - Even then, with my eyes open, there were times when I wasn't even sure if we were friends. 2 - 3 - But when I closed my eyes, 4 - it felt like a concert just for me.
-3
quotes/deltarune/d_how_did_i.txt
··· 1 - What is this place, anyway...? 2 - How did I get here? 3 - ... as if I haven't asked that question a billion times already.
-8
quotes/deltarune/d_that_nightmare.txt
··· 1 - I... 2 - I always did have that nightmare. 3 - Walking into the darkness, 4 - With the light shining from the doorway... 5 - Then the door slams behind me. 6 - And everything goes black. 7 - ... is this that nightmare? 8 - ... or was everything else a dream?
-9
quotes/deltarune/felt_it_shining.txt
··· 1 - INCREDIBLE. 2 - 3 - I FELT IT THERE 4 - 5 - SHINING. 6 - 7 - YOUR POWER. 8 - 9 - A LITTLE FURTHER.
-9
quotes/deltarune/fountain_glowing_smile.txt
··· 1 - * (Susie's smile, which 2 - seemed to defeat everything) 3 - * (Shined on in front of you, 4 - and for a moment) 5 - * (It was as if all 6 - the darkness had been 7 - blown away.) 8 - * (As if in imitation, 9 - your SOUL began to glow...)
-6
quotes/deltarune/gerson_as_youre_green.txt
··· 1 - As long as you're GREEN, 2 - you can't run away! 3 - 4 - Unless you FACE DANGER 5 - HEAD ON... These bullets 6 - will do you in!
-2
quotes/deltarune/gerson_bench_dream.txt
··· 1 - * "Throughout my career, some of my best ideas came from dreams." 2 - * "Take a rest here. If anyone asks -- you're writing!"
-6
quotes/deltarune/gerson_between_the_lines.txt
··· 1 - * Well now, a fairytale is a pretty little thing. 2 - * Ain't it nice to believe a glimmer here and there...? 3 - * I jus' think, those words shine a bit too bright. 4 - * A path so blue, it's all you can see. 5 - * So I say... why don't we go between the lines? 6 - * It's darker there... Geheh... geheheh!
-8
quotes/deltarune/gerson_burnin_up_everything.txt
··· 1 - But you, my dear? 2 - I see a story lit 3 - up in your eyes... 4 - 5 - Burnin' bright... 6 - Burnin' black. 7 - Burnin' up 8 - everything.
-7
quotes/deltarune/gerson_chapter_1.txt
··· 1 - Chapter 1 2 - The March of 3 - the Dark King. 4 - 5 - The heroes defeat 6 - the king and 7 - stop the dragon.
-7
quotes/deltarune/gerson_chapter_2.txt
··· 1 - Chapter 2 2 - The City of 3 - the Shining. 4 - 5 - The heroes do 6 - battle in chariots 7 - to save the Queen.
-8
quotes/deltarune/gerson_chapter_3.txt
··· 1 - Chapter 3, The 2 - Isles of 3 - Northernlight. 4 - 5 - The heroes travel 6 - among the islands 7 - and catch a glimpse 8 - of a lost land.
-7
quotes/deltarune/gerson_chapter_4.txt
··· 1 - Chapter 4, The 2 - Trials of the 3 - Holy Hammer. 4 - 5 - A great smith gives 6 - the heroes a 7 - terrible weapon.
-11
quotes/deltarune/gerson_chapter_5.txt
··· 1 - Chapter 5, The 2 - Field of Pink 3 - and Gold. 4 - 5 - The vast garden is 6 - charred in an inferno 7 - of jealousy. 8 - 9 - ...What happens next? 10 - 11 - Geheheh! Who knows?
-9
quotes/deltarune/gerson_choose_eternity.txt
··· 1 - Gah ha ha ha 2 - ha ha ha!!! Well 3 - ain't that somethin'. 4 - 5 - At this late hour, 6 - with the bells ringing 7 - out their justice. 8 - 9 - You choose eternity.
-5
quotes/deltarune/gerson_count_on.txt
··· 1 - * (You sipped the tea. ... it's very sweet.) 2 - * (You drank it slowly, feeling a warmth spread across your body...) 3 - * (Until, only a thin, sickly syrup was left cold at the bottom.) 4 - 5 - * That's the nice thing about tea. You can always count on it, geheheh...
-17
quotes/deltarune/gerson_heroes.txt
··· 1 - Now, I don't know 2 - much about "heroes"... 3 - ... But I know that 4 - whatever ya call 'em, 5 - there is someone. 6 - 7 - Someone who'll never 8 - give up trying to 9 - do the right thing, 10 - no matter what. 11 - 12 - There's no prophecy 13 - or legend 'bout anyone 14 - like that. 15 - 16 - It's just something 17 - I know is true.
-9
quotes/deltarune/gerson_how_it_all_ends.txt
··· 1 - The bell's ringin' now. 2 - Seems we've reached 3 - the final rounds. 4 - 5 - So... how do you 6 - think it all ends? 7 - 8 - No, how do you want 9 - it to end?
-9
quotes/deltarune/gerson_ocean_of_ink.txt
··· 1 - Miss, hold on 2 - dearly to that thought. 3 - 4 - For the tides of fate 5 - are drawing close. 6 - 7 - And soon, an ocean 8 - of ink shall wash 9 - across the pages.
-8
quotes/deltarune/gerson_old_tale.txt
··· 1 - Did you ever hear 2 - that old tale? 3 - 4 - Yes, the old tale, 5 - based upon the 6 - prophecy... 7 - 8 - Lord of the Hammer.
-9
quotes/deltarune/gerson_pick_up_the_pen.txt
··· 1 - The ones who could write 2 - the next, the youth, 3 - the pen was lying 4 - there for them to 5 - pick up. 6 - 7 - To make the next page. 8 - 9 - ...they never did.
-9
quotes/deltarune/gerson_susie_pen_of_hope_1.txt
··· 1 - But... there is one 2 - thing that can 3 - overwrite the dark. 4 - 5 - A white pen, 6 - known as hope. 7 - 8 - Miss! I believe... 9 - this is what you hold.
-9
quotes/deltarune/gerson_susie_pen_of_hope_2.txt
··· 1 - "Me? Nah, Kris 2 - has the pen. 3 - 4 - My weapon's like a 5 - hairbrush or something." 6 - 7 - "Geh-hahahahaha! 8 - Is that so? 9 - Is that SO???"
-7
quotes/deltarune/gerson_susie_quitting_1.txt
··· 1 - * ... Shouldn't you be helping that pumpkin fella? 2 - 3 - * Nah, Ralsei's got it covered. I... I quit. 4 - 5 - * Quit? Quit what? 6 - 7 - * You know. Healing. That green sparkle thing.
-6
quotes/deltarune/gerson_susie_quitting_2.txt
··· 1 - * Hmm. So you don't like doing it? 2 - 3 - * Well, uh... I dunno, actually. 4 - * At first, I was really... I was really excited I could do it. 5 - * But like... I sort of realized... 6 - * I just suck at it. Heh.
-7
quotes/deltarune/gerson_susie_quitting_3.txt
··· 1 - * What if you practiced? Put a little shell into it. 2 - 3 - * I mean, I kind of... When people aren't looking... 4 - * I was kind of trying to practice a little, but... 5 - * I sorta realized, y'know? 6 - * I'll never be as good as Ralsei, so what's the point? 7 - * So everyone can see how... bad I am?
-6
quotes/deltarune/gerson_susie_quitting_4.txt
··· 1 - * Hm. Is that so. 2 - * Didn't take you for a purple bellied coward. 3 - 4 - * Coward...? Hey, what do you mean by that!? 5 - 6 - * You're afraid of making some little green sparkles.
-6
quotes/deltarune/gerson_susie_the_prophecy.txt
··· 1 - * Oopsy daisy! Darn my clumsiness, geheh... 2 - 3 - * Damn it! You broke it again, didn't you...? 4 - 5 - * Well, we were following that Prophecy so closely... 6 - * I couldn't see quite where I was going, geh-heh!!
-13
quotes/deltarune/gerson_swallowed_up.txt
··· 1 - There was only one more 2 - chapter... After that, 3 - 4 - It all stopped. 5 - 6 - That next book, it 7 - never did get written. 8 - 9 - The story, it became so 10 - grand, so overwhelming, 11 - 12 - Some say it swallowed up 13 - the author himself.
-3
quotes/deltarune/gerson_the_motions_1.txt
··· 1 - * ... You're really just going through the motions, huh? 2 - * Something tough's going on with you. ... I can tell that sorta thing. 3 - * And now, you still gotta go through your day. Still gotta talk, fight, do homework...
-3
quotes/deltarune/gerson_the_motions_2.txt
··· 1 - * I bet it all feels pointless, don't it. It's almost an insult that the earth's still turnin'. 2 - * ... but don't you give up. Even if you gotta do those motions... 3 - * Trying to find something, anything in them, that means a little something... ya hear?
-7
quotes/deltarune/gerson_what_happens_next.txt
··· 1 - * Ain't no better story than one told with sparkling eyes. 2 - * She ain't got no fear, that one. Doubt, irony, that's what poisons your story... 3 - * That, and too much predictability. 4 - * So, young man... What do you think happens next? 5 - 6 - * H... huh? 7 - * I... I guess we'll... just have to see.
-5
quotes/deltarune/hand_pushed_you.txt
··· 1 - * (It seems the way is blocked.) 2 - * (You put out your hand...) 3 - * (And, just out of view, just perfectly out of view, 4 - * Another hand pressed against yours 5 - * And pushed you back.)
-2
quotes/deltarune/knight_beat.txt
··· 1 - * Kris coughed. 2 - * The enemy slowly tilted its head...
-1
quotes/deltarune/knight_heart.txt
··· 1 - * Your heartbeat becomes twisted.
-4
quotes/deltarune/knight_kris_analysis.txt
··· 1 - * Kris analyzed the enemy! 2 - 3 - * But Kris 4 - couldn't learn anything.
-3
quotes/deltarune/knight_kris_breath_quicken.txt
··· 1 - * Kris held their breath. 2 - * Their heartbeat quickened. 3 - * The SOUL now moves faster.
-3
quotes/deltarune/knight_kris_breath_smile.txt
··· 1 - * Kris held their breath... 2 - * Kris smiled. 3 - * Nothing happened.
-1
quotes/deltarune/knight_kris_kneel.txt
··· 1 - * Kris kneeled in silence.
-1
quotes/deltarune/knight_ralsei_swoon.txt
··· 1 - * Ralsei became a pile of fluff.
-7
quotes/deltarune/knight_ralsei_talk.txt
··· 1 - * Ralsei tried talking... 2 - 3 - * Please... please, don't do this... 4 - * If the Roaring happens, then... then... 5 - * Please... stop...! 6 - 7 - * (... but nothing happened.)
-1
quotes/deltarune/knight_roaring.txt
··· 1 - * The Roaring Knight appeared.
-1
quotes/deltarune/knight_susie_swoon.txt
··· 1 - * Susie was hurt and beaten.
-7
quotes/deltarune/knight_susie_talk.txt
··· 1 - * I don't know what the hell you are, but... 2 - * Leave Toriel alone! You hear me!? 3 - * ... 4 - * ... Fine, you don't wanna listen? 5 - * Then we'll just. Have to do things the hard way. 6 - 7 - * (Susie will not ACT any more.)
-1
quotes/deltarune/knight_susie_warn.txt
··· 1 - * Susie struggled to give some kind of warning.
-3
quotes/deltarune/kris_never_wash_it_all_away.txt
··· 1 - * (You ran water over your hands, and dried them.) 2 - * (Between your fingers, a faint grey crease glittered stubbornly.) 3 - * (... You can never wash it all away.)
-3
quotes/deltarune/kris_pencil_1.txt
··· 1 - * (Kris picked up a pencil.) 2 - * (They began by sketching what they had seen behind the tree.) 3 - * (But, as they sketched, the line began to err, as if the pencil had lost its way.)
-3
quotes/deltarune/kris_pencil_2.txt
··· 1 - * (So they started again, drawing over the lines they had already made.) 2 - * (Many times, they would suddenly halt, or the pencil drifted into a new drawing...) 3 - * (So, they started over, over, over again.)
-2
quotes/deltarune/kris_pencil_3.txt
··· 1 - * (Through many repetitions, their movements became faster and smoother...) 2 - * (... until without looking, they had completed a sketch in a single, wild line.)
-3
quotes/deltarune/kris_pencil_4.txt
··· 1 - * (...) 2 - * (In the end, as a result of layering the sketches on top of each other...) 3 - * (... The surface of the canvas was nothing more than a monochrome smear.)
-2
quotes/deltarune/kris_susie_be_okay.txt
··· 1 - * (It's hard to put into words, but for some reason, seeing Susie next to you...) 2 - * (... You felt like, whatever you were, for just right now, it might be okay.)
-5
quotes/deltarune/kris_taken_enough.txt
··· 1 - * There's 5 dollars in your brother's drawer. Take it? 2 - 3 - Do not 4 - 5 - * (You have already taken enough.)
-5
quotes/deltarune/kris_unseen_shape.txt
··· 1 - * (...) 2 - * (Kris got up. It seemed the painting was complete.) 3 - * (...) 4 - * (But, their hand, smudged with graphite...) 5 - * (... was still moving, as if to trace some unseen shape.)
-7
quotes/deltarune/noelle_alone_with_kris.txt
··· 1 - * To be honest, I've kind of been waiting for... 2 - * A chance for us to be alone all day. 3 - * ... 4 - * There's... something I want to talk to you about, Kris. 5 - * ... 6 - * I want to talk to you about... 7 - * ... last night.
-4
quotes/deltarune/noelle_everything_important_1.txt
··· 1 - * Huh...? You mean, um... how I... got locked out of my house? 2 - * That's why I... well, I used to end up at your place sometimes. 3 - * Y'know, you'd help me... faha, break into my own room. 4 - * Even then, my mom... never gave your family a spare key.
-4
quotes/deltarune/noelle_everything_important_2.txt
··· 1 - * She never even kept anything at City Hall, either. 2 - * No keys, no documents, no... handmade gifts, anything. 3 - * Everything important... she always... 4 - * ... keeps locked down at home.
-7
quotes/deltarune/noelle_kris_ThornRing.txt
··· 1 - * K... Kris? Kris, what did you say? 2 - * K... Kris? Kris, what is that? 3 - * Kris... that's... not the thorn, is it? 4 - * That's not... the... ThornRing, is it...? 5 - * K... Kris... c'mon... you... you still... 6 - * What about... what about last night? What about... 7 - * Didn't you... want to... protect me...?
-8
quotes/deltarune/noelle_kris_dont_mention.txt
··· 1 - * Kris, there... there was something else you did. 2 - * Something else... I have to ask you about. 3 - * After you said all of that... you... 4 - * Took my hand... and... 5 - * Pulled... a thorn out of my finger. 6 - * Then you said... 7 - * You said... you said that when I saw you again... 8 - * You said... not to mention you did any of this... or...
-5
quotes/deltarune/noelle_kris_its_just_us.txt
··· 1 - * ? 2 - * Kris, it's okay! It's just us! 3 - * You just said not to mention any of that... 4 - * Because it'd get... heard. 5 - * ... Kris... Kris, it's okay, it's just us.
-4
quotes/deltarune/noelle_kris_piano.txt
··· 1 - * H... huh? Thanks! Where'd that come from? 2 - * I don't think you've ever... complimented me, before. Fahaha. 3 - * You know, if you wanted to join the choir, you could... 4 - * I always missed, um... liked hearing you play the piano.
-3
quotes/deltarune/noelle_kris_talk_later.txt
··· 1 - * ... Kris? You came over to... check on me? 2 - * That's really nice of you, I... 3 - * ... Do you... think I could talk you later?
-5
quotes/deltarune/noelle_kris_voice_1.txt
··· 1 - * That's when I felt it. 2 - * A hand on my wrist. 3 - * I froze. 4 - * And then I heard your voice. 5 - * Kris's voice.
-4
quotes/deltarune/noelle_kris_voice_2A.txt
··· 1 - * You said, sorry for taking your watch, Noelle. 2 - * You said, it was all a stupid prank. 3 - * You said, Berdly is going to get better. 4 - * And you put the watch back on my wrist.
-4
quotes/deltarune/noelle_kris_voice_2B.txt
··· 1 - * You said, sorry for being weird at the hospital, Noelle. 2 - * You said, sorry for being weird yesterday, Noelle. 3 - * You said, it was all a stupid prank. 4 - * You said, Berdly was going to get better.
-5
quotes/deltarune/noelle_kris_your_voice.txt
··· 1 - * ... Kris, your voice, your deadpan, mumbly voice... 2 - * Even if you sounded kind of weak and shaky... 3 - * I don't know why, but it felt so long since I heard it. 4 - * I even started sniffling! Haha! 5 - * Kris, Kris, you're back...!
-7
quotes/deltarune/noelle_nightmare.txt
··· 1 - * Last night, I... I couldn't sleep. 2 - * Every time I closed my eyes... 3 - * All I could see 4 - was snow. 5 - * I was terrified, Kris. 6 - * Terrified it was all real. 7 - * I went out on the porch to breathe, and...
-4
quotes/deltarune/plaque_gerson_hope.txt
··· 1 - * (It's a plaque bearing the words of a famous writer.) 2 - * ("Hope comes to those who believe. And for those that cannot...") 3 - * ("... May our hope shine so brightly...") 4 - * ("... That they, too, may keep shelter from the dark.")
-4
quotes/deltarune/ralsei_dark_not_real.txt
··· 1 - In other words... when the lights come back on... 2 - This world is no longer "real". 3 - The places you go, the people you meet... none of them are. 4 - Even I, the one telling you this... I too, am...
-10
quotes/deltarune/ralsei_dont_need_a_room.txt
··· 1 - * It's... 2 - * It's... empty...? 3 - 4 - * ... 5 - * My own room? ... I... I don't need a room! 6 - * ... 7 - * ...at least... that's what I thought. 8 - * So... So I never... 9 - 10 - * ... You... you LIVE here. Why do WE have rooms and not you?
-5
quotes/deltarune/ralsei_forget_us_1.txt
··· 1 - * It's funny, I was... overjoyed to make friends. 2 - * Then, selfishly, I... started feeling sad, too. 3 - * Sad I couldn't be your friend in real life, Susie. 4 - * That I CAN'T do your homework. That I can't go with you. 5 - * That I... can't be there to help you when you really need me.
-3
quotes/deltarune/ralsei_forget_us_2.txt
··· 1 - * I'm sorry. That's why I... If, you know, me, Lancer... 2 - * We can't do it all. So... if you ever feel like we're not enough... 3 - * Just forget about us and make some real friends, okay?
-7
quotes/deltarune/ralsei_friendship_far_greater.txt
··· 1 - * I 2 - * Being with you two... 3 - * I... 4 - * The more I'm with you two, I realize... 5 - * Friendship... isn't just... 6 - * About being happy all the time. 7 - * It's something far, far greater than I could've imagined...
-2
quotes/deltarune/ralsei_healing_the_outside.txt
··· 1 - * I was wondering why my healing wasn't working... 2 - * I suppose... I was only healing the outside.
-7
quotes/deltarune/ralsei_i_am_smiling.txt
··· 1 - * ... 2 - * S-sorry... don't look at me... I... 3 - * I don't mean to always... 4 - * Make everything about me... 5 - * I'm sorry. I... 6 - * Everything's OK, Kris. Look, I... 7 - * I'm smiling!
-5
quotes/deltarune/ralsei_i_do_for_you_1.txt
··· 1 - * Kris, Susie is so nice... 2 - * It... makes my heart hurt. 3 - * I don't really have any hobbies, or interests. 4 - * Baking, sewing, singing... those are all just... 5 - * Things I thought to do... for you two.
-7
quotes/deltarune/ralsei_i_do_for_you_2.txt
··· 1 - * ... 2 - * But recently... 3 - * I'm starting to feel like... 4 - * Like I'm developing my own opinions. 5 - * My own likes. 6 - * My own dislikes. 7 - * My own desires. My own... fears.
-8
quotes/deltarune/ralsei_i_just.txt
··· 1 - * I just 2 - * I thought if I said something different 3 - * If we did something different 4 - * If we were just kind enough 5 - * Perhaps by the time we got here... it would change. 6 - * But... but no matter what we do... 7 - * Our fate... 8 - * ... is already decided.
-3
quotes/deltarune/ralsei_knight_check_susie.txt
··· 1 - * Kris, I... I'll be fine. 2 - * But... if I'm hurt this badly, then Susie must be... 3 - * Kris... you'll... make sure she's alright, won't you?
-7
quotes/deltarune/ralsei_knight_the_prophecy.txt
··· 1 - * Somehow, I thought we... had won... for a moment... 2 - * That... we would be able to end the battle... here and now... 3 - * ... you... 4 - * You were so brave... and yet... 5 - * In the end... our struggle... 6 - * It's only beginning, isn't it...? 7 - * ... Isn't it, Kris...?
-4
quotes/deltarune/ralsei_knight_will_be_fine.txt
··· 1 - * K... Kris... I... 2 - * ... 3 - * I'll be... fine. 4 - * You do... whatever you need to.
-3
quotes/deltarune/ralsei_kris_thank_susie.txt
··· 1 - * Kris, at least as long as you're there... 2 - * I can know she won't be lonely, or hungry. 3 - * ... Thank you, Kris.
-3
quotes/deltarune/ralsei_kris_you_were_cool.txt
··· 1 - * Umm... Kris? 2 - * You were 3 - * You were cool when you played the piano earlier!
-7
quotes/deltarune/ralsei_let_it_just_be.txt
··· 1 - * I didn't ask to know, but I do. 2 - * I... I'm sorry if it creeps you out! 3 - * I'm sorry if I... don't tell you everything. 4 - * It's just... sometimes, knowing things... 5 - * It hurts. 6 - * And if anyone's going to hurt... 7 - * Let it... just be me, okay?
-6
quotes/deltarune/ralsei_my_purpose.txt
··· 1 - * Making you two happy... 2 - * Is... my purpose. 3 - * I... 4 - * If I think about it... 5 - * There's no reason... 6 - * For me... to have... anything.
-5
quotes/deltarune/ralsei_noelle_bad.txt
··· 1 - * N... Noelle? 2 - * Kris, I... 3 - * I think that's... 4 - * ... 5 - * A bad choice.
-4
quotes/deltarune/ralsei_not_filling.txt
··· 1 - * I realized... being friends is... a responsibility. 2 - * A responsibility... I can't... fully complete. 3 - * The cakes I bake, the cotton candy we eat, it might be yummy. 4 - * ... But, when you return to the Light World, you won't be full.
-3
quotes/deltarune/ralsei_pain_i_can_take.txt
··· 1 - * Susie... Kris... as long as... 2 - * There's any pain I can take in your place... 3 - * I'll be there, okay?
-8
quotes/deltarune/ralsei_scared.txt
··· 1 - * K... Kris? Kris, you... 2 - * Haha... 3 - * I... I'm scared... 4 - * It's getting harder and harder 5 - * To just stand there smiling all the time. 6 - * To be the one 7 - * Pretending I'm not afraid, too. 8 - * But
-10
quotes/deltarune/ralsei_should_i_even.txt
··· 1 - * ... please... tell me... 2 - * Should I... 3 - * Should a... Darkner... be feeling like this? 4 - 5 - Please be 6 - yourself 7 - 8 - * ... I... 9 - * I... 10 - * K... Kris, hearing that from you, I might just...
-8
quotes/deltarune/ralsei_something_even_worse.txt
··· 1 - * I... I want to. I want to believe again. 2 - * I want to believe... it can change! 3 - * That there isn't just one ending! 4 - * But... 5 - * What would that be...? 6 - * If there was something else, what would it be...? 7 - * And how do we know 8 - * It wouldn't be something even worse?
-7
quotes/deltarune/ralsei_sorry_i_say_nothing.txt
··· 1 - * I 2 - * I'm 3 - * I'm sorry, I 4 - * I know so many things, I 5 - * I know so many things I, don't know. When to say them. 6 - * I don't want... to say them. To... worry... you two. 7 - * So I end up... I end up not saying... anything.
-8
quotes/deltarune/ralsei_susie_DONT_PLEASE.txt
··· 1 - * No... 2 - * SUSIE, STOP!!! 3 - * SUSIE, DON'T LOOK-- 4 - * S-Susie, I'm sorry, I-- 5 - * I, I'm sorry, I-- 6 - * I'm sorry, I-- 7 - 8 - * Shut up.
-9
quotes/deltarune/ralsei_susie_changed.txt
··· 1 - * Aww, Susie! 2 - * You've changed so tremendously since I met you. 3 - * No, rather, you've finally just been able to... 4 - * Be... yourself. 5 - * I feel... haha, I just feel so proud of you, Susie. 6 - 7 - * Hey, you've changed too, dude. 8 - 9 - * I... I have?
-3
quotes/deltarune/ralsei_susie_hog_all_the.txt
··· 1 - * S... Susie...! What are you doing...!? 2 - 3 - * ... Like I'm going to let Kris hog all the bullets.
-9
quotes/deltarune/ralsei_susie_never_cake.txt
··· 1 - * Oh! Th... this is really yummy! 2 - 3 - * Why the heck are you so surprised? 4 - 5 - * I... I've actually never eaten cake before... 6 - * Since, I was making it for you two, after all. 7 - * ... I can't believe it tastes this good! 8 - 9 - * Haha, dude, you're such a weirdo sometimes.
-6
quotes/deltarune/ralsei_susie_noogie_to_cry.txt
··· 1 - * Susie...! 2 - 3 - * Uhh, first noogie to make someone cry? 4 - 5 - * H-hahaha! Sorry! 6 - * I'm just... glad you're friends with me.
-9
quotes/deltarune/ralsei_susie_not_the_old_man_1.txt
··· 1 - * Susie... 2 - * That wasn't the old man. 3 - 4 - * H... huh? 5 - 6 - * One of the statues in his shape from his study... 7 - * It must have begun moving when you made this Dark World. 8 - 9 - * A... a statue...? Then... the old man...?
-8
quotes/deltarune/ralsei_susie_not_the_old_man_2.txt
··· 1 - * He's... not in this world, sadly. 2 - * Darkners patterned after the memory of a loved one... 3 - * Tend to only appear in very specific darknesses. 4 - * ... even if we tried to bring him to Castle Town, he... 5 - 6 - * THEN WHY DIDN'T YOU SAY SOMETHING EARLIER!? 7 - 8 - * H... huh?
-5
quotes/deltarune/ralsei_susie_so_kind_how.txt
··· 1 - * Susie... 2 - * ... she's... 3 - * She's a really really nice person, Kris. 4 - * How can she be so kind...? 5 - * ... how?
-10
quotes/deltarune/ralsei_susie_sounding_weird.txt
··· 1 - * I'll be more honest, Susie. I promise. 2 - * Even if it means... sounding... weird. 3 - 4 - * ... Ralsei? 5 - * You couldn't get any weirder if you tried. 6 - 7 - * You too, Susie! 8 - 9 - * ... uh, thanks. 10 - * ... well, let's get out of here, Kris.
-7
quotes/deltarune/ralsei_susies_infectuous_hope.txt
··· 1 - * I... I want to. I want to believe again. 2 - * I want to believe... it can change! 3 - * That there isn't just one ending! 4 - * Susie's hope... Her naรฏve hope... 5 - * It's... infectious, isn't it, Kris? 6 - * ... So, until we see fate with our own eyes... 7 - * Let's believe, too.
-6
quotes/deltarune/ralsei_tenna_empathy_1.txt
··· 1 - * Mr. Tenna... I... understand how you feel. 2 - * To want to be... important. To be... useful. 3 - * Perhaps... you might not be watched much anymore... 4 - * But... that doesn't make you a failure, Tenna! 5 - * You've brought smiles, light into Lightner's lives... 6 - * ... to Kris's family and friends, for so long.
-6
quotes/deltarune/ralsei_tenna_empathy_2.txt
··· 1 - * So, there's nothing to be ashamed of. 2 - * If... that ever comes to an end. 3 - * Darkners... all become obsolete eventually. 4 - * But we aren't "real", Tenna. 5 - * We shouldn't make Lightners worry about what happens to us. 6 - * It'd just... make them unhappy, wouldn't it?
-6
quotes/deltarune/ralsei_the_one_thing_i_like.txt
··· 1 - * Kris, I'm sorry, but... 2 - * This is... my face. 3 - * And if there's one thing I like about myself, it's this... 4 - * So, even if you think it's boring, or too similar to... 5 - * D-don't laugh, Kris. If you think it's the same as... 6 - * OK, I get it! You don't think it's too similar! But...
-6
quotes/deltarune/ralsei_the_titan.txt
··· 1 - * A Titan. 2 - * It's the fear-of-dark. 3 - * It's the bump-in-the-night. 4 - * It's the shadow of the backside of your mind. 5 - * It has no consciousness. 6 - * It only exists to destroy.
-4
quotes/deltarune/ralsei_too_much_fun.txt
··· 1 - * Huh? How I'm doing? Umm... 2 - * Those games were a lot of fun, weren't they? 3 - * I've never played such a game before... I kind of... 4 - * I kind of got so wrapped up...
-4
quotes/deltarune/ramb_i_saw_you.txt
··· 1 - * Kris... I'm an ignorant plug. Can't say I know your motivation. 2 - * But... I saw. I saw you make it, you know. The Fountain. 3 - * Could it be... you just wanted to have fun again? 4 - * Heh heh heh... some REAL fun, just like the old days.
-3
quotes/deltarune/ramb_just_play_your_games.txt
··· 1 - * Your mother. Your poor mother, what will happen if she wakes up...? 2 - * ... ah, what am I doing? A power strip, giving YOU a lecture... 3 - * ... just play your games, luv.
-4
quotes/deltarune/ramb_was_it_even.txt
··· 1 - * ... 2 - * That's been my purpose, y'know. Letting you play your games. 3 - * But, heh, now that the games are almost over... 4 - * Makes you think, was it really all such a good idea?
-2
quotes/deltarune/ramb_whyd_you_do.txt
··· 1 - * Kris... if not for fun, why'd you do it? 2 - * ... mum's the word, innit. Fair play, fair play.
-5
quotes/deltarune/rudy_carol_frozen.txt
··· 1 - * (Like, when Noelle was a kid...) 2 - * (She made paper snowflakes, for Carol to hang up at work...) 3 - * (But Carol just... wouldn't take 'em there.) 4 - * (Because she wanted to keep the damn things safe.) 5 - * (She preserved 'em, and stuck 'em up on the wall at home...)
-2
quotes/deltarune/rudy_locked_out.txt
··· 1 - * (Noelle... wasn't stuck outside the gate again, was she, Kris...?) 2 - * (... Carol... damn, I keep telling her to keep a key at City Hall...)
-3
quotes/deltarune/rudy_noelle_snow_angels.txt
··· 1 - * (... course, you know Noelle, she had to cry over that.) 2 - * (Until she went and made snow angels with you guys...) 3 - * (... After that, she forgot she was sad at all.)
-2
quotes/deltarune/savepoint_actors.txt
··· 1 - * (After the performance, when all the actors have gone home...) 2 - * (You feel a sense of emptiness.)
-2
quotes/deltarune/savepoint_cold.txt
··· 1 - * (A draft blows, and you feel the cold night's presence.) 2 - * (Still, this never bothered you. In fact...)
-2
quotes/deltarune/savepoint_wind.txt
··· 1 - * (The wind blows fiercely, as if it fears the night itself.) 2 - * (The final act draws near.)
-7
quotes/deltarune/shall_we_hasten.txt
··· 1 - IF YOU ARE SO 2 - DETERMINED 3 - TO TRY ONCE MORE 4 - 5 - THEN 6 - 7 - SHALL WE HASTEN?
-7
quotes/deltarune/shelter_lock.txt
··· 1 - * (On the shelter was a strange panel.) 2 - * (It looked like a digital security lock...) 3 - * (There were three entry fields, and three symbols besides them.) 4 - * (One had a pine tree beside it.) 5 - * (One had an officer's badge beside it.) 6 - 7 - * (And one had the DELTA RUNE.)
-5
quotes/deltarune/spamtom_mere_puppet.txt
··· 1 - * It seems after all I couldn't be anything more than a simple puppet. 2 - * But you three... You're strong. 3 - * With a power like that... 4 - * Maybe you three can break your own strings. 5 - * Let me become your strength.
-9
quotes/deltarune/susie_as_a_team.txt
··· 1 - * Hey... It's okay, man. 2 - * That Last Prophecy thing... I don't need to see it. 3 - * So... if it helps, whenever you gotta... 4 - * You can go ahead alone. 5 - * But everything else? 6 - * We're doing it together. As a team. 7 - * And NOTHING'S gonna change that. 8 - * NOTHING! 9 - * Whether you like it or not, toothpaste boy!
-4
quotes/deltarune/susie_asriel_difference.txt
··· 1 - * HAHAHA! Why did you photoshop Ralsei to be... 2 - * ... What? That's your brother? 3 - * ... Yeah, now that I look at it, he looks, uh, different. 4 - * I SAID I get it! You can stop pointing out the differences!
-5
quotes/deltarune/susie_be_nice_to_mom.txt
··· 1 - * Heh, what are those little horns you're wearing? 2 - * It's almost like you were trying to... 3 - * ... 4 - * ... heh. 5 - * ... Kris, you better be nice to your mom.
-8
quotes/deltarune/susie_broken_toy_1.txt
··· 1 - * I've never been able to... make friends. 2 - * I was always... the scary girl. The bad kid. 3 - * The only times anyone ever got close, 4 - * ... were as a joke. 5 - * And even if I did start to make a real friend... 6 - * ... I'd always end up... 7 - * Moving away. 8 - * When I moved here, I thought it would be the same.
-4
quotes/deltarune/susie_broken_toy_2.txt
··· 1 - * I felt like you. 2 - * I felt like a broken toy no one wanted. 3 - * ... 4 - * Kris's mom saw me. Toriel.
-7
quotes/deltarune/susie_broken_toy_3.txt
··· 1 - * Saw me sitting on the bench in the graveyard, crying. 2 - * She asked me what was wrong, and... 3 - * Told me everything was going to be okay. 4 - * Took me to the diner. Bought me a hot chocolate. 5 - * Talked to me. Told me I'd make friends. 6 - * That... gave me hope. Even just a little. 7 - * ...
-7
quotes/deltarune/susie_broken_toy_4.txt
··· 1 - * And recently. That hope's been growing. 2 - * Because... now I have friends. 3 - * Real friends. 4 - * Friends I wanna keep. 5 - * Kris. Ralsei. Lancer... Toriel, too. 6 - * Even though I thought I was broken... 7 - * I just needed to find the right people, y'know?
-5
quotes/deltarune/susie_broken_toy_5.txt
··· 1 - * So... 2 - * Don't worry, dude. 3 - * Someone out there wants you. Promise. 4 - * Kris, you'll help me ask around town, right? 5 - * Someone's gotta want a TV.
-11
quotes/deltarune/susie_choose_eternity.txt
··· 1 - An ending, huh... 2 - 3 - ...If I could chooseโ€ฆ 4 - I guess... 5 - 6 - I wouldn't have an 7 - ending! 8 - 9 - It's just better if 10 - stuff just... 11 - goes on forever, right?
-3
quotes/deltarune/susie_dark_world_real.txt
··· 1 - * ... But yeah, maybe it's not real. The dark world. 2 - * (I just wish Ralsei would realize...) 3 - * (That doesn't matter to me so much.)
-6
quotes/deltarune/susie_gerson_dragon_blazers.txt
··· 1 - * Isn't that what Dragon Blazers was based on? 2 - 3 - * Yep, yep. And so they changed parts of the story. 4 - * Of course, the biggest fans got mad... but, isn't it interesting? 5 - * The book was already an interpretation of something else. 6 - * Stories can be retold. They can be changed... That's what I believe.
-8
quotes/deltarune/susie_gerson_remind_me.txt
··· 1 - * Why'd you... help me so much? 2 - * You didn't have to. 3 - 4 - * You remind me a bit of someone I used to know. 5 - 6 - * ... Who? 7 - 8 - * Just a character from that old story...
-8
quotes/deltarune/susie_gerson_teaching.txt
··· 1 - * You were... trying to teach me something earlier, weren't you. 2 - 3 - * That? Naw, you did all that yourself. 4 - 5 - * C'mon man, you... you're the only person I've met... 6 - * Who teaching me made me actually feel LESS stupid. 7 - 8 - * Geheheh, well, it helps that I'm losing my mind, don't it?
-10
quotes/deltarune/susie_gerson_the_doragon.txt
··· 1 - * The words on a wall called you a hero. 2 - * ... Whatever you end up being, I'm sure it'll be tremendous. 3 - 4 - * ... 5 - * ... anyway, which Dragon Blazers character am I? 6 - * Gahahaha, right! 7 - 8 - * You are... the Dragon. 9 - 10 - * Heheh. That's what I was hoping.
-4
quotes/deltarune/susie_heal_no.txt
··· 1 - ??% Heal 2 - 3 - It seems the user doesn't 4 - want to use this spell.
-6
quotes/deltarune/susie_hope_crossed.txt
··· 1 - * Kris! Kris...! 2 - * Hey, look! It's me!! 3 - * "The girl with hope crossed on her heart." 4 - * Man, that's... That's bad ass, isn't it!? 5 - * You think there's anything else about us? 6 - * Don't just stand there, let's keep looking!
-3
quotes/deltarune/susie_kris_doggy_ears.txt
··· 1 - * Arright, Kris. If Ralsei can't come to the Light World... 2 - * Then YOU'RE gonna wear doggy ears and do my homework. 3 - * ... stop howling! Man, you're such a pain in the ass. Heh.
-7
quotes/deltarune/susie_kris_giant_bloodstain.txt
··· 1 - * ... what the hell is this giant bloodstain? 2 - * Just use VINEGAR, dumbass. Vinegar and hot water. Cleans it up. 3 - * ... look, we don't have time to... 4 - 5 - * (Somehow, you and Susie made time to clean up the stain.) 6 - 7 - * Dammit. We're gonna be late. Dammit.
-1
quotes/deltarune/susie_kris_hug_the_freezer.txt
··· 1 - * Stop hugging the fridge, freezer freak.
-7
quotes/deltarune/susie_kris_just_hear_you_play.txt
··· 1 - * ... I... don't even really know if I broke it, but... 2 - * I hit it as hard as I could. Hard enough to do some damage. 3 - * ... and I ran. 4 - * ... A week ago, if I knew you did piano... 5 - * ... I probably would've just hated that about you. 6 - * But now... 7 - * I kinda... just wanna hear you play.
-6
quotes/deltarune/susie_kris_mouth_closed.txt
··· 1 - * ... huh? 2 - * ... Why are you talking... with your mouth closed? 3 - * ... "Nothing"? 4 - * ... then why'd you wait so long to say it? 5 - * ... 6 - * Guess we should go.
-3
quotes/deltarune/susie_kris_my_prize.txt
··· 1 - * Hey, the hell does your brother have all the prizes? 2 - * Wait a sec... 3 - * There you go. The Susie prize. For having me over your house.
-10
quotes/deltarune/susie_kris_no_need.txt
··· 1 - * ... 2 - * You don't have 3 - to say anything back, 4 - Kris. 5 - * I... 6 - * ... 7 - * I know... 8 - * You're thinking the 9 - same thing 10 - * ... aren't you, dumbass?
-2
quotes/deltarune/susie_kris_or_something.txt
··· 1 - * Hey, didn't know you owned a bird. Or, uh... something. 2 - * ... don't really like how you nodded at "or something."
-7
quotes/deltarune/susie_kris_plink.txt
··· 1 - * Woah, Kris, you can play the piano? 2 - 3 - * (Plink...) 4 - 5 - * Hahaha! Almost thought you were serious for a sec! 6 - * (... what's with that pissed off look?) 7 - * ... damn. Got kinda hyped for a sec.
-7
quotes/deltarune/susie_kris_we_are_friends.txt
··· 1 - * Haha, like you're one to talk, idiot! 2 - * You got so few friends... 3 - * Your mom was even happy you, heh... 4 - * ... uh, made friends with me, I guess. 5 - * ... 6 - * ... man, we could've been friends way before. 7 - * Isn't that stupid...?
-6
quotes/deltarune/susie_kris_wont_you_play_1.txt
··· 1 - * Ralsei, wait a sec. 2 - * Kris... today in church, when you were asleep... 3 - * Your mom... said you used to play good. 4 - * I dunno why you're pretending you can't now. 5 - * Maybe you're like... embarrassed or something? 6 - * But like... y'know...
-10
quotes/deltarune/susie_kris_wont_you_play_2.txt
··· 1 - * ... Kris, you should play. 2 - 3 - * (You thought about Susie's words and took a deep breath.) 4 - * (Your hands began to move on their own...) 5 - 6 - * Hey, why'd you stop? That was cool. 7 - * ... You should, uh, play more. 8 - * Seriously, you're an idiot if you quit playing. 9 - 10 - * (... Susie... is nice, isn't she, Kris.)
-6
quotes/deltarune/susie_kris_your_piano.txt
··· 1 - * ... Y'know, when you played piano earlier... it was... 2 - * Okay, it was actually kinda cool. I mean that. 3 - * I'm not saying you should join the choir or whatever but... 4 - * If you were playing every week. 5 - * ... I might actually wanna go to church. 6 - * ...
-8
quotes/deltarune/susie_letter_alvin.txt
··· 1 - * "Alvin 2 - * Your old man was proud of you. 3 - * The reason he didnt come to church alot 4 - * is beacause he wanted you to follow your dream. 5 - * Hes sorry he never said that right. 6 - * So write your own story's. 7 - * Its okay if even if there not perfect. 8 - * Sinserely a friend"
-9
quotes/deltarune/susie_ralsei_bad_bum.txt
··· 1 - * Before... you were all stuck up about being good, right? 2 - * Now... you're actually kinda normal. 3 - 4 - * I suppose I just... started appreciating you more, Susie. 5 - * For example, I thought your Rude Buster was scary... 6 - * But now, I think it's really b-- 7 - * Umm, bad-bum! 8 - 9 - * Just say "bad ass", idiot!
-10
quotes/deltarune/susie_ralsei_cant_go_1.txt
··· 1 - * Just... uh... 2 - * I mean, we don't even have to play any games. 3 - * We could... just make a mess. Cause some trouble. 4 - * Just... 5 - * I dunno, could be... fun if you... 6 - * ... just went, you know? 7 - 8 - * No, Susie... I... 9 - * I... want to, I just... 10 - * I can't go.
-9
quotes/deltarune/susie_ralsei_cant_go_2.txt
··· 1 - * ... 2 - * ... um, did I say something weird? 3 - 4 - * No! No, not at all, Susie! 5 - * I'm happy to be invited by you, Susie, I am... 6 - * It's just... 7 - * I'm a Darkner. 8 - * I can't go to the Light World. 9 - * Even if I... wanted to.
-11
quotes/deltarune/susie_ralsei_ill_do_it_all.txt
··· 1 - * What, that... Last Prophecy thing...? 2 - 3 - * Susie... y-you didn't... 4 - 5 - * No, I mean, we almost did, but-- 6 - 7 - * Please. Please let me go ahead from now on. 8 - * I'll do every area. I'll do all the work. 9 - * Next time, too. 10 - * You two can just hang out and... have fun! 11 - * I could do all the puzzles, I could...
-7
quotes/deltarune/susie_ralsei_no_discards.txt
··· 1 - * Hear that, Ralsei? 2 - * No one's gonna get thrown away. 3 - * Not you. Not me. Not Kris. 4 - * Got it? 5 - 6 - * ... 7 - * ... got it, Susie.
-8
quotes/deltarune/susie_ralsei_real_feelings.txt
··· 1 - * But Ralsei, how can you say you're not real!? 2 - * I can see you... I can feel you... I can hear you... 3 - * "Normal objects" don't have... feelings and stuff! 4 - 5 - * That's right, Susie. 6 - * They... shouldn't. 7 - * That's why... I don't want you and Kris... 8 - * ... to worry too much about us, OK?
-11
quotes/deltarune/susie_ralsei_say_anything.txt
··· 1 - * ... hey, Ralsei. If you know the whole prophecy already... 2 - * ... why didn't you just say how to do the piano? 3 - 4 - * H-huh...? Well, I just thought... 5 - * I just thought, isn't it better if I don't... say anything? 6 - 7 - * ... 8 - * Why? 9 - 10 - * Um... 11 - * H-Hey, I think I found a light!
-11
quotes/deltarune/susie_ralsei_so_you_wont.txt
··· 1 - * Ralsei... 2 - * Is that... why you've been acting... so weird today? 3 - * Why you keep... 4 - 5 - * ... I... I've just been trying to stay ahead of you two. 6 - * So you don't have to see it. So you won't... 7 - * Because it would... 8 - 9 - * See... see what? 10 - 11 - * The ending... of the prophecy.
-8
quotes/deltarune/susie_ralsei_the_whole_deal.txt
··· 1 - * ... man. 2 - * There's nothing that pisses me off more... 3 - * ... than people who don't tell you the whole deal. 4 - 5 - * ... You mean, Tenna...? 6 - 7 - * Yeah. I dunno what, but... he's hiding something. 8 - * ... Let's go take a look around, Kris.
-12
quotes/deltarune/susie_ralsei_wont_forget_1.txt
··· 1 - * ... Ralsei... you... 2 - * You idiot! How can you SAY that?! 3 - * You think I could just FORGET you!? 4 - * Forget Lancer!? Forget everyone!? 5 - * I don't care what you say you are... 6 - * You're real to me, OK!? 7 - 8 - * ... 9 - * Susie, I... 10 - * I'm happy to hear that, but... 11 - 12 - * SHUT UP and listen to me!
-7
quotes/deltarune/susie_ralsei_wont_forget_2.txt
··· 1 - * No matter what you say... 2 - * No matter what you are... 3 - * Me... you... Kris... We're a TEAM, alright? 4 - * None of us are gonna forget each other! 5 - * So please... 6 - * Just... 7 - * ... just...
-7
quotes/deltarune/susie_ralsei_your_room_1.txt
··· 1 - * That's the stupidest goddamn thing I ever heard! 2 - * You see this stupid excuse for a room? 3 - * I don't care what the reason is. 4 - * Me and Kris are gonna fill it up. Wall to wall. 5 - * Until you have the most badass room in the castle. 6 - 7 - * Susie...
-6
quotes/deltarune/susie_ralsei_your_room_2.txt
··· 1 - * Look. You can't be the only one with an empty room. 2 - * It's... 3 - * It's too sad, y'know? 4 - * Wherever we go, we'll keep an eye out for furniture, 5 - * Right, Kris? 6 - * C'mon.
-8
quotes/deltarune/susie_something_important_1.txt
··· 1 - * Gotta be honest, Kris. I've never... 2 - * I've never really gotten picked... 3 - * ... for anything before. 4 - * No one ever wanted to be my partner in class. 5 - * ... Even got chosen last in gym. 6 - * But this...? 7 - * This is like... Something else, y'know? 8 - * Something... important.
-6
quotes/deltarune/susie_something_important_2.txt
··· 1 - * Kris... before, I didn't think I could be a hero. 2 - * I wasn't good enough. I mean, I was... bad. 3 - * I still kinda am bad. In some ways, I guess. 4 - * But now, I... I got hope crossed on my heart. 5 - * Hope, written in truth. Written somewhere... 6 - * Written somewhere no one can erase!
-11
quotes/deltarune/susie_stupid_dream_1.txt
··· 1 - * ... 2 - * Hey, Kris... 3 - * ... 4 - * Can I tell you 5 - * ... a stupid dream 6 - I have? 7 - * ... 8 - * No matter what 9 - * I... want to keep 10 - being friends 11 - with you and Ralsei.
-12
quotes/deltarune/susie_stupid_dream_2.txt
··· 1 - * When the sun 2 - comes up again... 3 - * I want tomorrow 4 - to be the same 5 - as yesterday. 6 - * And the next day 7 - * To be just the 8 - same as that. 9 - * That in the end 10 - * We can always go back 11 - to the way things were 12 - before.
-8
quotes/deltarune/susie_tea_for_wimps.txt
··· 1 - * It's kinda funny... I always thought tea parties were for wimps. 2 - * I mean, don't get me wrong. They still are. But... 3 - * Just feels like when I'm with you guys... 4 - * I don't even... need to think about that. 5 - 6 - * Aww, Susie! 7 - 8 - * Guess your stupid attitude's been rubbing off on me.
-3
quotes/deltarune/susie_tea_got_options.txt
··· 1 - * Like... I don't want to say I've been getting soft. But... 2 - * I just always... HAD to be tough, you know? 3 - * Now it just... feels like I... have options.
-10
quotes/deltarune/susie_tenna_gaming.txt
··· 1 - * Just, uh... 2 - * Wanted to say the games were pretty fun, I guess. 3 - 4 - * They... are? 5 - 6 - * I mean, you SAW when you interrupted us, but... Ralsei... 7 - * He was being kind of... weird, earlier. 8 - * I feel like this cheered him up. 9 - * And... me too. 10 - * It's just been taking my mind off... everything... I guess.
-4
quotes/deltarune/susie_tried_piano_1.txt
··· 1 - * ... That... Kinda reminds me of something. 2 - * So... In one of the places I used to live, there was like... 3 - * There was this plaza... and they put this public piano in it. 4 - * So I started playing it. Thinking, y'know, it might be fun.
-7
quotes/deltarune/susie_tried_piano_2.txt
··· 1 - * But... I guess... 2 - * Maybe because I was... so bad... 3 - * Or because I just looked like I shouldn't be playing... 4 - * They told me I wasn't... allowed to play it. 5 - * So I backed off. 6 - * And then... They let somebody else play. 7 - * ... someone better.
-9
quotes/deltarune/susie_tried_piano_3.txt
··· 1 - * ... 2 - * I don't know... I don't know why I expected I'd be able to play it. 3 - * ... Is what I thought... then. 4 - * ... 5 - * That night, when no one else was around... 6 - * ... I came back. 7 - * And you know what I did? 8 - * ... I smashed it. 9 - * Yeah... I smashed the piano.
-8
quotes/deltarune/susie_wont_let_it_happen.txt
··· 1 - * The hell are you apologizing for? 2 - * You're worried about THAT!? Seriously!? 3 - * ... this stupid prophecy? 4 - * Like something like that would happen. 5 - * I wouldn't let it happen... Kris wouldn't let it happen... 6 - * And obviously YOU wouldn't let it happen. 7 - * So... 8 - * Why wouldn't you laugh?
-3
quotes/deltarune/tenna_beautiful_princess_1.txt
··· 1 - * I... I just wanted to be watched... 2 - * I just wanted you to look at me... 3 - * One last time..
-6
quotes/deltarune/tenna_beautiful_princess_2.txt
··· 1 - * ... 2 - * Since... since a little while ago... 3 - * All that I've been broadcasting, is the same reruns, over and over... 4 - * I... I don't have anything anyone wants to watch anymore... 5 - * I'm irrelevant... I'm junk... 6 - * I don't wanna be thrown away...
-7
quotes/deltarune/tenna_holidays_1.txt
··· 1 - * ENOUGH of your dumb games!! 2 - * Why the hell do you want us to play so bad anyway!? 3 - 4 - * Kris. 5 - * Kris knows why. 6 - * Do you remember it, Kris? 7 - * When everyone came over for the holidays?
-6
quotes/deltarune/tenna_holidays_2.txt
··· 1 - Back when I was just a little old TV. 2 - Everyone used to gather around my glow to watch the specials, 3 - Smiling. Laughing. Eating butterscotch pie. 4 - Wasn't that just so much... fun? 5 - To watch me. 6 - I... was so happy.
-10
quotes/deltarune/tenna_holidays_3.txt
··· 1 - Then... 2 - People stopped coming. 3 - Everyone disappeared... one by one... 4 - No one wanted to watch TV anymore. 5 - No one wanted to play games anymore. 6 - 7 - I've been all alone... 8 - Until now. 9 - When the KNIGHT appeared... 10 - The ROARING KNIGHT.
-3
quotes/deltarune/tenna_kris_december_1.txt
··· 1 - * Oh, Kris! Remember DECEMBER? Oh, that little musical prodigy... 2 - * Remember when she'd grab the remote and swap it from cartoons... 3 - * ... to that WILD music video channel Toriel FORBID you to see?
-3
quotes/deltarune/tenna_kris_december_2.txt
··· 1 - * Dess, rocking her guitar and singing along with those nasty, nasty songs! 2 - * Didn't you all think she was just so... COOL? 3 - * HECK! Maybe you could get her to come back and watch again sometime!?
-12
quotes/deltarune/tenna_susie_kris_1.txt
··· 1 - * I... can't lie. I've been a bit, alone, until recently. 2 - 3 - * I... can relate, I guess. 4 - 5 - * But hosting you and lil' Ralsei has been an HONOR! Even if you're a tad sassy! 6 - 7 - * Umm, Kris was having fun, too. 8 - 9 - * Oh... yeah, Kris! 10 - * Haha, Kris! Good old Kris! What a stinker! 11 - 12 - * ... don't you like Kris?
-7
quotes/deltarune/tenna_susie_kris_2.txt
··· 1 - * What the - are you kidding!? I LOVE Kris! They were one of my original viewers! 2 - * I'm just not sure if, heh, they... uhh, love TV. 3 - 4 - * Huh? Why wouldn't they... 5 - 6 - * HEY would you look at that!! The NEXT BOARD's ready!!! 7 - * It was GREAT talking to you, Susie!! Good luck out there!!
-5
quotes/deltarune/tree_painted_over.txt
··· 1 - * (...) 2 - * (Next, a tree was painted over it...) 3 - * (... painted over everything...) 4 - * (... leaving nothing but a simple still life.) 5 - * (... After all, there wasn't anything to look at in the room besides a single tree.)
-10
quotes/deltarune/yet_you_persist.txt
··· 1 - VERY 2 - 3 - INTERESTING. 4 - 5 - YOUR LOSS HERE 6 - IS ALL 7 - BUT GUARANTEED. 8 - 9 - AND YET 10 - YOU PERSIST...
-10
quotes/deltarune/you_promised.txt
··· 1 - ... 2 - Kris... 3 - 4 - ... 5 - don't 6 - forget, 7 - Kris... 8 - 9 - ... 10 - you promised.
-3
quotes/xenoblade-chronicles-3/monica_in_the_mud.txt
··· 1 - Whether it ends in a lie or not is up to you. 2 - We all have to crawl face down in the mud. 3 - Believing...we'll come out smiling.
+2 -3
redis_random_push.py
··· 14 14 import redis 15 15 import typer 16 16 17 - def main(source_key, destination_key, rename_key: bool = False, password: str = "", host: str = "localhost", port: int = 16379, quotes_pattern: str = "quotes/**/*.txt", quotes_list_file: Path | None = None, log_level: str = "INFO") -> None: 17 + def main(source_key, destination_key, rename_key: bool = False, password: str = "", host: str = "localhost", port: int = 16379, quotes_pattern: str = "quotes/**/*.txt", log_level: str = "INFO") -> None: 18 18 logging.getLogger().setLevel(os.getenv("LOGGING") or log_level) 19 19 20 - new_quotes = tuple(iglob(quotes_pattern, recursive=True)) if not quotes_list_file else tuple(line for line in quotes_list_file.read_text().split("\n") if line) 21 - logging.info("Quotes to add: `%s`", new_quotes) 20 + new_quotes = tuple(iglob(quotes_pattern, recursive=True)) 22 21 r = redis.Redis(host=host, port=port, decode_responses=True, password=password) 23 22 24 23 logging.info("Fetching existing list at key `%s`.", source_key)
+24
src/data.rs
··· 1 + /// A newtype over [String] to represent a single quote. 2 + /// [Quote] does not implement [PartialEq] or [Eq] as, on principle, two 3 + /// quotes may be comprised of the same contents whilst still 4 + /// representing distinct quotes in practice (e.g. two different lines of dialogue which happen to be the same). 5 + #[derive(Debug, Clone)] 6 + pub struct Quote(String); 7 + 8 + impl<S: AsRef<str>> From<S> for Quote { 9 + fn from(value: S) -> Self { 10 + Self(value.as_ref().to_owned()) 11 + } 12 + } 13 + 14 + impl From<Quote> for String { 15 + fn from(value: Quote) -> Self { 16 + value.0 17 + } 18 + } 19 + 20 + impl Quote { 21 + pub fn get(&self) -> &str { 22 + &self.0 23 + } 24 + }
+94
src/lib.rs
··· 1 + pub mod data; 2 + pub mod sink; 3 + pub mod storage; 4 + 5 + pub mod run { 6 + use std::time::Duration; 7 + 8 + use crate::sink::{BskySink, PostQuote, SinkManager, StdoutSink}; 9 + use crate::storage::{ 10 + FetchQuote, QuoteCycle, queue::MemoryQueueStorage, source::FsFilterSourceManager, 11 + }; 12 + use cron_lite::CronEvent; 13 + use futures::StreamExt; 14 + use kameo::prelude::*; 15 + use tokio::time::timeout; 16 + 17 + pub async fn entrypoint() -> Result<(), Box<dyn std::error::Error>> { 18 + // TODO: Clean up this function's internals. 19 + // The current structure is alright, but it was stitched together 20 + // quickly just to confirm that everything is functioning as it should. 21 + let use_bsky = std::env::var("USE_BLUESKY").unwrap_or("0".to_string()) == "1"; 22 + let bsky = if use_bsky { 23 + Some(BskySink::spawn( 24 + BskySink::new_session( 25 + std::env::var("BLUESKY_USERNAME").expect("Bluesky username not supplied"), 26 + std::env::var("BLUESKY_PASSWORD") 27 + .expect("Bluesky application password not supplied"), 28 + ) 29 + .await 30 + .expect("Could not connect to Bluesky with supplied credentials"), 31 + )) 32 + } else { 33 + None 34 + }; 35 + 36 + let sink = { 37 + let stdout = StdoutSink::spawn(StdoutSink); 38 + SinkManager::spawn(SinkManager::new(Some(stdout), bsky)) 39 + }; 40 + 41 + let cycle = { 42 + let source = FsFilterSourceManager::spawn(FsFilterSourceManager::default()); 43 + let queue = MemoryQueueStorage::spawn(MemoryQueueStorage::new()); 44 + 45 + QuoteCycle::spawn(QuoteCycle::with_thread_rng(source, queue)) 46 + }; 47 + 48 + use cron_lite::Schedule; 49 + const POSTING_TIMEOUT: Duration = Duration::from_secs(60); 50 + const POSTING_INTERVAL: &str = "*/10 * * * * * *"; 51 + let schedule = 52 + Schedule::new(POSTING_INTERVAL).expect("Schedule should be a valid cron expression"); 53 + let now = chrono::Utc::now(); 54 + 55 + let mut tick_stream = schedule.stream(&now); 56 + 57 + while let Some(tick) = tick_stream.next().await { 58 + if let CronEvent::Missed(missed_at) = tick { 59 + eprintln!( 60 + "Missed event tick at {}. Current time: {}. Skipping post.", 61 + missed_at, 62 + chrono::Utc::now() 63 + ); 64 + continue; 65 + } 66 + 67 + // We store the code to perform the next posting iteration as one atomic future which we wrap with a timeout. 68 + // This means that, if we miss a posting window due to the timeout, we will not get multiple consecutive or late posts. 69 + let next_post_iteration = async || -> Result<(), Box<dyn std::error::Error>> { 70 + let next_quote = cycle 71 + .ask(FetchQuote) 72 + .await 73 + .map_err(|_| "fetch quote should always succeed")?; 74 + 75 + // Note: By using `tell`, we don't know when each sink's code will have completed. 76 + // If any sink uses, say, a file or stdout, that resource may well be contested between 77 + // consecutive iterations of this loop. 78 + sink.tell(PostQuote(next_quote)).await?; 79 + println!(); 80 + 81 + Ok(()) 82 + }; 83 + 84 + if let Err(e) = timeout(POSTING_TIMEOUT, next_post_iteration()).await { 85 + eprintln!( 86 + "Could not submit post in time to all sinks. Timeout error: {}", 87 + e 88 + ); 89 + } 90 + } 91 + 92 + Ok(()) 93 + } 94 + }
+1 -310
src/main.rs
··· 1 - use bsky_sdk::api::app::bsky::feed::post; 2 - use bsky_sdk::api::types::string::Datetime; 3 - use bsky_sdk::{BskyAgent, api::types::Object}; 4 - 5 - use glob::glob; 6 - use grep::{regex, searcher::sinks}; 7 - use rand::seq::SliceRandom; 8 - use redis::aio::ConnectionManagerConfig; 9 - 10 - use std::{sync::Arc, time::Duration}; 11 - use tokio_cron_scheduler::{Job, JobScheduler}; 12 - 13 - use redis::AsyncCommands; 14 - 15 - const DEFAULT_QUEUE: &str = "queue:default"; 16 - const EVENT_QUEUE: &str = "queue:event"; 17 - 18 - // See https://cron.help for what these strings mean 19 - const POSTING_INTERVAL_CRON: &str = "0 0,30 * * * *"; 20 - const POSTING_INTERVAL_DEBUG: &str = "1/10 * * * * *"; 21 - const EVENT_UPDATE_INTERVAL: &str = "55 23 * * *"; 22 - 23 - const POSTING_RETRIES: i32 = 5; 24 - 25 - fn prepare_post<I: Into<String>>(text: I) -> post::RecordData { 26 - post::RecordData { 27 - text: text.into(), 28 - created_at: Datetime::now(), 29 - embed: None, 30 - entities: None, 31 - facets: None, 32 - labels: None, 33 - langs: None, 34 - reply: None, 35 - tags: None, 36 - } 37 - } 38 - 39 - #[derive(Clone, Debug)] 40 - struct QuoteFilter { 41 - path: String, 42 - content: String, 43 - dates: Vec<String>, 44 - } 45 - 46 - impl QuoteFilter { 47 - pub async fn get_quote( 48 - &self, 49 - mut con: impl redis::aio::ConnectionLike + AsyncCommands + Clone, 50 - ) -> Result<String, ()> { 51 - // 1: Attempt to read from the event (priority) queue 52 - let event_quote: Option<String> = con.lpop(EVENT_QUEUE, None).await.ok(); 53 - if let Some(quote) = event_quote { 54 - return Ok(quote); 55 - } 56 - 57 - // 2: Otherwise, we read from the regular queue, repopulating it if it's empty 58 - self.reshuffle_quotes(con.clone(), DEFAULT_QUEUE).await?; 59 - con.lpop(DEFAULT_QUEUE, None).await.map_err(|_| ()) 60 - } 61 - 62 - async fn reshuffle_quotes( 63 - &self, 64 - mut con: impl redis::aio::ConnectionLike + AsyncCommands, 65 - output_queue: &str, 66 - ) -> Result<(), ()> { 67 - let len: u64 = con.llen(output_queue).await.map_err(|_| ())?; 68 - // NOTE: The following assumes the queue hasn't been repopulated by any other client 69 - // in-between the call to llen and the execution of the pipeline. 70 - // Hopefully won't be a problem :) 71 - if len == 0 { 72 - let mut file_contents = self.read_files(); 73 - 74 - { 75 - let mut rand = rand::rng(); 76 - file_contents.shuffle(&mut rand); 77 - } 78 - 79 - let mut pipeline = redis::pipe(); 80 - for file_contents in file_contents.into_iter() { 81 - pipeline.lpush(output_queue, file_contents.as_str()); 82 - } 83 - let _: () = pipeline.query_async(&mut con).await.map_err(|_| ())?; 84 - } 85 - 86 - Ok(()) 87 - } 88 - 89 - fn read_files(&self) -> Vec<String> { 90 - let matcher = regex::RegexMatcher::new(&self.content).unwrap(); 91 - let mut searcher = grep::searcher::Searcher::new(); 92 - let mut results = Vec::new(); 93 - 94 - for file in glob(&self.path).unwrap() { 95 - let file = match file { 96 - Ok(file) => file, 97 - Err(_) => continue, 98 - }; 99 - 100 - let mut matched = false; 101 - let sink = sinks::Lossy(|_lnum, _line| { 102 - matched = true; 103 - Ok(false) 104 - }); 105 - 106 - let search_result = searcher.search_path(&matcher, &file, sink); 107 - if !matched || search_result.is_err() { 108 - continue; 109 - } 110 - 111 - let contents = std::fs::read_to_string(file).unwrap(); 112 - results.push(contents.trim().to_string()); 113 - } 114 - 115 - results 116 - } 117 - } 118 - 119 - #[derive(Clone)] 120 - struct RedisState { 121 - con_manager: redis::aio::ConnectionManager, 122 - } 123 - 124 - impl RedisState { 125 - pub async fn new(url: String) -> Result<Self, ()> { 126 - let redis = redis::Client::open(url).map_err(|_| ())?; 127 - let config = ConnectionManagerConfig::new() 128 - .set_response_timeout(std::time::Duration::from_secs(10)) 129 - .set_number_of_retries(3); 130 - let con_manager = redis::aio::ConnectionManager::new_with_config(redis, config) 131 - .await 132 - .map_err(|_| ())?; 133 - 134 - Ok(RedisState { con_manager }) 135 - } 136 - 137 - pub async fn fetch_quote(&self, filter: &QuoteFilter) -> Result<String, ()> { 138 - loop { 139 - match filter.get_quote(self.con_manager.clone()).await { 140 - Ok(text) => return Ok(text), 141 - Err(_) => eprintln!("Error fetching quote from redis storage. Retrying..."), 142 - }; 143 - } 144 - } 145 - } 146 - 147 - #[derive(Clone)] 148 - struct BlueskyState { 149 - bsky_agent: BskyAgent, 150 - bsky_session: Object<bsky_sdk::api::com::atproto::server::create_session::OutputData>, 151 - } 152 - 153 - impl BlueskyState { 154 - pub async fn new_session(username: String, password: String) -> Result<Self, ()> { 155 - let agent = BskyAgent::builder().build().await.map_err(|_| ())?; 156 - let session = agent.login(username, password).await.map_err(|_| ())?; 157 - 158 - Ok(Self { 159 - bsky_agent: agent, 160 - bsky_session: session, 161 - }) 162 - } 163 - 164 - pub async fn submit_post(self, post: String) -> Result<(), ()> { 165 - let post = prepare_post(post.as_str()); 166 - 167 - for current_try in 0..POSTING_RETRIES { 168 - if let Err(e) = self.bsky_agent.create_record(post.clone()).await { 169 - eprintln!("Could not post quote: `{e}`"); 170 - eprintln!("Attempting to refresh login..."); 171 - 172 - if let Err(e) = self 173 - .bsky_agent 174 - .resume_session(self.bsky_session.clone()) 175 - .await 176 - { 177 - eprintln!("Failed to resume sessions due to following error: {e}") 178 - } 179 - } else { 180 - if current_try > 0 { 181 - eprintln!("Successfully posted quote on retry #{current_try}"); 182 - } 183 - return Ok(()); 184 - } 185 - } 186 - 187 - Err(()) 188 - } 189 - } 190 - 191 - #[derive(Clone)] 192 - struct State { 193 - redis: RedisState, 194 - bsky_session: Option<BlueskyState>, 195 - } 196 - 197 - impl State { 198 - pub fn redis(&self) -> &RedisState { 199 - &self.redis 200 - } 201 - 202 - pub fn bsky(&self) -> Option<&BlueskyState> { 203 - self.bsky_session.as_ref() 204 - } 205 - } 206 - 207 1 #[tokio::main] 208 2 async fn main() -> Result<(), Box<dyn std::error::Error>> { 209 - let debug_mode = std::env::var("DEBUG").unwrap_or("0".to_string()) == "1"; 210 - let use_bsky = std::env::var("USE_BLUESKY").unwrap_or("0".to_string()) == "1"; 211 - 212 - let redis_state = 213 - RedisState::new(std::env::var("REDIS_URL").unwrap_or("redis://localhost".to_string())) 214 - .await 215 - .expect("Initial redis connection failure"); 216 - let bsky_state = if use_bsky { 217 - Some( 218 - BlueskyState::new_session( 219 - std::env::var("BLUESKY_USERNAME").expect("Bluesky username not supplied"), 220 - std::env::var("BLUESKY_PASSWORD") 221 - .expect("Bluesky application password not supplied"), 222 - ) 223 - .await 224 - .expect("Could not connect to Bluesky with supplied credentials"), 225 - ) 226 - } else { 227 - None 228 - }; 229 - 230 - let app_state = Arc::new(State { 231 - redis: redis_state, 232 - bsky_session: bsky_state, 233 - }); 234 - 235 - let sched = JobScheduler::new().await?; 236 - 237 - /* 238 - let event_filter = Arc::new(QuoteFilter { 239 - content: r"\b(?i:mother|mommy|mama|mom)\b".to_string(), 240 - path: "test/**/ 241 - *.txt".to_string(), 242 - dates: vec![], 243 - }); 244 - */ 245 - 246 - let regular_filter = Arc::new(QuoteFilter { 247 - content: r".*".to_string(), 248 - path: if !debug_mode { 249 - "quotes/**/*.txt".to_string() 250 - } else { 251 - "test/**/*.txt".to_string() 252 - }, 253 - dates: vec![], 254 - }); 255 - 256 - let posting_interval = if !debug_mode { 257 - POSTING_INTERVAL_CRON 258 - } else { 259 - POSTING_INTERVAL_DEBUG 260 - }; 261 - 262 - let post_job = Job::new_async(posting_interval, move |_uuid, _| { 263 - let filter = regular_filter.clone(); 264 - let app_state = app_state.clone(); 265 - 266 - Box::pin(async move { 267 - // We try fetching a new quote from our redis storage until we succeed 268 - let text = match app_state.redis().fetch_quote(&filter).await { 269 - Ok(text) => text, 270 - Err(_) => { 271 - eprintln!("Error fetching quote from redis storage."); 272 - return; 273 - } 274 - }; 275 - 276 - if let Some(bsky) = app_state.bsky() { 277 - if let Err(_) = bsky.clone().submit_post(text).await { 278 - eprintln!("Error posting to bluesky."); 279 - return; 280 - } 281 - } else { 282 - // Let's just print the quote! 283 - println!("{}\n", text); 284 - } 285 - }) 286 - })?; 287 - 288 - // Add async job 289 - sched.add(post_job).await?; 290 - 291 - // sched 292 - // .add(Job::new_async(EVENT_UPDATE_INTERVAL, move |_uuid, _| { 293 - // let filter = event_filter.clone(); 294 - // let con = con_event_monitor.clone(); 295 - // let _agent = agent_event_monitor.clone(); // Can be used later to e.g. update profile 296 - 297 - // Box::pin(async move { 298 - // // For testing purposes, let's always upload events 299 - // reshuffle_quotes(&filter, con.clone(), EVENT_QUEUE) 300 - // .await 301 - // .unwrap(); 302 - // }) 303 - // })?) 304 - // .await?; 305 - 306 - sched 307 - .start() 308 - .await 309 - .expect("Error starting tokio scheduler. Shutting down..."); 310 - loop { 311 - tokio::time::sleep(Duration::from_secs(10)).await; 312 - } 3 + audquotes::run::entrypoint().await 313 4 }
+192
src/sink.rs
··· 1 + use crate::data::Quote; 2 + use bsky_sdk::{BskyAgent, api::types::Object}; 3 + use kameo::prelude::*; 4 + 5 + /// A newtype over [Quote] used to prompt the [SinkManager] to 6 + /// submit a new quote to all its configured sinks. 7 + #[derive(Debug, Clone)] 8 + pub struct PostQuote(pub Quote); 9 + 10 + /// Error type for internal communication between 11 + /// the [SinkManager] and its sinks. 12 + /// The error reporting performed internally does not necessarily match the 13 + /// behavior which other modules will observe. 14 + #[derive(Debug, Clone)] 15 + pub enum PostFailure { 16 + /// Indicates that a given quote could not be posted to a sink, 17 + /// but that it *may* be retried. The `reinitialize` boolean signals 18 + /// whether the sink should be reinitialized before further attempts. 19 + Retry { reinitialize: bool }, 20 + 21 + /// Indicates that a given quote could not be posted to a sink, 22 + /// as it is unsupported by it in some way (e.g. quote exceeds the sink's length limit). 23 + Unsupported, 24 + 25 + /// Indicates that a given quote could not be posted to a sink 26 + /// due to the occurrence of some unrecoverable error. 27 + /// It is thus unlikely that the sink will work in the future, even if 28 + /// reinitialized. 29 + Unrecoverable, 30 + } 31 + 32 + pub type PostResult = Result<(), PostFailure>; 33 + 34 + /// Represents internal implementation details of the interactions between 35 + /// the [SinkManager] and its sinks. 36 + pub trait QuoteSink: Actor + Message<PostQuote, Reply = PostResult> {} 37 + 38 + /// A [QuoteSink] which will output the contents of each quote 39 + /// over Stdout. Is primarily meant for testing and observing sink behavior. 40 + #[derive(Actor)] 41 + pub struct StdoutSink; 42 + 43 + impl Message<PostQuote> for StdoutSink { 44 + type Reply = PostResult; 45 + 46 + async fn handle( 47 + &mut self, 48 + PostQuote(quote): PostQuote, 49 + _ctx: &mut Context<Self, Self::Reply>, 50 + ) -> Self::Reply { 51 + println!("{}", quote.get()); 52 + Ok(()) 53 + } 54 + } 55 + 56 + /// A [QuoteSink] which will post the contents of each quote to Bluesky. 57 + #[derive(Actor)] 58 + pub struct BskySink { 59 + bsky_agent: BskyAgent, 60 + bsky_session: Object<bsky_sdk::api::com::atproto::server::create_session::OutputData>, 61 + } 62 + 63 + impl BskySink { 64 + pub async fn new_session(username: String, password: String) -> Result<Self, ()> { 65 + let agent = BskyAgent::builder().build().await.map_err(|_| ())?; 66 + let session = agent.login(username, password).await.map_err(|_| ())?; 67 + 68 + Ok(Self { 69 + bsky_agent: agent, 70 + bsky_session: session, 71 + }) 72 + } 73 + 74 + async fn submit_post(&mut self, quote: Quote) -> Result<(), ()> { 75 + let post = bsky_sdk::api::app::bsky::feed::post::RecordData { 76 + text: quote.into(), 77 + created_at: bsky_sdk::api::types::string::Datetime::now(), 78 + embed: None, 79 + entities: None, 80 + facets: None, 81 + labels: None, 82 + langs: None, 83 + reply: None, 84 + tags: None, 85 + }; 86 + 87 + if let Err(e) = self 88 + .bsky_agent 89 + .resume_session(self.bsky_session.clone()) 90 + .await 91 + { 92 + eprintln!("Failed to resume sessions due to following error: {e}"); 93 + return Err(()); 94 + } 95 + 96 + match self.bsky_agent.create_record(post.clone()).await { 97 + Ok(_) => Ok(()), 98 + Err(_) => Err(()), 99 + } 100 + } 101 + } 102 + 103 + impl Message<PostQuote> for BskySink { 104 + type Reply = PostResult; 105 + 106 + async fn handle( 107 + &mut self, 108 + PostQuote(quote): PostQuote, 109 + _ctx: &mut Context<Self, Self::Reply>, 110 + ) -> Self::Reply { 111 + match self.submit_post(quote).await { 112 + Ok(_) => Ok(()), 113 + Err(_) => Err(PostFailure::Unrecoverable), 114 + } 115 + } 116 + } 117 + 118 + /// Supervises all [QuoteSink] actors within the program, forwarding 119 + /// [PostQuote] messages to them as they are received. 120 + /// The SinkManager will attempt to reinitialize failed sinks upon 121 + /// encountering recoverable errors. 122 + #[derive(Actor)] 123 + pub struct SinkManager { 124 + // Uh oh. As the [Actor] trait is *not* dyn-compatible, 125 + // and I do not own its definition, I'm fairly certain that I cannot 126 + // do asynchronous dynamic dispatch for it here. 127 + // I've decided I'll limit this to one sink per implementation right now. 128 + stdout_sink: Option<ActorRef<StdoutSink>>, 129 + bsky_sink: Option<ActorRef<BskySink>>, 130 + // ... 131 + } 132 + 133 + impl SinkManager { 134 + pub fn new( 135 + stdout_sink: Option<ActorRef<StdoutSink>>, 136 + bsky_sink: Option<ActorRef<BskySink>>, 137 + ) -> Self { 138 + Self { 139 + stdout_sink, 140 + bsky_sink, 141 + } 142 + } 143 + } 144 + 145 + pub type SinkReplies = Vec<Result<(), ()>>; 146 + 147 + impl Message<PostQuote> for SinkManager { 148 + type Reply = SinkReplies; 149 + 150 + async fn handle( 151 + &mut self, 152 + msg: PostQuote, 153 + _ctx: &mut Context<Self, Self::Reply>, 154 + ) -> Self::Reply { 155 + use futures::future::join_all; 156 + 157 + let stdout_result = self 158 + .stdout_sink 159 + .as_ref() 160 + .map(|s| s.ask(msg.clone()).into_future()); 161 + 162 + let bsky_result = self 163 + .bsky_sink 164 + .as_ref() 165 + .map(|s| s.ask(msg.clone()).into_future()); 166 + 167 + let futures = [stdout_result, bsky_result].into_iter().flatten(); 168 + let results = join_all(futures).await; 169 + 170 + results.iter().map(|r| r.clone().or(Err(()))).collect() 171 + } 172 + } 173 + 174 + mod test { 175 + #[tokio::test] 176 + async fn stdout_sink() { 177 + use super::*; 178 + 179 + let stdout = StdoutSink::spawn(StdoutSink); 180 + let manager = SinkManager::spawn(SinkManager::new(Some(stdout), None)); 181 + 182 + let messages = ["First test!", "Second test.", "Third..."]; 183 + for msg in messages { 184 + manager.tell(PostQuote(msg.into())).await.unwrap(); 185 + tokio::time::sleep(std::time::Duration::from_secs(5)).await; 186 + } 187 + 188 + // Hopefully we don't crash...! 189 + // TODO: Sink that actually stores every quote it "posts"? 190 + // Could help in verifying everything was sent correctly. 191 + } 192 + }
+424
src/storage.rs
··· 1 + use kameo::prelude::*; 2 + 3 + use crate::storage::{ 4 + queue::{DequeueQuote, EnqueueQuotes}, 5 + source::SourceQuotes, 6 + }; 7 + 8 + use crate::data::Quote; 9 + 10 + mod rng { 11 + use rand::SeedableRng; 12 + 13 + pub struct PrngState { 14 + rng: rand::rngs::SmallRng, 15 + } 16 + 17 + impl PrngState { 18 + pub fn from_thread_rng() -> Self { 19 + Self { 20 + rng: rand::rngs::SmallRng::from_rng(&mut rand::rng()), 21 + } 22 + } 23 + 24 + pub fn from_seed(seed: u64) -> Self { 25 + Self { 26 + rng: rand::rngs::SmallRng::seed_from_u64(seed), 27 + } 28 + } 29 + 30 + pub fn shuffle_slice<T>(&mut self, slice: &mut [T]) { 31 + use rand::seq::SliceRandom; 32 + slice.shuffle(&mut self.rng); 33 + } 34 + } 35 + 36 + mod test { 37 + #[tokio::test] 38 + async fn shuffle_slice() { 39 + use super::*; 40 + 41 + let mut data = vec![1, 2, 3, 4]; 42 + let mut rng = PrngState::from_thread_rng(); 43 + 44 + rng.shuffle_slice(&mut data); 45 + println!("{:?}", data); 46 + } 47 + } 48 + } 49 + 50 + pub mod source { 51 + use super::*; 52 + 53 + // TODO: Should the quote source filters be 54 + // generic over the exact manager implementation being used? 55 + /// Message to request that a SourceManager source its quotes once again. 56 + pub struct SourceQuotes; 57 + pub type SourceReply = Result<Vec<Quote>, ()>; 58 + 59 + /// Subtrait of Actor which specifically 60 + /// denotes actors that can handle all relevant source messages. 61 + pub trait SourceManager: Actor + Message<SourceQuotes, Reply = SourceReply> {} 62 + 63 + impl<T> SourceManager for T where T: Message<SourceQuotes, Reply = SourceReply> {} 64 + 65 + /// Implementation of [`SourceManager`] which sources quotes from a Vec 66 + /// that it holds in memory, without accessing external services. 67 + /// Its main purpose is to be used for testing. 68 + #[derive(Actor)] 69 + pub struct MemorySourceManager { 70 + quotes: Vec<Quote>, 71 + } 72 + 73 + impl MemorySourceManager { 74 + pub fn new(quotes: impl IntoIterator<Item = impl Into<Quote>>) -> Self { 75 + Self { 76 + quotes: quotes.into_iter().map(Into::into).collect(), 77 + } 78 + } 79 + } 80 + 81 + impl Message<SourceQuotes> for MemorySourceManager { 82 + type Reply = SourceReply; 83 + 84 + async fn handle( 85 + &mut self, 86 + _msg: SourceQuotes, 87 + _ctx: &mut Context<Self, Self::Reply>, 88 + ) -> Self::Reply { 89 + // We just clone the quotes we've been holding onto since startup 90 + Ok(self.quotes.clone()) 91 + } 92 + } 93 + 94 + /// Uses a [QuoteFilter] to source quotes from the local filesystem 95 + /// at the beginning of each cycle. 96 + #[derive(Actor)] 97 + pub struct FsFilterSourceManager { 98 + filter: QuoteFilter, 99 + } 100 + 101 + impl FsFilterSourceManager { 102 + pub fn new(filter: QuoteFilter) -> Self { 103 + Self { filter } 104 + } 105 + } 106 + 107 + impl Default for FsFilterSourceManager { 108 + fn default() -> Self { 109 + Self { 110 + filter: QuoteFilter { 111 + content: r".*".to_string(), 112 + // TODO: Maybe make this a compile-time constant for debugging 113 + path: "test/**/*.txt".to_string(), 114 + _dates: vec![], 115 + }, 116 + } 117 + } 118 + } 119 + 120 + impl Message<SourceQuotes> for FsFilterSourceManager { 121 + type Reply = SourceReply; 122 + 123 + async fn handle( 124 + &mut self, 125 + _msg: SourceQuotes, 126 + _ctx: &mut Context<Self, Self::Reply>, 127 + ) -> Self::Reply { 128 + self.filter.read_files() 129 + } 130 + } 131 + 132 + #[derive(Clone, Debug)] 133 + pub struct QuoteFilter { 134 + path: String, 135 + content: String, 136 + _dates: Vec<String>, 137 + } 138 + 139 + impl QuoteFilter { 140 + // TODO: actually leverage async I/O 141 + fn read_files(&self) -> Result<Vec<Quote>, ()> { 142 + use glob::glob; 143 + use grep::{regex, searcher::sinks}; 144 + 145 + let matcher = regex::RegexMatcher::new(&self.content).map_err(|_| ())?; 146 + let mut searcher = grep::searcher::Searcher::new(); 147 + let mut results = Vec::new(); 148 + 149 + for file in glob(&self.path).map_err(|_| ())? { 150 + let file = match file { 151 + Ok(file) => file, 152 + Err(_) => continue, 153 + }; 154 + 155 + let mut matched = false; 156 + let sink = sinks::Lossy(|_lnum, _line| { 157 + matched = true; 158 + Ok(false) 159 + }); 160 + 161 + let search_result = searcher.search_path(&matcher, &file, sink); 162 + if !matched || search_result.is_err() { 163 + continue; 164 + } 165 + 166 + let contents = std::fs::read_to_string(file).map_err(|_| ())?; 167 + results.push(contents.trim().into()); 168 + } 169 + 170 + Ok(results) 171 + } 172 + } 173 + } 174 + 175 + pub mod queue { 176 + use std::collections::VecDeque; 177 + 178 + use super::*; 179 + 180 + // Messages to interact with the quote queue 181 + pub struct DequeueQuote; 182 + pub type DequeueReply = Result<Option<Quote>, ()>; 183 + 184 + pub struct EnqueueQuotes(pub Vec<Quote>); 185 + pub type EnqueueReply = Result<(), ()>; 186 + 187 + /// Subtrait of Actor which specifically 188 + /// denotes actors that can handle all relevant queue messages. 189 + pub trait QueueManager: 190 + Actor 191 + + Message<EnqueueQuotes, Reply = EnqueueReply> 192 + + Message<DequeueQuote, Reply = DequeueReply> 193 + { 194 + } 195 + 196 + impl<T> QueueManager for T where 197 + T: Message<EnqueueQuotes, Reply = EnqueueReply> 198 + + Message<DequeueQuote, Reply = DequeueReply> 199 + { 200 + } 201 + 202 + /// A basic implementation of an in-memory queue of quotes. 203 + /// Its contents are *not* persisted across application restarts, so it 204 + /// is only suited for testing purposes. 205 + #[derive(Actor, Default)] 206 + pub struct MemoryQueueStorage { 207 + quotes: VecDeque<Quote>, 208 + } 209 + 210 + impl MemoryQueueStorage { 211 + pub fn new() -> Self { 212 + Self { 213 + quotes: VecDeque::new(), 214 + } 215 + } 216 + } 217 + 218 + impl Message<EnqueueQuotes> for MemoryQueueStorage { 219 + /// We only need to signal success or failure in this instance, 220 + /// with no added metadata in either case. 221 + type Reply = EnqueueReply; 222 + 223 + async fn handle( 224 + &mut self, 225 + msg: EnqueueQuotes, 226 + _ctx: &mut Context<Self, Self::Reply>, 227 + ) -> Self::Reply { 228 + for q in msg.0 { 229 + self.quotes.push_back(q); 230 + } 231 + 232 + Ok(()) 233 + } 234 + } 235 + 236 + impl Message<DequeueQuote> for MemoryQueueStorage { 237 + type Reply = DequeueReply; 238 + 239 + async fn handle( 240 + &mut self, 241 + _msg: DequeueQuote, 242 + _ctx: &mut Context<Self, Self::Reply>, 243 + ) -> Self::Reply { 244 + // Note: this can never fail, since the quotes are stored in memory 245 + Ok(self.quotes.pop_front()) 246 + } 247 + } 248 + } 249 + 250 + #[derive(Actor)] 251 + pub struct QuoteCycle<S: source::SourceManager, Q: queue::QueueManager> { 252 + rng: rng::PrngState, 253 + source_manager: ActorRef<S>, 254 + queue_manager: ActorRef<Q>, 255 + } 256 + 257 + impl<S: source::SourceManager, Q: queue::QueueManager> QuoteCycle<S, Q> { 258 + pub fn new( 259 + rng: rng::PrngState, 260 + source_manager: ActorRef<S>, 261 + queue_manager: ActorRef<Q>, 262 + ) -> Self { 263 + Self { 264 + rng, 265 + source_manager, 266 + queue_manager, 267 + } 268 + } 269 + 270 + pub fn with_thread_rng(source_manager: ActorRef<S>, queue_manager: ActorRef<Q>) -> Self { 271 + Self { 272 + rng: rng::PrngState::from_thread_rng(), 273 + source_manager, 274 + queue_manager, 275 + } 276 + } 277 + } 278 + 279 + /// A message to [QuoteCycle] to fetch one more quote from its storage. 280 + pub struct FetchQuote; 281 + 282 + impl<S, Q> Message<FetchQuote> for QuoteCycle<S, Q> 283 + where 284 + S: source::SourceManager, 285 + Q: queue::QueueManager, 286 + { 287 + type Reply = Result<Quote, ()>; 288 + 289 + async fn handle( 290 + &mut self, 291 + _msg: FetchQuote, 292 + _ctx: &mut Context<Self, Self::Reply>, 293 + ) -> Self::Reply { 294 + // 1. We query our queue storage for the next quote 295 + if let Some(next_quote) = self.queue_manager.ask(DequeueQuote).await.map_err(|_| ())? { 296 + // if there is a quote, we simply return it and move on 297 + return Ok(next_quote); 298 + } 299 + 300 + // 2. Otherwise, we must repopulate the queue through our source 301 + let mut refreshed_quotes = self 302 + .source_manager 303 + .ask(SourceQuotes) 304 + .await 305 + .map_err(|_| ())?; 306 + 307 + // 3. We shuffle the newly-sourced quotes 308 + self.rng.shuffle_slice(&mut refreshed_quotes); 309 + let refreshed_quotes = refreshed_quotes; // No longer mutable 310 + 311 + // TODO: Perhaps we should assert that the new quotes are non-empty? 312 + // 4. We enqueue the newly-sourced quotes... 313 + self.queue_manager 314 + .ask(EnqueueQuotes(refreshed_quotes)) 315 + .await 316 + .map_err(|_| ())?; 317 + 318 + // 5. and, finally, we return the first among them. 319 + match self.queue_manager.ask(DequeueQuote).await { 320 + Ok(Some(q)) => Ok(q), 321 + Ok(None) => panic!("Newly-enqueued quotes should never be empty"), 322 + Err(_) => Err(()), 323 + } 324 + } 325 + } 326 + 327 + mod test { 328 + #[tokio::test] 329 + async fn memory_queue() { 330 + use super::Quote; 331 + use super::queue::*; 332 + use kameo::prelude::*; 333 + 334 + let queue_manager = MemoryQueueStorage::spawn(MemoryQueueStorage::new()); 335 + 336 + let sample_quotes = ["Test no.1", "Test no.2", "Test no.3"]; 337 + 338 + queue_manager 339 + .ask(EnqueueQuotes( 340 + sample_quotes.iter().cloned().map(Quote::from).collect(), 341 + )) 342 + .await 343 + .expect("In-memory quote queue storage should be valid for insertion"); 344 + 345 + for text in sample_quotes.iter() { 346 + assert_eq!( 347 + *text, 348 + queue_manager 349 + .ask(DequeueQuote) 350 + .await 351 + .expect("In-memory queue storage should never panic on dequeue") 352 + .expect("In-memory queue storage should never be initialized as empty") 353 + .get() 354 + ); 355 + } 356 + } 357 + 358 + #[tokio::test] 359 + async fn memory_source() { 360 + use super::source::*; 361 + use kameo::prelude::*; 362 + 363 + let sample_quotes = ["Minie", "Miney", "Moe", "and", "some", "more"]; 364 + 365 + let source_manager = MemorySourceManager::spawn(MemorySourceManager::new(sample_quotes)); 366 + 367 + let quotes = source_manager 368 + .ask(SourceQuotes) 369 + .await 370 + .expect("In-memory quote queue storage should be valid for insertion"); 371 + 372 + assert_eq!( 373 + sample_quotes.as_slice(), 374 + quotes 375 + .into_iter() 376 + // Since [Quote] doesn't implement any Equality trait, 377 + // we map the strings into quotes instead 378 + .map(String::from) 379 + .collect::<Vec<_>>() 380 + .as_slice(), 381 + ); 382 + } 383 + 384 + #[tokio::test] 385 + async fn memory_cycle() { 386 + use std::{collections::HashMap, ops::AddAssign}; 387 + 388 + use super::FetchQuote; 389 + use super::QuoteCycle; 390 + use super::queue::*; 391 + use super::source::*; 392 + use kameo::prelude::*; 393 + 394 + let sample_quotes = ["Minie", "Miney", "Moe"]; 395 + let cycle = { 396 + let source = MemorySourceManager::spawn(MemorySourceManager::new(sample_quotes)); 397 + let queue = MemoryQueueStorage::spawn(MemoryQueueStorage::new()); 398 + 399 + QuoteCycle::spawn(QuoteCycle::with_thread_rng(source, queue)) 400 + }; 401 + 402 + // We loop over `sample_quotes` twice to simulate the queue being exhausted fully, then re-sourced 403 + // Since the `cycle` manager will shuffle the quote sequence, we will verify that each 404 + // quote appears *exactly* `LOOPS` times throughout these iterations. 405 + const LOOPS: usize = 3; 406 + let mut quote_counts = HashMap::new(); 407 + for _ in 0..(sample_quotes.len() * LOOPS) { 408 + let next_quote = cycle.ask(FetchQuote).await.unwrap(); 409 + quote_counts 410 + .entry(next_quote.get().to_owned()) 411 + .or_insert(0) 412 + .add_assign(1); 413 + } 414 + 415 + let quote_counts = quote_counts; // no longer mut 416 + assert!( 417 + // Note: technically speaking, different quotes could contain equivalent strings, 418 + // which would make this test fail; a "more proper" invariant check would ensure 419 + // verify that all counts are a multiple of the amount of times `sample_quotes` was chained, 420 + // and that the sum of all counts equals the total number of times a `FetchQuote` message was sent. 421 + quote_counts.into_values().into_iter().all(|c| c == LOOPS) 422 + ); 423 + } 424 + }