From f305255a32cbcdf510de5ce6601297ab09782504 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Thu, 20 Feb 2020 17:27:49 +0100 Subject: [PATCH] rolling-4 --- Readme.md | 173 +++++++++++++++++++++++++++++++++++++++++++++++++--- main.lua | 48 +++++++++++++-- message.lua | 16 ++++- 3 files changed, 222 insertions(+), 15 deletions(-) diff --git a/Readme.md b/Readme.md index 5dd1db0..d1be488 100644 --- a/Readme.md +++ b/Readme.md @@ -9,8 +9,6 @@ Adds roles, colors, unicode, hud notifications, and chat bridges (IRC & discord) ## About -Help can be found under `config_help.md` in the same folder as this. - Depends on [`modlib`](https://github.com/appgurueu/modlib). Modlib has been updated to add features required by this mod, so make sure to get the newest version. Backwards compatibility was kept as far as I know. Code licensed under the GPLv3 (GNU Public License Version 3). Written by Lars Mueller alias LMD or appguru(eu). @@ -60,6 +58,13 @@ Targets/Mentions: Roles or chatters mentioned using `@` * Many under-the-hood changes cleaning up stuff & fixing bugs (improving the code & architecture) * See `config_help.md` and the sources for all details +### `rolling-4` + +* Merged `config_help.md` into the Readme +* Adds `adv_chat.register_on_chat_message` which works much like `minetest.register_on_chat_message` +* Adds basic logging (of global messages) +* See the sources for details + ## API ### HUD notifications @@ -73,16 +78,16 @@ There are already some vote mods out there. Will probably release a better one a ### IRC Bridge -If enabled, creates a bridge to an IRC channel. For more details see `config_help.md`. +If enabled, creates a bridge to an IRC channel. For more details see the [Configuration]. ### Discord Bridge -If enabled, creates a bridge to a Discord guild channel. For more details see `config_help.md`. +If enabled, creates a bridge to a Discord guild channel. For more details see the [Configuration]. Note that you need to create your own OAuth application (bot) but can of course use the provided implementation. ### More -See the code and `config_help.md`. Feel free to contact me. +See the code and [Configuration] options. Feel free to contact me. ## How it works @@ -135,11 +140,165 @@ Making Minetest & IRC chat compatible with Discord required the introduction of * No double nicknames on Discord. If there are double nicknames, one of them gets an appendix, which is not guaranteed to be the same each time. So better make sure this doesn't happen. * Spaces (` `) and commata (`,`) in Discord nicknames are replaced by underscores (`_`) -### Internal process bridge protocol +## Configuration + +### Locations + +JSON Configuration: `/config/adv_chat.json` + +Text Logs: `/logs/adv_chat/.json` + +Readme: `/adv_chat/Readme.md` + +### Default Configuration + +Located under `/adv_chat/default_config.json` + +```json +{ + "schemes" : { + "minetest" : {"message_prefix": "", "mention_prefix": "#FFFF00@", "mention_delim": "#FFFF00, ", "content_prefix": "#FFFF00: #FFFFFF"}, + "other" : null + }, + "bridges" : { + "discord" : null, + "irc" : null + } +} +``` + +### Example Configuration + +```json +{ + "schemes" : { + "minetest" : {"message_prefix": "Somebody - namely ", "mention_prefix": "#FFFF00 - wrote to ", "mention_delim": "#FFFF00 and ", "content_prefix": "#FFFF00: #FFFFFF", "message_suffix": " :D"}, + "other" : null + }, + "bridges" : { + "discord" : {"channelname":"allgemein", "prefix": "?", "minetest_prefix": "!","token":"S.U.Pxxs.E.R.T.9998OKEN", "blacklist":{"~~new_role~~":true}, "guild_id": 580416319703351296}, + "irc" : {"channelname":"#mtchatbridgetest", "prefix": "?", "minetest_prefix": "!", "nickname": "MT_Chat_Bridge", "network": "irc.freenode.net", "port": 7000, "ssl": true} + } +} + +``` + +### Usage + +#### `schemes` + +Specifies the chat message format, `minetest` is for the one used on the Minetest chat, `irc` is IRC, and `discord` for Discord. + +* `message_prefix` - Prefix for the message +* `mention_prefix` - Prefix for mentionpart. +* `mention_delim` - Mention delimiter. +* `content_prefix` - Message/sendername delimiter. +* `message_suffix` - Suffix for the message + +If you want to use color escape sequences, type something like `#66FF00 colorized text here`, and replace `#66FF00` with your color of choice in hex format. + +Messages are formatted as `message_prefix + sendername + mention_prefix + {mentions, mention_delim} + delim + message + message_suffix` + +#### `bridges` + +Configuration for IRC/Discord chat bridges. If `irc` or `discord` are set to `false` or `null`, the corresponding chat bridges aren't created. + +##### `discord` + +Table with the following entries : + +* `token`: Discord bot token, required +* `channelname`: Name of bridge channel, required as well +* `prefix`, `minetest_prefix`: Prefixes for Discord/Minetest commands, required +* `role_blacklist`/`role_whitelist`: Blacklist/whitelist of Discord roles. If both or none are set, Discord roles are ignored. +* `guild_id`: Guild ID, string. If swines add your bot to other servers, force it to use the server with the specified Guild ID. Optional. If unset, bot will use the guild it joined first. +* `bridge`: Optional. Forces type of process bridge to use. Choices are `"file"` and `"socket"`. Sockets are recommended but require `luasocket`. +* `convert_internal_markdown`/`convert_minetest_markdown`: Optional boolean. Whether Markdown sent from Minetest/internal chat messages should be left untouched as if it was Discord Markdown +* `handle_irc_styles`: Optional string. How IRC styles should be converted to Discord Markdown. Possible values: `"disabled"`, `"escape_markdown"` and `"convert"` +* `strip_discord_markdown_in_minetest`: Optional boolean. Whether Discord Markdown should be stripped from Minetest chat. + +Example : + +```json + { + "discord": { + "prefix": "?", + "minetest_prefix": "!", + "token": "Ao.663438supers.76trange8343", + "channelname": "ingame-chat", + "blacklist":{"~~new_role~~":true}, + "guild_id": "580416319703351296" + } + } +``` + +##### `irc` + +Table with fields. Required are: + +* `network`: IRC network, for example `irc.freenode.net` +* `port`: Port, on [Freenode](https://freenode.net/kb/answer/chat) it would be `7000` if SSL is used, or else `6667`. Just google "connecting to network" for your IRC network of choice to get detailed information. +* `ssl`: Whether to use encryption (SSL) to communicate with the IRC network. Setting this to `true` is recommended. +* `nickname`: Bot nickname +* `channelname`: IRC channel name, for example `#minetest-server` +* `prefix`, `minetest_prefix`: Prefixes for IRC bot/Minetest chatcommands, required + +Optional fields are: + +* `bridge`: Type of process bridge to use can be forced here. Choices are `"file"` and `"socket"`. Sockets are recommended but require `luasocket`. +* `convert_minetest_colors`: How colors from Minetest chat messages should be converted to IRC. Possible values are `"disabled"`, `"safest"`, `"safe"` and `"hex_safe"` and `"hex"` +* `handle_internal_markdown`: How Markdown sent from internal MT should be converted to IRC text styles. Possible values are `"disabled"`, `"strip"` and `"convert"` +* `handle_minetest_markdown`: How Markdown sent from Minetest should be converted to IRC text styles. Possible values are `"disabled"`, `"strip"` and `"convert"` +* `handle_discord_markdown`: How Markdown sent from Discord should be converted to IRC text styles. Possible values are `"disabled"`, `"strip"` and `"convert"` + +Example: + +```json + { + "irc": { + "prefix": "?", + "minetest_prefix": "!", + "channelname": "#minetest-server", + "nickname": "SERVERNAME_Chat", + "port": 7000, + "ssl": true, + "network": "irc.freenode.net" + } + } +``` + +##### `chatcommand_whitelist`/`chatcommand_blacklist` + +Whitelist/blacklist of chatcommands which are not available from Discord or IRC. If both or none are set, all chatcommands are blacklisted. + +### Recommendations + +#### Consistency + +It is recommended to **keep consistency**. To do so, channel & chat bot names could be similar across Discord and IRC. The same goes for prefixes. + +#### Prefixes + +You should try to keep prefixes similar and memorable, while ensuring that there are no collisions. I recommend the combination of `?` for Discord/IRC commands and `!` for Minetest commands. +Other neat combinations I have thought of are `+` and `-`, or `;` and `:`. Keep in mind that prefixes should be easy to type as well, and that others might have a different keyboard layout. + +#### Discord Avatar + +Pixel-art Minetest skin heads always work well as avatars. For an example look you could look at my [Robby-Head](https://github.com/appgurueu/artwork/blob/master/robbyhead.png). +There are tons of skins out there and it's fairly easy to extract the faces (but make sure you don't violate the licenses when using the images). +A good starting point is [Addis Open MT-Skin Database](http://minetest.fensta.bplaced.net/). You can, however, of course also design it yourself. Just grab your favorite pixel-art program and draw a 8x8 head. +You should also make sure to scale the small image up (to at least 256x256), because else Discord scales it up "for you" which makes it lose it's sharp edges. + +#### Security + +Only two basic hints : Always enable SSL, and don't give your bot token to anyone. +And of course make sure your server isn't hacked. Messages are sent as plain text over the sockets or file bridges. + +## Internal process bridge protocol I "developed" a simple protocol using files for communication between the processes. There are three files : Output, input and logs. After a process has read it's input, it deletes the content. The connected processes both run two threads, one for handling input, and the other for serving the output. It works message-based. Messages are delimited by newlines (linefeed, `\n`). They start with a message-type identifier wrapped in square brackets, followed by the parameters, delimited by spaces. -Example : `[PMS]singlechatter[irc] singleplayer Hi` +Example : `[PMS]singlechatter[irc] singleplayer Hi` \ No newline at end of file diff --git a/main.lua b/main.lua index 2e50a13..1530338 100644 --- a/main.lua +++ b/main.lua @@ -4,10 +4,39 @@ -- * log config (should messages which are not sent to all, pms be logged etc), issues with GDPR and similar stuff possible -- * add commands for registering/unregistering roles & adding/removing roles, but this is more of an API -log.create_channel("adv_chat") -- Create log channel -data.create_mod_storage("adv_chat") --Create mod storage -player_ext.set_property_default("adv_chat.roles",{}) -player_ext.set_property_default("adv_chat.blocked",{chatters={}, roles={}}) +modlib.log.create_channel("adv_chat") -- Create log channel +modlib.data.create_mod_storage("adv_chat") --Create mod storage +modlib.player.set_property_default("adv_chat.roles",{}) +modlib.player.set_property_default("adv_chat.blocked",{chatters={}, roles={}}) + +registered_on_chat_messages = {} + +function register_on_chat_message(func) + table.insert(registered_on_chat_messages, func) +end + +register_on_chat_message(function(sendername, content, msg) + if not msg.targets then + modlib.log.write("adv_chat", "[MSG] "..sendername..": "..content) + end +end) + +function unregister_on_chat_message(func) + for index, func_2 in modlib.table.rpairs(func) do + if func == func_2 then + table.remove(registered_on_chat_messages, index) + end + end +end + +function call_registered_on_chat_messages(name, message, msg_info) + for _, func in ipairs(registered_on_chat_messages) do + if func(name, message, msg_info) then + return true + end + end + return false +end channels={} --channelname -> definition : {hud_pos, mode, autoremove, max_messages, max_lines, wrap_chars, smartwrap} roles={} -- Role -> players -> true @@ -62,6 +91,12 @@ end function send_to_targets(msg) message.mentionpart(msg) + if modlib.table.is_empty(msg.valid_targets) then + return + end + if message.handle_on_chat_messages(msg) then + return msg.handled_by_on_chat_messages + end --IFNDEF bridge local discord_mentioned, irc_mentioned=msg.targets.discord, msg.targets.irc --ENDIF @@ -236,6 +271,9 @@ function get_color(chatter) end function send_to_all(msg) + if message.handle_on_chat_messages(msg) then + return msg.handled_by_on_chat_messages + end --IFNDEF irc if msg.sent_to ~= "irc" then irc_bridge.write("[MSG]"..message.build(msg, "irc")) @@ -358,7 +396,7 @@ on_chat_message=function(sender, msg) local adv_msg=message.new(chatters[sender], mentions, msg_content) message.mentionpart(adv_msg) table.insert(mentions, sender) - send_to_targets(adv_msg)--sender, table_ext.set(mentions), msg, mt_msg, "nobody") + send_to_targets(adv_msg) if #adv_msg.invalid_mentions == 1 then minetest.chat_send_player(sender, "The target "..adv_msg.invalid_mentions[1].." is inexistant.") elseif #adv_msg.invalid_mentions > 1 then diff --git a/message.lua b/message.lua index b5ca9c3..42f0069 100644 --- a/message.lua +++ b/message.lua @@ -82,18 +82,21 @@ function message.mentionpart(msg) if not msg.mentionpart then msg.invalid_mentions={} msg.targets={} + msg.valid_targets={} msg.mentionpart={} for _, mention in ipairs(msg.mentions or {}) do if not msg.targets[mention] then msg.targets[mention]=true if roles[mention] then - table.insert(msg.mentionpart,roles[mention].color) + table.insert(msg.mentionpart, roles[mention].color) table.insert(msg.mentionpart, mention) + msg.valid_targets[mention] = roles[mention] elseif chatters[mention] then table.insert(msg.mentionpart, chatters[mention].color) table.insert(msg.mentionpart, mention) + msg.valid_targets[mention] = chatters[mention] else - table.insert(msg.invalid_mentions,mention) + table.insert(msg.invalid_mentions, mention) end end end @@ -196,7 +199,6 @@ function message.mentionpart_target(msg, target) return msg[text] end - function message.build(msg, target) local build=target.."_build" if not msg[build] then @@ -224,4 +226,12 @@ function message.build(msg, target) msg[build]=builder.scheme.message_prefix..(source or "")..(mentions or "")..content..builder.scheme.message_suffix end return msg[build] +end + +function message.handle_on_chat_messages(msg) + local on_chat_messages = call_registered_on_chat_messages(msg.chatter.name, msg.content, msg) + if on_chat_messages then + msg.handled_by_on_chat_messages = on_chat_messages + return on_chat_messages + end end \ No newline at end of file