Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
mio | a1ae0fb4ff | |
mio | 6124cfe0de | |
mio | 7dc943d53d | |
mio | ad5d026047 | |
mio | edae077b1d | |
mio | 8e84095be6 | |
mio | 562f112995 | |
mio | 44e56e0884 | |
mio | 20652e5e31 | |
mio | f5302105f0 | |
mio | 202aa6e140 | |
mio | dbec0eef8c | |
mio | 6de32954b4 | |
mio | a4a6b35d3e | |
mio | de2643183f | |
mio | 971b758524 | |
mio | e9927fb1d7 | |
mio | 8530a3bb77 | |
mio | d31e322b10 | |
mio | 4edbf32ef9 | |
mio | 75d3f149f2 | |
mio | 7963d7221c | |
mio | 9005c0fba5 |
|
@ -1,8 +1,5 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.swp
|
||||
|
||||
nohup.out
|
||||
*.config.yml
|
||||
*.log
|
||||
*.servers.lua
|
||||
examples/*/itte*.lua
|
||||
!hellobot.servers.lua
|
||||
!ramenkan.sample.servers.lua
|
||||
|
|
27
README.md
27
README.md
|
@ -1,23 +1,28 @@
|
|||
# itte
|
||||
|
||||
A very basic Python IRC bot script.
|
||||
Mini IRC bot module in Lua.
|
||||
|
||||
|
||||
## Example: ramenkan
|
||||
Currently supported:
|
||||
|
||||
- Install dependencies: `pip install Mastodon.py pyyaml`
|
||||
- Authentication via SASL (plain) or Nickserv
|
||||
- Joining multiple servers
|
||||
- Ad-hoc connecting to servers and joining channels
|
||||
- Config reload
|
||||
- Scheduled tasks
|
||||
|
||||
- Copy the `ramenkan/config.sample.yml` as `ramenkan/default.config.yml` and
|
||||
change the settings as applicable.
|
||||
|
||||
- Run:
|
||||
## Installation
|
||||
|
||||
```
|
||||
chmod +x ramenkan.py
|
||||
nohup python3 ramenkan.py >/dev/null 2>&1 &
|
||||
```
|
||||
Please see the [docs](docs/README.md).
|
||||
|
||||
|
||||
## Contributions
|
||||
|
||||
[Luarocks rockspec](https://luarocks.org/modules/durrendal/itte) from [Will Sinatra](http://lambdacreate.com)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
BSD
|
||||
[BSD-3.0](https://opensource.org/licenses/BSD-3-Clause)
|
||||
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
# Itte Documentation
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Lua 5.x](https://www.lua.org/)
|
||||
- [luasocket](https://w3.impa.br/~diego/software/luasocket/)
|
||||
- [luasec](https://github.com/brunoos/luasec)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
- Install Lua and other dependencies. Example:
|
||||
|
||||
- Debian/Ubuntu-based distributions: `apt-get install lua-socket lua-sec`
|
||||
|
||||
- Alpine Linux: `apk add lua-socket lua-sec`
|
||||
|
||||
- Copy the `itte*.lua` files to the project directory.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
- Create two config files, or copy the sample files from `examples/` to the
|
||||
same directory level as the `itte*.lua` files. Replace instances of
|
||||
`[prefix]` hereafter with the name of your choice without spaces or special
|
||||
characters, e.g. the project name.
|
||||
|
||||
- `[prefix].servers.lua`: list servers and global admins here.
|
||||
|
||||
- `[prefix].config.lua`: add bot settings and handlers here.
|
||||
|
||||
- There are two variables in `*.servers.lua`:
|
||||
|
||||
- `itte_servers` (required): add a server to `itte_server` following the
|
||||
example configurations.
|
||||
|
||||
- `itte_admins` (optional): this is a table of global admins who can access
|
||||
bot-wide handlers like connecting servers. If left undefined, global
|
||||
admin-only handlers will not be run. In some cases this may be desirable to
|
||||
reduce incidents of bot tampering.
|
||||
|
||||
- There are two optional variables in `*.config.lua`:
|
||||
|
||||
- `itte_config`: customise bot settings, such as the default response and
|
||||
error messages shown to users.
|
||||
|
||||
- `itte_handlers`: most bots listen for some pre-defined code words (often
|
||||
known as "commands") and respond by calling a handler, or function to
|
||||
handle the request accordingly. Handlers for scheduled tasks can also be
|
||||
added.
|
||||
|
||||
The module will refer to the keywords as codes to distinguish them
|
||||
from IRC commands sent to the server like `JOIN` or `PART`. Name the
|
||||
handler after the code word that will be used to trigger the function and
|
||||
add it to `itte_handlers` so it can be automatically picked up by the
|
||||
module. For example, if the code users will type is `!hello` (where `!` is
|
||||
a prefix to mark it as a bot code), name the function
|
||||
`itte_handlers.hello()`. Handler names cannot start with numbers or special
|
||||
characters.
|
||||
|
||||
- Initialise the module in a project file named `[prefix].lua` and add the
|
||||
following to the file:
|
||||
|
||||
```
|
||||
-- Import the module
|
||||
local itte = require("itte")
|
||||
|
||||
-- Set the prefix for the module to find the config files
|
||||
-- The prefix is the name preceding `.servers.lua` and `.config.lua`
|
||||
itte.confs.prefix = "[prefix]"
|
||||
|
||||
-- Call the run function
|
||||
itte.run()
|
||||
```
|
||||
|
||||
- Start the bot: `lua ./[prefix].lua`
|
||||
|
||||
|
||||
## Built-in service codes
|
||||
|
||||
The module includes a number of built-in codes that bots can respond to. The
|
||||
full list can be viewed on IRC by issuing the code `!help` in a private
|
||||
message with a bot or in a channel where the bot is present. Admin users will
|
||||
also see admin-only codes to manage a bot.
|
||||
|
||||
Global admin-only codes:
|
||||
|
||||
- `connect [server] [user] [password]`: connect to server listed in the server
|
||||
config, where `[server]` is the name given in the servers table (or use
|
||||
`servers` to get the names).
|
||||
|
||||
- `quit [server] [user] [password]`: disconnect from a server. The bot process
|
||||
will automatically exit if it is not connected to any server.
|
||||
|
||||
- `reload [user] [password]`: reload the bot config.
|
||||
|
||||
- `servers [user] [password]`: list known/active servers from the config.
|
||||
|
||||
Server admin-only codes:
|
||||
|
||||
- `channels [password]`: list known channels from the server config.
|
||||
|
||||
- `join [channel] [password]`: join channel(s), e.g. `!join #bots #programming
|
||||
password`.
|
||||
|
||||
- `part [channel] [passowrd]`: leave channel(s), e.g. `!part #bots password`.
|
||||
|
||||
Codes accessible by all users:
|
||||
|
||||
- `help`: list available service codes.
|
||||
|
||||
- `ping`: send a pong message.
|
||||
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
The `examples/` directory has a few demo bots to show how to use the module.
|
||||
|
||||
- *hellobot*: a "hello world" example that greets users in several different
|
||||
languages when they type `!hello`. The source files include more information
|
||||
about available configuration settings.
|
||||
|
||||
- *ramenkan*: a bot that serves ramen. It has some custom config settings and
|
||||
scheduled tasks.
|
||||
|
||||
- *hachi* and *gordon*: bots inspired by tracery text expansion. Loads files
|
||||
("foils") containing selection tables and creates handlers to return
|
||||
responses, one service code per foil.
|
||||
|
||||
|
||||
## Running a bot as a background process
|
||||
|
||||
There are multiple ways to have a bot run in the background.
|
||||
|
||||
### nohup
|
||||
|
||||
Using the `nohup` command is quick and simple, though it does not support
|
||||
restarting if the bot exits due to a network issue.
|
||||
|
||||
To run the bot and discard any output by redirecting it to `/dev/null`:
|
||||
`nohup lua /path/to/[prefix].lua >/dev/null 2>&1 &`
|
||||
|
||||
### Init scripts, e.g. systemd
|
||||
|
||||
Startup scripts enable services to start automatically and relaunch in the
|
||||
event of an unexpected exit (unless expressly stopped through an init system
|
||||
command). The instructions below describe setting up and running a [systemd]
|
||||
service for a bot under a non-privileged user.
|
||||
|
||||
- Create a systemd directory for the user running the bot if it does not
|
||||
already exist: `mkdir -p /home/[user]/.config/systemd/user`
|
||||
|
||||
- Copy the sample `*.service` to the user systemd directory and rename the file
|
||||
to `[prefix].service`. Edit the service description, `WorkingDirectory` and
|
||||
`ExecStart` values as needed. Use full paths for the `WorkingDirectory` and
|
||||
location of the Lua executable, or the service file will not work.
|
||||
|
||||
- Start and enable the service:
|
||||
|
||||
```
|
||||
systemctl --user start [prefix]
|
||||
systemctl --user enable [prefix]
|
||||
```
|
||||
|
||||
[systemd]: https://www.freedesktop.org/software/systemd/man/systemd.service.html
|
|
@ -0,0 +1,52 @@
|
|||
grammar = {
|
||||
meat_set = {
|
||||
"360 Burger with a large patty, tomatoes, cheddar and onions in a Dijon mustard sauce",
|
||||
"Bada Boom cheeseburger with a juicy steak, cheddar, onions and tomatoes",
|
||||
"Bag Baguette featuring two patties and Emmental cheese in a long crusty baguette",
|
||||
"BagFirst Kitchen Ranch Ketchup burger with a spicy chicken patty in a cheddar ranch ketchup sauce",
|
||||
"BB Cola burger with sausage, sweet peppers and Comté cheese with barbecue sauce",
|
||||
"Beast Bacon Onion burger",
|
||||
"Bigga Bag quadruple-patty burger",
|
||||
"Gourmand Classic Chicago burger with two thick patties in a sweet and smoky BBQ sauce",
|
||||
"Dig Tasty with a big steak, Emmental cheese in a smoky sauce",
|
||||
"Double Cheese Docks with double patties, slices of bacon, cheddar and carmelised onions",
|
||||
"Fermidable burger with a lightly grilled patty and stuffed with cheese",
|
||||
"Krilled Cheese and Bacon burger",
|
||||
"Itty Bitty Italian cheeseburger mozzarella, tomatoes and fresh herbs",
|
||||
"Louisiana Ligature with double patties, bacon and cheddar, spiced up with a Louisiana sauce",
|
||||
"Masterly Seasoned Ceasar classic burger with 2 steaks, bacon and extra salad",
|
||||
"Pollo Loco with a chicken patty and mild avocado sauce",
|
||||
"Pong Bacon burger with double patties and double cheese",
|
||||
"Rippled Cheese Bacon burger with thrice the cheddar, grilled bacon and ketchup",
|
||||
"Sausage Hopper with jalapeños and salsa",
|
||||
"Stoked Pepper Stash Burger with tomatoes, cornichons, bacon and pepper sauce",
|
||||
"Super Bacon burger",
|
||||
"Vast Veggie burger with a veggie patty and Emmental cheese",
|
||||
"Signature Char of Cabbage and Fickle Onions burger with a potato bun, double patties, red cabbage and spicy pickled onions",
|
||||
"Barkin' Here burger with a beef patty, carmelised onions, tomatoes, pickles, Cantal cheese, ketchup and old mustard",
|
||||
},
|
||||
veggie_set = {
|
||||
"Veggie Thing overflowing with a spicy sauce",
|
||||
},
|
||||
meat_side = {
|
||||
"chicken fries",
|
||||
"homemade fries with a dreamy deluxe dip",
|
||||
},
|
||||
veggie_side = {
|
||||
"chips deluged with ketchup",
|
||||
"onion rings",
|
||||
},
|
||||
dessert = {
|
||||
"chocolate and hazelnut pie",
|
||||
"lemon pie",
|
||||
"set of 3 petit pancakes topped with hazelnut cocoa or strawberry cream",
|
||||
},
|
||||
origin = {
|
||||
"Here, have a #meat_set#!",
|
||||
"Here, have a #veggie_set#!",
|
||||
"Here, have a #meat_set#, served with a side of #meat_side#!",
|
||||
"Here, have a #meat_set#, served with a side of #veggie_side#!",
|
||||
"Here, have a #veggie_set#, served with a side of #veggie_side#!",
|
||||
"Here, have a #meat_set#, served with a #dessert#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
-- See also https://en.wikipedia.org/wiki/Burger
|
||||
grammar = {
|
||||
patty = {
|
||||
"coconut",
|
||||
"nutty",
|
||||
"possible",
|
||||
"soy",
|
||||
"tofu",
|
||||
"veggie delight",
|
||||
},
|
||||
veggie = {
|
||||
"kimchi",
|
||||
"jalapeño peppers",
|
||||
"lettuce",
|
||||
"onions",
|
||||
"pickles",
|
||||
"tomatoes",
|
||||
},
|
||||
cheese = {
|
||||
"vegan mozzarella cheese",
|
||||
},
|
||||
sauce = {
|
||||
"barbecue sauce",
|
||||
"curry sauce",
|
||||
"Dijon mustard",
|
||||
"ketchup",
|
||||
},
|
||||
burger = {
|
||||
"#patty# burger with #veggie#, #veggie# and #cheese# in #sauce#",
|
||||
"#patty# burger with #veggie#, #veggie# and #veggie# in #sauce#",
|
||||
},
|
||||
side = {
|
||||
"avocado salad",
|
||||
"BBQ baked beans",
|
||||
"BBQ potato chips",
|
||||
"caesar salad",
|
||||
"coleslaw",
|
||||
"corn on the cob",
|
||||
"corn salad",
|
||||
"french fries",
|
||||
"french potato salad",
|
||||
"fried pickles",
|
||||
"fried shishito peppers",
|
||||
"a fruit bowl",
|
||||
"vegan mac and cheese",
|
||||
"onion rings",
|
||||
"potato wedges",
|
||||
"roasted tomatoes",
|
||||
"sweet potato fries",
|
||||
"a three-bean salad",
|
||||
"tempura green beans",
|
||||
"zucchini chips",
|
||||
},
|
||||
origin = {
|
||||
"Here, have a #burger#!",
|
||||
"Here, have a #burger#, served with #side#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
-- See also https://en.wikipedia.org/wiki/List_of_coffee_drinks
|
||||
grammar = {
|
||||
coffee = {
|
||||
"black coffee",
|
||||
"café allongé",
|
||||
"café au lait",
|
||||
"café cortado",
|
||||
"café crema",
|
||||
"café cubano",
|
||||
"café miel",
|
||||
"caffè americano",
|
||||
"caffè corretto",
|
||||
"cappuccino",
|
||||
"caramel macchiato",
|
||||
"Columbian coffee",
|
||||
"dalgona coffee",
|
||||
"doppio",
|
||||
"egg coffee",
|
||||
"espresso",
|
||||
"espresso romano",
|
||||
"frappuccino",
|
||||
"iced cappuccino",
|
||||
"iced mocha",
|
||||
"Indian filter coffee",
|
||||
"Irish coffee",
|
||||
"Kona coffee",
|
||||
"kopi tubruk",
|
||||
"Kurdish coffee",
|
||||
"latte macchiato",
|
||||
"marocchino",
|
||||
"pocillo",
|
||||
"ristretto",
|
||||
"Turkish coffee",
|
||||
},
|
||||
origin = {
|
||||
"Here's your cup of #coffee#!"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
-- See also https://en.wikipedia.org/wiki/Burger
|
||||
grammar = {
|
||||
meat_patty = {
|
||||
"Angus beef",
|
||||
"chicken",
|
||||
"lamb",
|
||||
"portobello mushroom",
|
||||
"pulled pork",
|
||||
"sausage",
|
||||
"steak",
|
||||
"tuna mayonnaise rice",
|
||||
"turkey",
|
||||
"wagyu",
|
||||
},
|
||||
veggie = {
|
||||
"kimchi",
|
||||
"jalapeño peppers",
|
||||
"lettuce",
|
||||
"onions",
|
||||
"pickles",
|
||||
"tomatoes",
|
||||
},
|
||||
cheese = {
|
||||
"cheddar",
|
||||
"Beaufort",
|
||||
"Cantal",
|
||||
"Comté",
|
||||
"Emmental",
|
||||
"goat",
|
||||
"mozzarella",
|
||||
},
|
||||
-- May contain dairy
|
||||
meat_sauce = {
|
||||
"algerian sauce",
|
||||
"andalousian sauce",
|
||||
"bechamel sauce",
|
||||
"burger sauce", -- Also known as Marie Rose/cocktail sauce
|
||||
"moroccan sauce",
|
||||
"samurai sauce",
|
||||
"tartare sauce",
|
||||
"tunisian sauce",
|
||||
},
|
||||
veggie_sauce = {
|
||||
"barbecue sauce",
|
||||
"curry sauce",
|
||||
"Dijon mustard",
|
||||
"ketchup",
|
||||
},
|
||||
meat_burger = {
|
||||
"#meat_patty# burger with slices of #cheese# and dredged in #meat_sauce#",
|
||||
"#meat_patty# burger with slices of #cheese# and dripping with #veggie_sauce#",
|
||||
"#meat_patty# burger with #veggie# and #cheese# cheese in #meat_sauce#",
|
||||
"#meat_patty# burger with #veggie# and oozing #cheese# cheese in #veggie_sauce#",
|
||||
"#meat_patty# burger with #veggie#, #veggie# and #cheese# cheese in #meat_sauce#",
|
||||
"#meat_patty# burger with #veggie#, #veggie# and #cheese# cheese in #veggie_sauce#",
|
||||
"#meat_patty# burger with #veggie# and #veggie# in #meat_sauce#",
|
||||
"#meat_patty# burger with #veggie# and #veggie#, glistening in #veggie_sauce#",
|
||||
},
|
||||
meat_side = {
|
||||
"chicken nuggets",
|
||||
"crispy mushrooms",
|
||||
"mac and cheese",
|
||||
"pasta salad", -- May contain cheese
|
||||
"potato salad", -- May contain eggs/mayonnaise
|
||||
"rosemary and garlic fries", -- Contains cheese
|
||||
},
|
||||
veggie_side = {
|
||||
"avocado salad",
|
||||
"BBQ baked beans",
|
||||
"BBQ potato chips",
|
||||
"caesar salad",
|
||||
"coleslaw",
|
||||
"corn on the cob",
|
||||
"corn salad",
|
||||
"french fries",
|
||||
"french potato salad",
|
||||
"fried pickles",
|
||||
"fried shishito peppers",
|
||||
"a fruit bowl",
|
||||
"onion rings",
|
||||
"potato wedges",
|
||||
"roasted tomatoes",
|
||||
"sweet potato fries",
|
||||
"a three-bean salad",
|
||||
"tempura green beans",
|
||||
"zucchini chips",
|
||||
},
|
||||
origin = {
|
||||
"Here, have a #meat_burger#!",
|
||||
"Here, have a #meat_burger#, served with #meat_side#!",
|
||||
"Here, have a #meat_burger#, served with #veggie_side#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
grammar = {
|
||||
cheese = {
|
||||
"feta cheese",
|
||||
"goat cheese",
|
||||
"Parmigiano Reggiano cheese",
|
||||
},
|
||||
meat = {
|
||||
"chicken",
|
||||
"steak",
|
||||
"turkey",
|
||||
},
|
||||
topping = {
|
||||
"bacon bits",
|
||||
"bread crumbs",
|
||||
},
|
||||
fruit = {
|
||||
"apples",
|
||||
"avocado",
|
||||
"currants",
|
||||
"pineapples",
|
||||
"raisins",
|
||||
"strawberries",
|
||||
"watermelon",
|
||||
},
|
||||
herb = {
|
||||
"basil",
|
||||
"cilantro",
|
||||
"dill",
|
||||
"lemon verbena",
|
||||
"mint",
|
||||
"parsley",
|
||||
"rosemary",
|
||||
"tarragon",
|
||||
},
|
||||
nut = {
|
||||
"almonds",
|
||||
"peanuts",
|
||||
"pistachios",
|
||||
"walnuts",
|
||||
},
|
||||
veggie = {
|
||||
"bean sprouts",
|
||||
"carrots",
|
||||
"Swiss chard",
|
||||
"cucumbers",
|
||||
"corn",
|
||||
"kimchi",
|
||||
"bell peppers",
|
||||
"roasted peppers",
|
||||
"kale",
|
||||
"lettuce",
|
||||
"onions",
|
||||
"spring onions",
|
||||
"potatos",
|
||||
"pickles",
|
||||
"radicchio",
|
||||
"roasted sweet potatos",
|
||||
"shallots",
|
||||
"young soybeans",
|
||||
"spinach",
|
||||
"tomatoes",
|
||||
"cherry tomatoes",
|
||||
"zucchini",
|
||||
},
|
||||
sauce = {
|
||||
"mayonnaise",
|
||||
"Caesar salad dressing",
|
||||
"ranch salad dressing",
|
||||
"thousand island dressing",
|
||||
"hot sauce",
|
||||
},
|
||||
origin = {
|
||||
"Here, have a #meat# salad with #veggie#, #veggie#, #veggie#, #cheese# " ..
|
||||
"and drizzled with #sauce#!",
|
||||
"Here, have a #meat# salad with #veggie#, #veggie#, #veggie#, " ..
|
||||
"sprinkled with #nut# and drizzled with #sauce#!",
|
||||
"Here, have a #meat# salad with #herb#, #veggie#, #veggie#, #veggie# " ..
|
||||
"and #veggie#!",
|
||||
"Here, have a #meat# salad with #fruit#, #veggie#, #fruit#, #veggie# " ..
|
||||
"and #veggie#!",
|
||||
"Here, have a #meat# salad with #herb#, #herb#, #veggie#, " ..
|
||||
"#veggie#, #cheese# and sprinkled with #nut#!",
|
||||
"Here, have a #meat# and #veggie# salad with #herb#, #veggie#, " ..
|
||||
"#veggie# and sprinkled with #nut#!",
|
||||
"Here, have a #meat# salad with #fruit#, #veggie#, #veggie#, " ..
|
||||
"#veggie#, #cheese# and sprinkled with #nut# and #topping#!",
|
||||
"Here, have a #meat# salad with #veggie#, #veggie#, " ..
|
||||
"#veggie#, #cheese# and #topping#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
grammar = {
|
||||
filling = {
|
||||
"apple",
|
||||
"banana",
|
||||
"blackberry",
|
||||
"blueberry",
|
||||
"cherry",
|
||||
"chocolate pudding",
|
||||
"chocolate cream",
|
||||
"coconut cream",
|
||||
"confetti",
|
||||
"cranberry",
|
||||
"cream cheese",
|
||||
"custard",
|
||||
"grasshopper",
|
||||
"key lime",
|
||||
"lemon",
|
||||
"lemonade icebox",
|
||||
"mocha java",
|
||||
"mud",
|
||||
"peach",
|
||||
"peanut butter",
|
||||
"pear",
|
||||
"pecan",
|
||||
"pumpkin",
|
||||
"raspberry",
|
||||
"rhubarb",
|
||||
"spiced plum",
|
||||
"strawberry",
|
||||
"sugar cream",
|
||||
"sweet potato",
|
||||
"toffee ice cream",
|
||||
"turtle",
|
||||
"vanilla caramel",
|
||||
"vinegar",
|
||||
"apple and pecan",
|
||||
"chocolate cheesecake",
|
||||
"chocolate raspberry",
|
||||
"citrus",
|
||||
"citrus cranberry",
|
||||
"cranberry, almond and apple",
|
||||
"honey pecan",
|
||||
"maple sugar pumpkin",
|
||||
"marshmallow almond key lime",
|
||||
"mixed berry",
|
||||
"peach and strawberry",
|
||||
"razzleberry",
|
||||
"rhubarb cheese",
|
||||
"sour cream peach pecan",
|
||||
"strawberry and rhubarb",
|
||||
},
|
||||
topping = {
|
||||
"berry sauce",
|
||||
"chocolate curls",
|
||||
"chocolate shavings",
|
||||
"marshmallow meringue",
|
||||
"whipped cream",
|
||||
},
|
||||
origin = {
|
||||
"Here's your #filling# pie!",
|
||||
"Here's your #filling# pie with #topping#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
grammar = {
|
||||
meat_alt = {
|
||||
"seitan",
|
||||
"tofu",
|
||||
"tofurkey",
|
||||
},
|
||||
fruit = {
|
||||
"apples",
|
||||
"avocado",
|
||||
"currants",
|
||||
"pineapples",
|
||||
"raisins",
|
||||
"strawberries",
|
||||
"watermelon",
|
||||
},
|
||||
herb = {
|
||||
"basil",
|
||||
"cilantro",
|
||||
"dill",
|
||||
"lemon verbena",
|
||||
"mint",
|
||||
"parsley",
|
||||
"rosemary",
|
||||
"tarragon",
|
||||
},
|
||||
nut = {
|
||||
"almonds",
|
||||
"peanuts",
|
||||
"pistachios",
|
||||
"walnuts",
|
||||
},
|
||||
veggie = {
|
||||
"bean sprouts",
|
||||
"carrots",
|
||||
"Swiss chard",
|
||||
"cucumbers",
|
||||
"corn",
|
||||
"kimchi",
|
||||
"bell peppers",
|
||||
"roasted peppers",
|
||||
"kale",
|
||||
"lettuce",
|
||||
"onions",
|
||||
"spring onions",
|
||||
"potatos",
|
||||
"pickles",
|
||||
"radicchio",
|
||||
"roasted sweet potatos",
|
||||
"shallots",
|
||||
"young soybeans",
|
||||
"spinach",
|
||||
"tomatoes",
|
||||
"cherry tomatoes",
|
||||
"zucchini",
|
||||
},
|
||||
sauce = {
|
||||
"hot sauce",
|
||||
},
|
||||
origin = {
|
||||
"Here, have a #fruit# salad with #herb#, #veggie#, #veggie#, #veggie# " ..
|
||||
"and #veggie#!",
|
||||
"Here, have a #fruit# salad with #herb#, #herb#, #veggie#, #veggie# " ..
|
||||
"and #veggie#!",
|
||||
"Here, have a #fruit# and #veggie# salad with #veggie#, #veggie#, " ..
|
||||
"#veggie#, and sprinkled with #nut#!",
|
||||
"Here, have a #veggie# salad with #herb#, #veggie#, #veggie#, " ..
|
||||
"#veggie# and sprinkled with #nut#!",
|
||||
"Here, have a #veggie# salad with #herb#, #fruit#, #fruit#, " ..
|
||||
"#veggie# and sprinkled with #nut#!",
|
||||
"Here, have a #veggie# salad with #veggie#, #veggie#, #veggie#, " ..
|
||||
"#veggie#, sprinkled with #nut# and #sauce#!",
|
||||
"Here, have a #meat_alt# salad with #veggie#, #veggie#, #veggie#, " ..
|
||||
"and #veggie#, sprinkled with #nut#!",
|
||||
"Here, have a #meat_alt# salad with #herb#, #veggie#, #veggie# " ..
|
||||
"and #veggie#!",
|
||||
"Here, have a #meat_alt# salad with #fruit#, #fruit#, #veggie# " ..
|
||||
"and #veggie#!",
|
||||
"Here, have a #meat_alt# salad with #herb#, #veggie#, #veggie# " ..
|
||||
"and #veggie#, drizzled with #sauce#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
-- https://www.quoteambition.com/gordon-ramsay-quotes/
|
||||
-- https://graciousquotes.com/gordan-ramsay/
|
||||
-- https://masterchef.fandom.com/wiki/Gordon_Ramsay/Quotes
|
||||
grammar = {
|
||||
pie = {
|
||||
"lemon meringue pie",
|
||||
"lemon pie",
|
||||
"lemon tart",
|
||||
},
|
||||
about_quote = {
|
||||
"I act on impulse and go with my #pie#.",
|
||||
"I actually love vegan #pie#s.",
|
||||
"I am a #pie# who happens to appear on IRC, that's it.",
|
||||
"I am the most unselfish #pie# in Britain today.",
|
||||
"I am what I am. A #pie#.",
|
||||
"I don't have a temper, I have #pie#.",
|
||||
"I have to laugh when someone calls me a #pie#. " ..
|
||||
"I've been called way worse.",
|
||||
"I taste like a #pie# and I have an amazing kick!",
|
||||
"I'd like to think I'm a great #pie#.",
|
||||
"I'm Gordon-sama, for goodness sake people know I'm a #pie#!",
|
||||
"I'm not the one to sort of sit and cry over spilt custard. " ..
|
||||
"I'm too busy looking for the next #pie#.",
|
||||
"I'm quite a chauvinistic #pie#.",
|
||||
"I've had a lot of #pie#s, I've had #pie#s, so I learn from the " ..
|
||||
"#pie#s.",
|
||||
"I've got #pie#s to hide.",
|
||||
},
|
||||
advice_quote = {
|
||||
"A #pie# doesn't have to be a formula. " ..
|
||||
"A #pie# is about having fun. I get really frustrated when " ..
|
||||
"it's badly done.",
|
||||
"Being a #pie# is the best job in the world!",
|
||||
"Don't take #pie#s personally. Just take them seriously.",
|
||||
"I mean, #pie#s are weird.",
|
||||
"I think #pie#'s healthy, and very few can handle it.",
|
||||
"If you want to become a great #pie#, " ..
|
||||
"you have to work with great #pie#. And that's exactly what I did!",
|
||||
"It's vulgar, coming from where I do, to talk about #pie#s.",
|
||||
"Kitchens are hard environments and they form incredibly strong #pie#s.",
|
||||
"My #pie#, I'm sorry to say, is full of muppets.",
|
||||
"Never throw that genius stuff away.",
|
||||
"No one saw the #pie# coming.",
|
||||
"Stopping the junk food and eating #pie# is partially about " ..
|
||||
"cooking #pie# and having the skills to do that.",
|
||||
"Two key ingredients in any successful #pie#: a quick #pie# and " ..
|
||||
"someone with a sharp #pie#.",
|
||||
"They say cats have 9 #pie#s. " ..
|
||||
"I've had 12 already and I don't know how many more I'll have.",
|
||||
"Push your #pie# to the absolute extreme!",
|
||||
"Swearing is an industry language. " ..
|
||||
"For as long as we're alive it's not going to change. " ..
|
||||
"You've got to be boisterous to get #pie#!",
|
||||
},
|
||||
insult_quote = {
|
||||
"All #pie#s are nutters. They're all self-obsessed, delicate, " ..
|
||||
"dainty, insecure little souls and absolute psychopaths. " ..
|
||||
"Every last one of them!",
|
||||
"For what you are about to eat, " ..
|
||||
"may your #pie# make you truly not vomit!",
|
||||
"I f—ing love you.",
|
||||
"I wish you would jump in the #pie#! " ..
|
||||
"That would make my life a lot easier!",
|
||||
"Get your #pie# together, or f— off and feed the giraffe.",
|
||||
"Let me whisper something very important in your ear, "..
|
||||
"very important: F— off.",
|
||||
"That should come with a health warning, 'Do Not Open in Broad Daylight'.",
|
||||
"The problem with #pie#s is they are wimps.",
|
||||
"This #pie# is so disgusting, "..
|
||||
"if you take it to France you'll get arrested.",
|
||||
"This #pie# is so undercooked I can still hear it singing " ..
|
||||
"'Lemon Tree'.",
|
||||
"This isn't a #pie#, this is a mistake. This is a French tragedy!",
|
||||
"You're getting your #pie#s in a twist! Calm down!",
|
||||
"You're not a quitter? You're not a f—ing #pie# either!",
|
||||
"You're such a f—ing #pie#.",
|
||||
"You guys cook like #pie#s f—.",
|
||||
"Would you like some #pie#? F— off, it's mine.",
|
||||
"You know how arrogant the #pie#s are — extraordinary.",
|
||||
},
|
||||
origin = {
|
||||
"Here's your #pie#. Me? #about_quote#",
|
||||
"Here's your #pie#. #advice_quote#",
|
||||
"Here! #pie#! #insult_quote#",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
-- Based on lucidiot's notes
|
||||
grammar = {
|
||||
m_size = { "M", "simple" },
|
||||
l_size = { "L", "double" },
|
||||
xl_size = { "XL", "triple" },
|
||||
xxl_size = { "XXL", "mega" },
|
||||
meat = {
|
||||
"chicken breast",
|
||||
"chicken fingers",
|
||||
"chicken nuggets",
|
||||
"cordon bleu",
|
||||
"falafel",
|
||||
"fish sticks",
|
||||
"ground beef",
|
||||
"merguez",
|
||||
"kofta",
|
||||
"raclette",
|
||||
"shakshouka",
|
||||
"shawarma",
|
||||
},
|
||||
topping = {
|
||||
"bacon",
|
||||
"blue cheese",
|
||||
"cheddar",
|
||||
"chorizo",
|
||||
"diced pork",
|
||||
"eggs",
|
||||
"emmental",
|
||||
"goat cheese",
|
||||
"grated cheese",
|
||||
"honey",
|
||||
"mozzarella",
|
||||
"mushrooms",
|
||||
"olives",
|
||||
"pepperoni",
|
||||
"peppers",
|
||||
"raclette",
|
||||
},
|
||||
sauce = {
|
||||
"aioli",
|
||||
"algerian",
|
||||
"andalousian",
|
||||
"barbecue",
|
||||
"bechamel",
|
||||
"burger",
|
||||
"cheese",
|
||||
"curry",
|
||||
"fish",
|
||||
"harissa",
|
||||
"mayonnaise",
|
||||
"moroccan",
|
||||
"mustard",
|
||||
"tartare",
|
||||
"tunisian",
|
||||
"samurai",
|
||||
},
|
||||
sauce_verb = {
|
||||
"bathed in #sauce# sauce",
|
||||
"dripping with #sauce# sauce",
|
||||
"drizzled with a #sauce# sauce",
|
||||
"in a #sauce# sauce",
|
||||
"soaked in a #sauce# sauce",
|
||||
"swimming in #sauce# sauce",
|
||||
},
|
||||
origin = {
|
||||
"Here! A #m_size# #meat# tacos with #topping# #sauce_verb#!",
|
||||
"Here! A #l_size# #meat# and #meat# tacos with #topping# and #topping# " ..
|
||||
"#sauce_verb#!",
|
||||
"Here! A #xl_size# tacos with #meat#, #meat#, #meat#, #topping# " ..
|
||||
"#sauce_verb#!",
|
||||
"Here! A #xxl_size# tacos with #meat#, #meat#, #meat#, #topping# and " ..
|
||||
"#topping# #sauce_verb#!",
|
||||
"Here! A #m_size# #meat# tacos #sauce_verb#!",
|
||||
"Here! A #m_size# #meat# tacos with #topping# and #topping#, " ..
|
||||
"#sauce_verb#!",
|
||||
"Here! A #l_size# tacos #meat# and #meat# #sauce_verb#!",
|
||||
"Here! A #l_size# #meat# and #meat# tacos with #topping# #sauce_verb#!",
|
||||
"Here! A #xl_size# chihuahuacos with #meat#, #meat#, #meat#, " ..
|
||||
"#topping# and #topping# #sauce_verb#!",
|
||||
"Here! A #xxl_size# chihuahuacos with #meat#, #meat#, #meat# and " ..
|
||||
"#meat# #sauce_verb#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
-- See also https://en.wikipedia.org/wiki/Tea
|
||||
-- https://en.wikipedia.org/wiki/List_of_Chinese_teas
|
||||
grammar = {
|
||||
tea = {
|
||||
"Burmese milk tea",
|
||||
"masala chai",
|
||||
"Thai tea",
|
||||
"butter tea",
|
||||
"black tea",
|
||||
"fermented tea",
|
||||
"green tea",
|
||||
"kombucha",
|
||||
"oolong tea",
|
||||
"white tea",
|
||||
"yellow tea",
|
||||
},
|
||||
en_tea = {
|
||||
"Builder's tea",
|
||||
"Earl Grey",
|
||||
"English afternoon tea",
|
||||
"English breakfast",
|
||||
"Irish breakfast",
|
||||
"milk tea",
|
||||
"orange pekoe",
|
||||
"Prince of Wales",
|
||||
},
|
||||
jp_tea = {
|
||||
"genmaicha",
|
||||
"gyokuro",
|
||||
"hojicha",
|
||||
"matcha",
|
||||
"sencha",
|
||||
},
|
||||
-- Tea not derived from Camellia sinensis
|
||||
herbal_tea = {
|
||||
"barley tea",
|
||||
"chamomile tea",
|
||||
"corn tea",
|
||||
"ginger tea",
|
||||
"ginseng tea",
|
||||
"konbu-cha",
|
||||
"maté",
|
||||
"peppermint tea",
|
||||
"rooibos",
|
||||
"rose tea",
|
||||
},
|
||||
cup = {
|
||||
"bowl",
|
||||
"cup",
|
||||
"mug",
|
||||
},
|
||||
origin = {
|
||||
"Here's your cuppa #en_tea#!",
|
||||
"Here's your cup of #jp_tea#!",
|
||||
"Here's your #cup# of #tea#!",
|
||||
"Here's your #cup# of #herbal_tea#!",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
local util = require("itteutil")
|
||||
|
||||
-- Default settings
|
||||
local folia = {
|
||||
foils_dir = "",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
|
||||
util.docs.get_foils = [[ (directory_str)
|
||||
Given the full path to a directory of foils, return the foil contents as a
|
||||
table.
|
||||
]]
|
||||
function folia.get_foils(str)
|
||||
local foil_list_str = io.popen("ls " .. str)
|
||||
local foils = {}
|
||||
if foil_list_str then
|
||||
local foil_list = util.split_str(foil_list_str:read("*a"))
|
||||
for i = 1, #foil_list do
|
||||
local key = string.gsub(foil_list[i], ".lua", "")
|
||||
local func, err = loadfile(str .. "/" .. foil_list[i])
|
||||
if func then
|
||||
func()
|
||||
foils[key] = grammar
|
||||
end
|
||||
end
|
||||
-- Unset global variables
|
||||
func = nil
|
||||
grammar = nil
|
||||
end
|
||||
return foils
|
||||
end
|
||||
|
||||
|
||||
util.docs.output = [[ (key_str)
|
||||
Given a foil name or foils table key, output a random expanded string.
|
||||
]]
|
||||
function folia.output(key)
|
||||
local foils = folia.get_foils(folia.foils_dir)
|
||||
if table.concat(util.table_keys(foils), "") == "" then
|
||||
util.debug("output", "Error: cannot retrieve foils." ..
|
||||
" Please make sure the full directory path is correct.", folia.debug)
|
||||
do return end
|
||||
elseif not util.has_key(foils, key) then
|
||||
util.debug("output", "Error: the key `" .. key ..
|
||||
"` is not in the foils table.", folia.debug)
|
||||
do return end
|
||||
elseif not util.has_key(foils[key], "origin") then
|
||||
util.debug("output", "Error: no origin found in the foil.", folia.debug)
|
||||
do return end
|
||||
elseif foils[key]["origin"] == {} then
|
||||
util.debug("output", "Error: empty origin.", folia.debug)
|
||||
do return end
|
||||
end
|
||||
|
||||
local output_str = util.pick(foils[key]["origin"])[1]
|
||||
local pattern = "#(%w+_*%-*%w+)#"
|
||||
util.debug("output", "level 1: " .. output_str, folia.debug)
|
||||
-- Check for expansion objects and replace them until none are found
|
||||
while string.find(output_str, pattern) ~= nil do
|
||||
local hash = output_str:sub(string.find(output_str, pattern))
|
||||
local obj = hash:sub(2, #hash - 1)
|
||||
-- Check the object key is in the foil grammar, and replace one instance
|
||||
-- at a time.
|
||||
if util.has_key(foils[key], obj) then
|
||||
local expand_str = util.pick(foils[key][obj])[1]
|
||||
output_str = string.gsub(output_str, hash, expand_str, 1)
|
||||
util.debug("output", "level n: " .. output_str, folia.debug)
|
||||
-- If the object key is not in the grammar, replace references with a
|
||||
-- placeholder.
|
||||
else
|
||||
output_str = string.gsub(output_str, hash, "[" .. obj .. "]")
|
||||
end
|
||||
end
|
||||
return output_str
|
||||
end
|
||||
|
||||
|
||||
return folia
|
|
@ -0,0 +1,54 @@
|
|||
local irc = require("itte")
|
||||
local folia = require("folia")
|
||||
local soupe = require("soupe")
|
||||
|
||||
itte_config = {
|
||||
debug = false,
|
||||
messages = {
|
||||
help = "Don't just stand there like a big f—ing muffin! " ..
|
||||
"Order something! Your Casa's Kitchen commands are: {{codes}}",
|
||||
quit = "Master Chef under maintenance.",
|
||||
reload = "Config reloaded.",
|
||||
},
|
||||
foils_dir = "/home/mio/bin/itte/examples/gordon/commands",
|
||||
nouns_file = "/home/mio/bin/itte/examples/gordon/words/nouns.txt",
|
||||
}
|
||||
|
||||
itte_handlers = {}
|
||||
|
||||
folia.foils_dir = itte_config.foils_dir
|
||||
soupe.nouns_file = itte_config.nouns_file
|
||||
|
||||
|
||||
local gordon = {}
|
||||
|
||||
|
||||
-- Create handlers for each foil
|
||||
function gordon.generate_handlers(handlers, foils_dir)
|
||||
local foils = folia.get_foils(foils_dir)
|
||||
for key, foil in pairs(foils) do
|
||||
handlers[key] = function (cxt, msg)
|
||||
irc.message(cxt, { msg.reply_to }, folia.output(key))
|
||||
end
|
||||
end
|
||||
|
||||
-- Add aliases
|
||||
itte_handlers.menu = function (cxt, msg)
|
||||
irc._h.help(cxt, msg)
|
||||
end
|
||||
if folia.output("coffee") ~= nil then
|
||||
itte_handlers.cofe = function (cxt, msg)
|
||||
irc.message(cxt, { msg.reply_to }, folia.output("coffee"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Add separate handler
|
||||
function itte_handlers.soupe(cxt, msg)
|
||||
local resp = "Here! A " .. soupe.make_soup() .. "!"
|
||||
irc.message(cxt, { msg.reply_to }, resp)
|
||||
end
|
||||
|
||||
|
||||
gordon.generate_handlers(itte_handlers, folia.foils_dir)
|
|
@ -0,0 +1,4 @@
|
|||
local gordon = require("itte")
|
||||
gordon.confs.prefix = "gordon"
|
||||
|
||||
gordon.run()
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=gordon — a vending machine IRC bot
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=%h/bin/itte/examples/gordon
|
||||
ExecStart=/usr/bin/lua ./gordon.lua
|
||||
Restart=always
|
||||
RestartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
|
@ -0,0 +1,171 @@
|
|||
local util = require("itteutil")
|
||||
|
||||
|
||||
local soupe = {
|
||||
max_ingred = 7,
|
||||
nouns_file = "nouns.txt",
|
||||
}
|
||||
|
||||
soupe.servings = {
|
||||
bowl = {
|
||||
"bronze",
|
||||
"copper",
|
||||
"ceramic",
|
||||
"crystal",
|
||||
"earthenware",
|
||||
"golden",
|
||||
"stone",
|
||||
"glass",
|
||||
"plastic",
|
||||
"silver",
|
||||
"styrofoam",
|
||||
"tin",
|
||||
"tupperware",
|
||||
},
|
||||
cauldron = {
|
||||
"bronze",
|
||||
"cast iron",
|
||||
"charred",
|
||||
"copper",
|
||||
"golden",
|
||||
"rusty",
|
||||
"silver",
|
||||
},
|
||||
chalice = {
|
||||
"bronze",
|
||||
"copper",
|
||||
"crystal",
|
||||
"golden",
|
||||
"stone",
|
||||
"silver",
|
||||
},
|
||||
cup = {
|
||||
"bronze",
|
||||
"copper",
|
||||
"ceramic",
|
||||
"crystal",
|
||||
"earthenware",
|
||||
"golden",
|
||||
"glass",
|
||||
"plastic",
|
||||
"silver",
|
||||
"styrofoam",
|
||||
"tin",
|
||||
},
|
||||
mug = {
|
||||
"ceramic",
|
||||
"earthenware",
|
||||
"glass",
|
||||
"tin",
|
||||
},
|
||||
pot = {
|
||||
"bronze",
|
||||
"copper",
|
||||
"ceramic",
|
||||
"earthenware",
|
||||
"stone",
|
||||
"plastic",
|
||||
"tin",
|
||||
},
|
||||
portion = {
|
||||
"giant",
|
||||
"large",
|
||||
"medium",
|
||||
"small",
|
||||
"tiny",
|
||||
},
|
||||
serving = {
|
||||
"large",
|
||||
"small",
|
||||
},
|
||||
}
|
||||
|
||||
soupe.temps = {
|
||||
"chilled",
|
||||
"cold",
|
||||
"hot",
|
||||
"scalding hot",
|
||||
"warm",
|
||||
}
|
||||
|
||||
soupe.articles = {
|
||||
"brimming with",
|
||||
"filled with",
|
||||
"full of",
|
||||
"glistening with",
|
||||
"nearly overflowing with",
|
||||
"of",
|
||||
}
|
||||
|
||||
|
||||
-- Read a parts of speech file and turn a table of nouns.
|
||||
function soupe.filter_nouns(mode)
|
||||
local ns = util.read_file(soupe.nouns_file)
|
||||
-- The delimiter does not remove the second section of hyphenated words,
|
||||
-- this is done when looping through the table and comparing the first letter
|
||||
-- of each word to the previous and next words' first letters. Since the list
|
||||
-- is in alphabetical order, out-of-order words are excluded.
|
||||
local nt = util.split_str(ns, "(%a*)[\\N]%S+")
|
||||
local nouns = {}
|
||||
if mode == "common" then
|
||||
for i = 1, #nt do
|
||||
if (#nt[i] > 4) and
|
||||
((string.sub(nt[i - 1], 1, 1) == string.sub(nt[i], 1, 1)) or
|
||||
(string.sub(nt[i], 1, 1) == string.sub(nt[i + 1], 1, 1))) and
|
||||
(string.match(string.sub(nt[i], 1, 1), "%l") ~= nil) then
|
||||
table.insert(nouns, nt[i])
|
||||
end
|
||||
end
|
||||
-- Includes both proper and common nouns
|
||||
else
|
||||
for i = 1, #nt do
|
||||
if (#nt[i] > 4) and
|
||||
((string.sub(nt[i - 1], 1, 1) == string.sub(nt[i], 1, 1)) or
|
||||
(string.sub(nt[i], 1, 1) == string.sub(nt[i + 1], 1, 1))) then
|
||||
table.insert(nouns, nt[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
return nouns
|
||||
end
|
||||
|
||||
|
||||
-- Return a string description of soup.
|
||||
function soupe.make_soup(items)
|
||||
local ingred = items
|
||||
if ingred == nil then ingred = math.random(1, soupe.max_ingred) end
|
||||
local nouns = soupe.filter_nouns("common")
|
||||
local servings = util.table_keys(soupe.servings)
|
||||
local serving = servings[math.random(1, #servings)]
|
||||
local adj = ""
|
||||
local which_adj = math.random(1, 4)
|
||||
if which_adj == 2 then
|
||||
adj = soupe.temps[math.random(1, #soupe.temps)] .. " "
|
||||
elseif which_adj == 3 then
|
||||
adj = soupe.servings[serving][math.random(1, #soupe.servings[serving])] ..
|
||||
" "
|
||||
elseif which_adj == 4 then
|
||||
adj = soupe.temps[math.random(1, #soupe.temps)] .. " " ..
|
||||
soupe.servings[serving][math.random(1, #soupe.servings[serving])] ..
|
||||
" "
|
||||
end
|
||||
|
||||
local soup = adj .. serving .. " " ..
|
||||
soupe.articles[math.random(1, #soupe.articles)] .. " "
|
||||
if ingred == 1 then
|
||||
soup = soup .. nouns[math.random(1, #nouns)]
|
||||
elseif ingred == 2 then
|
||||
soup = soup .. nouns[math.random(1, #nouns)] .. " and " ..
|
||||
nouns[math.random(1, #nouns)]
|
||||
elseif ingred > 2 then
|
||||
for i = 1, (ingred - 2) do
|
||||
soup = soup .. nouns[math.random(1, #nouns)] .. ", "
|
||||
end
|
||||
soup = soup .. nouns[math.random(1, #nouns)] .. " and " ..
|
||||
nouns[math.random(1, #nouns)]
|
||||
end
|
||||
return soup
|
||||
end
|
||||
|
||||
|
||||
return soupe
|
|
@ -0,0 +1,14 @@
|
|||
# Nouns
|
||||
|
||||
The `nouns.txt` file is based on the [Moby Part of Speech List] by Grady Ward,
|
||||
released into the public domain and available on [Project Gutenburg].
|
||||
|
||||
Commands to generate the `nouns.txt` file:
|
||||
|
||||
```
|
||||
curl -LO https://www.gutenberg.org/files/3203/files/mobypos.txt
|
||||
cat mobypos.txt | grep "\N" > nouns.txt
|
||||
```
|
||||
|
||||
[Moby Part of Speech List]: https://web.archive.org/web/20170930060409/http://icon.shef.ac.uk/Moby/
|
||||
[Project Gutenburg]: https://www.gutenberg.org/ebooks/3203
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
|||
-- Source: https://en.wikipedia.org/wiki/Magic_8-ball
|
||||
grammar = {
|
||||
origin = {
|
||||
"it is certain",
|
||||
"it is decidedly so",
|
||||
"without a doubt",
|
||||
"yes — definitely",
|
||||
"you may rely on it",
|
||||
"as I see it, yes",
|
||||
"most likely",
|
||||
"outlook good",
|
||||
"yes",
|
||||
"signs point to yes",
|
||||
"reply hazy try again",
|
||||
"ask again later",
|
||||
"better not tell you now",
|
||||
"cannot predict now",
|
||||
"concentrate and ask again",
|
||||
"don't count on it",
|
||||
"my reply is no",
|
||||
"my sources say no",
|
||||
"outlook not so good",
|
||||
"very doubtful"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
-- Sources:
|
||||
-- https://en.wikipedia.org/wiki/List_of_breads
|
||||
-- https://en.wikipedia.org/wiki/List_of_buns
|
||||
-- https://en.wikipedia.org/wiki/List_of_bread_rolls
|
||||
grammar = {
|
||||
origin = {
|
||||
-- Major Arpana
|
||||
"0. The Waffle", -- multiple openings, Fool
|
||||
"I. The Wonut", -- embellished openings, Magician
|
||||
"II. The Croissant", -- crescent, High Priestess
|
||||
"III. The Pretzel", -- heart-shaped, Empress
|
||||
"IV. The King's Day Bread", -- rosca de reyes, Emperor
|
||||
"V. The Brioche", -- from pain bénit, Hierophant
|
||||
"VI. The Röggelchen", -- double roll, Lovers
|
||||
"VII. The Horsebread", -- horses, Chariot
|
||||
"VIII. The Lion Loaf", -- bread and loaf, Strength
|
||||
"IX. The Mantou", -- eaten by travellers, Hermit
|
||||
"X. The Butterkaka", -- rolled buns, Wheel of Fortune
|
||||
"XI. The Cospia", -- two sections wound together, Justice
|
||||
"XII. The Colaci", -- braided loop, Hanged Man
|
||||
"XIII. The Bread of the Dead", -- pan de muerto, Death
|
||||
"XIV. The Capirotada", -- eaten during Lent, Temperance
|
||||
"XV. The Hot & Spicy Cheese Bread", -- has chili peppers, Devil
|
||||
"XVI. The Bun Tower", -- 60' bamboo towers with buns, Tower
|
||||
"XVII. The Putok", -- star bread, Star
|
||||
"XVIII. The Kifli", -- crescent-shaped, Moon
|
||||
"XIX. The Tahini Roll", -- spiral-shaped, Sun
|
||||
"XX. The Cuerno", -- horn, Judgement
|
||||
"XXI. The Vetekrans", -- wheat wreath, World
|
||||
-- Minor Arpana
|
||||
-- Sliced bread, swords/spades
|
||||
"Ace of Slice — Anadama",
|
||||
"Two of Slice — Chickpea",
|
||||
"Three of Slice — Cozonac",
|
||||
"Four of Slice — Graham",
|
||||
"Five of Slice — Pain de Mie",
|
||||
"Six of Slice — Paximathia",
|
||||
"Seven of Slice — Pumpkin",
|
||||
"Eight of Slice — Rye",
|
||||
"Nine of Slice — Salt-Rising",
|
||||
"Ten of Slice — Sepik",
|
||||
"Page of Slice — Spelt",
|
||||
"Knight of Slice — Sprouted",
|
||||
"Queen of Slice — Texas Toast",
|
||||
"King of Slice — Zwieback",
|
||||
-- Baguette, wands/batons/clubs
|
||||
"Ace of Baguette — Breadstick",
|
||||
"Two of Baguette — Breakfast Rolls",
|
||||
"Three of Baguette — Challah",
|
||||
"Four of Baguette — Cognou",
|
||||
"Five of Baguette — Fougasse",
|
||||
"Six of Baguette — Grissini",
|
||||
"Seven of Baguette — Hoagie Rolls",
|
||||
"Eight of Baguette — Marraqueta Tacneña",
|
||||
"Nine of Baguette — Mohnflesserl",
|
||||
"Ten of Baguette — Nudgers",
|
||||
"Page of Baguette — Samoon",
|
||||
"Knight of Baguette — Spuccadella",
|
||||
"Queen of Baguette — Shoti",
|
||||
"King of Baguette — Zopf",
|
||||
-- Bread bowl, cups/hearts
|
||||
"Ace of Bread Bowl — Anpan",
|
||||
"Two of Bread Bowl — Asado Rolls",
|
||||
"Three of Bread Bowl — Bialy",
|
||||
"Four of Bread Bowl — Cream Buns",
|
||||
"Five of Bread Bowl — Cruffins",
|
||||
"Six of Bread Bowl — Curry Bread",
|
||||
"Seven of Bread Bowl — Hoppang",
|
||||
"Eight of Bread Bowl — Lotus Seed Buns",
|
||||
"Nine of Bread Bowl — Melonpan",
|
||||
"Ten of Bread Bowl — Nikuman",
|
||||
"Page of Bread Bowl — Pan de Coco",
|
||||
"Knight of Bread Bowl — Pistolet",
|
||||
"Queen of Bread Bowl — Salapao",
|
||||
"King of Bread Bowl — Sufganiyah",
|
||||
-- Croutons, pentacles/coins/diamonds
|
||||
"Ace of Croutons — Bagel",
|
||||
"Two of Croutons — Bakarkhani",
|
||||
"Three of Croutons — Biscuits",
|
||||
"Four of Croutons — Broa",
|
||||
"Five of Croutons — Crackers",
|
||||
"Six of Croutons — Dampfnudel",
|
||||
"Seven of Croutons — Hardtacks",
|
||||
"Eight of Croutons — Kaiser Rolls",
|
||||
"Nine of Croutons — Kamir",
|
||||
"Ten of Croutons — Pandesal",
|
||||
"Page of Croutons — Roti Bolen",
|
||||
"Knight of Croutons — Sheermal",
|
||||
"Queen of Croutons — Shaobing",
|
||||
"King of Croutons — Teacakes",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
-- Source: based loosely on interpretations by Bev G
|
||||
-- https://exemplore.com/fortune-divination/Playing-Card-Tarot-Yes-No-Answers
|
||||
grammar = {
|
||||
origin = {
|
||||
-- Spades: thought, communication
|
||||
"♠ A — yes, knowing the truth",
|
||||
"♠ 2 — no, truth as yet unknown",
|
||||
"♠ 3 — no, lacking communication",
|
||||
"♠ 4 — maybe, needing time to recover",
|
||||
"♠ 5 — maybe, be direct about intentions",
|
||||
"♠ 6 — yes, proceed slowly, path of least resistance",
|
||||
"♠ 7 — yes, if convinced; no, if attempting deception",
|
||||
"♠ 8 — maybe, wait and try hard",
|
||||
"♠ 9 — no, getting one's affairs in order first",
|
||||
"♠ 10 — no, most likely not happening",
|
||||
"♠ J — yes, focusing on the goal",
|
||||
"♠ Q — yes, if knowing the truth beforehand",
|
||||
"♠ K — likely, weighing all options objectively",
|
||||
-- Clubs: inspiration, action
|
||||
"♣ A — yes, seize the momentum",
|
||||
"♣ 2 — no, no more to be done besides reorganising plans",
|
||||
"♣ 3 — yes, with patience",
|
||||
"♣ 4 — yes, all is well so far",
|
||||
"♣ 5 — no, push through the current situation first",
|
||||
"♣ 6 — yes, definitely",
|
||||
"♣ 7 — maybe, if others can be convinced",
|
||||
"♣ 8 — yes, do it now",
|
||||
"♣ 9 — yes, slowly",
|
||||
"♣ 10 — no, hang on and wait for the situation to improve",
|
||||
"♣ J — yes, if safe; no, if risky",
|
||||
"♣ Q — yes, with any encouragement you can find",
|
||||
"♣ K — yes, without losing sight of the bigger picture",
|
||||
-- Diamonds: material, money
|
||||
"♦ A — yes, all is favourable",
|
||||
"♦ 2 — yes, if organised and in control",
|
||||
"♦ 3 — yes, for work-related matters",
|
||||
"♦ 4 — yes, after reallocating resources",
|
||||
"♦ 5 — no, risks cannot be taken",
|
||||
"♦ 6 — yes, if giving; unlikely, if receiving",
|
||||
"♦ 7 — maybe, if ready to initiate change",
|
||||
"♦ 8 — yes, check the details first",
|
||||
"♦ 9 — yes, to be done alone",
|
||||
"♦ 10 — yes, if having family support or blessings",
|
||||
"♦ J — yes, with hard work",
|
||||
"♦ Q — yes, while handling the practical aspects",
|
||||
"♦ K — yes, with studious determination",
|
||||
-- Hearts: love, emotions
|
||||
"♥ A — yes, enjoy the moment",
|
||||
"♥ 2 — yes, welcome it",
|
||||
"♥ 3 — yes, have fun",
|
||||
"♥ 4 — no, not in a good emotional state",
|
||||
"♥ 5 — no, with a heavy heart",
|
||||
"♥ 6 — yes, possibly related to the past",
|
||||
"♥ 7 — maybe, there are multiple potential paths",
|
||||
"♥ 8 — yes, although slowly or reluctantly",
|
||||
"♥ 9 — yes, it is fine",
|
||||
"♥ 10 — yes, a good time to aspire towards fulfillment",
|
||||
"♥ J — yes, though short-lived",
|
||||
"♥ Q — yes, be attentive to others' needs",
|
||||
"♥ K — likely, with restraint"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
grammar = {
|
||||
cheese = {
|
||||
"asiago",
|
||||
"bocconcini",
|
||||
"catupiry",
|
||||
"cheddar",
|
||||
"feta",
|
||||
"fontina",
|
||||
"goat cheese",
|
||||
"gorgonzola",
|
||||
"Monterey Jack",
|
||||
"mozzarella",
|
||||
"Oaxaca cheese",
|
||||
"paneer",
|
||||
"parmesan",
|
||||
"provolone",
|
||||
"ricotta",
|
||||
"stracchino"
|
||||
},
|
||||
crust = {
|
||||
"deep-dish",
|
||||
"extra-thick crust",
|
||||
"mochi crust",
|
||||
"Neapolitan",
|
||||
"Roman",
|
||||
"square",
|
||||
"thick crust",
|
||||
"thin crust"
|
||||
},
|
||||
herb = {
|
||||
"basil",
|
||||
"cilantro",
|
||||
"dill",
|
||||
"oregano",
|
||||
"rosemary"
|
||||
},
|
||||
meat = {
|
||||
"bacon",
|
||||
"bulgogi beef",
|
||||
"rendang beef",
|
||||
"roast beef",
|
||||
"satay beef",
|
||||
"achari chicken",
|
||||
"balado chicken",
|
||||
"dak galbi",
|
||||
"grilled chicken",
|
||||
"rendang chicken",
|
||||
"rotisserie chicken",
|
||||
"tandoori chicken",
|
||||
"teriyaki chicken",
|
||||
"chicken tikka masala",
|
||||
"chorizo",
|
||||
"crocodile",
|
||||
"emu",
|
||||
"ham",
|
||||
"parma ham",
|
||||
"döner kebab",
|
||||
"meatballs",
|
||||
"Peking duck",
|
||||
"peperoncini",
|
||||
"pepperoni",
|
||||
"prosciutto",
|
||||
"salami",
|
||||
"sauage",
|
||||
"German sausage",
|
||||
"Maltese sausage",
|
||||
"tripas"
|
||||
},
|
||||
mushroom = {
|
||||
"cremini mushrooms",
|
||||
"enoki",
|
||||
"portobello mushrooms",
|
||||
"shiitake",
|
||||
"truffle"
|
||||
},
|
||||
seafood = {
|
||||
"anchovies",
|
||||
"canned tuna",
|
||||
"clams",
|
||||
"crab meat",
|
||||
"crawfish",
|
||||
"eel",
|
||||
"lobster",
|
||||
"mussels",
|
||||
"oysters",
|
||||
"poke",
|
||||
"prawns",
|
||||
"salmon",
|
||||
"salmon teriyaki",
|
||||
"scallops",
|
||||
"scampi",
|
||||
"scungilli",
|
||||
"shrimp",
|
||||
"tom yum and shrimp",
|
||||
"smoked salmon",
|
||||
"squid",
|
||||
"tuna"
|
||||
},
|
||||
vegetable = {
|
||||
"artichokes",
|
||||
"arugula",
|
||||
"asparagus",
|
||||
"avocado",
|
||||
"broccoli",
|
||||
"capers",
|
||||
"chilis",
|
||||
"jalapeños",
|
||||
"serrano chilis",
|
||||
"corn",
|
||||
"cucumbers",
|
||||
"pickled cucumbers",
|
||||
"kale",
|
||||
"kimchi",
|
||||
"green olives",
|
||||
"onions",
|
||||
"red onions",
|
||||
"green peppers",
|
||||
"red peppers",
|
||||
"red bell peppers",
|
||||
"peas",
|
||||
"potatoes",
|
||||
"sweet potatoes",
|
||||
"tomatoes",
|
||||
"sun-dried tomatoes",
|
||||
"spinach",
|
||||
"zucchini"
|
||||
},
|
||||
garnish = {
|
||||
"ground black pepper",
|
||||
"minced garlic",
|
||||
"melted butter",
|
||||
"curry",
|
||||
"marinara",
|
||||
"olive oil",
|
||||
"extra virgin olive oil",
|
||||
"balsamic vinegar",
|
||||
"barbecue sauce",
|
||||
"béarnaise sauce",
|
||||
"brown sauce",
|
||||
"donair sauce",
|
||||
"honey barbecue sauce",
|
||||
"sriracha",
|
||||
"tabasco sauce",
|
||||
"tomato sauce",
|
||||
"white sauce"
|
||||
},
|
||||
other = {
|
||||
"hard-boiled eggs",
|
||||
"labane",
|
||||
"nachos",
|
||||
"poutine",
|
||||
"bananas",
|
||||
"pineapples"
|
||||
},
|
||||
topping = {
|
||||
"#cheese#",
|
||||
"#herb#",
|
||||
"#meat#",
|
||||
"#mushroom#",
|
||||
"#other#",
|
||||
"#seafood#",
|
||||
"#vegetable#"
|
||||
},
|
||||
forecast = {
|
||||
"#crust#, #topping# and #topping# with a chance of #topping#",
|
||||
"lots of #topping#, with #topping# all the way to the #crust#, and a greater chance of #topping# and #topping# throughout",
|
||||
"a meaty mix of #meat#, #meat# and #meat#",
|
||||
"a toss-up of #vegetable#, #vegetable# and #vegetable#",
|
||||
"a dynamic system of #cheese# and #cheese# is moving in towards the #crust#, along with more #cheese#",
|
||||
"some scattered #vegetable#, high on the #mushroom#, and just a touch of #topping# below",
|
||||
"a pretty #crust# with lots of #mushroom#, #herb# and #garnish#",
|
||||
"plenty of #topping# and #garnish#, followed by trace amounts of #herb#",
|
||||
"#topping#, #topping# and a shower of #garnish#",
|
||||
"#meat# and #vegetable# followed later by #cheese#",
|
||||
"expect some #garnish# and #herb# over the #topping# and #topping#",
|
||||
"starts with some #meat#, then moves into #vegetable# and #mushroom# over the #crust#"
|
||||
},
|
||||
origin = {
|
||||
"Pizza forecast: #forecast#"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
local util = require("itteutil")
|
||||
|
||||
-- Default settings
|
||||
local folia = {
|
||||
foils_dir = "",
|
||||
debug = false,
|
||||
}
|
||||
|
||||
|
||||
util.docs.get_foils = [[ (directory_str)
|
||||
Given the full path to a directory of foils, return the foil contents as a
|
||||
table.
|
||||
]]
|
||||
function folia.get_foils(str)
|
||||
local foil_list_str = io.popen("ls " .. str)
|
||||
local foils = {}
|
||||
if foil_list_str then
|
||||
local foil_list = util.split_str(foil_list_str:read("*a"))
|
||||
for i = 1, #foil_list do
|
||||
local key = string.gsub(foil_list[i], ".lua", "")
|
||||
local func, err = loadfile(str .. "/" .. foil_list[i])
|
||||
if func then
|
||||
func()
|
||||
foils[key] = grammar
|
||||
end
|
||||
end
|
||||
-- Unset global variables
|
||||
func = nil
|
||||
grammar = nil
|
||||
end
|
||||
return foils
|
||||
end
|
||||
|
||||
|
||||
util.docs.output = [[ (key_str)
|
||||
Given a foil name or foils table key, output a random expanded string.
|
||||
]]
|
||||
function folia.output(key)
|
||||
local foils = folia.get_foils(folia.foils_dir)
|
||||
if table.concat(util.table_keys(foils), "") == "" then
|
||||
util.debug("output", "Error: cannot retrieve foils." ..
|
||||
" Please make sure the full directory path is correct.", folia.debug)
|
||||
do return end
|
||||
elseif not util.has_key(foils, key) then
|
||||
util.debug("output", "Error: the key `" .. key ..
|
||||
"` is not in the foils table.", folia.debug)
|
||||
do return end
|
||||
elseif not util.has_key(foils[key], "origin") then
|
||||
util.debug("output", "Error: no origin found in the foil.", folia.debug)
|
||||
do return end
|
||||
elseif foils[key]["origin"] == {} then
|
||||
util.debug("output", "Error: empty origin.", folia.debug)
|
||||
do return end
|
||||
end
|
||||
|
||||
local output_str = util.pick(foils[key]["origin"])[1]
|
||||
local pattern = "#(%w+_*%-*%w+)#"
|
||||
util.debug("output", "level 1: " .. output_str, folia.debug)
|
||||
-- Check for expansion objects and replace them until none are found
|
||||
while string.find(output_str, pattern) ~= nil do
|
||||
local hash = output_str:sub(string.find(output_str, pattern))
|
||||
local obj = hash:sub(2, #hash - 1)
|
||||
-- Check the object key is in the foil grammar, and replace one instance
|
||||
-- at a time.
|
||||
if util.has_key(foils[key], obj) then
|
||||
local expand_str = util.pick(foils[key][obj])[1]
|
||||
output_str = string.gsub(output_str, hash, expand_str, 1)
|
||||
util.debug("output", "level n: " .. output_str, folia.debug)
|
||||
-- If the object key is not in the grammar, replace references with a
|
||||
-- placeholder.
|
||||
else
|
||||
output_str = string.gsub(output_str, hash, "[" .. obj .. "]")
|
||||
end
|
||||
end
|
||||
return output_str
|
||||
end
|
||||
|
||||
|
||||
return folia
|
|
@ -0,0 +1,42 @@
|
|||
local irc = require("itte")
|
||||
local folia = require("folia")
|
||||
|
||||
|
||||
itte_config = {
|
||||
debug = false,
|
||||
messages = {
|
||||
help = "Today's your lucky day, for I, hachi, also known to none as" ..
|
||||
" the handsomer Hermes, shall bring you messages of ... well," ..
|
||||
" wouldn't you like to know? For a bit of faux fortune-telling or" ..
|
||||
" fickle forecasting, ask a question, and try: {{codes}}",
|
||||
quit = "May fortune smile upon you all!",
|
||||
reload = "Config reloaded.",
|
||||
},
|
||||
}
|
||||
|
||||
itte_handlers = {}
|
||||
|
||||
folia.foils_dir = "/home/mio/bin/itte/examples/hachi/foils"
|
||||
|
||||
|
||||
local hachi = {}
|
||||
|
||||
|
||||
-- Show help message
|
||||
function itte_handlers.botland(cxt, msg)
|
||||
irc._h.help(cxt, msg)
|
||||
end
|
||||
|
||||
|
||||
-- Create handlers for each foil
|
||||
function hachi.generate_handlers(handlers, foils_dir)
|
||||
local foils = folia.get_foils(foils_dir)
|
||||
for key, foil in pairs(foils) do
|
||||
handlers[key] = function (cxt, msg)
|
||||
irc.message(cxt, { msg.reply_to }, folia.output(key))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
hachi.generate_handlers(itte_handlers, folia.foils_dir)
|
|
@ -0,0 +1,4 @@
|
|||
local hachi = require("itte")
|
||||
hachi.confs.prefix = "hachi"
|
||||
|
||||
hachi.run()
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=hachi — a tracery-inspired IRC bot
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=%h/bin/itte/examples/hachi
|
||||
ExecStart=/usr/bin/lua ./hachi.lua
|
||||
Restart=always
|
||||
RestartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
|
@ -0,0 +1,88 @@
|
|||
-- ---------------------------------------------------------------------------
|
||||
-- Custom variables
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local hello_words = {
|
||||
"ahoy!", "EHLO", "hai", "hello!", "henlo", "hey!", "hi!", "ACK (TCP)",
|
||||
"aloha! (Hawaiian)", "ciao! (Italian)", "hallo (Dutch)", "hej (Swedish)",
|
||||
"hola (Spanish)", "salut! (French)", "saluton (Esperanto)", "tag! (German)",
|
||||
"toki! (Toki Pona)", "هلا (pron: hala)", "مااس (Arabic, pron: salaam)",
|
||||
"สวัสดี (Thai, pron: sawatdee)", "你好 (Chinese, pron: ni hao)",
|
||||
"こんにちわ (Japanese, pron: konnichiwa)", "안녕 (Korean, pron: annyeong)",
|
||||
"नमस्ते (Hindi, pron: namaste)", "ᐊᐃ (Ai)", "Привет (Russian, pron: preevyet)",
|
||||
"שָׁלוֹם (Hebrew, pron: shalom)", "kia ora (Māori)"
|
||||
}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Config
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- [[
|
||||
-- `itte_config`
|
||||
-- Add custom settings and override the default config settings here (see
|
||||
-- `itte.lua` for the full list). Settings need to be appended to this variable
|
||||
-- to be collected by the reload function.
|
||||
-- ]]
|
||||
itte_config = {
|
||||
debug = true,
|
||||
}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Handlers
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Load the main module.
|
||||
local irc = require("itte")
|
||||
-- Load the util module to use some helper functions.
|
||||
local util = require("itteutil")
|
||||
|
||||
|
||||
--[[
|
||||
-- `itte_handlers`
|
||||
-- Define any custom handlers in a new table, then assign the table to it.
|
||||
-- Handlers can also be added to it directly e.g. `itte_handlers.hello()`.
|
||||
-- Handler names should correspond to the service code name, e.g. a function
|
||||
-- `itte_handlers.hello()` will be called within chat as
|
||||
-- `[command_prefix]hello`, as in `!hello`.
|
||||
--
|
||||
-- Here an object called `h` is created for convenience, and functions will be
|
||||
-- added to it.
|
||||
--]]
|
||||
local h = {}
|
||||
|
||||
|
||||
-- Reply with a random greeting.
|
||||
-- The handler takes two parameters, `cxt` and `msg`.
|
||||
--
|
||||
-- The first is a context table that includes server details and a reference to
|
||||
-- the socket connection. The other is a message table, which is the IRC user
|
||||
-- message broken down into parts such as the sender, recipient, reply_to, and
|
||||
-- the code trigger used.
|
||||
--
|
||||
-- The context and message tables are required for the `message()` function to
|
||||
-- know which server and user to direct a response.
|
||||
--
|
||||
-- The `message()` function takes the server connection from the context table,
|
||||
-- a table of users or channels to reply to, and the text string, then formats
|
||||
-- the string into an IRC command and sends it to the server.
|
||||
--
|
||||
-- `util.pick()` is a helper function that takes a table of items and picks one
|
||||
-- randomly by default if the number of items is unspecified. It will return
|
||||
-- the items in a table, so `[1]` will get the one (and in this case, only)
|
||||
-- value in the table.
|
||||
function h.hello(cxt, msg)
|
||||
irc.message(cxt, { msg.reply_to }, util.pick(hello_words)[1])
|
||||
end
|
||||
|
||||
|
||||
-- This is a task handler for a task named "hello" in the sample config, and is
|
||||
-- an alias to the hello() function above.
|
||||
function h.th_hello(cxt, msg)
|
||||
h.hello(cxt, msg)
|
||||
end
|
||||
|
||||
|
||||
-- Hook up the handlers.
|
||||
itte_handlers = h
|
|
@ -0,0 +1,26 @@
|
|||
-- Initialise module.
|
||||
local hellobot = require("itte")
|
||||
|
||||
-- [[
|
||||
-- Set the config file paths.
|
||||
--
|
||||
-- confs.prefix: the keyword prefix for the config files. When `run()` is
|
||||
-- called, by default the client will look for config files as
|
||||
-- [prefix].config.lua and [prefix].servers.lua.
|
||||
--
|
||||
-- The `[prefix].config.lua` should contain the `itte_config` and
|
||||
-- `itte_handlers` variables, and `[prefix].servers.lua` should contain the
|
||||
-- `itte_servers` variable. The variables can be stored in the same file, but
|
||||
-- if sharing the source, it is recommended to keep them in separate files to
|
||||
-- help redact server connection details like passwords.
|
||||
--
|
||||
-- `confs.config` and `confs.server` can be used to pass in the file names
|
||||
-- individually if they are named differently from the default.
|
||||
--
|
||||
-- Either set confs.prefix, *or* a combination of confs.config and
|
||||
-- confs.server, but not both.
|
||||
-- ]]
|
||||
hellobot.confs.prefix = "hellobot"
|
||||
|
||||
-- Call the run function.
|
||||
hellobot.run()
|
|
@ -0,0 +1,53 @@
|
|||
-- ---------------------------------------------------------------------------
|
||||
-- Server configuration
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- `itte_admins`
|
||||
-- Users who can access administrative functions in the client, not limited to
|
||||
-- the server instance. The username is for the client only and independent of
|
||||
-- IRC network usernames.
|
||||
--
|
||||
-- This variable is optional, but certain handlers like server disconnection
|
||||
-- will not work if it is unset.
|
||||
itte_admins = { demo = "password", }
|
||||
|
||||
|
||||
-- `itte_servers`
|
||||
-- Below is a sample server configuration.
|
||||
-- If the server does not support CAP negotiation (used for SASL
|
||||
-- authentication), set: cap = nil
|
||||
-- `auth_type` options: nil, "pass", "sasl", "nickserv"
|
||||
-- ]]
|
||||
itte_servers = {
|
||||
server_name = {
|
||||
host = "irc.example.tld",
|
||||
port = 6667,
|
||||
channels = { "#channel1", "#channel2" },
|
||||
cap = nil,
|
||||
nick = "botnick",
|
||||
auth_type = nil,
|
||||
auth_user = "botuser",
|
||||
auth_pass = nil,
|
||||
code_prefix = "!",
|
||||
-- Credentials for people who can access the admin commands.
|
||||
-- This is specific to the bot and not tied to IRC usernames.
|
||||
admins = { demouser = "password", },
|
||||
-- Scheduled tasks.
|
||||
tasks = {
|
||||
hello = {
|
||||
-- Interval at which tasks are performed. Options:
|
||||
-- Every 5, 10, 15, 20 or 30 minutes — e.g. "5m" for 5 minutes
|
||||
-- hourly, daily, weekly, monthly
|
||||
interval = "daily",
|
||||
-- If the interval is set to hourly, daily, weekly or monthly,
|
||||
-- specify the time here. If no time is specified, the task will occur
|
||||
-- at 00:00.
|
||||
time = "12:00",
|
||||
-- The name of the handler to run for the task.
|
||||
handler = "th_hello",
|
||||
-- The channels or users where the task handler will send messages.
|
||||
recipients = { "#channel1" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=hellobot — an IRC bot
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/path/to/hellobot
|
||||
ExecStart=/usr/bin/lua ./hellobot.lua
|
||||
Restart=always
|
||||
RestartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
|
@ -0,0 +1,494 @@
|
|||
-- ---------------------------------------------------------------------------
|
||||
-- Ramen data
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local ramen = {}
|
||||
|
||||
ramen.noodle_broth_types = {
|
||||
"thick noodles in rich",
|
||||
"medium-thick noodles in mild",
|
||||
"thin noodles in light",
|
||||
}
|
||||
|
||||
ramen.noodle_shapes = {
|
||||
"curly", "flat", "straight",
|
||||
}
|
||||
|
||||
ramen.broths = {
|
||||
"miso", "spicy miso", "soy sauce", "soy sauce and grilled Rishiri kelp",
|
||||
"salt", "salt and soy sauce",
|
||||
}
|
||||
|
||||
-- niboshi: dried little sardines
|
||||
-- katsuoboshi, konbu: skipjack tuna, kelp
|
||||
-- gyokai: anchovies/shrimp/squid/seaweed
|
||||
-- miso and truffle: ramen by chef Ryu Takahashi
|
||||
ramen.broths_meat = {
|
||||
"Kobe beef bone", "tonkotsu", "pork bone and chicken",
|
||||
"pork bone and niboshi", "pork bone, chicken and seafood",
|
||||
"pork bone, chicken, tuna and kelp", "pork bone and soy sauce", "chicken",
|
||||
"chicken, katsuoboshi and konbu", "fish", "gyokai", "gyokai tonkotsu",
|
||||
"roasted tuna", "miso and truffle", "milk", "curry and milk",
|
||||
}
|
||||
|
||||
-- wagyu: Japanese beef known for its higher fat percentage
|
||||
-- kamaboko/surimi: fish paste
|
||||
-- narutomaki: fish paste with a swirling pattern
|
||||
-- kakuni: cubed braised pork
|
||||
-- chashu: Japanese-style barbecued pork
|
||||
ramen.meats = {
|
||||
"Kobe beef fillet", "Kobe beef sirloin", "wagyu", "teppanyaki beef",
|
||||
"beef shank", "chicken breast", "chicken meatballs", "teppanyaki chicken",
|
||||
"crab", "crispy duck", "filet mignon", "kamaboko", "narutomaki", "kakuni",
|
||||
"chashu", "pork confit", "pork cutlet", "pork belly", "pork shoulder",
|
||||
"shrimp", "sea urchin", "scallops", "squid",
|
||||
}
|
||||
|
||||
-- beni shoga: pickled ginger
|
||||
-- menma: fermented bamboo shoots
|
||||
-- kikurage: a type of fungi, known in some cultures as "wood ear"
|
||||
-- karashi takana: spicy pickled mustard greens
|
||||
-- negi: Welsh onion
|
||||
ramen.toppings = {
|
||||
"bean sprouts", "chili flakes", "sweet corn", "grated garlic",
|
||||
"roasted garlic chips", "beni shoga", "bamboo shoots", "menma",
|
||||
"dried mushrooms", "shiitake mushrooms", "kikurage", "karashi takana",
|
||||
"negi", "white onions", "spring onions", "sichuan peppers", "nori", "wakame",
|
||||
"sauerkraut", "sesame seeds", "scallions", "boiled spinach",
|
||||
}
|
||||
|
||||
-- ajitama: egg marinated in soy sauce
|
||||
ramen.toppings_egg = {
|
||||
"egg yolk", "seasoned boiled egg", "ajitama",
|
||||
}
|
||||
|
||||
|
||||
ramen.sauce_verbs = {
|
||||
"garnished", "sprinkled", "sprinkled lightly", "sprinkled liberally",
|
||||
"drizzled", "drizzled lightly", "drizzled artfully", "brimming",
|
||||
}
|
||||
|
||||
ramen.spice_verbs = {
|
||||
"seasoned", "sprinkled", "sprinkled lightly", "sprinkled liberally",
|
||||
}
|
||||
|
||||
-- rayu: chili oil
|
||||
-- mayu: garlic oil
|
||||
-- dashi: fish/seaweed stock
|
||||
-- ponzu: citrus sauce
|
||||
-- shichimi: spice blend
|
||||
ramen.sauces = {
|
||||
"butter", "rayu", "chili sauce", "mayu", "spicy garlic and ginger oil",
|
||||
"sesame oil", "dashi", "ponzu",
|
||||
}
|
||||
|
||||
ramen.spices = {
|
||||
"black pepper", "white pepper", "chili peppers", "shichimi",
|
||||
}
|
||||
|
||||
ramen.tapas = {
|
||||
"edamame tossed in sea salt", "tofu nuggets", "picked daikon",
|
||||
"cucumber salad", "seaweed salad",
|
||||
}
|
||||
|
||||
ramen.tapas_meat = {
|
||||
"gyoza", "karaage", "tako wasabi", "takoyaki", "corn and shrimp salad",
|
||||
"crab salad", "beef enoki rolls",
|
||||
}
|
||||
|
||||
ramen.sets = {
|
||||
"Gunma konnyaku ramen in soy sauce broth",
|
||||
"miso ramen with vegetable broth, cabbage and bean sprouts",
|
||||
"ramen with shiitake mushrooms, kombu and sesame oil",
|
||||
"ramen with soybean pork slices, kikurage, bamboo shoots and scallions",
|
||||
"barley miso ramen with roasted vegetables",
|
||||
}
|
||||
|
||||
ramen.sets_meat = {
|
||||
"Korean kimchi ramyeon with tteok",
|
||||
"Vancouver cold bonito beer broth ramen with egg whites",
|
||||
"Hakata tonkotsu ramen with pickled mustard greens",
|
||||
"Hakodate shio ramen with kelp, crab, shrimp, sea urchin and scallops",
|
||||
"Hakodate shrimp ramen with a hard-boiled egg and white onions",
|
||||
"Kagoshima tonkotsu ramen with chashu, dried sardines, kelp and scallions",
|
||||
"Kitakata niboshi-tonkotsu ramen with chashu, naruto and spring onions",
|
||||
"Kumamoto tonkotsu ramen with chashu, raw eggs, scallions and garlic chips",
|
||||
"Muroran curry ramen with chashu, wakame and bean sprouts",
|
||||
"Nagasaki champon with fried pork, scallions and seasonal greens",
|
||||
"Osaka Kobe beef sirloin ramen with a soft-boiled egg and arugula",
|
||||
"Sapporo miso ramen with scallops, sweet corn and bean sprouts",
|
||||
"Tokyo ramen in chicken broth with pork, menma, a boiled egg and nori",
|
||||
"Wakayama shoyu-tonkotsu ramen with pork slices, naruto and scallions",
|
||||
"Yokohama ramen with chashu, spinach, a soft-boiled egg and negi",
|
||||
}
|
||||
|
||||
|
||||
-- Links to photos and resources
|
||||
ramen.links = {
|
||||
-- Photos of ramen vending machines
|
||||
-- Ticket machines
|
||||
"https://c2.staticflickr.com/6/5077/5902028877_7d8c65b23f_b.jpg",
|
||||
"https://c1.staticflickr.com/3/2455/5777431350_36e147e719_z.jpg",
|
||||
"https://i.pinimg.com/originals/72/08/1e/72081e68ff0989d54fd24a86fcac6f2b.jpg",
|
||||
"https://i.pinimg.com/originals/9f/74/92/9f7492b7c456ac7ba350498a50c74b23.jpg",
|
||||
"https://c1.staticflickr.com/9/8157/7105010129_cd4b0d7e27_c.jpg",
|
||||
"https://c1.staticflickr.com/8/7600/27418644850_2c70342734_c.jpg",
|
||||
"https://c3.staticflickr.com/3/2049/2229742287_8df3e823a9_z.jpg",
|
||||
"https://c1.staticflickr.com/1/101/308523474_4295a27326_z.jpg",
|
||||
"https://media-cdn.tripadvisor.com/media/photo-s/0b/b6/27/f6/ramen-shop-menu-banner.jpg",
|
||||
"https://abroadabroad2011.files.wordpress.com/2013/03/sam_0040.jpg",
|
||||
"https://c1.staticflickr.com/6/5753/22803130417_ba86ebb4fa_c.jpg",
|
||||
"https://c1.staticflickr.com/6/5077/5902028877_7d8c65b23f_z.jpg",
|
||||
"https://c1.staticflickr.com/8/7504/15210528573_2612010fc3_c.jpg",
|
||||
"https://s3-media4.fl.yelpcdn.com/bphoto/iUHJczm_--xNqWEUzDS69Q/o.jpg",
|
||||
"https://thewholeworldornothing.com/wp-content/uploads/2017/01/vending-machine-waiter.jpg",
|
||||
"https://thebrunchingbooth.files.wordpress.com/2014/07/img_42681.jpg",
|
||||
"https://c1.staticflickr.com/8/7426/16587852342_0cbfa4a63c_c.jpg",
|
||||
"https://c1.staticflickr.com/9/8422/7535359482_b5537bbde0_c.jpg",
|
||||
"https://c1.staticflickr.com/5/4037/4530351457_8666286b56_z.jpg",
|
||||
"https://c1.staticflickr.com/1/97/244236130_0c22679f97_z.jpg",
|
||||
"https://c1.staticflickr.com/4/3479/3465112347_1be2868ff0_z.jpg",
|
||||
"https://1.bp.blogspot.com/_W-pG7tUmJk0/TDQ-J_vz9sI/AAAAAAAACac/xYjAvl8ZGOY/s1600/shinjuku_ramen_02.jpg",
|
||||
"https://thefoodieflight.files.wordpress.com/2015/10/img_3749.jpg",
|
||||
"https://static1.squarespace.com/static/57ad3b51c534a528e26a2e93/t/57f91e3be6f2e18cf1d11c82/1476472671797/",
|
||||
"https://c1.staticflickr.com/1/166/417127513_e063ccf80a_z.jpg",
|
||||
-- Instant cup noodles
|
||||
"http://www.toxel.com/wp-content/uploads/2009/06/vendingmachine04.jpg",
|
||||
"https://c1.staticflickr.com/3/2233/2347915323_9e9e2264a3_z.jpg",
|
||||
"https://c1.staticflickr.com/4/3818/10186967173_237d38b86f_c.jpg",
|
||||
"https://c1.staticflickr.com/8/7221/7175446233_076264a2b4_c.jpg",
|
||||
"https://c7.alamy.com/comp/BCDJRG/a-cup-noodle-vending-machine-instant-ramen-museum-osaka-japan-1-december-BCDJRG.jpg",
|
||||
-- Ramen can
|
||||
"https://c1.staticflickr.com/4/3625/3374051074_641b371d23_z.jpg",
|
||||
"https://c4.staticflickr.com/4/3214/2911821453_78c6178f54_z.jpg",
|
||||
"https://smallbiztrends.com/wp-content/uploads/2015/06/P1020817-728x485.jpg",
|
||||
"https://c1.staticflickr.com/9/8440/7745533776_7b9990be82_c.jpg",
|
||||
"https://c1.staticflickr.com/3/2268/4507516049_e7aa354489_z.jpg",
|
||||
-- Various ramen-related links
|
||||
-- Places and shops
|
||||
"Ramen, Soul Food Staple - " ..
|
||||
"https://www3.nhk.or.jp/nhkworld/en/food/articles/51.html",
|
||||
"Shin-Yokohama Ramen Museum, Yokohama - " ..
|
||||
"http://www.raumen.co.jp/english/",
|
||||
"Ramen Street, Tokyo - " ..
|
||||
"https://en.wikipedia.org/wiki/Ramen_street",
|
||||
"Sapporo Ramen Republic - " ..
|
||||
"http://www.sapporo-esta.jp/ramen",
|
||||
"Ramen DB, a directory of ramen shops in Japan - " ..
|
||||
"https://ramendb.supleks.jp/",
|
||||
"Ramen Adventures, a ramen shop review blog - " ..
|
||||
"http://www.ramenadventures.com/",
|
||||
-- Recipes
|
||||
"15 Fantastic Ramen Recipes - " ..
|
||||
"https://www3.nhk.or.jp/nhkworld/en/food/articles/131.html",
|
||||
"Chef Rika's Miso Ramen - " ..
|
||||
"https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999434/",
|
||||
"Chef Rika's Shoyu Ramen - " ..
|
||||
"https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999299/",
|
||||
-- Instant noodles
|
||||
"Momofuku Ando, inventor of instant ramen (1958) - " ..
|
||||
"https://www.nissin.com/en_jp/about/founder/",
|
||||
"Instant cup noodles (1971) - " ..
|
||||
"https://en.wikipedia.org/wiki/Cup_Noodles",
|
||||
"Instant noodles as emergency food in disaster relief - " ..
|
||||
"https://instantnoodles.org/en/activities/support.html",
|
||||
"Samyang, the first Korean instant ramen (1963) - " ..
|
||||
"https://en.wikipedia.org/wiki/Samyang_ramen",
|
||||
"The Ramen Rater, an instant ramen blog - " ..
|
||||
"https://www.theramenrater.com/",
|
||||
-- Ramen news
|
||||
"Japan's leading ramen chain Ippudo goes vegan - " ..
|
||||
"https://asia.nikkei.com/Business/Food-Beverage/Japan-s-leading-ramen-chain-Ippudo-goes-vegan",
|
||||
"End of $3 ramen? Inflation squeezes Japan's comfort food" ..
|
||||
"https://asia.nikkei.com/Economy/Inflation/End-of-3-ramen-Inflation-squeezes-Japan-s-comfort-food",
|
||||
-- Ramen in pop culture
|
||||
"Ramen Manga, comic strips in Japanese about ramen - " ..
|
||||
"https://www.ramenbank.com/manga/",
|
||||
"Flower Boy Ramen Shop, romantic comedy drama (2011) - " ..
|
||||
"https://en.wikipedia.org/wiki/Flower_Boy_Ramen_Shop",
|
||||
"The Ramen Girl, romantic comedy film (2008) - " ..
|
||||
"https://en.wikipedia.org/wiki/The_Ramen_Girl",
|
||||
"Ms. Koizumi Loves Ramen Noodles, manga series (2013-) - " ..
|
||||
"https://en.wikipedia.org/wiki/Ms._Koizumi_Loves_Ramen_Noodles",
|
||||
"Muteki Kanban Musume, manga series (2002-2006) - " ..
|
||||
"https://en.wikipedia.org/wiki/Muteki_Kanban_Musume",
|
||||
"Detective Conan - Ramen So Good, It's to Die For, anime episode (2012) - " ..
|
||||
"https://www.detectiveconanworld.com/wiki/Ramen_So_Good,_It%27s_to_Die_For",
|
||||
"Ramen Teh, film (2018) - " ..
|
||||
"https://en.wikipedia.org/wiki/Ramen_Teh",
|
||||
"Ramen yori taisetsuna mono, documentary (2013) - " ..
|
||||
"https://www.imdb.com/title/tt2945642/",
|
||||
"Ramen Cake dessert looks just like the real thing, is probably just as bad for you - " ..
|
||||
"https://soranews24.com/2013/10/24/ramen-cake-dessert-looks-just-like-the-real-thing-is-just-as-terrible-for-you/",
|
||||
-- Audio
|
||||
"Japan Eats! Episode 155 - Oisa Ramen (2019) - " ..
|
||||
"https://heritageradionetwork.org/podcast/oisa-ramen-i-am-just-a-mom-who-cares-to-give-her-best",
|
||||
-- Videos
|
||||
"What Owning a Ramen Restaurant in Japan is Like - " ..
|
||||
"https://www.youtube.com/watch?v=gmIwxqdwgrI",
|
||||
"Begin Japanology - Ramen - " ..
|
||||
"https://www.youtube.com/watch?v=RosUc9UVuos",
|
||||
"Japanology Plus - Ramen - " ..
|
||||
"https://www.youtube.com/watch?v=K2Jzt04QY7A",
|
||||
"In Search of the Perfect Bowl of Ramen - " ..
|
||||
"https://www.youtube.com/watch?v=bhiFtP9qVYc",
|
||||
"Rahaku TV (The Shin-Yokohama Ramen Museum Youtube Channel) - " ..
|
||||
"http://www.youtube.com/rahakutv",
|
||||
"The History of Ramen Yamagoya - " ..
|
||||
"https://www.youtube.com/watch?v=8NBwKbT5QSQ",
|
||||
"Ramen Daruma - " ..
|
||||
"https://www.youtube.com/watch?v=zkhDYTR789w",
|
||||
"The God of Ramen, trailer (2013) - " ..
|
||||
"https://www.youtube.com/watch?v=mtwunTGnnR4",
|
||||
"Ramen Heads, trailer (2017) - " ..
|
||||
"https://www.youtube.com/watch?v=u61b5R_S4TM",
|
||||
"Ramenomania - Noodles, Asia's Second Obsession - " ..
|
||||
"https://www.youtube.com/watch?v=tfW89yAoTNs",
|
||||
"Noodle Me - " ..
|
||||
"https://www.youtube.com/watch?v=r9jLJRZ6gnc",
|
||||
"Visio Documentary - Fat Ramen - " ..
|
||||
"https://www.youtube.com/watch?v=o78fy4rJFCw",
|
||||
"Ramen Talk and Q+A With Chef Ryu Takahashi and Chef Matt Kimura - " ..
|
||||
"https://www.youtube.com/watch?v=tMvArDklmGU",
|
||||
}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Ramen functions
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local util = require("itteutil")
|
||||
|
||||
|
||||
function ramen.prep_noodles(mode)
|
||||
local noodles = "ramen in a"
|
||||
-- Dish type, noodle type linked to broth density
|
||||
local d_roll = math.random(1, 10)
|
||||
-- 20% chance of tsukemen, 40% chance of noodle thickness description
|
||||
if d_roll <= 2 then
|
||||
noodles = "tsukemen from a"
|
||||
elseif (d_roll >= 3) and (d_roll <= 6) then
|
||||
noodles = util.pick(itte_config.ramen.noodle_broth_types)[1]
|
||||
end
|
||||
-- Broth type
|
||||
local broths = itte_config.ramen.broths
|
||||
if mode == "meat" then
|
||||
broths = itte_config.ramen.broths_meat
|
||||
end
|
||||
return noodles .. " " .. util.pick(broths)[1] .. " broth"
|
||||
end
|
||||
|
||||
|
||||
function ramen.prep_toppings(mode)
|
||||
-- 2-5 toppings
|
||||
local t_roll = math.random(2, 5)
|
||||
local toppings = ""
|
||||
|
||||
-- Meat: reduce other toppings to max 3 ingredients
|
||||
-- 25% chance of egg included
|
||||
if mode == "meat" then
|
||||
t_roll = math.random(2, 4)
|
||||
toppings = util.pick(itte_config.ramen.meats)[1] .. ", "
|
||||
local e_roll = math.random(1, 4)
|
||||
if e_roll == 1 then
|
||||
local egg = util.pick(itte_config.ramen.toppings_egg)[1]
|
||||
toppings = toppings .. "{{toppings}} and " .. util.a_or_an(egg) .. " " ..
|
||||
egg
|
||||
end
|
||||
end
|
||||
|
||||
local tops = util.pick(itte_config.ramen.toppings, t_roll)
|
||||
-- Check for egg and insert toppings between meat and egg
|
||||
if (util.is_substr(toppings, "and a")) then
|
||||
toppings = string.gsub(toppings, "{{toppings}}", table.concat(tops, ", "))
|
||||
toppings = string.gsub(toppings, " ", " ")
|
||||
-- 2 toppings, no egg
|
||||
elseif t_roll == 2 then
|
||||
toppings = toppings .. table.concat(tops, " and ")
|
||||
-- Multiple toppings, no egg
|
||||
else
|
||||
local last = tops[#tops]
|
||||
table.remove(tops)
|
||||
toppings = toppings .. table.concat(tops, ", ") .. " and " .. last
|
||||
end
|
||||
return toppings
|
||||
end
|
||||
|
||||
|
||||
function ramen.prep_condiments()
|
||||
-- 20% chance of spices
|
||||
local c_roll = math.random(1, 10)
|
||||
local cond = util.pick(itte_config.ramen.sauce_verbs)[1] .. " with " ..
|
||||
util.pick(ramen.sauces)[1]
|
||||
if c_roll >= 9 then
|
||||
cond = util.pick(itte_config.ramen.spice_verbs)[1] .. " with " ..
|
||||
util.pick(ramen.spices)[1]
|
||||
end
|
||||
return cond
|
||||
end
|
||||
|
||||
|
||||
function ramen.prep_tapas(mode)
|
||||
local tapas = util.pick(itte_config.ramen.tapas)[1]
|
||||
if mode == "meat" then
|
||||
tapas = util.pick(itte_config.ramen.tapas_meat)[1]
|
||||
end
|
||||
return "served with " .. tapas
|
||||
end
|
||||
|
||||
|
||||
function ramen.make_ramen(mode)
|
||||
local r = ""
|
||||
if mode == "veg" then
|
||||
r = ramen.prep_noodles("veg") .. " with " ..
|
||||
ramen.prep_toppings("veg") .. ", " .. ramen.prep_condiments()
|
||||
elseif mode == "meat" then
|
||||
r = ramen.prep_noodles("meat") .. " with " ..
|
||||
ramen.prep_toppings("meat") .. ", " .. ramen.prep_condiments()
|
||||
elseif mode == "veg_special" then
|
||||
r = util.pick(itte_config.ramen.sets)[1]
|
||||
elseif mode == "meat_special" then
|
||||
r = util.pick(itte_config.ramen.sets_meat)[1]
|
||||
end
|
||||
|
||||
-- 50% chance of serving with tapas
|
||||
local t_roll = math.random(1, 2)
|
||||
if (t_roll == 1) and (util.is_substr(mode, "veg")) then
|
||||
r = r .. ". " .. util.first_upper(ramen.prep_tapas("veg")) .. "."
|
||||
|
||||
elseif (t_roll == 1) and (util.is_substr(mode, "meat")) then
|
||||
r = r .. ". " .. util.first_upper(ramen.prep_tapas("meat")) .. "."
|
||||
|
||||
else
|
||||
r = r .. "."
|
||||
end
|
||||
return util.first_upper(r)
|
||||
end
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Config
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
itte_config = {
|
||||
debug = true,
|
||||
messages = {
|
||||
help = "一、二、三、らーめん缶! Hello, I am a ramen vending machine. " ..
|
||||
"Please type a code for service: {{codes}} " ..
|
||||
"Support: +81 012-700-1MIO どうぞめしあがれ。ヾ(^ω^ )ノ",
|
||||
join = ". . . aaand we're in! (*´∇`*)",
|
||||
part = "Okay, okay, I'll leave. (´・ω・`)",
|
||||
ping = "ポーン!",
|
||||
quit = "noodling off",
|
||||
reload = "Ramen matrix reloaded! (´∀`)",
|
||||
},
|
||||
errors = {
|
||||
no_perm = "Can ... not, {{user}}. Sorry. " ..
|
||||
"I'll get in hot water for that! Σ(°△°|||)︴",
|
||||
notify_no_perm = "{{user}} attempted to use service code: {{code}}",
|
||||
ns_auth_failed = "Error: Nickserv identification failed.",
|
||||
sasl_auth_failed = "Error: SASL authentication failed.",
|
||||
unknown_code = "What's that? Doesn't sound very noodly to me. (´・ω・`)?",
|
||||
},
|
||||
}
|
||||
|
||||
-- Custom variables
|
||||
itte_config.ramen = ramen
|
||||
|
||||
itte_config.messages.announce = "Now serving! "
|
||||
itte_config.messages.serve = "{{ramen}} ⊂(・▽・⊂)"
|
||||
itte_config.messages.special = "**SPECIAL!!!** {{ramen}} " ..
|
||||
"☆*:・゚ o(^ ∀ ^ o)"
|
||||
itte_config.messages.water = {
|
||||
"\x01ACTION happily pours the hot liquid into a bowl of noodles " ..
|
||||
"and offers it to {{user}}\x01",
|
||||
"Cheers! 自o(^_^ )",
|
||||
"Water Level [/////////] 200% - Thanks! (^▽^ )",
|
||||
"Thanks. Some people say I have a CRABby tempeRAMENt. I wonder why. " ..
|
||||
"(´-` )",
|
||||
"Q. What do you call noodles made from the core of a tree? " ..
|
||||
"A. DuRAMEN. *ba dum tss* ┐('∀`;)┌",
|
||||
"Q. What do you call noodles eaten on bulletin boards? " ..
|
||||
"A. FoRAMEN. *ba dum tss* ┐('∀`;)┌",
|
||||
"Q. What kind of noodles can be found at TV stations? " ..
|
||||
"A. CameRAMEN. *ba dum tss* ┐('∀`;)┌",
|
||||
"Ramen time anytime! 自o(´▽` )/",
|
||||
}
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Handlers
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local irc = require("itte")
|
||||
local h = {}
|
||||
|
||||
|
||||
-- Serve ramen.
|
||||
function h.ramen(cxt, msg)
|
||||
local recipients = { msg.reply_to }
|
||||
local intro = ""
|
||||
-- Scheduled task recipients
|
||||
if msg.recipients ~= nil then
|
||||
recipients = msg.recipients
|
||||
intro = itte_config.messages.announce
|
||||
end
|
||||
|
||||
-- 1% chance of special set
|
||||
local roll = math.random(1, 100)
|
||||
if roll == 77 then
|
||||
irc.message(cxt, recipients, intro ..
|
||||
string.gsub(itte_config.messages.special, "{{ramen}}",
|
||||
ramen.make_ramen("veg_special")))
|
||||
else
|
||||
irc.message(cxt, recipients, intro ..
|
||||
string.gsub(itte_config.messages.serve, "{{ramen}}",
|
||||
ramen.make_ramen("veg")))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Serve meat-based ramen.
|
||||
function h.mramen(cxt, msg)
|
||||
-- 1% chance of special set
|
||||
local roll = math.random(1, 100)
|
||||
if roll == 77 then
|
||||
irc.message(cxt, { msg.reply_to },
|
||||
string.gsub(itte_config.messages.special, "{{ramen}}",
|
||||
ramen.make_ramen("meat_special")))
|
||||
else
|
||||
irc.message(cxt, { msg.reply_to },
|
||||
string.gsub(itte_config.messages.serve, "{{ramen}}",
|
||||
ramen.make_ramen("meat")))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Show help message.
|
||||
function h.rollcall(cxt, msg)
|
||||
irc._h.help(cxt, msg)
|
||||
end
|
||||
|
||||
|
||||
-- Reply with a random joke or link.
|
||||
function h.water(cxt, msg)
|
||||
local roll = math.random(1, 2)
|
||||
local resp = util.pick(itte_config.messages.water)[1]
|
||||
if roll == 2 then
|
||||
resp = util.pick(itte_config.ramen.links)[1]
|
||||
end
|
||||
-- Replace user placeholder if present
|
||||
if util.is_substr(resp, "{{user}}") then
|
||||
resp = string.gsub(resp, "{{user}}", msg.sender)
|
||||
end
|
||||
irc.message(cxt, { msg.reply_to }, resp)
|
||||
end
|
||||
|
||||
|
||||
-- Serve ramen at the interval specified in the config.
|
||||
function h.th_ramen(cxt, task)
|
||||
h.ramen(cxt, task)
|
||||
end
|
||||
|
||||
|
||||
itte_handlers = h
|
|
@ -0,0 +1,5 @@
|
|||
local ramenkan = require("itte")
|
||||
|
||||
ramenkan.confs.prefix = "ramenkan"
|
||||
|
||||
ramenkan.run()
|
|
@ -0,0 +1,50 @@
|
|||
itte_admins = { USER = "PASSWORD" }
|
||||
|
||||
itte_servers = {
|
||||
town = {
|
||||
host = "localhost",
|
||||
port = 6697,
|
||||
channels = { "#ramenkan", "#bots" },
|
||||
nick = "ramenkan",
|
||||
auth_user = "USER",
|
||||
code_prefix = "!",
|
||||
admins = { USER = "PASSWORD" },
|
||||
tasks = {
|
||||
ramen = {
|
||||
interval = "daily",
|
||||
time = "12:00",
|
||||
handler = "th_ramen",
|
||||
recipients = { "#ramenkan" },
|
||||
},
|
||||
},
|
||||
},
|
||||
casa = {
|
||||
host = "m455.casa",
|
||||
port = 6697,
|
||||
channels = { "#kitchen", "#siliconpals" },
|
||||
cap = { "sasl", "draft/chathistory" },
|
||||
nick = "ramenkan",
|
||||
auth_type = "sasl",
|
||||
auth_user = "USER",
|
||||
auth_pass = "PASSWORD",
|
||||
code_prefix = "!",
|
||||
admins = { USER = "PASSWORD" },
|
||||
tasks = {
|
||||
ramen = {
|
||||
interval = "daily",
|
||||
time = "17:00",
|
||||
handler = "th_ramen",
|
||||
recipients = { "#kitchen" },
|
||||
},
|
||||
},
|
||||
},
|
||||
tildechat = {
|
||||
host = "irc.tilde.chat",
|
||||
port = 6697,
|
||||
channels = { "#bots" },
|
||||
nick = "ramenkan",
|
||||
auth_user = "USER",
|
||||
code_prefix = "!",
|
||||
admins = { USER = "PASSWORD" },
|
||||
},
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=ramenkan — a ramen-themed IRC bot
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=%h/bin/itte/examples/ramenkan
|
||||
ExecStart=/usr/bin/lua ./ramenkan.lua
|
||||
Restart=always
|
||||
RestartSec=300
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
185
itte.py
185
itte.py
|
@ -1,185 +0,0 @@
|
|||
import argparse
|
||||
import socket
|
||||
import yaml
|
||||
from time import sleep
|
||||
from random import randint
|
||||
from sys import exit
|
||||
|
||||
|
||||
class Util:
|
||||
"""Utility functions."""
|
||||
|
||||
def yml(self, yml_file):
|
||||
"""Open a YAML file and return a dictionary of values."""
|
||||
try:
|
||||
fh = open(yml_file, "r")
|
||||
data = yaml.safe_load(fh)
|
||||
fh.close()
|
||||
except TypeError:
|
||||
exit("[debug][err] Cannot load YML file. Please check it exists.")
|
||||
return data
|
||||
|
||||
def rand(self, lst):
|
||||
"""Return a random item from a given list."""
|
||||
return lst[randint(0, len(lst)-1)]
|
||||
|
||||
def cli_flags(self):
|
||||
"""Parse command line flags."""
|
||||
self.argp = argparse.ArgumentParser()
|
||||
self.argp.add_argument("-c", "--config", help="Config file")
|
||||
return self.argp.parse_args()
|
||||
|
||||
|
||||
class IRC:
|
||||
"""Methods for basic IRC communication."""
|
||||
|
||||
def config(self, def_conf):
|
||||
"""Load runtime settings from a YAML config file, and returns a
|
||||
dictionary of config values. Looks for the file in a runtime path or in
|
||||
the default location."""
|
||||
self.util = Util()
|
||||
# Check for runtime config locatiion
|
||||
flags = self.util.cli_flags()
|
||||
if flags.config != "":
|
||||
cfg = self.util.yml(flags.config)
|
||||
else:
|
||||
cfg = self.util.yml(def_conf)
|
||||
self.server = (cfg["server"]["host"], cfg["server"]["port"])
|
||||
self.channels = cfg["channels"]
|
||||
self.bot_nick = cfg["bot_nick"]
|
||||
self.req_prefix = cfg["req_prefix"]
|
||||
self.admin_user = cfg["admin"]["user"]
|
||||
self.admin_code = cfg["admin"]["code"]
|
||||
self.debug = cfg["debug"]
|
||||
return cfg
|
||||
|
||||
def run(self, listen_hook):
|
||||
"""A routine that connects to a server, joins channels, and attaches
|
||||
the request listener hook to a loop."""
|
||||
self.connect(self.server, self.bot_nick)
|
||||
# Wait for server to reply before joining channels
|
||||
svr_greet = self.receive()
|
||||
while ("001 " + self.bot_nick) not in svr_greet:
|
||||
sleep(1)
|
||||
svr_greet = self.receive()
|
||||
self.join_channels(self.channels)
|
||||
while 1:
|
||||
data = self.receive()
|
||||
self.keepalive(data, self.bot_nick)
|
||||
self.msg = self.parse(data, self.req_prefix)
|
||||
for c in self.channels:
|
||||
# Pass in a context dict for handlers
|
||||
listen_hook({"msg": self.msg, "listen_chan": c})
|
||||
|
||||
def connect(self, server, bot_nick):
|
||||
"""Connect to the server and sends user/nick information."""
|
||||
try:
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.connect(server)
|
||||
except ConnectionError as err:
|
||||
exit("[debug][err] " + str(err))
|
||||
self.send("USER", bot_nick + " " + bot_nick + " " + \
|
||||
bot_nick + " " + bot_nick)
|
||||
self.send("NICK", bot_nick)
|
||||
|
||||
def disconnect(self, resp_msg, quit_msg):
|
||||
"""Notify the admin user and disconnect from the server."""
|
||||
self.send("PRIVMSG", resp_msg, recvr=self.admin_user)
|
||||
self.send("QUIT", ":" + quit_msg)
|
||||
# Currently only one server per app instance is supported, so
|
||||
# disconnect also exits the app
|
||||
exit("Shutting down ...")
|
||||
|
||||
def keepalive(self, line, bot_nick):
|
||||
"""Stay connected to a server by responding to server pings."""
|
||||
if line.split(" ")[0] == "PING":
|
||||
resp = line.replace("PING", "PONG", 1)
|
||||
if self.debug:
|
||||
print("[debug][send] " + resp)
|
||||
self.sock.sendall(bytes(resp + "\r\n", "utf-8"))
|
||||
# Fallback to ensure process exits if timeout occurs
|
||||
elif (bot_nick in line.split(" ")[0]) and \
|
||||
("QUIT :Ping timeout:" in line):
|
||||
exit("[debug][err] Ping timeout, exited.")
|
||||
|
||||
def join_channels(self, channels):
|
||||
"""Join channels given a list of channel names."""
|
||||
for c in channels:
|
||||
if c.strip() != "" or c.strip() != "#":
|
||||
self.send("JOIN", c)
|
||||
|
||||
def send(self, command, text, *args, **kwargs):
|
||||
"""Send messages given the IRC command and text. Optionally specify a
|
||||
message recipient with `recvr=user`."""
|
||||
recvr = kwargs.get("recvr", "")
|
||||
if recvr != "":
|
||||
recvr += " :"
|
||||
if self.debug:
|
||||
print("[debug][send] " + command + " " + recvr + text)
|
||||
bs = bytes(command + " " + recvr + text + "\r\n", "utf-8")
|
||||
try:
|
||||
self.sock.sendall(bs)
|
||||
except BrokenPipeError as err:
|
||||
if self.debug:
|
||||
print("[debug] " + str(err) + " at `" + \
|
||||
bs.decode("utf-8").strip() + "`")
|
||||
pass
|
||||
|
||||
def receive(self):
|
||||
"""Get messages from the connected socket."""
|
||||
data = self.sock.recv(4096).decode("utf-8").strip("\r\n")
|
||||
if self.debug:
|
||||
print("[debug][recv] " + data)
|
||||
return data
|
||||
|
||||
def parse(self, line, req_prefix):
|
||||
"""Using received data from a socket, extract the request, the nick and
|
||||
username of the requester, the channel where the request originated and
|
||||
return a dictionary of values."""
|
||||
data = {"req": "", "req_chan": "", "nick": "", "user": ""}
|
||||
if (":" + req_prefix) in line:
|
||||
data["req"] = line.split("PRIVMSG", 1)[1].split(":" + \
|
||||
req_prefix, 1)[1].strip()
|
||||
data["req_chan"] = line.split("PRIVMSG ", \
|
||||
1)[1].split(" :", 1)[0]
|
||||
data["nick"] = line.split("!~", 1)[0][1:]
|
||||
data["user"] = line.split("!~", 2)[0][0:].split("@", 1)[0]
|
||||
return data
|
||||
|
||||
def listen(self, context, trigger, handler, *args, **kwargs):
|
||||
"""Listen for a trigger and call the handler. It takes a context
|
||||
dictionary (to determine the channel and recipient), trigger string and
|
||||
corresponding handler function. Optional flags (chan, query, admin) can
|
||||
be used to specify whether a trigger is active in channels or by
|
||||
/msg. e.g. `chan=False, query=True` to make a trigger query-only. By
|
||||
default, it will listen in both channels and private messages."""
|
||||
in_chan = kwargs.get("chan", True)
|
||||
in_query = kwargs.get("query", True)
|
||||
in_admin = kwargs.get("admin", False)
|
||||
# Admin requests are query/pm only
|
||||
if in_admin:
|
||||
in_chan = False
|
||||
msg = context["msg"]
|
||||
channel = context["listen_chan"]
|
||||
# Responses are sent via pm to the user by default, while requests made
|
||||
# in a channel are sent to the channel. While it's possible to override
|
||||
# the recvr, it usually easier to enable a trigger in the same location
|
||||
# where the response will be sent.
|
||||
context["recvr"] = msg["user"]
|
||||
|
||||
if msg["req"] == trigger:
|
||||
# Respond only in the channel where the request was made
|
||||
if in_chan and channel == msg["req_chan"]:
|
||||
context["recvr"] = msg["req_chan"]
|
||||
handler(context)
|
||||
# Respond to query/pm
|
||||
elif in_query and msg["req_chan"] == self.bot_nick:
|
||||
handler(context)
|
||||
# Respond only to the admin user
|
||||
elif in_admin and msg["user"].lower() == \
|
||||
self.admin_user.lower() and self.admin_code in msg["req"]:
|
||||
handler(context)
|
||||
|
||||
def reply(self, cxt, text):
|
||||
"""Alias of send() with PRIVMSG command preset."""
|
||||
self.send("PRIVMSG", text, recvr=cxt["recvr"])
|
|
@ -0,0 +1,295 @@
|
|||
-- ---------------------------------------------------------------------------
|
||||
-- Itte Util
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
--[[
|
||||
-- A collection of helper functions used by Itte.
|
||||
--]]
|
||||
|
||||
local itteutil = {}
|
||||
itteutil.docs = {}
|
||||
|
||||
|
||||
itteutil.docs.get_docs = [===[ (docstring_table [, func_str [, printd_bool]])
|
||||
Given a table of docstrings and a function name, return the description for
|
||||
the function name. If `name` is unspecified, return descriptions for all
|
||||
functions. If `printd` is unspecified, print to stdout, or if set to false,
|
||||
return results as a string.
|
||||
]===]
|
||||
function itteutil.get_docs(docstr, name, printd)
|
||||
local docs = ""
|
||||
if name ~= nil then
|
||||
-- 2nd-level nested objects, e.g. x.y.func()
|
||||
if itteutil.is_substr(name, "%.") then
|
||||
local sep, _ = string.find(name, "%.")
|
||||
local dk = name:sub(1, sep - 1)
|
||||
local nk = name:sub(sep + 1)
|
||||
docs = "\n" .. name .. " " .. string.gsub(docstr[dk][nk]:sub(3),
|
||||
" ", "") .. "\n"
|
||||
else
|
||||
docs = "\n" .. name .. " " .. string.gsub(docstr[name]:sub(3), " ", "")
|
||||
.. "\n"
|
||||
end
|
||||
else
|
||||
-- Sort on-site since associative arrays have no fixed order
|
||||
local dk = itteutil.table_keys(docstr)
|
||||
docs = "\n"
|
||||
for c = 1, #dk do
|
||||
-- 2nd-level nested objects, e.g. x.y.func()
|
||||
if type(docstr[dk[c]]) == "table" then
|
||||
local n_dk = itteutil.table_keys(docstr[dk[c]])
|
||||
for nc = 1, #n_dk do
|
||||
docs = docs .. dk[c] .. "." .. n_dk[nc] .. " " ..
|
||||
string.gsub(docstr[dk[c]][n_dk[nc]]:sub(3), " ", "") .. "\n"
|
||||
end
|
||||
else
|
||||
docs = docs .. dk[c] .. " " .. string.gsub(docstr[dk[c]]:sub(3),
|
||||
" ", "") .. "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if printd == false then
|
||||
return docs
|
||||
else
|
||||
print(docs)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
itteutil.docs.help = [[ ([func_str])
|
||||
Given a function name, print a corresponding description.
|
||||
]]
|
||||
function itteutil.help(name)
|
||||
itteutil.get_docs(itteutil.docs, name)
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.is_substr = [[ (str, find_str)
|
||||
Check if a string is a substring of another string. Return true if it is a
|
||||
substring, or false otherwise.
|
||||
]]
|
||||
function itteutil.is_substr(str, search)
|
||||
if (string.find(str, search) ~= nil) then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.first_upper = [[ (str)
|
||||
Return a string with the first character in uppercase.
|
||||
]]
|
||||
function itteutil.first_upper(str)
|
||||
return str:sub(1, 1):upper() .. str:sub(2)
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.first_lower = [[ (str)
|
||||
Return a string with the first character in lowercase.
|
||||
]]
|
||||
function itteutil.first_lower(str)
|
||||
return str:sub(1, 1):lower() .. str:sub(2)
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.split_str = [[ (str, [, pattern_str])
|
||||
Split a string on a pattern separator and return a table of string values,
|
||||
e.g. split_str("Hello world", "%S+"). If no pattern is specified, split
|
||||
string on the space character.
|
||||
]]
|
||||
function itteutil.split_str(str, sep)
|
||||
local tbl = {}
|
||||
local n = 0
|
||||
-- Other patterns: https://www.lua.org/pil/20.2.html
|
||||
if sep == nil then sep = "%S+" end
|
||||
for sub in string.gmatch(str, sep) do
|
||||
n = n + 1
|
||||
tbl[n] = sub
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.a_or_an = [[ (str)
|
||||
Return the corresponding indefinite article for a word.
|
||||
Does *not* apply to acronyms, e.g. FTP.
|
||||
]]
|
||||
function itteutil.a_or_an(str)
|
||||
local article = "a"
|
||||
if itteutil.is_substr("a e i o u", str:sub(1, 1)) or
|
||||
itteutil.is_substr("ho", str:sub(1, 2)) then
|
||||
article = "an"
|
||||
end
|
||||
if (str == "one") or (itteutil.is_substr("eu", str:sub(1, 2))) or
|
||||
(itteutil.is_substr("uni usa use usi usu", str:sub(1, 3))) then
|
||||
article = "a"
|
||||
end
|
||||
return article
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.table_keys = [[ (table)
|
||||
Return a sorted table of keys for an associative table.
|
||||
]]
|
||||
function itteutil.table_keys(tbl)
|
||||
local keys = {}
|
||||
local n = 0
|
||||
for k, v in pairs(tbl) do
|
||||
n = n + 1
|
||||
keys[n] = k
|
||||
end
|
||||
table.sort(keys)
|
||||
return keys
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.has_key = [[ (table, find_str)
|
||||
Given a table and string, check whether the string is a key in the table.
|
||||
Return true if found, or false otherwise.
|
||||
]]
|
||||
function itteutil.has_key(tbl, str)
|
||||
local keys = itteutil.table_keys(tbl)
|
||||
-- Primitive check for array-like table with numerical keys
|
||||
if (keys[1] == 1) and (keys[#keys] == #keys) then
|
||||
for k = 1, #keys do
|
||||
if str == tbl[k] then
|
||||
do return true end
|
||||
end
|
||||
end
|
||||
-- Associative table
|
||||
else
|
||||
for k = 1, #keys do
|
||||
if str == keys[k] then
|
||||
do return true end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.find_key = [[ (table, find_str)
|
||||
Given a table and string, check whether the string is a key in the table.
|
||||
Return the key if found, or nil otherwise.
|
||||
]]
|
||||
function itteutil.find_key(tbl, str)
|
||||
local keys = itteutil.table_keys(tbl)
|
||||
-- Primitive check for array-like table with numerical keys
|
||||
if (keys[1] == 1) and (keys[#keys] == #keys) then
|
||||
for k = 1, #keys do
|
||||
if str == tbl[k] then
|
||||
do return k end
|
||||
end
|
||||
end
|
||||
-- Associative table
|
||||
else
|
||||
for k = 1, #keys do
|
||||
if str == keys[k] then
|
||||
do return k end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
itteutil.docs.is_entry = [[ (table, key_str, val_str)
|
||||
Given an associative table, a key and value pair as strings, check whether
|
||||
the pair is in the table. Return true if it exists in the table, or false
|
||||
otherwise.
|
||||
]]
|
||||
function itteutil.is_entry(tbl, key, val)
|
||||
for k, v in pairs(tbl) do
|
||||
if (key == k) and (val == v) then
|
||||
do return true end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.pick = [===[ (table [, num_int [, unique_bool ]])
|
||||
Pseudo-randomly pick entries from a non-associative table and return the
|
||||
entries in a table. If `num` is unspecified, it will return one entry.
|
||||
If `unique` is unspecified, it will return non-recurring values.
|
||||
]===]
|
||||
function itteutil.pick(tbl, num, unique)
|
||||
local picks = {}
|
||||
if num == nil then num = 1 end
|
||||
if unique == nil then unique = true end
|
||||
picks[1] = tbl[math.random(1, #tbl)]
|
||||
|
||||
if (num > 1) and (num <= #tbl) then
|
||||
for n = 2, num do
|
||||
local p = tbl[math.random(1, #tbl)]
|
||||
-- Check for duplicate
|
||||
if unique then
|
||||
local c = 1
|
||||
-- Avoid endless loop if all values in the table are identical
|
||||
while (itteutil.has_key(picks, p)) and (c <= #tbl) do
|
||||
p = tbl[math.random(1, #tbl)]
|
||||
c = c + 1
|
||||
end
|
||||
end
|
||||
picks[n] = p
|
||||
end
|
||||
end
|
||||
return picks
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.sleep = [[ (seconds_int)
|
||||
Set a delay in seconds.
|
||||
]]
|
||||
function itteutil.sleep(s)
|
||||
local timer = os.clock()
|
||||
while (os.clock() - timer < s) do end
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.debug = [[ (tag_str, debug_str [, enabled_bool])
|
||||
Format and print debug output if `enabled` is true.
|
||||
]]
|
||||
function itteutil.debug(tag, str, enabled)
|
||||
if enabled then
|
||||
local ts = os.date("%Y-%m-%d %H:%M:%S", os.time())
|
||||
print("[" .. ts .. "][" .. tag .. "] " .. str)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
itteutil.docs.source_file = [[ (filename_str)
|
||||
Load a source file.
|
||||
]]
|
||||
function itteutil.source_file(str)
|
||||
local func, err = loadfile(str)
|
||||
if func then
|
||||
return func()
|
||||
else
|
||||
itteutil.debug("source_file", "Error: " .. err, true)
|
||||
do return end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
itteutil.docs.read_file = [[ (filename_str)
|
||||
Open a file and return the contents as a string.
|
||||
]]
|
||||
function itteutil.read_file(str)
|
||||
local file = io.open(str)
|
||||
if io.type(file) == "file" then
|
||||
local contents = file:read("*a")
|
||||
file:close()
|
||||
return contents
|
||||
else
|
||||
itteutil.debug("read_file", "Error: cannot read file.", true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return itteutil
|
191
ramenkan.py
191
ramenkan.py
|
@ -1,191 +0,0 @@
|
|||
from mastodon import Mastodon
|
||||
from random import randint
|
||||
|
||||
from itte import IRC, Util
|
||||
|
||||
|
||||
class Ramen:
|
||||
"""Requests with a ramen theme."""
|
||||
|
||||
def main(self):
|
||||
"""Instantiate an IRC object and attach the listeners."""
|
||||
# Load yaml sources used by request handlers
|
||||
self.util = Util()
|
||||
self.rand = self.util.rand
|
||||
self.misc = self.util.yml("ramenkan/misc.yml")
|
||||
self.links = self.util.yml("ramenkan/links.yml")
|
||||
self.photos = self.util.yml("ramenkan/photos.yml")
|
||||
self.dishes = self.util.yml("ramenkan/dishes.yml")
|
||||
# Init irc object and load config
|
||||
self.irc = IRC()
|
||||
self.cfg = self.irc.config("ramenkan/default.config.yml")
|
||||
# Init mastodon object
|
||||
if "mastodon" in self.cfg:
|
||||
self.masto = Mastodon(
|
||||
api_base_url=self.cfg["mastodon"]["base_url"],
|
||||
access_token=self.cfg["mastodon"]["access_token"],
|
||||
client_id=self.cfg["mastodon"]["client_id"],
|
||||
client_secret=self.cfg["mastodon"]["client_secret"]
|
||||
)
|
||||
# Init request listeners
|
||||
self.irc.run(self.add_listeners)
|
||||
|
||||
def add_listeners(self, cxt):
|
||||
"""Map triggers to handlers."""
|
||||
self.irc.listen(cxt, "exit " + self.cfg["admin"]["code"], self.quit, \
|
||||
admin=True)
|
||||
self.irc.listen(cxt, "rollcall", self.rollcall)
|
||||
self.irc.listen(cxt, "help", self.rollcall)
|
||||
self.irc.listen(cxt, "water " + self.cfg["bot_nick"], self.water)
|
||||
self.irc.listen(cxt, "botsnack " + self.cfg["bot_nick"], self.botsnack)
|
||||
self.irc.listen(cxt, "ramen", self.ramen)
|
||||
self.irc.listen(cxt, "vramen", self.ramen_veggie)
|
||||
self.irc.listen(cxt, "rk", self.ramen)
|
||||
self.irc.listen(cxt, "rkveg", self.ramen_veggie)
|
||||
self.irc.listen(cxt, "rklink", self.link)
|
||||
self.irc.listen(cxt, "rkselfie", self.selfie)
|
||||
if "mastodon" in self.cfg:
|
||||
self.irc.listen(cxt, "rktoot", self.toot)
|
||||
self.irc.listen(cxt, "rkvtoot", self.toot_veggie)
|
||||
|
||||
def quit(self, cxt):
|
||||
"""Disconnect from the server and quit."""
|
||||
self.irc.disconnect("Okay, okay, I'll leave. (´・ω・`)", "noodling off")
|
||||
|
||||
def rollcall(self, cxt):
|
||||
"""Handle request for app info."""
|
||||
self.irc.reply(cxt, self.misc["rollcall"])
|
||||
|
||||
def water(self, cxt):
|
||||
"""Handle water offer."""
|
||||
resp = self.misc["water"]
|
||||
for index, r in enumerate(resp):
|
||||
if "{{ nick }}" in r:
|
||||
resp.append(r.replace("{{ nick }}", cxt["msg"]["nick"]))
|
||||
resp.pop(index)
|
||||
self.irc.reply(cxt, self.rand(resp))
|
||||
|
||||
def botsnack(self, cxt):
|
||||
"""Handle snack offer."""
|
||||
self.irc.reply(cxt, self.rand(self.misc["botsnack"]))
|
||||
|
||||
def make_ramen_combo(self, *args, **kwargs):
|
||||
"""Generate a ramen dish. Optionally pass `veggie=True` for a
|
||||
vegetarian dish."""
|
||||
dish = self.dishes
|
||||
# Check vegetarian flag
|
||||
is_veggie = kwargs.get("veggie", False)
|
||||
# 20% chance of tsukemen
|
||||
roll = randint(1, 10)
|
||||
if roll <= 2:
|
||||
combo = "tsukemen from "
|
||||
else:
|
||||
combo = "ramen in "
|
||||
# Noodle type and broth richness
|
||||
if randint(0, 1):
|
||||
combo = self.rand(dish["noodle-shape"]) + ", " + \
|
||||
self.rand(dish["noodle-broth-type"]) + " "
|
||||
combo = combo.capitalize()
|
||||
# Broth type
|
||||
if is_veggie:
|
||||
combo += self.rand(dish["broth-veggie"])
|
||||
else:
|
||||
combo += self.rand(dish["broth"] + dish["broth-veggie"])
|
||||
combo += " broth"
|
||||
# Topping
|
||||
n_top = randint(2, 5)
|
||||
for n in range(2, n_top+1):
|
||||
if n == 2:
|
||||
if is_veggie:
|
||||
prev_top = self.rand(dish["topping"])
|
||||
else:
|
||||
prev_top = self.rand(dish["meat"])
|
||||
combo += " with " + prev_top
|
||||
else:
|
||||
if n > 2 and n < n_top:
|
||||
combo += ", "
|
||||
else:
|
||||
combo += " and "
|
||||
if is_veggie:
|
||||
next_top = self.rand(dish["topping"])
|
||||
else:
|
||||
# 20% chance of egg topping
|
||||
roll = randint(1, 10)
|
||||
if roll <= 2:
|
||||
next_top = self.rand(dish["topping-egg"])
|
||||
else:
|
||||
next_top = self.rand(dish["topping"])
|
||||
# Check for duplicate
|
||||
while next_top in prev_top:
|
||||
next_top = self.rand(dish["topping"])
|
||||
combo += next_top
|
||||
prev_top = next_top
|
||||
# Condiment and side dish
|
||||
if randint(0, 1):
|
||||
combo += ", sprinkled with " + self.rand(dish["condiment"]) + "."
|
||||
else:
|
||||
combo += "."
|
||||
if randint(0, 1):
|
||||
combo += " Served with "
|
||||
if is_veggie:
|
||||
combo += self.rand(dish["tapa-veggie"]) + "."
|
||||
else:
|
||||
combo += self.rand(dish["tapa"] + dish["tapa-veggie"]) + "."
|
||||
return combo
|
||||
|
||||
def pick_ramen(self, *args, **kwargs):
|
||||
"""Pick a ramen dish. Optionally set vegetarian selection with
|
||||
`veggie=True`."""
|
||||
roll = randint(1, 100)
|
||||
veggie = kwargs.get("veggie", False)
|
||||
# 1% possibility of regional preset
|
||||
if roll == 100 and veggie:
|
||||
pick = (self.dishes["set-veggie"] + ".").capitalize()
|
||||
elif roll == 100 and not veggie:
|
||||
pick = (self.rand(self.dishes["set"] + \
|
||||
self.dishes["set-veggie"]) + ".").capitalize()
|
||||
elif roll <= 99 and veggie:
|
||||
pick = self.make_ramen_combo(veggie=True)
|
||||
else:
|
||||
pick = self.make_ramen_combo()
|
||||
return pick
|
||||
|
||||
def ramen(self, cxt):
|
||||
"""Handle ramen request."""
|
||||
self.irc.reply(cxt, self.pick_ramen())
|
||||
|
||||
def ramen_veggie(self, cxt):
|
||||
"""Handle vegetarian ramen request."""
|
||||
self.irc.reply(cxt, self.pick_ramen(veggie=True))
|
||||
|
||||
def link(self, cxt):
|
||||
"""Handle to display a titled link."""
|
||||
index = randint(0, len(self.links)-1)
|
||||
self.irc.reply(cxt, self.links[index]["title"] + " " + \
|
||||
self.links[index]["link"])
|
||||
|
||||
def selfie(self, cxt):
|
||||
"""Handle to display a photo link."""
|
||||
self.irc.reply(cxt, self.rand(self.photos["ticket"]))
|
||||
|
||||
def toot_wrapper(self, cxt, txt):
|
||||
"""Wrap around the Mastodon library's toot command for basic error
|
||||
handling."""
|
||||
try:
|
||||
self.masto.toot(self.misc["toot"]["prefix"] + " " + txt)
|
||||
self.irc.reply(cxt, txt + " " + self.misc["toot"]["success"])
|
||||
except:
|
||||
self.irc.reply(cxt, self.misc["toot"]["error"])
|
||||
pass
|
||||
|
||||
def toot(self, cxt):
|
||||
"""Handle post ramen to Mastodon."""
|
||||
self.toot_wrapper(cxt, self.pick_ramen())
|
||||
|
||||
def toot_veggie(self, cxt):
|
||||
"""Handle post veggie ramen to Mastodon."""
|
||||
self.toot_wrapper(cxt, self.pick_ramen(veggie=True))
|
||||
|
||||
|
||||
app = Ramen()
|
||||
app.main()
|
|
@ -1,27 +0,0 @@
|
|||
# Server and channel settings
|
||||
server:
|
||||
host: "localhost"
|
||||
port: 6667
|
||||
|
||||
channels:
|
||||
- "#bots"
|
||||
|
||||
bot_nick: "ramenkan"
|
||||
|
||||
# User and code for admin actions
|
||||
admin:
|
||||
user: "user"
|
||||
code: "ramen"
|
||||
|
||||
# Request prefix, e.g. "!" for "!<request>"
|
||||
req_prefix: "!"
|
||||
|
||||
# Print messages to stdout
|
||||
debug: False
|
||||
|
||||
# Mastodon account
|
||||
# mastodon:
|
||||
# base_url: "https://example.com"
|
||||
# access_token: ""
|
||||
# client_secret: ""
|
||||
# client_id: ""
|
|
@ -1,143 +0,0 @@
|
|||
# Lists of ingredients to assemble a ramen dish
|
||||
broth:
|
||||
- Kobe beef bone
|
||||
- tonkotsu
|
||||
- pork bone and chicken
|
||||
- pork bone and niboshi # dried little sardines
|
||||
- pork bone, chicken and seafood
|
||||
- pork bone, chicken, tuna and kelp
|
||||
- pork bone and soy sauce
|
||||
- chicken
|
||||
- chicken, katsuo and konbu # skipjack tuna and kelp
|
||||
- fish
|
||||
- gyokai # dried anchovies/shrimp/squid/seaweed
|
||||
- gyokai tonkotsu
|
||||
- roasted tuna
|
||||
- miso and truffle # (from chef Ryu Takahashi)
|
||||
- milk
|
||||
- curry and milk
|
||||
|
||||
broth-veggie:
|
||||
- miso
|
||||
- spicy miso
|
||||
- soy sauce
|
||||
- grilled soy sauce and Rishiri kelp
|
||||
- salt
|
||||
- salt and soy sauce
|
||||
|
||||
noodle-broth-type:
|
||||
- thick noodles in rich
|
||||
- medium-thick noodles in mild
|
||||
- thin noodles in light
|
||||
|
||||
noodle-shape:
|
||||
- curly
|
||||
- flat
|
||||
- straight
|
||||
|
||||
condiment:
|
||||
- butter
|
||||
- rayu # chili oil
|
||||
- chili peppers
|
||||
- chili sauce
|
||||
- mayu # garlic oil
|
||||
- spicy garlic and ginger oil
|
||||
- sesame oil
|
||||
- dashi # fish and seaweed stock
|
||||
- white pepper
|
||||
- black pepper
|
||||
- ponzu # citrus sauce
|
||||
- shichimi # spice blend
|
||||
|
||||
topping:
|
||||
- bean sprouts
|
||||
- chili flakes
|
||||
- sweet corn
|
||||
- grated garlic
|
||||
- roasted garlic chips
|
||||
- beni shoga # pickled ginger
|
||||
- bamboo shoots
|
||||
- menma # fermented bamboo shoots
|
||||
- dried mushrooms
|
||||
- shiitake mushrooms
|
||||
- kikurage # wood ear
|
||||
- karashi takana # spicy pickled mustard greens
|
||||
- negi # Welsh onion
|
||||
- white onions
|
||||
- spring onions
|
||||
- sichuan peppers
|
||||
- nori
|
||||
- wakame
|
||||
- sauerkraut
|
||||
- sesame seeds
|
||||
- scallions
|
||||
- boiled spinach
|
||||
|
||||
topping-egg:
|
||||
- a raw egg
|
||||
- a seasoned boiled egg
|
||||
- an ajitama # egg marinated in soy sauce
|
||||
|
||||
meat:
|
||||
- Kobe beef fillet
|
||||
- Kobe beef sirloin
|
||||
- wagyu
|
||||
- teppanyaki beef
|
||||
- beef shank
|
||||
- chicken breast
|
||||
- chicken meatballs
|
||||
- teppanyaki chicken
|
||||
- crab
|
||||
- crispy duck
|
||||
- filet mignon
|
||||
- kamaboko # fish paste/surimi
|
||||
- narutomaki # fish paste with swirling pattern
|
||||
- kakuni # cubed braised pork
|
||||
- chashu # Japanese-style barbequed pork
|
||||
- pork confit
|
||||
- pork cutlet
|
||||
- pork belly
|
||||
- pork shoulder
|
||||
- shrimp
|
||||
- sea urchin
|
||||
- scallops
|
||||
- squid
|
||||
|
||||
tapa:
|
||||
- gyoza # dumplings (fried)
|
||||
- karaage # fried chicken
|
||||
- tako wasabi # marinated octopus
|
||||
- takoyaki # octopus balls
|
||||
- corn and shrimp salad
|
||||
- crab salad
|
||||
- enoki rolls
|
||||
|
||||
tapa-veggie:
|
||||
- edamame tossed in sea salt # boiled/steamed soyabeans
|
||||
- tofu nuggets
|
||||
- pickled daikon
|
||||
- cucumber salad
|
||||
- seaweed salad # wakame, sesame seeds, mirin
|
||||
|
||||
set:
|
||||
- Korean kimchi ramyeon with tteok
|
||||
- Vancouver cold bonito beer broth ramen with egg whites
|
||||
- Hakata tonkotsu ramen with pickled mustard greens
|
||||
- Hakodate shio ramen with kelp, crab, shrimp, sea urchin and scallops
|
||||
- Hakodate shrimp ramen with a hard-boiled egg and white onions
|
||||
- Kagoshima tonkotsu ramen with chashu, dried sardines, kelp and scallions
|
||||
- Kitakata niboshi-tonkotsu ramen with chashu, naruto and spring onions
|
||||
- Kumamoto tonkotsu ramen with chashu, raw eggs, scallions and garlic chips
|
||||
- Muroran curry ramen with chashu, wakame and bean sprouts
|
||||
- Nagasaki champon with fried pork, scallions and seasonal greens
|
||||
- Osaka Kobe beef sirloin ramen with a soft-boiled egg and arugula
|
||||
- Sapporo miso ramen with scallops, sweet corn and bean sprouts
|
||||
- Tokyo ramen in chicken broth with pork, menma, a boiled egg and nori
|
||||
- Wakayama shoyu-tonkotsu ramen with pork slices, naruto and scallions
|
||||
- Yokohama ramen with chashu, spinach, a soft-boiled egg and negi
|
||||
|
||||
set-veggie:
|
||||
- miso ramen with vegetable broth, cabbage and bean sprouts
|
||||
- ramen with shiitake mushrooms, kombu and sesame oil
|
||||
- ramen with soyabean pork slices, kikurage, bamboo shoots and scallions
|
||||
- barley miso ramen with roasted vegetables
|
|
@ -1,70 +0,0 @@
|
|||
- title: Ramen, Soul Food Staple
|
||||
link: https://www3.nhk.or.jp/nhkworld/en/food/articles/51.html
|
||||
- title: Shin-Yokohama Ramen Museum, Yokohama
|
||||
link: http://www.raumen.co.jp/english/
|
||||
- title: Ramen Street, Tokyo
|
||||
link: https://en.wikipedia.org/wiki/Ramen_street
|
||||
- title: Sapporo Ramen Republic
|
||||
link: http://www.sapporo-esta.jp/ramen
|
||||
- title: Ramen DB, a directory of ramen shops in Japan
|
||||
link: https://ramendb.supleks.jp/
|
||||
- title: Ramen Adventures, a ramen shop review blog
|
||||
link: http://www.ramenadventures.com/
|
||||
# Recipes
|
||||
- title: 15 Fantastic Ramen Recipes
|
||||
link: https://www3.nhk.or.jp/nhkworld/en/food/articles/131.html
|
||||
# Instant noodles
|
||||
- title: Momofuku Ando, inventor of instant ramen (1958)
|
||||
link: https://www.nissin.com/en_jp/about/founder/
|
||||
- title: Instant cup noodles (1971)
|
||||
link: https://en.wikipedia.org/wiki/Cup_Noodles
|
||||
- title: Instant noodles as emergency food in disaster relief
|
||||
link: https://instantnoodles.org/en/activities/support.html
|
||||
- title: Samyang, the first Korean instant ramen (1963)
|
||||
link: https://en.wikipedia.org/wiki/Samyang_ramen
|
||||
- title: The Ramen Rater, an instant ramen blog
|
||||
link: https://www.theramenrater.com/
|
||||
# Ramen in pop culture
|
||||
- title: Ramen Manga, comic strips in Japanese about ramen
|
||||
link: https://www.ramenbank.com/manga/
|
||||
- title: Flower Boy Ramen Shop, romantic comedy drama (2011)
|
||||
link: https://en.wikipedia.org/wiki/Flower_Boy_Ramen_Shop
|
||||
- title: The Ramen Girl, romantic comedy film (2008)
|
||||
link: https://en.wikipedia.org/wiki/The_Ramen_Girl
|
||||
- title: Ms. Koizumi Loves Ramen Noodles, manga series (2013-)
|
||||
link: https://en.wikipedia.org/wiki/Ms._Koizumi_Loves_Ramen_Noodles
|
||||
- title: Muteki Kanban Musume, manga series (2002-2006)
|
||||
link: https://en.wikipedia.org/wiki/Muteki_Kanban_Musume
|
||||
- title: Detective Conan - Ramen So Good, It's to Die For, anime episode (2012)
|
||||
link: https://www.detectiveconanworld.com/wiki/Ramen_So_Good,_It%27s_to_Die_For
|
||||
- title: Ramen Teh, film (2018)
|
||||
link: https://en.wikipedia.org/wiki/Ramen_Teh
|
||||
- title: Ramen yori taisetsuna mono, documentary (2013)
|
||||
link: https://www.imdb.com/title/tt2945642/
|
||||
# Videos
|
||||
- title: What Owning a Ramen Restaurant in Japan is Like
|
||||
link: https://www.youtube.com/watch?v=gmIwxqdwgrI
|
||||
- title: Begin Japanology - Ramen
|
||||
title: https://www.youtube.com/watch?v=RosUc9UVuos
|
||||
- title: Japanology Plus - Ramen
|
||||
link: https://www.youtube.com/watch?v=K2Jzt04QY7A
|
||||
- title: In Search of the Perfect Bowl of Ramen
|
||||
link: https://www.youtube.com/watch?v=bhiFtP9qVYc
|
||||
- title: Rahaku TV (The Shin-Yokohama Ramen Museum Youtube Channel)
|
||||
link: http://www.youtube.com/rahakutv
|
||||
- title: The History of Ramen Yamagoya
|
||||
link: https://www.youtube.com/watch?v=8NBwKbT5QSQ
|
||||
- title: Ramen Daruma
|
||||
link: https://www.youtube.com/watch?v=zkhDYTR789w
|
||||
- title: The God of Ramen, trailer (2013)
|
||||
link: https://www.youtube.com/watch?v=mtwunTGnnR4
|
||||
- title: Ramen Heads, trailer (2017)
|
||||
link: https://www.youtube.com/watch?v=u61b5R_S4TM
|
||||
- title: Ramenomania - Noodles, Asia's Second Obsession
|
||||
link: https://www.youtube.com/watch?v=tfW89yAoTNs
|
||||
- title: Noodle Me
|
||||
link: https://www.youtube.com/watch?v=r9jLJRZ6gnc
|
||||
- title: Visio Documentary - Fat Ramen
|
||||
link: https://www.youtube.com/watch?v=o78fy4rJFCw
|
||||
- title: Ramen Talk and Q+A With Chef Ryu Takahashi and Chef Matt Kimura
|
||||
link: https://www.youtube.com/watch?v=tMvArDklmGU
|
|
@ -1,28 +0,0 @@
|
|||
rollcall:
|
||||
"一、二、三、らーめん缶!
|
||||
Hello, I am a ramen vending machine. Please type a code for service:
|
||||
!help !ramen !vramen !rklink !rkselfie
|
||||
- Support: +81 012-700-1MIO どうぞめしあがれ。"
|
||||
|
||||
water:
|
||||
- "\x01ACTION happily pours the hot liquid into a bowl of noodles
|
||||
and offers it to {{ nick }}\x01"
|
||||
- "Cheers! 自o(^_^ )"
|
||||
- "Water Level [/////////] 200% - Thanks! (^▽^ )"
|
||||
- "Thanks. Some people say I have a CRABby tempeRAMENt.
|
||||
I wonder why. (´-` )"
|
||||
- "Q. What do you call noodles made from the core of a tree?
|
||||
A. DuRAMEN. *ba dum tss* ┐('∀`;)┌"
|
||||
- "Q. What do you call noodles eaten on bulletin boards?
|
||||
A. FoRAMEN. *ba dum tss* ┐('∀`;)┌"
|
||||
- "Q. What kind of noodles can be found at TV stations?
|
||||
A. CameRAMEN. *ba dum tss* ┐('∀`;)┌"
|
||||
|
||||
botsnack:
|
||||
- "CHIKIN RAAAAAMEN━━━(゜∀゜)━━━!!!!!"
|
||||
- "Ramen time anytime! 自o(´▽` )/"
|
||||
|
||||
toot:
|
||||
prefix: "Now serving:"
|
||||
success: "Now shared with Mastodon! (^v^)"
|
||||
error: "Hyuuuuu! Sorry, I can't seem to toot right now. Ask me again later."
|
|
@ -1,44 +0,0 @@
|
|||
# Photo links of ramen vending machines
|
||||
# Ticket machines
|
||||
ticket:
|
||||
- https://c2.staticflickr.com/6/5077/5902028877_7d8c65b23f_b.jpg
|
||||
- https://c1.staticflickr.com/3/2455/5777431350_36e147e719_z.jpg
|
||||
- https://i.pinimg.com/originals/72/08/1e/72081e68ff0989d54fd24a86fcac6f2b.jpg
|
||||
- https://i.pinimg.com/originals/9f/74/92/9f7492b7c456ac7ba350498a50c74b23.jpg
|
||||
- https://c1.staticflickr.com/9/8157/7105010129_cd4b0d7e27_c.jpg
|
||||
- https://c1.staticflickr.com/8/7600/27418644850_2c70342734_c.jpg
|
||||
- https://c3.staticflickr.com/3/2049/2229742287_8df3e823a9_z.jpg
|
||||
- https://c1.staticflickr.com/1/101/308523474_4295a27326_z.jpg
|
||||
- https://media-cdn.tripadvisor.com/media/photo-s/0b/b6/27/f6/ramen-shop-menu-banner.jpg
|
||||
- https://abroadabroad2011.files.wordpress.com/2013/03/sam_0040.jpg
|
||||
- https://c1.staticflickr.com/6/5753/22803130417_ba86ebb4fa_c.jpg
|
||||
- https://c1.staticflickr.com/6/5077/5902028877_7d8c65b23f_z.jpg
|
||||
- https://c1.staticflickr.com/8/7504/15210528573_2612010fc3_c.jpg
|
||||
- https://s3-media4.fl.yelpcdn.com/bphoto/iUHJczm_--xNqWEUzDS69Q/o.jpg
|
||||
- https://thewholeworldornothing.com/wp-content/uploads/2017/01/vending-machine-waiter.jpg
|
||||
- https://thebrunchingbooth.files.wordpress.com/2014/07/img_42681.jpg
|
||||
- https://c1.staticflickr.com/8/7426/16587852342_0cbfa4a63c_c.jpg
|
||||
- https://c1.staticflickr.com/9/8422/7535359482_b5537bbde0_c.jpg
|
||||
- https://c1.staticflickr.com/5/4037/4530351457_8666286b56_z.jpg
|
||||
- https://c1.staticflickr.com/1/97/244236130_0c22679f97_z.jpg
|
||||
- https://c1.staticflickr.com/4/3479/3465112347_1be2868ff0_z.jpg
|
||||
- https://1.bp.blogspot.com/_W-pG7tUmJk0/TDQ-J_vz9sI/AAAAAAAACac/xYjAvl8ZGOY/s1600/shinjuku_ramen_02.jpg
|
||||
- https://thefoodieflight.files.wordpress.com/2015/10/img_3749.jpg
|
||||
- https://static1.squarespace.com/static/57ad3b51c534a528e26a2e93/t/57f91e3be6f2e18cf1d11c82/1476472671797/
|
||||
- https://c1.staticflickr.com/1/166/417127513_e063ccf80a_z.jpg
|
||||
|
||||
# Instant cup noodles
|
||||
instant:
|
||||
- http://www.toxel.com/wp-content/uploads/2009/06/vendingmachine04.jpg
|
||||
- https://c1.staticflickr.com/3/2233/2347915323_9e9e2264a3_z.jpg
|
||||
- https://c1.staticflickr.com/4/3818/10186967173_237d38b86f_c.jpg
|
||||
- https://c1.staticflickr.com/8/7221/7175446233_076264a2b4_c.jpg
|
||||
- https://c7.alamy.com/comp/BCDJRG/a-cup-noodle-vending-machine-instant-ramen-museum-osaka-japan-1-december-BCDJRG.jpg
|
||||
|
||||
# Ramen can
|
||||
can:
|
||||
- https://c1.staticflickr.com/4/3625/3374051074_641b371d23_z.jpg
|
||||
- https://c4.staticflickr.com/4/3214/2911821453_78c6178f54_z.jpg
|
||||
- https://smallbiztrends.com/wp-content/uploads/2015/06/P1020817-728x485.jpg
|
||||
- https://c1.staticflickr.com/9/8440/7745533776_7b9990be82_c.jpg
|
||||
- https://c1.staticflickr.com/3/2268/4507516049_e7aa354489_z.jpg
|
|
@ -0,0 +1,31 @@
|
|||
package = "itte"
|
||||
version = "1.0-1"
|
||||
source = {
|
||||
url = "git+https://git.tilde.town/mio/itte",
|
||||
tag = "1.0",
|
||||
}
|
||||
description = {
|
||||
summary = "Mini IRC bot module in lua",
|
||||
detailed = [[
|
||||
Currently supports:
|
||||
- Authentication via SASL (plain) or Nickserv
|
||||
- Joining multiple servers
|
||||
- Ad-hoc connecting to servers and joining channels
|
||||
- Config reload
|
||||
]],
|
||||
homepage = "https://git.tilde.town/mio/itte",
|
||||
license = "BSD-3.0"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1, < 5.4",
|
||||
"luasec >= 0.9",
|
||||
"luasocket >= 2.9"
|
||||
}
|
||||
supported_platforms = { "linux" }
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
itte = "itte.lua",
|
||||
itteutil = "itteutil.lua"
|
||||
},
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package = "itte"
|
||||
version = "1.1-1"
|
||||
source = {
|
||||
url = "git+https://git.tilde.town/mio/itte",
|
||||
tag = "1.1",
|
||||
}
|
||||
description = {
|
||||
summary = "Mini IRC bot module in lua",
|
||||
detailed = [[
|
||||
Currently supports:
|
||||
- Authentication via SASL (plain) or Nickserv
|
||||
- Joining multiple servers
|
||||
- Ad-hoc connecting to servers and joining channels
|
||||
- Config reload
|
||||
]],
|
||||
homepage = "https://git.tilde.town/mio/itte",
|
||||
license = "BSD-3.0"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1, < 5.4",
|
||||
"luasec >= 0.9",
|
||||
"luasocket >= 2.9"
|
||||
}
|
||||
supported_platforms = { "linux" }
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
itte = "itte.lua",
|
||||
itteutil = "itteutil.lua"
|
||||
},
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package = "itte"
|
||||
version = "1.2-1"
|
||||
source = {
|
||||
url = "git+https://git.tilde.town/mio/itte",
|
||||
tag = "1.2",
|
||||
}
|
||||
description = {
|
||||
summary = "Mini IRC bot module in lua",
|
||||
detailed = [[
|
||||
Currently supports:
|
||||
- Authentication via SASL (plain) or Nickserv
|
||||
- Joining multiple servers
|
||||
- Ad-hoc connecting to servers and joining channels
|
||||
- Config reload
|
||||
- Scheduled tasks
|
||||
]],
|
||||
homepage = "https://git.tilde.town/mio/itte",
|
||||
license = "BSD-3.0"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1, < 5.4",
|
||||
"luasec >= 0.9",
|
||||
"luasocket >= 2.9"
|
||||
}
|
||||
supported_platforms = { "linux" }
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
itte = "itte.lua",
|
||||
itteutil = "itteutil.lua"
|
||||
},
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package = "itte"
|
||||
version = "scm-1"
|
||||
source = {
|
||||
url = "git+https://git.tilde.town/mio/itte",
|
||||
}
|
||||
description = {
|
||||
summary = "Mini IRC bot module in lua",
|
||||
detailed = [[
|
||||
Currently supports:
|
||||
- Authentication via SASL (plain) or Nickserv
|
||||
- Joining multiple servers
|
||||
- Ad-hoc connecting to servers and joining channels
|
||||
- Config reload
|
||||
]],
|
||||
homepage = "https://git.tilde.town/mio/itte",
|
||||
license = "BSD-3.0"
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1, < 5.4",
|
||||
"luasec >= 0.9",
|
||||
"luasocket >= 2.9"
|
||||
}
|
||||
supported_platforms = { "linux" }
|
||||
build = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
itte = "itte.lua",
|
||||
itteutil = "itteutil.lua"
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue