diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..716df6b --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,17 @@ +unused_args = false +allow_defined_top = true + +globals = { + "minetest", +} + +read_globals = { + string = {fields = {"split"}}, + table = {fields = {"copy", "getn"}}, + + -- Builtin + "vector", "ItemStack", + "dump", "DIR_DELIM", "VoxelArea", "Settings", + + -- MTG +} diff --git a/README.md b/README.md index 94656a0..2eac811 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ -# smart_chat +# Smart Chat A simple chat, which supports chat-channels for minetest. + +## Description +Adds the ability to the plain chat, that user can join to channels. +The mod is easy extendable, because you only have to develop a module and +the chat has new commands. +There is a self-growing Helpsystem integrated too. + +## Requirements: +Minetest 5.x + +## Depends + +## optional Depends + +## License: +GPL 3.0 + +## Download +https://github.com/acmgit/smart_chat diff --git a/cmd_all.lua b/cmd_all.lua new file mode 100644 index 0000000..0f68575 --- /dev/null +++ b/cmd_all.lua @@ -0,0 +1,52 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "all", + Usage = "/c all ", + Description = S("Send's a message on all."), + Parameter = "", + Shortcut = "/c a ", + } + ) + +sc["all"] = function(player, parameter) + + local pprivs = minetest.get_player_privs(player) + + if not pprivs.basic_privs then + minetest.chat_send_player(player,sc.red .. S("Error - require 'basic_privs' privilege.")) + return + end + + if(parameter[2] == nil or parameter[2] == "") then + sc.print(player, sc.red .. S("Error: No Message given.")) + return + end + local message = "" + + for i = 2, #parameter, 1 do + message = message .. " " .. parameter[i] + + end + + local channel = sc.player[player] + + if(sc.player[player] ~= nil) then + minetest.chat_send_all(sc.yellow .. "[" .. sc.yellow .. player .. "@" .. channel + .. sc.yellow .. "] " .. sc.green .. message) + + else + minetest.chat_send_all(sc.yellow .. "[" .. sc.yellow .. player + .. sc.yellow .. "] " .. sc.green .. message) + + end + +end -- sc["all" + + +sc["a"] = function(player, parameter) + + sc["all"](player, parameter) + +end -- sc["a" diff --git a/cmd_channels.lua b/cmd_channels.lua new file mode 100644 index 0000000..5434cb9 --- /dev/null +++ b/cmd_channels.lua @@ -0,0 +1,40 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "channels", + Usage = "/c channels", + Description = S("Lists all channels on the Server."), + Parameter = "<>", + Shortcut = "/c c" + } + ) + +sc["channels"] = function(player) + + local list = {} + local all_player = minetest.get_connected_players() + + sc.print(player, sc.green .. S("Channels on Server:")) + + for _,players in pairs(all_player) do + local pname = players:get_player_name() + if(sc.player[pname] ~= nil) then + list[sc.player[pname]] = sc.player[pname] + + end -- if(sc.player[pname] ~= nil + + end -- for_,players + + for _,entry in pairs(list) do + sc.print(player, sc.orange .. entry) + + end + +end -- sc["list" + +sc["c"] = function(player, parameter) + + sc["channels"](player, parameter) + +end -- sc["l" diff --git a/cmd_help.lua b/cmd_help.lua new file mode 100644 index 0000000..be3b363 --- /dev/null +++ b/cmd_help.lua @@ -0,0 +1,61 @@ +local lib = smart_chat +local S = lib.S + +lib.register_help({ + Name = "help", + Usage = "/c help <> | ", + Description = S("Helpsystem for ") .." " .. lib.modname .. ".", + Parameter = "<> | " .. S("") .. lib.green .. "." .. + "\n" .. lib.orange .. "<>" .. + lib.green.. " - " .. S("Shows you the entire help for ") .. lib.modname .. + "." .. "\n" .. lib.orange .. S("") .. + lib.green .. " - " .. S("Shows you the help for ") .. + lib.modname .. "-" .. S("command") .. ".", + Shortcut = "/c h <> | ", + } + ) + +lib["help"] = function(player, parameter) + if(parameter[2] == "" or parameter[2] == nil) then + lib.print(player, lib.green .. S("Commands for ") .. lib.modname .. " " .. lib.orange .. + lib.version .. "." .. lib.revision .. lib.green .. ".") + for _,value in pairs(lib.helpsystem) do + lib.print(player, lib.yellow .. "---------------") + lib.print(player, lib.green .. S("Name: ") .. lib.orange .. value.Name) + lib.print(player, lib.green .. S("Description: ") .. lib.yellow .. value.Description) + lib.print(player, lib.green .. S("Usage: ") .. lib.orange .. value.Usage) + lib.print(player, lib.green .. S("Parameter: ") .. lib.orange .. value.Parameter) + lib.print(player, lib.green .. S("Shortcut: ") .. lib.orange .. value.Shortcut) + + end -- for _,value + + lib.print(player, lib.yellow .. "---------------") + + else + if(lib.helpsystem[parameter[2]] ~= nil) then + lib.print(player, lib.green .. S("Name: ") .. lib.orange .. + lib.helpsystem[parameter[2]].Name) + lib.print(player, lib.green .. S("Description: ") .. lib.yellow .. + lib.helpsystem[parameter[2]].Description) + lib.print(player, lib.green .. S("Usage: ") .. lib.orange .. + lib.helpsystem[parameter[2]].Usage) + lib.print(player, lib.green .. S("Parameter: ") .. lib.orange .. + lib.helpsystem[parameter[2]].Parameter) + lib.print(player, lib.green .. S("Shortcut: ") .. lib.orange .. + lib.helpsystem[parameter[2]].Parameter) + + else + lib.print(player, lib.red .. S("No entry in help for command") .. " <" .. + lib.orange .. parameter[2] .. lib.red .. "> " .. S("found" .. ".")) + + end -- if(lib.help[parameter[2 + + end -- if(parameter[2] + +end -- function help + +lib["h"] = function(player, parameter) + + lib["help"](player, parameter) + +end -- lib["h" diff --git a/cmd_invite.lua b/cmd_invite.lua new file mode 100644 index 0000000..4656142 --- /dev/null +++ b/cmd_invite.lua @@ -0,0 +1,46 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "invite", + Usage = "/c invite ", + Description = S("Invites a to your Channel."), + Parameter = "", + Shortcut = "/c i ", + } + ) + +sc["invite"] = function(player, parameter) + + if(parameter[2] == nil or parameter[2] == "") then + sc.print(player, sc.red .. S("Error: No playername given.")) + return + end + + local guest = parameter[2] + local channel = sc.player[player] + + if(channel == nil) then + sc.print(player, sc.red .. S("Error: You can not invite a player in the public chat.")) + return + end + + if(minetest.get_player_by_name(guest)) then + minetest.chat_send_player(guest, sc.yellow .. "[" .. sc.yellow .. player .. "@" .. channel .. sc.yellow .. "] " + .. sc.green .. S("Invites you in the channel: ") .. sc.orange .. channel .. ". " + .. sc.green .. S("Enter /c j ") .. sc.green .. channel .. S(" to join the Channel.") + ) + sc.report(player,sc.green .. player .. " invites " .. sc.orange .. guest .. sc.green .. " to join the Channel.") + + else + sc.print(player, sc.red .. S("Error: No Player with the name found.")) + + end + +end -- sc["invite" + +sc["i"] = function(player, parameter) + + sc["invite"](player, parameter) + +end -- sc["i" diff --git a/cmd_join.lua b/cmd_join.lua new file mode 100644 index 0000000..ebee395 --- /dev/null +++ b/cmd_join.lua @@ -0,0 +1,30 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "join", + Usage = "/c join ", + Description = S("Join or change a channel to ."), + Parameter = "", + Shortcut = "/c j ", + } + ) + +sc["join"] = function(player, parameter) + + if(parameter[2] == nil or parameter[2] == "") then + sc.print(player, sc.red .. S("Error: No channel to join given.")) + return + end + + sc.report(player, S("Leaves the Channel.")) + sc.player[player] = parameter[2] + sc.report(player, S("Enter the Channel.")) + +end -- sc["join" + +sc["j"] = function(player, parameter) + + sc["join"](player, parameter) + +end -- sc["j" diff --git a/cmd_leave.lua b/cmd_leave.lua new file mode 100644 index 0000000..dfdb650 --- /dev/null +++ b/cmd_leave.lua @@ -0,0 +1,29 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "leave", + Usage = "/c leave", + Description = S("Leaves a channel"), + Parameter = "", + Shortcut = "/c l", + } + ) + +sc["leave"] = function(player) + if(sc.player[player] ~= nil) then + sc.report(player, S("Leaves the Channel.")) + sc.player[player] = nil + sc.report(player, S("Enter the public Chat.")) + else + sc.print(player, sc.red .. S("Error: You're already in the public chat.")) + + end -- if(sc.player[player] + +end -- sc["leave" + +sc["l"] = function(player, parameter) + + sc["leave"](player, parameter) + +end -- sc["l" diff --git a/cmd_list.lua b/cmd_list.lua new file mode 100644 index 0000000..4557c4d --- /dev/null +++ b/cmd_list.lua @@ -0,0 +1,52 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "list", + Usage = "/c list ", + Description = S("Lists all player in the channel."), + Parameter = "<>", + Shortcut = "/c li ", + } + ) + +sc["list"] = function(player, parameter) + + local channel = sc.player[player] + local all_player = minetest.get_connected_players() + + + if(parameter[2] == "" or parameter[2] == nil) then + sc.print(player, sc.green .. "Player in Channel:") + for _,players in pairs(all_player) do + local pname = players:get_player_name() + + if(sc.check_channel(pname, channel)) then + sc.print(player, sc.orange .. pname) + + end -- if(sc.check_channel + + end -- for_,players + + else + local channelname = parameter[2] + sc.print(player, sc.green .. S("Player in Channel") .. " [" .. sc.orange .. channelname .. sc.green .. "]:") + for _,players in pairs(all_player) do + local pname = players:get_player_name() + + if(sc.check_channel(pname, channelname)) then + sc.print(player, sc.orange .. pname) + + end -- if(sc.check_channel + + end -- for _,players + + end -- if(paramter[2] + +end -- sc["list" + +sc["li"] = function(player, parameter) + + sc["list"](player, parameter) + +end -- sc["l" diff --git a/cmd_toggle.lua b/cmd_toggle.lua new file mode 100644 index 0000000..05594cb --- /dev/null +++ b/cmd_toggle.lua @@ -0,0 +1,34 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "toggle", + Usage = "/c toggle", + Description = S("Turn's the permanent public Chat on or off."), + Parameter = "<>", + Shortcut = "/c t", + } + ) + +sc["toggle"] = function(player) + + local status = sc.public[player] + + if(status == nil) then + sc.public[player] = player + minetest.chat_send_player(player, sc.green .. S("The permanent public chat is now on.")) + + else + sc.public[player] = nil + minetest.chat_send_player(player, sc.orange .. S("The permanent public chat is now off.")) + + end + + +end -- sc["toggle" + +sc["t"] = function(player, parameter) + + sc["toggle"](player, parameter) + +end -- sc["t" diff --git a/cmd_where.lua b/cmd_where.lua new file mode 100644 index 0000000..747e341 --- /dev/null +++ b/cmd_where.lua @@ -0,0 +1,50 @@ +local sc = smart_chat +local S = sc.S + +sc.register_help({ + Name = "where", + Usage = "/c where ", + Description = S("Show's the room, where is."), + Parameter = "<>", + Shortcut = "/c w ", + } + ) + +sc["where"] = function(player, parameter) + + + + if(parameter[2] == "" or parameter[2] == nil) then + sc.print(sc.red .. S("Error: No name given.")) + + else + local pname = parameter[2] + local channel = sc.player[pname] + + if(minetest.get_player_by_name(pname)) then + local room = sc.player[pname] + if(room ~= nil) then + sc.print(player, sc.green .. S("Player [") .. sc.orange .. pname + .. sc.green .. S(" is in Channel {") + .. sc.yellow .. channel .. sc.green .. "}.") + + else + sc.print(player, sc.green .. S("Player [") .. sc.orange .. pname + .. sc.green .. S("] is in the public Chat.")) + + end -- if(room ~= nil) + + else -- if(minetest.get_player_by_name + sc.print(player, sc.red .. S("Error: Player is not online.")) + + end -- if(minetest.get_player_by_name + + end -- if(paramter[2] + +end -- sc["where" + +sc["w"] = function(player, parameter) + + sc["where"](player, parameter) + +end -- sc["w" diff --git a/core.lua b/core.lua new file mode 100644 index 0000000..134a345 --- /dev/null +++ b/core.lua @@ -0,0 +1,33 @@ +local sc = smart_chat + +minetest.register_on_chat_message(function(player, message) + + if(player ~= "" or player ~= nil) then + + --local playername = minetest.get_player_by_name(player) + return sc.chat(player, message) + + + else + + return false -- Systemmessage, no processing for us. + + end + +end) -- register_on_chatmessage() + + +minetest.register_on_joinplayer(function(player) + + local playername = player:get_player_name() + sc.player[playername] = nil -- the public Chat + sc.public[playername] = nil + +end) -- register_on_joinplayer() + +minetest.register_on_leaveplayer(function(player) + local playername = player:get_player_name() + sc.player[playername] = nil + sc.public[playername] = nil + +end) diff --git a/i18n.py b/i18n.py new file mode 100755 index 0000000..5e87937 --- /dev/null +++ b/i18n.py @@ -0,0 +1,426 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. +# +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer +# LGPLv2.1+ +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. + +from __future__ import print_function +import os, fnmatch, re, shutil, errno +from sys import argv as _argv + +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file'] +} + +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 60 + +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["verbose"])} + add output information +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.abspath('~/.minetest/mods/')}") + run_all_subfolders("~/.minetest/mods") + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.+?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + pass + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = modname + "." + language_code + ".tr" + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name): + lOut = [f"# textdomain: {mod_name}\n"] + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + localizedStrings.sort() + lOut.append("") + lOut.append(source) + lOut.append("") + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file +def read_lua_file_strings(lua_file): + lOut = [] + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) + return lOut + +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +def import_tr_file(tr_file): + dOut = {} + text = None + if os.path.exists(tr_file): + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line[:3] == "###": + # Reset comment block if we hit a header + latest_comment_block = None + continue + if line[:1] == "#": + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): + for name in files: + if fnmatch.fnmatch(name, "*.lua"): + fname = os.path.join(root, name) + found = read_lua_file_strings(fname) + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") + + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut + +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print("Unable to find modname in folder " + folder) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir()] + for subfolder in subfolders: + update_mod(subfolder + "/") + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir()]: + update_folder(modfolder + "/") + + +main() diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..be0667f --- /dev/null +++ b/init.lua @@ -0,0 +1,89 @@ +--[[ + **************************************************************** + ******* Simple Chat ****** + ******* A Mod to manage the Chat in Minetest ****** + ******* License: GPL 3.0 ****** + ******* by A.C.M. ****** + **************************************************************** +--]] + + +smart_chat = {} + +local sc = smart_chat +sc.player = {} +sc.public = {} + +sc.version = 1 +sc.revision = 0 + +sc.modname = minetest.get_current_modname() +sc.modpath = minetest.get_modpath(sc.modname) + +local path = sc.modpath + +sc.helpsystem = {} + +-- Colors for Chat +sc.green = minetest.get_color_escape_sequence('#00FF00') +sc.red = minetest.get_color_escape_sequence('#FF0000') +sc.orange = minetest.get_color_escape_sequence('#FF6700') +sc.blue = minetest.get_color_escape_sequence('#0000FF') +sc.yellow = minetest.get_color_escape_sequence('#FFFF00') +sc.purple = minetest.get_color_escape_sequence('#FF00FF') +sc.pink = minetest.get_color_escape_sequence('#FFAAFF') +sc.white = minetest.get_color_escape_sequence('#FFFFFF') +sc.black = minetest.get_color_escape_sequence('#000000') +sc.grey = minetest.get_color_escape_sequence('#888888') +sc.light_blue = minetest.get_color_escape_sequence('#8888FF') +sc.light_green = minetest.get_color_escape_sequence('#88FF88') +sc.light_red = minetest.get_color_escape_sequence('#FF8888') + +sc.S = nil +local S + +if(minetest.get_translator ~= nil) then + S = minetest.get_translator(sc.modname) + +else + S = function ( s ) return s end + +end + +sc.S = S + +dofile(path .. "/lib.lua") +dofile(path .. "/cmd_help.lua") +dofile(path .. "/core.lua") +dofile(path .. "/cmd_join.lua") +dofile(path .. "/cmd_leave.lua") +dofile(path .. "/cmd_list.lua") +dofile(path .. "/cmd_channels.lua") +dofile(path .. "/cmd_all.lua") +dofile(path .. "/cmd_invite.lua") +dofile(path .. "/cmd_toggle.lua") +dofile(path .. "/cmd_where.lua") + +--[[ + **************************************************************** + ******* Registered Chatcommands ****** + **************************************************************** +--]] + +minetest.register_chatcommand("c",{ + param = " ", + privs = { + interact = true, + shout = true + }, + description = S("Gives Simple_Chat a command with or without Parameter.") .. "\n", + func = function(player, cmd) + if(cmd.type == "string") then + cmd = cmd:lower() + end + local command = sc.split(cmd) + sc.check(player, command) + + end -- function + +}) -- minetest.register_chatcommand diff --git a/lib.lua b/lib.lua new file mode 100644 index 0000000..105749c --- /dev/null +++ b/lib.lua @@ -0,0 +1,185 @@ +local lib = smart_chat +local mn = lib.modname +--[[ + **************************************************************** + ******* Function split(parameter) ****** + **************************************************************** + Split Command and Parameter and write it to a table +--]] +function lib.split(parameter) + local cmd = {} + for word in string.gmatch(parameter, "[%w%-%:%.2f%_]+") do + table.insert(cmd, word) + + end -- for word + + return cmd + +end -- function lib.split + +--[[ + **************************************************************** + ******* Function check(command) ****** + **************************************************************** + Check if the command is valid +--]] +function lib.check(player, cmd) + + if(cmd ~= nil and cmd[1] ~= nil) then + if(lib[cmd[1]] ~= nil) then + -- Command is valid, execute it with parameter + lib[cmd[1]](player, cmd) + + else -- A command is given, but + -- Command not found, report it. + if(cmd[1] ~= nil) then + lib.print(player, lib.red .. mn ..": Unknown Command \"" .. + lib.orange .. cmd[1] .. lib.red .. "\".") + + else + if(lib["help"]) then + lib["help"](player, cmd) + + else + lib.print(player, lib.red .. "Unknown Command. No helpsystem available.") + + end --if(distancer["help"] + + end -- if(cmd[1] + + end -- if(distancer[cmd[1 + + else + lib.print(player, lib.red .. "No Command for " .. mn .. " given.") + lib.print(player, lib.red .. "Try /c help.") + + end -- if(not cmd) + +end -- function lib.check(cmd + +--[[ + **************************************************************** + ******* Function register_help() ****** + **************************************************************** + Registers a new Entry in the Helpsystem for an Command. +]]-- +function lib.register_help(entry) + + lib.helpsystem[entry.Name] = { + Name = entry.Name, + Usage = entry.Usage, + Description = entry.Description, + Parameter = entry.Parameter, + Shortcut = entry.Shortcut, + } + +end + +--[[ + **************************************************************** + ******* Function display_chat_message(message) ****** + **************************************************************** +]]-- + +function lib.print(player, text) + local lprint = minetest.chat_send_player + --local playername = minetest.get_player_by_name(player) + lprint(player, text) + +end -- function distancer.print( + +function lib.check_global(cplayer) + if(lib.player[cplayer] == nil) then + return true + + else + return false + + end + +end + +function lib.check_channel(cplayer, channel) + + if(lib.player[cplayer] == channel) then + return true + + else + return false + + end -- if(sc.player[ + +end -- lib.check_channel + +--[[ + **************************************************************** + ******* Function report() ****** + **************************************************************** +]]-- +function lib.report(player, message) + local all_player = minetest.get_connected_players() + local channel = lib.player[player] + + for _,players in pairs(all_player) do + local pname = players:get_player_name() + + if(lib.check_channel(pname, channel)) then + lib.print(pname, lib.orange .. "<" .. lib.yellow .. player .. lib.orange .. "> " .. message) + + end -- if(check_channel + + end -- for _,players + +end -- lib.report( + +--[[ + **************************************************************** + ******* Function print_all() ****** + **************************************************************** +]]-- +function lib.chat(playername, text) + local all_player = minetest.get_connected_players() + local channel = lib.player[playername] -- Get the Channel of the player + + for _,players in pairs(all_player) do + local pname = players:get_player_name() + + if(channel == nil) then + if(lib.check_global(pname)) then + minetest.chat_send_player(pname, "<" .. playername .. ">" .. text) + minetest.log("action", "CHAT: # <" .. playername .. ">" .. text) + + end -- if(lib.check_global( + + elseif(lib.check_channel(pname, channel)) then + minetest.chat_send_player(pname, lib.yellow .. "<" .. lib.orange .. playername .. "@" + .. channel .. lib.yellow .. ">" .. text) + minetest.log("action", "CHAT: # <" .. playername .. "@" .. channel .. ">" .. text) + + end -- if(channel == nil + + end -- for _,players + + -- Send's the message to all public Players too + for _,players in pairs(lib.public) do + if(players ~= playername) then + minetest.chat_send_player(players, "<" .. playername .. ">" .. text) + + end -- if(players ~= playername + + end -- for _,players + + return true + +end -- function chat + +--[[ + **************************************************************** + ******* Function show_version() ****** + **************************************************************** +]]-- + +function lib.show_version() + print("[MOD]" .. lib.modname .. " v " .. lib.version .. "." .. lib.revision .. " loaded. \n") + +end -- lib.show_version diff --git a/locale/smart_chat.de.tr b/locale/smart_chat.de.tr new file mode 100644 index 0000000..2b73202 --- /dev/null +++ b/locale/smart_chat.de.tr @@ -0,0 +1,80 @@ +# textdomain: smart_chat + + +### cmd_all.lua ### + +Error - require 'basic_privs' privilege.=Fehler - 'basic_privs' benötigt. +Error: No Message given.=Fehler: Keine Nachricht angebeben. +Send's a message on all.=Sendet eine Nachricht an alle. + +### cmd_channels.lua ### + +Channels on Server:=Kanäle am Server: +Lists all channels on the Server.=Listet alle Kanäle auf dem Server. + +### cmd_help.lua ### + += +Commands for =Kommanden für +Description: =Beschreibung: +Helpsystem for =Hilfssystem für +Name: =Name: +No entry in help for command=Kein Eintrag in der Hilfe für Kommando +Parameter: =Parameter: +Shortcut: =Kurzbefehl: +Shows you the entire help for =Zeigt dir die gesamte Hilfe für +Shows you the help for =Zeigt dir die Hilfe für +Usage: =Benutzung: +command=Kommando +found.=gefunden. + +### cmd_invite.lua ### + + to join the Channel.= ein, um den Kanal zu betreten. +Enter /c j =Gib /c j +Error: No Player with the name found.=Fehler: Kein Spieler mit dem Namen gefunden. +Error: No playername given.=Fehler: Kein Spielernamen angegeben. +Error: You can not invite a player in the public chat.=Fehler: Du kannst keinen Spieler in den öffentlichen Chat einladen. +Invites a to your Channel.=Lädt einen in deinen Kanal ein. +Invites you in the channel: =Lädt dich ein in den Kanal: + +### cmd_join.lua ### + +Enter the Channel.=Betritt den Kanal. +Error: No channel to join given.=Fehler: Kein Kanal zum Betreten angegeben. +Join or change a channel to .=Betritt oder wechselt den Kanal zu . + +### cmd_join.lua ### +### cmd_leave.lua ### + +Leaves the Channel.=Verlässt den Kanal. + +### cmd_leave.lua ### + +Enter the public Chat.=Betritt den öffentlichen Chat. +Error: You're already in the public chat.=Fehler: Du bist schon im öffentlichem Chat. +Leaves a channel=Verlässt einen Kanal + +### cmd_list.lua ### + +Lists all player in the channel.=Zeigt alle Spieler in dem Kanal. +Player in Channel=Spieler im Kanal + +### cmd_toggle.lua ### + +The permanent public chat is now off.=Der permanente öffentliche Chat ist nun aus. +The permanent public chat is now on.=Der permanente öffentliche Chat ist nun ein. +Turn's the permanent public Chat on or off.=Schaltet den permanent öffentlichen Chat ein oder aus. + +### cmd_where.lua ### + + is in Channel {= ist im Kanal { +Error: No name given.=Fehler: Keinen Namen angegeben. +Error: Player is not online.=Fehler: Spieler ist nicht online. +Player [=Spieler [ +Show's the room, where is.=Zeigt den Raum, wo sich aufhält. +] is in the public Chat.=] ist im öffentlichem Chat. + +### init.lua ### + +Gives Simple_Chat a command with or without Parameter.=Gibt Simple_Chat ein Kommando mit oder ohne Parameter. diff --git a/locale/template.txt b/locale/template.txt new file mode 100644 index 0000000..985f43b --- /dev/null +++ b/locale/template.txt @@ -0,0 +1,80 @@ +# textdomain: smart_chat + + +### cmd_all.lua ### + +Error - require 'basic_privs' privilege.= +Error: No Message given.= +Send's a message on all.= + +### cmd_channels.lua ### + +Channels on Server:= +Lists all channels on the Server.= + +### cmd_help.lua ### + += +Commands for = +Description: = +Helpsystem for = +Name: = +No entry in help for command= +Parameter: = +Shortcut: = +Shows you the entire help for = +Shows you the help for = +Usage: = +command= +found.= + +### cmd_invite.lua ### + + to join the Channel.= +Enter /c j = +Error: No Player with the name found.= +Error: No playername given.= +Error: You can not invite a player in the public chat.= +Invites a to your Channel.= +Invites you in the channel: = + +### cmd_join.lua ### + +Enter the Channel.= +Error: No channel to join given.= +Join or change a channel to .= + +### cmd_join.lua ### +### cmd_leave.lua ### + +Leaves the Channel.= + +### cmd_leave.lua ### + +Enter the public Chat.= +Error: You're already in the public chat.= +Leaves a channel= + +### cmd_list.lua ### + +Lists all player in the channel.= +Player in Channel= + +### cmd_toggle.lua ### + +The permanent public chat is now off.= +The permanent public chat is now on.= +Turn's the permanent public Chat on or off.= + +### cmd_where.lua ### + + is in Channel {= +Error: No name given.= +Error: Player is not online.= +Player [= +Show's the room, where is.= +] is in the public Chat.= + +### init.lua ### + +Gives Simple_Chat a command with or without Parameter.= diff --git a/mod.conf b/mod.conf new file mode 100644 index 0000000..215c734 --- /dev/null +++ b/mod.conf @@ -0,0 +1 @@ +name = smart_chat