diff --git a/README.html b/README.html index eafdc6a..dc43b96 100644 --- a/README.html +++ b/README.html @@ -316,10 +316,10 @@ Written by Drew (@topkecleon) and Kay M (@gnadelwartz).
/start
 
 You are Botadmin
-*Available commands*:
-*• /start*: _Start bot and get this message_.
-*• /help*: _Get this message_.
-*• /info*: _Get shorter info message about this bot_....
+Available commands:
+  /start: _Start bot and get this message_.
+  /help: _Get this message_.
+  /info: _Get shorter info message about this bot_....
 
 /info
 
@@ -329,12 +329,12 @@ It features background tasks and interactive chats, and can serve as an interfac
 

Log files

Bashbot actions are logged to BASHBOT.log. Telegram send/receive errors are logged to ERROR.log. Start bashbot in debug mode to see all messages sent to / received from Telegram, as well as bash command error messages.

To enable debug mode, start bashbot with debug as third argument: bashbot start debug

-
├── logs 
-│   ├── BASHBOT.log      # log what your bot is doing ...
-│   ├── ERROR.log        # connection errors from / to Telegram API
-│   │
-│   ├── DEBUG.log        # stdout/stderr of you bot (debug mode enabled)
-│   └── MESSAGE.log      # full text of all message send/received (debug mode enabled)
+
|__ logs
+|     |__ BASHBOT.log  # log what your bot is doing ...
+|     |__ ERROR.log    # connection errors from / to Telegram API
+|     |
+|     |__ DEBUG.log    # stdout/stderr of you bot (debug mode enabled)
+|     |__ MESSAGE.log  # full text of all message send/received (debug mode enabled)

Security Considerations

Running a Telegram Bot means it is connected to the public and you never know what's send to your Bot.

@@ -390,6 +390,6 @@ It features background tasks and interactive chats, and can serve as an interfac

@Gnadelwartz

That's it all guys!

If you feel that there's something missing or if you found a bug, feel free to submit a pull request!

-

$$VERSION$$ v1.21-0-gc85af77

+

$$VERSION$$ v1.30-0-g3266427

diff --git a/README.md b/README.md index 0b28c5d..87c5d05 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,10 @@ Now open the Telegram App on your mobile phone and start a chat with your bot (_ /start You are Botadmin -*Available commands*: -*• /start*: _Start bot and get this message_. -*• /help*: _Get this message_. -*• /info*: _Get shorter info message about this bot_.... +Available commands: + /start: _Start bot and get this message_. + /help: _Get this message_. + /info: _Get shorter info message about this bot_.... /info @@ -124,12 +124,12 @@ Start bashbot in debug mode to see all messages sent to / received from Telegram To enable debug mode, start bashbot with debug as third argument: `bashbot start debug` ``` -├── logs -│   ├── BASHBOT.log # log what your bot is doing ... -│   ├── ERROR.log # connection errors from / to Telegram API -│   │ -│   ├── DEBUG.log # stdout/stderr of you bot (debug mode enabled) -│   └── MESSAGE.log # full text of all message send/received (debug mode enabled) +|__ logs +| |__ BASHBOT.log # log what your bot is doing ... +| |__ ERROR.log # connection errors from / to Telegram API +| | +| |__ DEBUG.log # stdout/stderr of you bot (debug mode enabled) +| |__ MESSAGE.log # full text of all message send/received (debug mode enabled) ``` ---- @@ -238,4 +238,4 @@ See `mycommnds.sh.dist` for an example. If you feel that there's something missing or if you found a bug, feel free to submit a pull request! -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/README.txt b/README.txt index f05309f..c558942 100644 --- a/README.txt +++ b/README.txt @@ -1,324 +1,313 @@ -

-Bashbot - A Telegram bot written in bash. -

+ + .. + **** + ****oooooo***** + *****ooooooooooooo***** + *****oooooooooooooooooooooo**** + ****oooooooooooooooooooooooooooooooo** + *.*oooooooooooooooooooooooooooooooooooo** + *.ooooooooooooooooooooooooooooooooo**.... + *.oooooooooooooooooooooooooooo**......... + *.oooooooooooooooooooooooo**............. + *.ooooooooooooooooooo**.................. ____ _ _ _ + *.ooooooooooooooooo*.......,............. | _ \ | | | | | | + *.ooooooooooooooooo*.....,***,........... | |_) | __ _ ___ | |__ | |__ ___ | |_ + *.ooooooooooooooooo*....o*............... | _ < / _` |/ __|| '_ \ | '_ \ / _ \ | __| + *.ooooooooooooooooo*....*o***,........... | |_) || (_| |\__ \| | | || |_) || (_) || |_ + *.*oooooooooooooooo*........o*.....oo.... |____/ \__,_||___/|_| |_||_.__/ \___/ \__| + ****ooooooooooooo*....`***....oo.....* + *****oooooooo*......*..oo.....** + ******ooo*.............* + ***o*........** + **...** + + + + + +Bashbot README + + + Bashbot - A Telegram bot written in bash. + Written by Drew (@topkecleon) and Kay M (@gnadelwartz). +Contributions by Daniil Gentili (@danog), JuanPotato, BigNerd95, TiagoDanin, iicc1 and +dcoomber. +Released to the public domain wherever applicable. Elsewhere, consider it released under +the WTFPLv2 [http://www.wtfpl.net/txt/copying/]. +Linted by #ShellCheck -Contributions by Daniil Gentili (@danog), JuanPotato, BigNerd95, TiagoDanin, -iicc1 and dcoomber. +Prerequisites -Released to the public domain wherever applicable. -Elsewhere, consider it released under the -[WTFPLv2](http://www.wtfpl.net/txt/copying/). +Uses JSON.sh [http://github.com/dominictarr/JSON.sh] and the magic of sed. +Bashbot is written in bash. It depends on commands typically available in a Linux/Unix +Environment. For more concrete information on the common commands provided by recent +versions of coreutils [https://en.wikipedia.org/wiki/List_of_GNU_Core_Utilities_commands], +busybox or toybox, see Developer_Notes. +Note for MacOS and BSD Users: Bashbot will not run without installing additional software +as it uses modern bash and (gnu) grep/sed features. See Install Bashbot [doc/ +0_install.md]. +Note for embedded systems: You need to install a "real" bash as the vanilla installation +of busybox or toybox is not sufficient. See Install Bashbot [doc/0_install.md]. +Bashbot Documentation [https://github.com/topkecleon/telegram-bot-bash] and Downloads are +available on www.github.com. -Linted by [#ShellCheck](https://github.com/koalaman/shellcheck) +Documentation -## Prerequisites -Uses [JSON.sh](http://github.com/dominictarr/JSON.sh) and the magic of sed. -Bashbot is written in bash. It depends on commands typically available in a -Linux/Unix Environment. -For more concrete information on the common commands provided by recent -versions of -[coreutils](https://en.wikipedia.org/wiki/List_of_GNU_Core_Utilities_commands), -[busybox](https://en.wikipedia.org/wiki/BusyBox#Commands) or -[toybox](https://landley.net/toybox/help.html), see [Developer -Notes](doc/7_develop.md#common-commands). +* Introduction to Telegram Bots [https://core.telegram.org/bots] +* Install Bashbot [doc/0_install.md] -**Note for MacOS and BSD Users:** Bashbot will not run without installing -additional software as it uses modern bash and (gnu) grep/sed features. See -[Install Bashbot](doc/0_install.md). + o Install release + o Install from github + o Update Bashbot + o Notes on Updates -**Note for embedded systems:** You need to install a "real" bash as the vanilla -installation of busybox or toybox is not sufficient. See [Install -Bashbot](doc/0_install.md). +* Get Bottoken from Botfather [doc/1_firstbot.md] +* Getting Started [doc/2_usage.md] -Bashbot [Documentation](https://github.com/topkecleon/telegram-bot-bash) and -[Downloads](https://github.com/topkecleon/telegram-bot-bash/releases) are -available on [www.github.com](https://www.github.com). + o Managing your Bot + o Receive data + o Send messages + o Send files, locations, keyboards -## Documentation -* [Introduction to Telegram Bots](https://core.telegram.org/bots) -* [Install Bashbot](doc/0_install.md) - * Install release - * Install from github - * Update Bashbot - * Notes on Updates -* [Get Bottoken from Botfather](doc/1_firstbot.md) -* [Getting Started](doc/2_usage.md) - * Managing your Bot - * Receive data - * Send messages - * Send files, locations, keyboards -* [Advanced Features](doc/3_advanced.md) - * Access Control - * Interactive Chats - * Background Jobs - * Inline queries - * Send message errors -* [Expert Use](doc/4_expert.md) - * Handling UTF-8 character sets - * Run as other user or system service - * Schedule bashbot from Cron - * Use from CLI and Scripts - * Customize Bashbot Environment -* [Best Practices](doc/5_practice.md) - * Customize mycommands.sh - * Overwrite/disable commands - * Separate logic from commands - * Test your Bot with shellcheck -* [Function Reference](doc/6_reference.md) - * Sending Messages, Files, Keyboards - * User Access Control - * Inline Queries - * jsshDB Bashbot key-value storage - * Background and Interactive Jobs -* [Developer Notes](doc/7_develop.md) - * Debug bashbot - * Modules, addons, events - * Setup your environment - * Bashbot test suite -* [Examples Directory](examples/README.md) +* Advanced Features [doc/3_advanced.md] -### Your very first bashbot in a nutshell + o Access Control + o Interactive Chats + o Background Jobs + o Inline queries + o Send message errors -To install and run bashbot you need access to a Linux/Unix command line with -bash, a [Telegram client](https://telegram.org) and a mobile phone [with a -Telegram account](https://telegramguide.com/create-a-telegram-account/). +* Expert Use [doc/4_expert.md] -First you need to [create a new Telegram Bot token](doc/1_firstbot.md) for your -bot and write it down. + o Handling UTF-8 character sets + o Run as other user or system service + o Schedule bashbot from Cron + o Use from CLI and Scripts + o Customize Bashbot Environment -Now open a Linux/Unix terminal with bash, create a new directory, change to it -and install telegram-bot-bash: +* Best Practices [doc/5_practice.md] -```bash -# create bot dir -mkdir mybot -cd mybot + o Customize mycommands.sh + o Overwrite/disable commands + o Separate logic from commands + o Test your Bot with shellcheck -# download latest release with wget or from -https://github.com/topkecleon/telegram-bot-bash/releases/latest -wget "https://github.com/$(wget -q -"https://github.com/topkecleon/telegram-bot-bash/releases/latest" -O - | egrep -'/.*/download/.*/.*tar.gz' -o)" +* Function Reference [doc/6_reference.md] -# Extract the tar archive and go into bot dir -tar -xzf *.tar.gz -cd telegram-bot-bash + o Sending Messages, Files, Keyboards + o User Access Control + o Inline Queries + o jsshDB Bashbot key-value storage + o Background and Interactive Jobs -# initialize your bot -# Enter your bot token when asked, all other questions can be answered by -hitting the \ key. -./bashbot.sh init +* Developer Notes [doc/7_develop.md] -# Now start your bot -./bashbot.sh start + o Debug bashbot + o Modules, addons, events + o Setup your environment + o Bashbot test suite -Bottoken is valid ... -Bot Name: yourbotname_bot -Session Name: yourbotname_bot-startbot -Bot started successfully. -``` +* Examples Directory [examples/README.md] -Now open the Telegram App on your mobile phone and start a chat with your bot -(_your bot's username is shown after 'Bot Name:'_): -``` -/start +Your very first bashbot in a nutshell -You are Botadmin -*Available commands*: -*• /start*: _Start bot and get this message_. -*• /help*: _Get this message_. -*• /info*: _Get shorter info message about this bot_.... +To install and run bashbot you need access to a Linux/Unix command line with bash, a +Telegram client [https://telegram.org] and a mobile phone with_a_Telegram_account. +First you need to create a new Telegram Bot token [doc/1_firstbot.md] for your bot and +write it down. +Now open a Linux/Unix terminal with bash, create a new directory, change to it and install +telegram-bot-bash: -/info + # create bot dir + mkdir mybot + cd mybot -This is bashbot, the Telegram bot written entirely in bash. -It features background tasks and interactive chats, and can serve as an -interface for CLI programs. -``` -For more Information on how to install, customize and use your new bot, read -the [Documentation](#Documentation). + # download latest release with wget or from https://github.com/topkecleon/telegram-bot- + bash/releases/latest + wget "https://github.com/$(wget -q "https://github.com/topkecleon/telegram- + bot-bash/releases/latest" -O - | egrep '/.*/download/.*/.*tar.gz' -o)" -### Log files + # Extract the tar archive and go into bot dir + tar -xzf *.tar.gz + cd telegram-bot-bash -Bashbot actions are logged to `BASHBOT.log`. Telegram send/receive errors are -logged to `ERROR.log`. -Start bashbot in debug mode to see all messages sent to / received from + # initialize your bot + # Enter your bot token when asked, all other questions can be answered by hitting the + \ key. + ./bashbot.sh init + + # Now start your bot + ./bashbot.sh start + + Bottoken is valid ... + Bot Name: yourbotname_bot + Session Name: yourbotname_bot-startbot + Bot started successfully. + +Now open the Telegram App on your mobile phone and start a chat with your bot (your bot's +username is shown after 'Bot Name:'): + + /start + + You are Botadmin + Available commands: + /start: _Start bot and get this message_. + /help: _Get this message_. + /info: _Get shorter info message about this bot_.... + + /info + + This is bashbot, the Telegram bot written entirely in bash. + It features background tasks and interactive chats, and can serve as an interface for + CLI programs. + +For more Information on how to install, customize and use your new bot, read the +Documentation [#Documentation]. + +Log files + +Bashbot actions are logged to BASHBOT.log. Telegram send/receive errors are logged to +ERROR.log. Start bashbot in debug mode to see all messages sent to / received from Telegram, as well as bash command error messages. +To enable debug mode, start bashbot with debug as third argument: bashbot start debug -To enable debug mode, start bashbot with debug as third argument: `bashbot -start debug` + |__ logs + | |__ BASHBOT.log # log what your bot is doing ... + | |__ ERROR.log # connection errors from / to Telegram API + | | + | |__ DEBUG.log # stdout/stderr of you bot (debug mode enabled) + | |__ MESSAGE.log # full text of all message send/received (debug mode enabled) -``` -├── logs -│   ├── BASHBOT.log # log what your bot is doing ... -│   ├── ERROR.log # connection errors from / to Telegram API -│   │ -│   ├── DEBUG.log # stdout/stderr of you bot (debug mode enabled) -│   └── MESSAGE.log # full text of all message send/received (debug mode -enabled) -``` +------------------------------------------------------------------------------------------ ----- +Security Considerations -## Security Considerations -Running a Telegram Bot means it is connected to the public and you never know -what's send to your Bot. +Running a Telegram Bot means it is connected to the public and you never know what's send +to your Bot. +Bash scripts in general are not designed to be bulletproof, so consider this Bot as a +proof of concept. Bash programmers often struggle with 'quoting hell' and globbing, see +Implications of wrong quoting [https://unix.stackexchange.com/questions/171346/security- +implications-of-forgetting-to-quote-a-variable-in-bash-posix-shells]. +Whenever you are processing input from untrusted sources (messages, files, network) you +must be as careful as possible (e.g. set IFS appropriately, disable globbing with set - +f and quote everything). In addition remove unused scripts and examples from your Bot +(e.g. everything in example/) and disable/remove all unused bot commands. +It's important to escape or remove $ in input from user, files or network (as bashbot +does). One of the powerful features of Unix shells is variable and command substitution +using ${} and$() can lead to remote code execution (RCE) or remote information disclosure +(RID) bugs if unescaped $ is included in untrusted input (e.g. $$ or $(rm -rf /*)). +A powerful tool to improve your scripts is shellcheck. You can use it online [https:// +www.shellcheck.net/] or install_shellcheck_locally. Shellcheck is used extensively in +bashbot development to ensure a high code quality (e.g. it's not allowed to push changes +without passing all shellcheck tests). In addition bashbot has a test_suite to check if +important functionality is working as expected. -Bash scripts in general are not designed to be bulletproof, so consider this -Bot as a proof of concept. -Bash programmers often struggle with 'quoting hell' and globbing, -see [Implications of wrong -quoting](https://unix.stackexchange.com/questions/171346/security-implications-o -f-forgetting-to-quote-a-variable-in-bash-posix-shells). +Use printf whenever possible -Whenever you are processing input from untrusted sources (messages, files, -network) you must be as careful as possible -(e.g. set IFS appropriately, disable globbing with `set -f` and quote -everything). In addition remove unused scripts and examples -from your Bot (e.g. everything in `example/`) and disable/remove all unused bot -commands. +If you're writing a script that accepts external input (e.g. from the user as arguments or +the file system), you shouldn't use echo to display it. Use printf whenever possible +[https://unix.stackexchange.com/a/6581]. -It's important to escape or remove `$` in input from user, files or network -(_as bashbot does_). -One of the powerful features of Unix shells is variable and command -substitution using `${}` and`$()` can lead to remote code execution (RCE) or -remote information disclosure (RID) bugs if unescaped `$` is included in -untrusted input (e.g. `$$` or `$(rm -rf /*)`). +Run your Bot as a restricted user -A powerful tool to improve your scripts is `shellcheck`. You can [use it -online](https://www.shellcheck.net/) or -[install shellcheck -locally](https://github.com/koalaman/shellcheck#installing). Shellcheck is used -extensively in bashbot development -to ensure a high code quality (e.g. it's not allowed to push changes without -passing all shellcheck tests). -In addition bashbot has a [test suite](doc/7_develop.md) to check if important -functionality is working as expected. +I recommend running your bot as a user with almost no access rights. All files your Bot +has write access to are in danger of being overwritten/deleted if your bot is hacked. For +the same reason every file your Bot can read is in danger of being disclosed. Restrict +your Bots access rights to the absolute minimum. +Never run your Bot as root, this is the most dangerous you can do! Usually the user +'nobody' has almost no rights on Linux/Unix systems. See Expert use [doc/4_expert.md] on +how to run your Bot as an other user. -### Use printf whenever possible +Secure your Bot installation -If you're writing a script that accepts external input (e.g. from the user as -arguments or the file system), -you shouldn't use echo to display it. [Use printf whenever -possible](https://unix.stackexchange.com/a/6581). +Your Bot configuration must not be readable by other users. Everyone who can read your +Bots token is able to act as your Bot and has access to all chats the Bot is in! +Everyone with read access to your Bot files can extract your Bots data. Especially your +Bot config inconfig.jssh must be protected against other users. No one except you should +have write access to the Bot files. The Bot should be restricted to have write access +tocount.jssh and data-bot-bash only, all other files must be write protected. +To set access rights for your bashbot installation to a reasonable default runsudo ./ +bashbot.sh init after every update or change to your installation directory. -### Run your Bot as a restricted user -**I recommend running your bot as a user with almost no access rights.** -All files your Bot has write access to are in danger of being -overwritten/deleted if your bot is hacked. -For the same reason every file your Bot can read is in danger of being -disclosed. Restrict your Bots access rights to the absolute minimum. - -**Never run your Bot as root, this is the most dangerous you can do!** Usually -the user 'nobody' has almost no rights on Linux/Unix systems. See [Expert -use](doc/4_expert.md) on how to run your Bot as an other user. - -### Secure your Bot installation -**Your Bot configuration must not be readable by other users.** Everyone who -can read your Bots token is able to act as your Bot and has access to all chats -the Bot is in! - -Everyone with read access to your Bot files can extract your Bots data. -Especially your Bot config in`config.jssh` must be protected against other -users. No one except you should have write access to the Bot files. The Bot -should be restricted to have write access to`count.jssh` and `data-bot-bash` -only, all other files must be write protected. - -To set access rights for your bashbot installation to a reasonable default -run`sudo ./bashbot.sh init` after every update or change to your installation -directory. - -## FAQ - -### Is this Bot insecure? -Bashbot is not more (in)secure than a Bot written in another language. We have -done our best to make it as secure as possible. But YOU are responsible for the -bot commands you wrote and you should know about the risks ... - -**Note:** Up to version 0.941 (mai/22/2020) telegram-bot-bash had a remote code -execution bug, please update if you use an older version! - -### Why Bash and not the much better xyz? -Well, that's a damn good question... maybe because I'm a Unix admin from the -stone age. Nevertheless there are more reasons from my side: - -- bashbot will run wherever bash and (gnu) sed is available, from embedded -Linux to mainframe -- easy to integrate with other shell scripts, e.g. for sending system message / -health status -- no need to install or learn a new programming language, library or framework -- no database, not event driven, not object oriented ... - -### Can I have the single bashbot.sh file back? -At the beginning bashbot was simply the file`bashbot.sh` that you could copy -everywhere and run the bot. Now we have 'commands.sh', 'mycommands.sh', -'modules/*.sh' and much more. - -Hey no problem, if you are finished with your cool bot, -run`dev/make-standalone.sh` to create a stripped down version of your bot -containing only -'bashbot.sh' and 'commands.sh'! For more information see [Create a stripped -down version of your Bot](doc/7_develop.md). - -### Can I send messages from CLI and scripts? -Of course you can send messages from command line and scripts! Simply install -bashbot as [described here](#Your-really-first-bashbot-in-a-nutshell), -send the message '/start' to set yourself as botadmin and then stop the bot -with `./bashbot.sh stop`. - -Bashbot provides some ready to use scripts for sending messages from command -line in `bin/` dir, e.g. `send_message.sh`. - -```bash -bin/send_message.sh BOTADMIN "This is my first message send from CLI" - -bin/send_message.sh --help -``` - -You can also source bashbot for use in your scripts, for more information see -[Expert Use](doc/4_expert.md). +FAQ -### Blocked by telegram? -This may happen if too many or wrong requests are sent to api.telegram.org, -e.g. using a invalid token or invalid API calls. -If the block stay for longer time you can ask telegram service to unblock your -IP-Address. +Is this Bot insecure? +Bashbot is not more (in)secure than a Bot written in another language. We have done our +best to make it as secure as possible. But YOU are responsible for the bot commands you +wrote and you should know about the risks ... +Note: Up to version 0.941 (mai/22/2020) telegram-bot-bash had a remote code execution bug, +please update if you use an older version! + +Why Bash and not the much better xyz? + +Well, that's a damn good question... maybe because I'm a Unix admin from the stone age. +Nevertheless there are more reasons from my side: + +* bashbot will run wherever bash and (gnu) sed is available, from embedded Linux to + mainframe +* easy to integrate with other shell scripts, e.g. for sending system message / health + status +* no need to install or learn a new programming language, library or framework +* no database, not event driven, not object oriented ... + + +Can I have the single bashbot.sh file back? + +At the beginning bashbot was simply the filebashbot.sh that you could copy everywhere and +run the bot. Now we have 'commands.sh', 'mycommands.sh', 'modules/*.sh' and much more. +Hey no problem, if you are finished with your cool bot, rundev/make-standalone.sh to +create a stripped down version of your bot containing only 'bashbot.sh' and 'commands.sh'! +For more information see Create a stripped down version of your Bot [doc/7_develop.md]. + +Can I send messages from CLI and scripts? + +Of course you can send messages from command line and scripts! Simply install bashbot as +described here [#Your-really-first-bashbot-in-a-nutshell], send the message '/start' to +set yourself as botadmin and then stop the bot with ./bashbot.sh stop. +Bashbot provides some ready to use scripts for sending messages from command line in bin/ +dir, e.g. send_message.sh. + + bin/send_message.sh BOTADMIN "This is my first message send from CLI" + + bin/send_message.sh --help + +You can also source bashbot for use in your scripts, for more information see Expert Use +[doc/4_expert.md]. + +Blocked by telegram? + +This may happen if too many or wrong requests are sent to api.telegram.org, e.g. using a +invalid token or invalid API calls. If the block stay for longer time you can ask telegram +service to unblock your IP-Address. You can check with curl or wget if you are blocked by Telegram: -```bash -curl -m 10 https://api.telegram.org/bot -#curl: (28) Connection timed out after 10001 milliseconds -wget -t 1 -T 10 https://api.telegram.org/bot -#Connecting to api.telegram.org (api.telegram.org)|46.38.243.234|:443... -failed: Connection timed out. + curl -m 10 https://api.telegram.org/bot + #curl: (28) Connection timed out after 10001 milliseconds -nc -w 2 api.telegram.org 443 || echo "your IP seems blocked by telegram" -#your IP seems blocked by telegram -``` + wget -t 1 -T 10 https://api.telegram.org/bot + #Connecting to api.telegram.org (api.telegram.org)|46.38.243.234|:443... failed: + Connection timed out. -Bashbot offers the option to recover from broken connections (blocked). -Therefore you can provide a function -named `bashbotBlockRecover()` in `mycommands.sh`, the function is called every -time when a broken connection is detected. - -Possible actions are: Check if network is working, change IP-Address or simply -wait some time. -See `mycommnds.sh.dist` for an example. - ---- + nc -w 2 api.telegram.org 443 || echo "your IP seems blocked by telegram" + #your IP seems blocked by telegram +Bashbot offers the option to recover from broken connections (blocked). Therefore you can +provide a function named bashbotBlockRecover() in mycommands.sh, the function is called +every time when a broken connection is detected. +Possible actions are: Check if network is working, change IP-Address or simply wait some +time. See mycommnds.sh.dist for an example. +------------------------------------------------------------------------------------------ @Gnadelwartz -## That's it all guys! +That's it all guys! -If you feel that there's something missing or if you found a bug, feel free to -submit a pull request! +If you feel that there's something missing or if you found a bug, feel free to submit a +pull request! + +$$VERSION$$ v1.30-0-g3266427 -#### $$VERSION$$ v1.21-0-gc85af77 diff --git a/addons/antiFlood.sh b/addons/antiFlood.sh index b014341..b4012a3 100644 --- a/addons/antiFlood.sh +++ b/addons/antiFlood.sh @@ -4,7 +4,7 @@ # this addon counts how many files, e.g. stickers, are sent to # a chat and takes actions if threshold is reached # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # used events: # @@ -37,7 +37,7 @@ ANTIFL_BAN="5" # 5 minutes # initialize after installation or update if [[ "$1" = "init"* ]]; then - jssh_newDB "addons/$ANTIFL_ME" + jssh_newDB "addons/${ANTIFL_ME}" fi @@ -46,7 +46,7 @@ fi if [[ "$1" = "start"* ]]; then ANTIFL_ADMIN="$(getConfigKey "botadmin")" #load existing chat settings on start - jssh_readDB "ANTIFL_CHATS" "addons/$ANTIFL_ME" + jssh_readDB "ANTIFL_CHATS" "addons/${ANTIFL_ME}" # register to CMD BASHBOT_EVENT_CMD["${ANTIFL_ME}"]="${ANTIFL_ME}_cmd" @@ -71,14 +71,14 @@ if [[ "$1" = "start"* ]]; then "/afdo" | "/afactive") [[ "${CMD[1]}" =~ ^[-0-9]+$ ]] && user_is_botadmin "${USER[ID]}" && chat="$3" ANTIFL_CHATS["${chat}","active"]="yes" - jssh_writeDB "ANTIFL_CHATS" "addons/$ANTIFL_ME" & + jssh_writeDB "ANTIFL_CHATS" "addons/${ANTIFL_ME}" & send_normal_message "${USER[ID]}" "Antiflood activated for chat ${chat}" & ;; # command /afactive starts counter meausares "/afstop") [[ "${CMD[1]}" =~ ^[-0-9]+$ ]] && user_is_botadmin "${USER[ID]}" && chat="$3" ANTIFL_CHATS["${chat}","active"]="no" - jssh_writeDB "ANTIFL_CHATS" "addons/$ANTIFL_ME" & + jssh_writeDB "ANTIFL_CHATS" "addons/${ANTIFL_ME}" & send_normal_message "${USER[ID]}" "Antiflood stopped for chat ${chat}" & ;; esac @@ -90,7 +90,7 @@ if [[ "$1" = "start"* ]]; then # save settings and reset flood level every BAN Min antiFlood_timer(){ ANTIFL_ACTUALS=( ) - jssh_writeDB "ANTIFL_CHATS" "addons/$ANTIFL_ME" & + jssh_writeDB "ANTIFL_CHATS" "addons/${ANTIFL_ME}" & } # register to inline and command diff --git a/addons/example.sh b/addons/example.sh index 1cbf168..87a8caf 100644 --- a/addons/example.sh +++ b/addons/example.sh @@ -4,7 +4,7 @@ # Addons can register to bashbot events at startup # by providing their name and a callback per event # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # # If an event occurs each registered event function is called. # diff --git a/bashbot.rc b/bashbot.rc index bf895c7..9e51c8c 100755 --- a/bashbot.rc +++ b/bashbot.rc @@ -5,7 +5,7 @@ # # tested on: ubuntu, opensuse, debian # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # shellcheck disable=SC2009 # shellcheck disable=SC2181 @@ -30,34 +30,40 @@ runcmd="echo Dry run:" # not activated until you edit lines below runas="nobody" # uncomment one of the example lines to fit your system -# runcmd="su $runas -s /bin/bash -c " # runasuser with *su* -# runcmd="runuser $runas -s /bin/bash -c " # runasuser with *runuser* +# runcmd="su ${runas} -s /bin/bash -c " # runasuser with *su* +# runcmd="runuser ${runas} -s /bin/bash -c " # runasuser with *runuser* # edit the values of the following lines to fit your config: -start="cd /usr/local/telegram-bot-bash; /usr/local/telegram-bot-bash/bashbot.sh" # location of your bashbot.sh script -name='' # your bot name as given to botfather, e.g. mysomething_bot +# your bot installation dir +bashbot="cd /usr/local/telegram-bot-bash; /usr/local/telegram-bot-bash/bashbot.sh" +# your bot name as given to botfather, e.g. mysomething_bot +name="" +# set additionl parameter, e.g. debug +mode="" # END Configuration ####################### -[ "$name" = "" ] && name="$runas" +[ "${name}" = "" ] && name="${runas}" case "$1" in 'start') - $runcmd "$start start" # >/dev/null 2>&1 /dev/null 2>&1 /dev/null 2>&1 + kill -9 $(ps -u "${runas}" | grep inotifywait | sed 's/ .*//') >/dev/null 2>&1 fi ;; *) @@ -83,4 +90,4 @@ case "$1" in RETVAL=1 ;; esac -exit $RETVAL +exit "${RETVAL}" diff --git a/bashbot.sh b/bashbot.sh index 8d7906f..a3a9958 100755 --- a/bashbot.sh +++ b/bashbot.sh @@ -15,8 +15,8 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -# Usage: bashbot.sh [-h|--help|BOTCOMMANDS] - BOTCOMMANDS="start, stop, status, help, init, suspendback, resumeback, killback" +# Usage: bashbot.sh BOTCOMMAND +BOTCOMMANDS="-h help init start stop status suspendback resumeback killback" # # Exit Codes: # 0 - success (hopefully) @@ -30,7 +30,7 @@ # 8 - curl/wget missing # 10 - not bash! # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ################################################################## # emmbeded system may claim bash but it is not @@ -42,7 +42,7 @@ fi # are we running in a terminal? NN="\n" -if [ -t 1 ] && [ -n "$TERM" ]; then +if [ -t 1 ] && [ -n "${TERM}" ]; then INTERACTIVE='yes' RED='\e[31m' GREEN='\e[32m' @@ -63,47 +63,80 @@ if [ "$({ LC_ALL=C.utf-8 printf "%b" "\u1111"; } 2>/dev/null)" = "\u1111" ]; the fi +# in UTF-8 äöü etc. are part of [:alnum:] and ranges (e.g. a-z), but we want ASCII a-z ranges! +# for more information see doc/4_expert.md#Character_classes +azazaz='abcdefghijklmnopqrstuvwxyz' # a-z :lower: +AZAZAZ='ABCDEFGHIJKLMNOPQRSTUVWXYZ' # A-Z :upper: +o9o9o9='0123456789' # 0-9 :digit: +azAZaz="${azazaz}${AZAZAZ}" # a-zA-Z :alpha: +azAZo9="${azAZaz}${o9o9o9}" # a-zA-z0-9 :alnum: + # some important helper functions # returns true if command exist _exists() { - [ "$(type -t "${1}")" = "file" ] + [ "$(type -t "$1")" = "file" ] } # execute function if exists _exec_if_function() { - [ "$(type -t "${1}")" != "function" ] && return 1 + [ "$(type -t "$1")" != "function" ] && return 1 "$@" } # returns true if function exist _is_function() { - [ "$(type -t "${1}")" = "function" ] + [ "$(type -t "$1")" = "function" ] } # round $1 in international notation! , returns float with $2 decimal digits # if $2 is not given or is not a positive number zero is assumed _round_float() { - local digit="${2}"; [[ "${2}" =~ ^[0-9]+$ ]] || digit="0" - { LC_ALL=C.utf-8 printf "%.${digit}f" "${1}"; } 2>/dev/null + local digit="$2"; [[ "$2" =~ ^[${o9o9o9}]+$ ]] || digit="0" + { LC_ALL=C.utf-8 printf "%.${digit}f" "$1"; } 2>/dev/null +} +# date is external, printf is much faster +_date(){ + printf "%(%c)T\n" -1 } setConfigKey() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^[-${azAZo9},._]+$ ]] || return 3 [ -z "${BOTCONFIG}" ] && return 1 printf '["%s"]\t"%s"\n' "${1//,/\",\"}" "${2//\"/\\\"}" >>"${BOTCONFIG}.jssh" } getConfigKey() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 - [ -r "${BOTCONFIG}.jssh" ] && sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' <"${BOTCONFIG}.jssh" | tail -n 1 + [[ "$1" =~ ^[-${azAZo9},._]+$ ]] || return 3 + [ -r "${BOTCONFIG}.jssh" ] && sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' "${BOTCONFIG}.jssh" | tail -n 1 } # check if $1 seems a valid token # return true if token seems to be valid check_token(){ - [[ "${1}" =~ ^[0-9]{8,10}:[a-zA-Z0-9_-]{35}$ ]] && return 0 + [[ "$1" =~ ^[${o9o9o9}]{8,10}:[${azAZo9}_-]{35}$ ]] && return 0 return 1 } # log $1 with date -log_error(){ printf "%s: %s\n" "$(date)" "$*" >>"${ERRORLOG}"; } -log_debug(){ printf "%s: %s\n" "$(date)" "$*" >>"${DEBUGLOG}"; } -log_update(){ printf "%s: %s\n" "$(date)" "$*" >>"${UPDATELOG}"; } +log_error(){ printf "%(%c)T: %s\n" -1 "$*" >>"${ERRORLOG}"; } +log_debug(){ printf "%(%c)T: %s\n" -1 "$*" >>"${DEBUGLOG}"; } +log_update(){ printf "%(%c)T: %s\n" -1 "$*" >>"${UPDATELOG}"; } # log $1 with date, special first \n -log_message(){ printf "\n%s: %s\n" "$(date)" "${1/\\n/$'\n'}" >>"${MESSAGELOG}"; } +log_message(){ printf "\n%(%c)T: %s\n" -1 "${1/\\n/$'\n'}" >>"${MESSAGELOG}"; } +# curl is preferred, try detect curl even not in PATH +# sets BASHBOT_CURL to point to curl +DETECTED_CURL="curl" +detect_curl() { + local file warn="Warning: Curl not detected, try fallback to wget! pls install curl or adjust BASHBOT_CURL/BASHBOT_WGET environment variables." + # custom curl command + [ -n "${BASHBOT_CURL}" ] && return 0 + # use wget + [ -n "${BASHBOT_WGET}" ] && DETECTED_CURL="wget" && return 1 + # default use curl in PATH + BASHBOT_CURL="curl" + _exists curl && return 0 + # search in usual locations + for file in /usr/bin /bin /usr/local/bin; do + [ -x "${file}/curl" ] && BASHBOT_CURL="${file}/curl" && return 0 + done + # curl not in PATH and not in usual locations + DETECTED_CURL="wget" + log_update "${warn}"; [ -n "${BASHBOTDEBUG}" ] && log_debug "${warn}" + return 1 +} # additional tests if we run in debug mode export BASHBOTDEBUG @@ -113,16 +146,16 @@ export BASHBOTDEBUG # shellcheck disable=SC2094 debug_checks(){ { [ -z "${BASHBOTDEBUG}" ] && return - local DATE WHERE MYTOKEN; DATE="$(date)"; WHERE="${1}"; shift - printf "%s: debug_checks: %s: bashbot.sh %s\n" "${DATE}" "${WHERE}" "${@##*/}" + local token where="$1"; shift + printf "%(%c)T: debug_checks: %s: bashbot.sh %s\n" -1 "${where}" "${1##*/}" # shellcheck disable=SC2094 - [ -z "${DEBUGLOG}" ] && printf "%s: %s\n" "${DATE}" "DEBUGLOG not set! ==========" - MYTOKEN="$(getConfigKey "bottoken")" - [ -z "${MYTOKEN}" ] && printf "%s: %s\n" "${DATE}" "Bot token is missing! ==========" - check_token "${MYTOKEN}" || printf "%s: %s\n" "${DATE}" "Invalid bot token! ==========" - [ -z "$(getConfigKey "botadmin")" ] && printf "%s: %s\n" "${DATE}" "Bot admin is missing! ==========" + [ -z "${DEBUGLOG}" ] && printf "%(%c)T: %s\n" -1 "DEBUGLOG not set! ==========" + token="$(getConfigKey "bottoken")" + [ -z "${token}" ] && printf "%(%c)T: %s\n" -1 "Bot token is missing! ==========" + check_token "${token}" || printf "%(%c)T: %s\n%s\n" -1 "Invalid bot token! ==========" "${token}" + [ -z "$(getConfigKey "botadmin")" ] && printf "%(%c)T: %s\n" -1 "Bot admin is missing! ==========" # call user defined debug_checks if exists - _exec_if_function my_debug_checks "${DATE}" "${WHERE}" "$*" + _exec_if_function my_debug_checks "$(_date)" "${where}" "$*" } >>"${DEBUGLOG}" } @@ -142,35 +175,37 @@ MODULEDIR="${SCRIPTDIR}/modules" # adjust locations based on source and real name [[ "${SCRIPT}" != "${REALME}" || "$1" == "source" ]] && SOURCE="yes" -if [ -n "$BASHBOT_HOME" ]; then - SCRIPTDIR="$BASHBOT_HOME" +if [ -n "${BASHBOT_HOME}" ]; then + SCRIPTDIR="${BASHBOT_HOME}" else BASHBOT_HOME="${SCRIPTDIR}" fi -[ -z "${BASHBOT_ETC}" ] && BASHBOT_ETC="$BASHBOT_HOME" -[ -z "${BASHBOT_VAR}" ] && BASHBOT_VAR="$BASHBOT_HOME" +[ -z "${BASHBOT_ETC}" ] && BASHBOT_ETC="${BASHBOT_HOME}" +[ -z "${BASHBOT_VAR}" ] && BASHBOT_VAR="${BASHBOT_HOME}" ADDONDIR="${BASHBOT_ETC:-.}/addons" RUNUSER="${USER}" # USER is overwritten by bashbot array :-(, save original # provide help case "$1" in - ""|"-h"*) [ -z "${SOURCE}" ] && printf "${ORANGE}Available commands: ${GREY}${BOTCOMMANDS}${NN}" && exit + "") [ -z "${SOURCE}" ] && printf "${ORANGE}Available commands: ${GREY}${BOTCOMMANDS}${NN}" && exit ;; - "--h"*) sed -nE -e '/(NOT EDIT)|(shellcheck)/d' -e '3,/###/p' <"$0" + "-h"*) LOGO="${BASHBOT_HOME:-.}/doc/bashbot.ascii" + { [ -r "${LOGO}" ] && cat "${LOGO}" + sed -nE -e '/(NOT EDIT)|(shellcheck)/d' -e '3,/###/p' "$0"; } | more exit;; "help") HELP="${BASHBOT_HOME:-.}/README" if [ -n "${INTERACTIVE}" ];then - _exists w3m && w3m "$HELP.html" && exit - _exists lynx && lynx "$HELP.html" && exit - _exists less && less "$HELP.txt" && exit + _exists w3m && w3m "${HELP}.html" && exit + _exists lynx && lynx "${HELP}.html" && exit + _exists less && less "${HELP}.txt" && exit fi - cat "$HELP.txt" + cat "${HELP}.txt" exit;; esac # OK, ENVIRONMENT is set up, let's do some additional tests -if [[ -z "${SOURCE}" && -z "$BASHBOT_HOME" ]] && ! cd "${RUNDIR}" ; then +if [[ -z "${SOURCE}" && -z "${BASHBOT_HOME}" ]] && ! cd "${RUNDIR}" ; then printf "${RED}ERROR: Can't change to ${RUNDIR} ...${NN}" exit 1 fi @@ -253,7 +288,6 @@ ERRORLOG="${LOGDIR}/ERROR.log" UPDATELOG="${LOGDIR}/BASHBOT.log" MESSAGELOG="${LOGDIR}/MESSAGE.log" -debug_checks "start SOURCE=${SOURCE:-no}" "$@" # read BOTTOKEN from bot database if not set if [ -z "${BOTTOKEN}" ]; then BOTTOKEN="$(getConfigKey "bottoken")" @@ -278,11 +312,11 @@ if ! check_token "${BOTTOKEN}"; then "${RED}:${NC}<35_alphanumeric_characters-hash> ${RED}e.g. =>${NC} 123456789${RED}:${NC}Aa-Zz_0Aa-Zz_1Aa-Zz_2Aa-Zz_3Aa-Zz_4\n\n"\ "${GREY}Your bot token: '${NC}${BOTTOKEN//:/${RED}:${NC}}'\n" - if [[ ! "${BOTTOKEN}" =~ ^[0-9]{8,10}: ]]; then + if [[ ! "${BOTTOKEN}" =~ ^[${o9o9o9}]{8,10}: ]]; then printf "${GREY}\tHint: Bot id not a number or wrong len: ${NC}$(($(wc -c <<<"${BOTTOKEN%:*}")-1)) ${GREY}but should be${NC} 8-10\n" [ -n "$(getConfigKey "botid")" ] && printf "\t${GREEN}Did you mean: \"${NC}$(getConfigKey "botid")${GREEN}\" ?${NN}" fi - [[ ! "${BOTTOKEN}" =~ :[a-zA-Z0-9_-]{35}$ ]] &&\ + [[ ! "${BOTTOKEN}" =~ :[${azAZo9}_-]{35}$ ]] &&\ printf "${GREY}\tHint: Hash contains invalid character or has not len${NC} 35 ${GREY}, hash len is ${NC}$(($(wc -c <<<"${BOTTOKEN#*:}")-1))\n" printf "\n" fi @@ -293,16 +327,13 @@ fi BASHBOT_RETRY="" # retry by default URL="${BASHBOT_URL:-https://api.telegram.org/bot}${BOTTOKEN}" -ME_URL=$URL'/getMe' - -UPD_URL=$URL'/getUpdates?offset=' -GETFILE_URL=$URL'/getFile' +ME_URL=${URL}'/getMe' ################# # BASHBOT COMMON functions declare -rx SCRIPT SCRIPTDIR MODULEDIR RUNDIR ADDONDIR BOTACL DATADIR COUNTFILE -declare -rx BOTTOKEN URL ME_URL UPD_URL GETFILE_URL +declare -rx BOTTOKEN URL ME_URL declare -ax CMD declare -Ax UPD BOTSENT USER MESSAGE URLS CONTACT LOCATION CHAT FORWARD REPLYTO VENUE iQUERY @@ -319,6 +350,7 @@ if [ -r "${COMMANDS}" ]; then else [ -z "${SOURCE}" ] && printf "${RED}Warning: ${COMMANDS} does not exist or is not readable!.${NN}" fi +debug_checks "start SOURCE=${SOURCE:-no}" "$@" ############### # load modules @@ -343,8 +375,8 @@ fi # outputs final filename download() { local empty="no.file" file="${2:-${empty}}" - if [[ "$file" = *"/"* ]] || [[ "$file" = "."* ]]; then file="${empty}"; fi - while [ -f "${DATADIR:-.}/${file}" ] ; do file="$RAMDOM-${file}"; done + if [[ "${file}" = *"/"* ]] || [[ "${file}" = "."* ]]; then file="${empty}"; fi + while [ -f "${DATADIR:-.}/${file}" ] ; do file="${RANDOM}-${file}"; done getJson "$1" >"${DATADIR:-.}/${file}" || return printf '%s\n' "${DATADIR:-.}/${file}" } @@ -373,69 +405,38 @@ killallproc() { # shellcheck disable=SC2046 [ -n "${procid}" ] && kill $(proclist -9 "$1") fi - debug_checks "end killallproc" "${1}" + debug_checks "end killallproc" "$1" } # $ chat $2 msg_id $3 nolog -declare -xr DELETE_URL=$URL'/deleteMessage' +declare -xr DELETE_URL=${URL}'/deleteMessage' delete_message() { - [ -z "$3" ] && log_update "Delete Message CHAT=${1} MSG_ID=${2}" - sendJson "${1}" '"message_id": '"${2}"'' "${DELETE_URL}" + [ -z "$3" ] && log_update "Delete Message CHAT=$1 MSG_ID=$2" + sendJson "$1" '"message_id": '"$2"'' "${DELETE_URL}" } get_file() { [ -z "$1" ] && return - sendJson "" '"file_id": "'"${1}"'"' "${GETFILE_URL}" + sendJson "" '"file_id": "'"$1"'"' "${URL}/getFile" printf '%s\n' "${URL}"/"$(JsonGetString <<< "${res}" '"result","file_path"')" } -# curl is preferred, try detect curl even not in PATH -# return TRUE if curl is found or custom curl detected -# return FALSE if no curl is found or wget is forced by BASHBOT_WGET -# sets BASHBOT_CURL to point to curl -DETECTED_CURL="curl" -function detect_curl() { - # custom curl command - [ -n "${BASHBOT_CURL}" ] && return 0 - # use wget - if [ -n "${BASHBOT_WGET}" ]; then - DETECTED_CURL="wget" - return 1 - fi - # default use curl in PATH - BASHBOT_CURL="curl" - _exists curl && return 0 - # search in usual locations - local file - for file in /usr/bin /bin /usr/local/bin; do - if [ -x "${file}/curl" ]; then - BASHBOT_CURL="${file}/curl" - return 0 - fi - done - # curl not in PATH and not in usual locations - DETECTED_CURL="wget" - local warn="Warning: Curl not detected, try fallback to wget! pls install curl or adjust BASHBOT_CURL/BASHBOT_WGET environment variables." - log_update "${warn}"; [ -n "${BASHBOTDEBUG}" ] && log_debug "${warn}" - return 1 -} - # iconv used to filter out broken utf characters, if not installed fake it if ! _exists iconv; then log_update "Warning: iconv not installed, pls imstall iconv!" function iconv() { cat; } fi -TIMEOUT="${BASHBOT_TIMEOUT}" -[[ "$TIMEOUT" =~ ^[0-9]+$ ]] || TIMEOUT="20" +TIMEOUT="${BASHBOT_TIMEOUT:-20}" +[[ "${TIMEOUT}" =~ ^[${o9o9o9}]+$ ]] || TIMEOUT="20" # usage: sendJson "chat" "JSON" "URL" sendJson(){ local json chat="" - if [ -n "${1}" ]; then - chat='"chat_id":'"${1}"',' - [[ "${1}" == *[!0-9-]* ]] && chat='"chat_id":"'"${1}"' NAN",' # chat id not a number! + if [ -n "$1" ]; then + chat='"chat_id":'"$1"',' + [[ "$1" == *[!${o9o9o9}-]* ]] && chat='"chat_id":"'"$1"' NAN",' # chat id not a number! fi # compose final json json='{'"${chat} $(iconv -f utf-8 -t utf-8 -c <<<"$2")"'}' @@ -451,7 +452,7 @@ sendJson(){ fi # OK here we go ... # route to curl/wget specific function - res="$(sendJson_do "${json}" "${3}")" + res="$(sendJson_do "${json}" "$3")" # check telegram response sendJsonResult "${res}" "sendJson (${DETECTED_CURL})" "$@" [ -n "${BASHBOT_EVENT_SEND[*]}" ] && event_send "send" "${@}" & @@ -463,8 +464,8 @@ sendJson(){ if detect_curl ; then # here we have curl ---- [ -z "${BASHBOT_CURL}" ] && BASHBOT_CURL="curl" + # $1 URL, $2 hack: log getJson if not "" getJson(){ - [[ -n "${BASHBOTDEBUG}" && -n "${3}" ]] && log_debug "getJson (curl) URL=${1##*/}" # shellcheck disable=SC2086 "${BASHBOT_CURL}" -sL -k ${BASHBOT_CURL_ARGS} -m "${TIMEOUT}" "$1" } @@ -473,19 +474,19 @@ if detect_curl ; then sendJson_do(){ # shellcheck disable=SC2086 "${BASHBOT_CURL}" -s -k ${BASHBOT_CURL_ARGS} -m "${TIMEOUT}"\ - -d "${1}" -X POST "${2}" -H "Content-Type: application/json" | "${JSONSHFILE}" -b -n 2>/dev/null + -d "$1" -X POST "$2" -H "Content-Type: application/json" | "${JSONSHFILE}" -b -n 2>/dev/null } #$1 Chat, $2 what, $3 file, $4 URL, $5 caption sendUpload() { [ "$#" -lt 4 ] && return if [ -n "$5" ]; then - [ -n "${BASHBOTDEBUG}" ] &&\ - log_update "sendUpload CHAT=${1} WHAT=${2} FILE=${3} CAPT=${5}" - # shellcheck disable=SC2086 + [ -n "${BASHBOTDEBUG}" ] &&\ + log_update "sendUpload CHAT=$1 WHAT=$2 FILE=$3 CAPT=$5" + # shellcheck disable=SC2086 res="$("${BASHBOT_CURL}" -s -k ${BASHBOT_CURL_ARGS} "$4" -F "chat_id=$1"\ -F "$2=@$3;${3##*/}" -F "caption=$5" | "${JSONSHFILE}" -b -n 2>/dev/null )" else - # shellcheck disable=SC2086 + # shellcheck disable=SC2086 res="$("${BASHBOT_CURL}" -s -k ${BASHBOT_CURL_ARGS} "$4" -F "chat_id=$1"\ -F "$2=@$3;${3##*/}" | "${JSONSHFILE}" -b -n 2>/dev/null )" fi @@ -496,7 +497,6 @@ else # NO curl, try wget if _exists wget; then getJson(){ - [[ -n "${BASHBOTDEBUG}" && -z "${3}" ]] && log_debug "getJson (wget) URL=${1##*/}" # shellcheck disable=SC2086 wget --no-check-certificate -t 2 -T "${TIMEOUT}" ${BASHBOT_WGET_ARGS} -qO - "$1" } @@ -504,8 +504,8 @@ else # usage: "JSON" "URL" sendJson_do(){ # shellcheck disable=SC2086 - wget --no-check-certificate -t 2 -T "${TIMEOUT}" ${BASHBOT_WGET_ARGS} -qO - --post-data="${1}" \ - --header='Content-Type:application/json' "${2}" | "${JSONSHFILE}" -b -n 2>/dev/null + wget --no-check-certificate -t 2 -T "${TIMEOUT}" ${BASHBOT_WGET_ARGS} -qO - --post-data="$1" \ + --header='Content-Type:application/json' "$2" | "${JSONSHFILE}" -b -n 2>/dev/null } sendUpload() { log_error "Sorry, wget does not support file upload" @@ -526,9 +526,9 @@ fi # retry sendJson # $1 function $2 sleep $3 ... $n arguments sendJsonRetry(){ - local retry="${1}"; shift - [[ "${1}" =~ ^\ *[0-9.]+\ *$ ]] && sleep "${1}"; shift - printf "%s: RETRY %s %s %s\n" "$(date)" "${retry}" "${1}" "${2:0:60}" + local retry="$1"; shift + [[ "$1" =~ ^\ *[${o9o9o9}.]+\ *$ ]] && sleep "$1"; shift + printf "%(%c)T: RETRY %s %s %s\n" -1 "${retry}" "$1" "${2:0:60}" case "${retry}" in 'sendJson'*) sendJson "$@" @@ -544,7 +544,7 @@ sendJsonRetry(){ return ;; esac - [ "${BOTSENT[OK]}" = "true" ] && log_error "Retry OK:${retry} ${1} ${2:0:60}" + [ "${BOTSENT[OK]}" = "true" ] && log_error "Retry OK:${retry} $1 ${2:0:60}" } >>"${ERRORLOG}" # process sendJson result @@ -553,19 +553,21 @@ sendJsonRetry(){ sendJsonResult(){ local offset=0 BOTSENT=( ) + Json2Array 'UPD' <<<"$1" [ -n "${BASHBOTDEBUG}" ] && log_message "New Result ==========\n$1" - BOTSENT[OK]="$(JsonGetLine '"ok"' <<< "${1}")" + BOTSENT[OK]="${UPD["ok"]}" if [ "${BOTSENT[OK]}" = "true" ]; then - BOTSENT[ID]="$(JsonGetValue '"result","message_id"' <<< "${1}")" + BOTSENT[ID]="${UPD["result","message_id"]}" + BOTSENT[CHAT]="${UPD["result","chat","id"]}" + [ -n "${UPD["result"]}" ] && BOTSENT[RESULT]="${UPD["result"]}" return # hot path everything OK! else # oops something went wrong! - if [ "${1}" != "" ]; then - BOTSENT[ERROR]="$(JsonGetValue '"error_code"' <<< "${1}")" - BOTSENT[DESCRIPTION]="$(JsonGetString '"description"' <<< "${1}")" - grep -qs -F '"parameters","retry_after"' <<< "${1}" &&\ - BOTSENT[RETRY]="$(JsonGetValue '"parameters","retry_after"' <<< "${1}")" + if [ -n "$1" ]; then + BOTSENT[ERROR]="${UPD["error_code"]}" + BOTSENT[DESCRIPTION]="${UPD["description"]}" + [ -n "${UPD["parameters","retry_after"]}" ] && BOTSENT[RETRY]="${UPD["parameters","retry_after"]}" else BOTSENT[OK]="false" BOTSENT[ERROR]="999" @@ -574,8 +576,8 @@ sendJsonResult(){ # log error [[ "${BOTSENT[ERROR]}" = "400" && "${BOTSENT[DESCRIPTION]}" == *"starting at byte offset"* ]] &&\ offset="${BOTSENT[DESCRIPTION]%* }" - printf "%s: RESULT=%s FUNC=%s CHAT[ID]=%s ERROR=%s DESC=%s ACTION=%s\n" "$(date)"\ - "${BOTSENT[OK]}" "${2}" "${3}" "${BOTSENT[ERROR]}" "${BOTSENT[DESCRIPTION]}" "${4:${offset}:100}" + printf "%(%c)T: RESULT=%s FUNC=%s CHAT[ID]=%s ERROR=%s DESC=%s ACTION=%s\n" -1\ + "${BOTSENT[OK]}" "$2" "$3" "${BOTSENT[ERROR]}" "${BOTSENT[DESCRIPTION]}" "${4:${offset}:100}" # warm path, do not retry on error, also if we use wegt [ -n "${BASHBOT_RETRY}${BASHBOT_WGET}" ] && return @@ -583,8 +585,8 @@ sendJsonResult(){ # throttled, telegram say we send too many messages if [ -n "${BOTSENT[RETRY]}" ]; then BASHBOT_RETRY="$(( ++BOTSENT[RETRY] ))" - printf "Retry %s in %s seconds ...\n" "${2}" "${BASHBOT_RETRY}" - sendJsonRetry "${2}" "${BASHBOT_RETRY}" "${@:3}" + printf "Retry %s in %s seconds ...\n" "$2" "${BASHBOT_RETRY}" + sendJsonRetry "$2" "${BASHBOT_RETRY}" "${@:3}" unset BASHBOT_RETRY return fi @@ -592,12 +594,12 @@ sendJsonResult(){ if [ "${BOTSENT[ERROR]}" == "999" ];then # check if default curl and args are OK if ! curl -sL -k -m 2 "${URL}" >/dev/null 2>&1 ; then - printf "%s: BASHBOT IP Address seems blocked!\n" "$(date)" + printf "%(%c)T: BASHBOT IP Address seems blocked!\n" -1 # user provided function to recover or notify block if _exec_if_function bashbotBlockRecover; then BASHBOT_RETRY="2" - printf "bashbotBlockRecover returned true, retry %s ...\n" "${2}" - sendJsonRetry "${2}" "${BASHBOT_RETRY}" "${@:3}" + printf "bashbotBlockRecover returned true, retry %s ...\n" "$2" + sendJsonRetry "$2" "${BASHBOT_RETRY}" "${@:3}" unset BASHBOT_RETRY fi return @@ -605,9 +607,9 @@ sendJsonResult(){ # are not blocked, default curl and args are working if [ -n "${BASHBOT_CURL_ARGS}" ] || [ "${BASHBOT_CURL}" != "curl" ]; then printf "Problem with \"%s %s\"? retry %s with default config ...\n"\ - "${BASHBOT_CURL}" "${BASHBOT_CURL_ARGS}" "${2}" + "${BASHBOT_CURL}" "${BASHBOT_CURL_ARGS}" "$2" BASHBOT_RETRY="2"; BASHBOT_CURL="curl"; BASHBOT_CURL_ARGS="" - sendJsonRetry "${2}" "${BASHBOT_RETRY}" "${@:3}" + sendJsonRetry "$2" "${BASHBOT_RETRY}" "${@:3}" unset BASHBOT_RETRY fi fi @@ -636,7 +638,7 @@ title2Json(){ # get bot name and id from telegram getBotName() { declare -A BOTARRAY - Json2Array 'BOTARRAY' <<<"$(getJson "$ME_URL" | "${JSONSHFILE}" -b -n 2>/dev/null)" + Json2Array 'BOTARRAY' <<<"$(getJson "${ME_URL}" | "${JSONSHFILE}" -b -n 2>/dev/null)" [ -z "${BOTARRAY["result","username"]}" ] && return 1 # save botname and id setConfigKey "botname" "${BOTARRAY["result","username"]}" @@ -647,15 +649,14 @@ getBotName() { # pure bash implementation, done by KayM (@gnadelwartz) # see https://stackoverflow.com/a/55666449/9381171 JsonDecode() { - local out="$1" remain="" U="" + local remain U out="$1" local regexp='(.*)\\u[dD]([0-9a-fA-F]{3})\\u[dD]([0-9a-fA-F]{3})(.*)' - while [[ "${out}" =~ $regexp ]] ; do + while [[ "${out}" =~ ${regexp} ]] ; do U=$(( ( (0xd${BASH_REMATCH[2]} & 0x3ff) <<10 ) | ( 0xd${BASH_REMATCH[3]} & 0x3ff ) + 0x10000 )) remain="$(printf '\\U%8.8x' "${U}")${BASH_REMATCH[4]}${remain}" out="${BASH_REMATCH[1]}" done - #printf "%b\n" "${out}${remain}" # seems to work ... dealyed to next dev - echo -e "${out}${remain}" + printf "%b\n" "${out}${remain}" } JsonGetString() { @@ -675,7 +676,7 @@ process_updates() { max="$(grep -F ',"update_id"]' <<< "${UPDATE}" | tail -1 | cut -d , -f 2 )" Json2Array 'UPD' <<<"${UPDATE}" for ((num=0; num<=max; num++)); do - process_client "$num" "${debug}" + process_client "${num}" "${debug}" done } @@ -698,12 +699,12 @@ process_client() { MESSAGE[0]="/_edited_message " fi process_message "${num}" "${debug}" - printf "%s: update received FROM=%s CHAT=%s CMD=%s\n" "$(date)" "${USER[USERNAME]:0:20} (${USER[ID]})"\ + printf "%(%c)T: update received FROM=%s CHAT=%s CMD=%s\n" -1 "${USER[USERNAME]:0:20} (${USER[ID]})"\ "${CHAT[USERNAME]:0:20}${CHAT[TITLE]:0:30} (${CHAT[ID]})"\ "${MESSAGE:0:30}${CAPTION:0:30}${URLS[*]:0:30}" >>"${UPDATELOG}" else process_inline "${num}" "${debug}" - printf "%s: iQuery received FROM=%s iQUERY=%s\n" "$(date)"\ + printf "%(%c)T: iQuery received FROM=%s iQUERY=%s\n" -1\ "${iQUERY[USERNAME]:0:20} (${iQUERY[USER_ID]})" "${iQUERY[0]}" >>"${UPDATELOG}" fi ##### @@ -723,7 +724,7 @@ process_client() { jssh_countKeyDB_async "${CHAT[ID]}" "${COUNTFILE}" } -declare -Ax BASHBOT_EVENT_INLINE BASHBOT_EVENT_MESSAGE BASHBOT_EVENT_CMD BASHBOT_EVENT_REPLY BASHBOT_EVENT_FORWARD BASHBOT_EVENT_SEND +declare -Ax BASHBOT_EVENT_INLINE BASHBOT_EVENT_MESSAGE BASHBOT_EVENT_CMD BASHBOT_EVENT_REPLYTO BASHBOT_EVENT_FORWARD BASHBOT_EVENT_SEND declare -Ax BASHBOT_EVENT_CONTACT BASHBOT_EVENT_LOCATION BASHBOT_EVENT_FILE BASHBOT_EVENT_TEXT BASHBOT_EVENT_TIMER BASHBOT_BLOCKED start_timer(){ @@ -737,7 +738,7 @@ start_timer(){ EVENT_SEND="0" event_send() { # max recursion level 5 to avoid fork bombs - (( EVENT_SEND++ )); [ "$EVENT_SEND" -gt "5" ] && return + (( EVENT_SEND++ )); [ "${EVENT_SEND}" -gt "5" ] && return # shellcheck disable=SC2153 for key in "${!BASHBOT_EVENT_SEND[@]}" do @@ -753,7 +754,7 @@ event_timer() { for key in "${!BASHBOT_EVENT_TIMER[@]}" do timer="${key##*,}" - [[ ! "$timer" =~ ^-*[1-9][0-9]*$ ]] && continue + [[ ! "${timer}" =~ ^-*[1-9][0-9]*$ ]] && continue if [ "$(( EVENT_TIMER % timer ))" = "0" ]; then _exec_if_function "${BASHBOT_EVENT_TIMER[${key}]}" "timer" "${key}" "${debug}" [ "$(( EVENT_TIMER % timer ))" -lt "0" ] && \ @@ -845,7 +846,7 @@ event_message() { } pre_process_message(){ - local num="${1}" + local num="$1" # unset everything to not have old values CMD=( ); iQUERY=( ); MESSAGE=(); CHAT=(); USER=(); CONTACT=(); LOCATION=(); unset CAPTION REPLYTO=( ); FORWARD=( ); URLS=(); VENUE=( ); SERVICE=( ); NEWMEMBER=( ); LEFTMEMBER=( ); PINNED=( ); MIGRATE=( ) @@ -858,7 +859,7 @@ pre_process_message(){ return 0 } process_inline() { - local num="${1}" + local num="$1" iQUERY[0]="$(JsonDecode "${UPD["result",${num},"inline_query","query"]}")" iQUERY[USER_ID]="${UPD["result",${num},"inline_query","from","id"]}" iQUERY[FIRST_NAME]="$(JsonDecode "${UPD["result",${num},"inline_query","from","first_name"]}")" @@ -1006,7 +1007,6 @@ process_message() { ######################### # main get updates loop, should never terminate declare -A BASHBOTBLOCKED -export BASHBOT_UPDATELOG="${BASHBOT_UPDATELOG-nolog}" # allow to be "" start_bot() { local DEBUGMSG OFFSET=0 # adaptive sleep defaults @@ -1017,9 +1017,9 @@ start_bot() { DEBUGMSG="Start BASHBOT updates in Mode \"${1:-normal}\" ==========" log_update "${DEBUGMSG}" # redirect to Debug.log - [[ "${1}" == *"debug" ]] && exec &>>"${DEBUGLOG}" - log_debug "${DEBUGMSG}"; DEBUGMSG="${1}" - [[ "${DEBUGMSG}" == "xdebug"* ]] && set -x && unset BASHBOT_UPDATELOG + [[ "$1" == *"debug" ]] && exec &>>"${DEBUGLOG}" + log_debug "${DEBUGMSG}"; DEBUGMSG="$1" + [[ "${DEBUGMSG}" == "xdebug"* ]] && set -x # cleaup old pipes and empty logfiles find "${DATADIR}" -type p -delete find "${DATADIR}" -size 0 -name "*.log" -delete @@ -1033,7 +1033,7 @@ start_bot() { # start timer events if [ -n "${BASHBOT_START_TIMER}" ] ; then # shellcheck disable=SC2064 - trap "event_timer $DEBUGMSG" ALRM + trap "event_timer ${DEBUGMSG}" ALRM start_timer & # shellcheck disable=SC2064 trap "kill -9 $!; exit" EXIT INT HUP TERM QUIT @@ -1042,7 +1042,7 @@ start_bot() { jssh_deleteKeyDB "CLEAN_COUNTER_DATABASE_ON_STARTUP" "${COUNTFILE}" [ -f "${COUNTFILE}.jssh.flock" ] && rm -f "${COUNTFILE}.jssh.flock" # store start time and cleanup botconfig on startup - jssh_updateKeyDB "startup" "$(date)" "${BOTCONFIG}" + jssh_updateKeyDB "startup" "$(_date)" "${BOTCONFIG}" [ -f "${BOTCONFIG}.jssh.flock" ] && rm -f "${BOTCONFIG}.jssh.flock" # read blocked users jssh_readDB_async "BASHBOTBLOCKED" "${BLOCKEDFILE}" @@ -1054,7 +1054,7 @@ start_bot() { # adaptive sleep in ms rounded to next 0.1 s sleep "$(_round_float "${nextsleep}e-3" "1")" # get next update - UPDATE="$(getJson "${UPD_URL}${OFFSET}" "${BASHBOT_UPDATELOG}" 2>/dev/null | "${JSONSHFILE}" -b -n 2>/dev/null | iconv -f utf-8 -t utf-8 -c)" + UPDATE="$(getJson "${URL}/getUpdates?offset=${OFFSET}" 2>/dev/null | "${JSONSHFILE}" -b -n 2>/dev/null | iconv -f utf-8 -t utf-8 -c)" # did we get an response? if [ -n "${UPDATE}" ]; then # we got something, do processing @@ -1067,7 +1067,7 @@ start_bot() { OFFSET="$(grep <<< "${UPDATE}" '\["result",[0-9]*,"update_id"\]' | tail -1 | cut -f 2)" ((OFFSET++)) - if [ "$OFFSET" != "1" ]; then + if [ "${OFFSET}" != "1" ]; then nextsleep="100" process_updates "${DEBUGMSG}" fi @@ -1091,7 +1091,7 @@ start_bot() { # initialize bot environment, user and permissions bot_init() { [ -n "${BASHBOT_HOME}" ] && cd "${BASHBOT_HOME}" || exit 1 - local DEBUG="$1" + local runuser chown touser botname DEBUG="$1" # upgrade from old version # currently no action printf "Check for Update actions ...\n" @@ -1103,24 +1103,37 @@ bot_init() { [ -r "${addons}" ] && source "${addons}" "init" "${DEBUG}" done printf "Done.\n" - # setup bashbot - [[ "${UID}" -eq "0" ]] && RUNUSER="nobody" - printf "Enter User to run bashbot [$RUNUSER]: " - read -r TOUSER - [ -z "$TOUSER" ] && TOUSER="$RUNUSER" - if ! id "$TOUSER" &>/dev/null; then - printf "${RED}User \"$TOUSER\" not found!${NN}" + # ask for bashbot user + runuser="${RUNUSER}"; [ "${UID}" = "0" ] && runuser="nobody" + printf "Enter User to run bashbot [${runuser}]: " + read -r chown + [ -z "${chown}" ] && chown="${runuser}"; touser="${chown%:*}" + # check user ... + if ! id "${touser}" &>/dev/null; then + printf "${RED}User \"${touser}\" does not exist!${NN}" exit 3 - else - printf "Adjusting files and permissions for user \"${TOUSER}\" ...\n" - [ -w "bashbot.rc" ] && sed -i '/^[# ]*runas=/ s/runas=.*$/runas="'$TOUSER'"/' "bashbot.rc" - chmod 711 . - chmod -R o-w ./* - chmod -R u+w "${COUNTFILE}"* "${BLOCKEDFILE}"* "${DATADIR}" logs "${LOGDIR}/"*.log 2>/dev/null - chmod -R o-r,o-w "${COUNTFILE}"* "${BLOCKEDFILE}"* "${DATADIR}" "${BOTACL}" 2>/dev/null - # jsshDB must writeable by owner - find . -name '*.jssh*' -exec chmod u+w \{\} + - chown -R "$TOUSER" . ./* + elif [[ "${UID}" != "0" && "${touser}" != "${runuser}" ]]; then + # different user but not root ... + printf "${ORANGE}You are not root, adjusting permissions may fail. Try \"sudo ./bashbot.sh init\"${NN}Press to stop or to continue..." 1>&2 + [ -n "${INTERACTIVE}" ] && read -r runuser + fi + # adjust permissions + printf "Adjusting files and permissions for user \"${touser}\" ...\n" + chmod 711 . + chmod -R o-w ./* + chmod -R u+w "${COUNTFILE}"* "${BLOCKEDFILE}"* "${DATADIR}" logs "${LOGDIR}/"*.log 2>/dev/null + chmod -R o-r,o-w "${COUNTFILE}"* "${BLOCKEDFILE}"* "${DATADIR}" "${BOTACL}" 2>/dev/null + # jsshDB must writeable by owner + find . -name '*.jssh*' -exec chmod u+w \{\} + + chown -Rf "${chown}" . ./* + printf "Done.\n" + # adjust values in bashbot.rc + if [ -w "bashbot.rc" ]; then + printf "Adjust user and botname in bashbot.rc ...\n" + sed -i '/^[# ]*runas=/ s|runas=.*$|runas="'"${touser}"'"|' "bashbot.rc" + sed -i '/^[# ]*bashbot=/ s|bashbot=.*$|bashbot="cd '"${PWD}"'; '"${PWD}"'/'"${0##*/}"'"|' "bashbot.rc" + botname="$(getConfigKey "botname")" + [ -n "${botname}" ] && sed -i '/^[# ]*name=/ s|name=.*$|name="'"${botname}"'"|' "bashbot.rc" printf "Done.\n" fi # ask to check bottoken online @@ -1135,7 +1148,7 @@ bot_init() { # check if botconf seems valid printf "${GREEN}This is your bot config:${NN}" sed 's/^/\t/' "${BOTCONFIG}.jssh" | grep -vF '["bot_config_key"]' - if check_token "$(getConfigKey "bottoken")" && [[ "$(getConfigKey "botadmin")" =~ ^[0-9]+$ ]]; then + if check_token "$(getConfigKey "bottoken")" && [[ "$(getConfigKey "botadmin")" =~ ^[${o9o9o9}]+$ ]]; then printf "Bot config seems to be valid. Should I make a backup copy? (Y/n) Y\b" read -r ANSWER if [[ -z "${ANSWER}" || "${ANSWER}" =~ ^[^Nn] ]]; then @@ -1167,7 +1180,7 @@ if [ -z "${SOURCE}" ]; then ############## # internal options only for use from bashbot and developers # shellcheck disable=SC2221,SC2222 - case "${1}" in + case "$1" in # update botname when starting only "botname"|"start"*) ME="$(getBotName)" @@ -1179,7 +1192,7 @@ if [ -z "${SOURCE}" ]; then else printf "${GREY}Info: Can't get Botname from Telegram, try cached one ...${NN}" ME="$(getConfigKey "botname")" - if [ -z "$ME" ]; then + if [ -z "${ME}" ]; then printf "${RED}ERROR: No cached botname, can't continue! ...${NN}" exit 1 fi @@ -1194,7 +1207,7 @@ if [ -z "${SOURCE}" ]; then ME="$(getConfigKey "botname")" # read until terminated while read -r line ;do - [ -n "$line" ] && send_message "$2" "$line" + [ -n "${line}" ] && send_message "$2" "${line}" done # cleanup datadir, keep logfile if not empty rm -f -r "${DATADIR:-.}/$3" @@ -1248,7 +1261,7 @@ if [ -z "${SOURCE}" ]; then BOTPID="$(proclist "${SESSION}")" # shellcheck disable=SC2086 [ -n "${BOTPID}" ] && kill ${BOTPID} - nohup "$SCRIPT" "startbot" "$2" "${SESSION}" &>/dev/null & + nohup "${SCRIPT}" "startbot" "$2" "${SESSION}" &>/dev/null & printf "Session Name: %s\n" "${SESSION}" sleep 1 if [ -n "$(proclist "${SESSION}")" ]; then diff --git a/bin/bashbot_env.inc.sh b/bin/bashbot_env.inc.sh index f1da22b..b837412 100644 --- a/bin/bashbot_env.inc.sh +++ b/bin/bashbot_env.inc.sh @@ -13,7 +13,7 @@ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # CREATED: 18.12.2020 12:27 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== ############ diff --git a/bin/bashbot_stats.sh b/bin/bashbot_stats.sh index ddf34d6..53a6b25 100755 --- a/bin/bashbot_stats.sh +++ b/bin/bashbot_stats.sh @@ -3,7 +3,7 @@ # # FILE: bin/bashbot_stats.sh # -# USAGE: bashbot_stats.sh [-h|--help] [debug] +USAGE='bashbot_stats.sh [-h|--help] [debug]' # # DESCRIPTION: output bashbot user stats # @@ -16,14 +16,14 @@ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # CREATED: 23.12.2020 20:34 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== #### # parse args case "$1" in "-h"*) - echo "usage: send_message [-h|--help] [debug]" + printf "usage: %s\n" "${USAGE}" exit 1 ;; '--h'*) diff --git a/bin/delete_message.sh b/bin/delete_message.sh new file mode 100755 index 0000000..6d42a40 --- /dev/null +++ b/bin/delete_message.sh @@ -0,0 +1,63 @@ +#!/bin/bash +#=============================================================================== +# +# FILE: bin/delete_message.sh +# +USAGE='delete_message.sh [-h|--help] "CHAT[ID]" "MESSAGE[ID]" [debug]' +# +# DESCRIPTION: delete a message in the given user/group +# +# OPTIONS: CHAT[ID] - ID number of CHAT or BOTADMIN +# MESSAGE[ID] - message to delete +# +# -h - display short help +# --help - this help +# +# Set BASHBOT_HOME to your installation directory +# +# LICENSE: WTFPLv2 http://www.wtfpl.net/txt/copying/ +# AUTHOR: KayM (gnadelwartz), kay@rrr.de +# CREATED: 03.01.2021 15:37 +# +#### $$VERSION$$ v1.30-0-g3266427 +#=============================================================================== + +#### +# parse args +DELETE="delete_message" +case "$1" in + '') + printf "missing arguments\n" + ;& + "-h"*) + printf 'usage: %s\n' "${USAGE}" + exit 1 + ;; + '--h'*) + sed -n '3,/###/p' <"$0" + exit 1 + ;; +esac + + +# set bashbot environment +# shellcheck disable=SC1090 +source "${0%/*}/bashbot_env.inc.sh" "${3:-debug}" # $3 debug + +#### +#### +# ready, do stuff here ----- +if [ "$1" == "BOTADMIN" ]; then + CHAT="${BOT_ADMIN}" +else + CHAT="$1" +fi + +# delete message +"${DELETE}" "${CHAT}" "$2" + +[ "${BOTSENT[OK]}" = "true" ] && BOTSENT[ID]="$2" + +# output send message result +jssh_printDB "BOTSENT" | sort -r + diff --git a/bin/send_broadcast.sh b/bin/send_broadcast.sh index 2d38083..9ccc90c 100755 --- a/bin/send_broadcast.sh +++ b/bin/send_broadcast.sh @@ -1,15 +1,18 @@ #!/bin/bash #=============================================================================== +# shellcheck disable=SC2059 # # FILE: bin/broadcast_message.sh # -# USAGE: broadcast_message.sh [-h|--help] [--doit] [--groups|--both] [format] "message ...." [debug] +USAGE='broadcast_message.sh [-h|--help] [--doit] [--groups|--both|--db=file] [format] "message ...." [debug]' # -# DESCRIPTION: send a message to all users the bot have seen (listet in count.jssh) +# DESCRIPTION: send a message to all users listed in a jsonDB (default count db) # # OPTIONS: --doit - broadcast is dangerous, simulate run without --doit # --groups - send to groups instead of users -# --both - send to users and groups +# --both - send to users and groups (default with --db) +# --db name - send to all user/groups in jsonDB database (e.g. blocked) +# db file: name.jssh, db keys are user/chat id, values are ignored # # format - normal, markdown, html (optional) # message - message to send in specified format @@ -24,13 +27,13 @@ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # CREATED: 16.12.2020 16:14 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== -# shellcheck disable=SC2059 #### # minimum messages seen in a chat before send a broadcast to it MINCOUNT=2 +USERDB="" #### # broadcast is dangerous, without --doit we do a dry run ... @@ -49,13 +52,18 @@ elif [ "$1" = "--groups" ]; then SENDTO="groups" GROUPSALSO=" only" shift +elif [ "$1" = "--db" ]; then + USERDB="${2%.jssh}" + MINCOUNT="" + GROUPSALSO=" and groups" + shift 2 fi #### # parse args ----------------- SEND="send_message" case "$1" in - "nor*"|"tex*") + "nor"*|"tex"*) SEND="send_normal_message" shift ;; @@ -71,11 +79,11 @@ case "$1" in printf "missing missing arguments\n" ;& "-h"*) - printf 'usage: send_message [-h|--help] [--groups|--both] [format] "message ...." [debug]\n' + printf 'usage: %s\n' "${USAGE}" exit 1 ;; '--h'*) - sed -n '3,/###/p' <"$0" + sed -n -e '/# shellcheck /d' -e '3,/###/p' <"$0" exit 1 ;; esac @@ -85,15 +93,16 @@ esac source "${0%/*}/bashbot_env.inc.sh" "$2" # $3 debug -# read in users +# read in users from given DB or count.jssh +database="${USERDB:-${COUNTFILE}}" declare -A SENDALL -jssh_readDB_async "SENDALL" "${COUNTFILE}" +jssh_readDB_async "SENDALL" "${database}" if [ -z "${SENDALL[*]}" ]; then - printf "${ORANGE}Countfile not found or empty,${NC}\n" + printf "${ORANGE}User database not found or empty: ${NC}${database}\n" fi # loop over users - printf "${GREEN}Sending broadcast message to all users of ${BOT_NAME}${NC}${GREY}" + printf "${GREEN}Sending broadcast message to ${SENDTO}${GROUPSALSO} of ${BOT_NAME} using database:${NC}${GREY} ${database##*/}" { # dry run [ -z "${DOIT}" ] && printf "${NC}\n${ORANGE}DRY RUN! use --doit as first argument to execute broadcast...${NC}\n" @@ -106,7 +115,7 @@ fi # ignore everything not a user or group [[ ! "${USER}" =~ ^[0-9-]*$ ]] && continue # ignore chats with no count or lower MINCOUNT - [[ ! "${SENDALL[${USER}]}" =~ ^[0-9]*$ || "${SENDALL[${USER}]}" -lt "${MINCOUNT}" ]] && continue + [[ -n "${MINCOUNT}" && ( ! "${SENDALL[${USER}]}" =~ ^[0-9]*$ || "${SENDALL[${USER}]}" -lt "${MINCOUNT}" ) ]] && continue (( COUNT++ )) if [ -z "${DOIT}" ]; then printf "${SEND} ${USER} $1 $2\n" diff --git a/bin/send_edit_message.sh b/bin/send_edit_message.sh index ef99801..ad8bb38 100755 --- a/bin/send_edit_message.sh +++ b/bin/send_edit_message.sh @@ -3,11 +3,11 @@ # # FILE: bin/send_message.sh # -# USAGE: send_edit_message.sh [-h|--help] [format] "CHAT[ID]" "MESSAGE[ID]" "message ...." [debug] +USAGE='send_edit_message.sh [-h|--help] [format|caption] "CHAT[ID]" "MESSAGE[ID]" "message ...." [debug]' # # DESCRIPTION: replace a message in the given user/group # -# OPTIONS: format - normal, markdown, html (optional) +# OPTIONS: format - normal, markdown, html or caption for file caption (optional) # CHAT[ID] - ID number of CHAT or BOTADMIN to send to yourself # MESSAGE[ID] - message to replace # message - message to send in specified format @@ -22,14 +22,14 @@ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # CREATED: 23.12.2020 16:52 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== #### # parse args SEND="edit_normal_message" case "$1" in - "nor*"|"tex*") + "nor"*|"tex"*) SEND="edit_normal_message" shift ;; @@ -37,15 +37,19 @@ case "$1" in SEND="edit_markdownv2_message" shift ;; - "html") + "htm"*) SEND="edit_html_message" shift ;; + "cap"*) + SEND="edit_message_caption" + shift + ;; '') printf "missing arguments\n" ;& "-h"*) - printf 'usage: send_edit_message [-h|--help] [format] "CHAT[ID]" "MESSAGE[ID]" "message ..." [debug]\n' + printf 'usage: %s\n' "${USAGE}" exit 1 ;; '--h'*) @@ -54,10 +58,9 @@ case "$1" in ;; esac - # set bashbot environment # shellcheck disable=SC1090 -source "${0%/*}/bashbot_env.inc.sh" "$4" # $4 debug +source "${0%/*}/bashbot_env.inc.sh" "${4:-debug}" # $4 debug #### #### diff --git a/bin/send_file.sh b/bin/send_file.sh index 04d6261..5a42b2c 100755 --- a/bin/send_file.sh +++ b/bin/send_file.sh @@ -2,15 +2,18 @@ #=============================================================================== # # FILE: bin/send_file.sh -# -# USAGE: send_file.sh [-h|--help] "CHAT[ID]" "file" "caption ...." [debug] +# +USAGE='send_file.sh [-h|--help] "CHAT[ID]" "file|URL" "caption ...." [type] [debug]' # # DESCRIPTION: send a file to the given user/group # # OPTIONS: CHAT[ID] - ID number of CHAT or BOTADMIN to send to yourself -# file - file to send, must be an absolute path or relative to pwd +# file - local file to send, must be an absolute path or relative to pwd # Note: must not contain .. or . and located below BASHBOT_ETC +# URL - send an URL instead local file +# # caption - message to send with file +# type - photo, video, sticker, voice, document (optional) # # -h - display short help # --help - this help @@ -21,18 +24,18 @@ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # CREATED: 25.12.2020 20:24 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== #### # parse args -SEND="upload_file" +SEND="send_file" case "$1" in '') printf "missing arguments\n" ;& "-h"*) - printf 'usage: send_file [-h|--help] "CHAT[ID]" "file" "caption ...." [debug]\n' + printf 'usage: %s\n' "${USAGE}" exit 1 ;; '--h'*) @@ -43,7 +46,7 @@ esac # set bashbot environment # shellcheck disable=SC1090 -source "${0%/*}/bashbot_env.inc.sh" "$4" # $4 debug +source "${0%/*}/bashbot_env.inc.sh" "${5:-debug}" # $5 debug #### # ready, do stuff here ----- @@ -54,10 +57,11 @@ else fi FILE="$2" -[[ "$2" != "/"* ]] && FILE="${PWD}/$2" +# convert to absolute path if not start with / or http:// +[[ ! ( "$2" == "/"* || "$2" =~ ^https*:// || "$2" == "file_id://"*) ]] && FILE="${PWD}/$2" # send message in selected format -"${SEND}" "${CHAT}" "${FILE}" "$3" +"${SEND}" "${CHAT}" "${FILE}" "$3" "$4" # output send message result jssh_printDB "BOTSENT" | sort -r diff --git a/bin/send_message.sh b/bin/send_message.sh index 47fac76..21faf2c 100755 --- a/bin/send_message.sh +++ b/bin/send_message.sh @@ -3,7 +3,7 @@ # # FILE: bin/send_message.sh # -# USAGE: send_message.sh [-h|--help] [format] "CHAT[ID]" "message ...." [debug] +USAGE='send_message.sh [-h|--help] [format] "CHAT[ID]" "message ...." [debug]' # # DESCRIPTION: send a message to the given user/group # @@ -21,14 +21,14 @@ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # CREATED: 16.12.2020 11:34 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== #### # parse args SEND="send_message" case "$1" in - "nor*"|"tex*") + "nor"*|"tex"*) SEND="send_normal_message" shift ;; @@ -44,7 +44,7 @@ case "$1" in printf "missing arguments\n" ;& "-h"*) - printf 'usage: send_message [-h|--help] [format] "CHAT[ID]" "message ...." [debug]\n' + printf 'usage: %s\n' "${USAGE}" exit 1 ;; '--h'*) @@ -55,7 +55,7 @@ esac # set bashbot environment # shellcheck disable=SC1090 -source "${0%/*}/bashbot_env.inc.sh" "$3" # $3 debug +source "${0%/*}/bashbot_env.inc.sh" "${3:-debug}" # $3 debug #### # ready, do stuff here ----- diff --git a/commands.sh b/commands.sh index 6d76673..f3f95a4 100644 --- a/commands.sh +++ b/commands.sh @@ -15,11 +15,10 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # -# adjust your language setting here, e.g. when run from other user or cron. -# https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment +# bashbot locale defaults to c.UTF-8, adjust locale in mycommands.sh if needed export 'LC_ALL=C.UTF-8' export 'LANG=C.UTF-8' export 'LANGUAGE=C.UTF-8' @@ -52,12 +51,12 @@ Get the code in my [GitHub](http://github.com/topkecleon/telegram-bot-bash) ' # load modules on startup and always on on debug -if [ -n "${1}" ]; then +if [ -n "$1" ]; then # load all readable modules for modules in "${MODULEDIR:-.}"/*.sh ; do - if [[ "${1}" == *"debug"* ]] || ! _is_function "$(basename "${modules}")"; then + if [[ "$1" == *"debug"* ]] || ! _is_function "$(basename "${modules}")"; then # shellcheck source=./modules/aliases.sh - [ -r "${modules}" ] && source "${modules}" "${1}" + [ -r "${modules}" ] && source "${modules}" "$1" fi done fi @@ -73,13 +72,13 @@ export FILE_REGEX="${BASHBOT_ETC}/.*" # load mycommands # shellcheck source=./commands.sh -[ -r "${BASHBOT_ETC:-.}/mycommands.sh" ] && source "${BASHBOT_ETC:-.}/mycommands.sh" "${1}" +[ -r "${BASHBOT_ETC:-.}/mycommands.sh" ] && source "${BASHBOT_ETC:-.}/mycommands.sh" "$1" -if [ -z "${1}" ] || [[ "${1}" == *"debug"* ]];then +if [ -z "$1" ] || [[ "$1" == *"debug"* ]];then # detect inline commands.... # no default commands, all processing is done in myinlines() - if [ "$INLINE" != "0" ] && [ -n "${iQUERY[ID]}" ]; then + if [ "${INLINE}" != "0" ] && [ -n "${iQUERY[ID]}" ]; then # forward iinline query to optional dispatcher _exec_if_function myinlines diff --git a/dev/all-tests.sh b/dev/all-tests.sh index 4923962..d0a2051 100755 --- a/dev/all-tests.sh +++ b/dev/all-tests.sh @@ -5,17 +5,11 @@ # # Description: run all tests, exit after failed test # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############################################################# -# magic to ensure that we're always inside the root of our application, -# no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -if [ "$GIT_DIR" != "" ] ; then - cd "$GIT_DIR/.." || exit 1 -else - printf "Sorry, no git repository %s\n" "$(pwd)" && exit 1 -fi +#shellcheck disable=SC1090 +source "${0%/*}/dev.inc.sh" ########################## # create test environment @@ -52,7 +46,7 @@ do "${test}" "${TESTENV}" ret=$? set +e - if [ "$ret" -eq 0 ] ; then + if [ "${ret}" -eq 0 ] ; then printf "OK: ---- %s\n" "${test}" passed=$((passed+1)) else @@ -64,7 +58,7 @@ done ########################### # cleanup depending on test state -if [ "$fail" -eq 0 ]; then +if [ "${fail}" -eq 0 ]; then printf 'SUCCESS ' exitcode=0 rm -rf "${TESTENV}" @@ -82,4 +76,4 @@ printf "%s\n\n" "${passed} / ${tests}" ls -ld /tmp/bashbot.test* 2>/dev/null && printf "Do not forget to delete bashbot test files in /tmp!!\n" -exit ${exitcode} +exit "${exitcode}" diff --git a/dev/dev.inc.sh b/dev/dev.inc.sh new file mode 100644 index 0000000..fa0fd75 --- /dev/null +++ b/dev/dev.inc.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +############################################################# +# +# File: dev/dev.inc.sh +# +# Description: common stuff for all dev scripts +# +#### $$VERSION$$ v1.30-0-g3266427 +############################################################# + +# magic to ensure that we're always inside the root of our application, +# no matter from which directory we'll run script + +# shellcheck disable=SC2034 +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +BASE_DIR=$(git rev-parse --show-toplevel 2>/dev/null) +if [ "${BASE_DIR}" != "" ] ; then + cd "${BASE_DIR}" || exit 1 +else + printf "Sorry, no git repository %s\n" "$(pwd)" && exit 1 +fi + diff --git a/dev/git-add.sh b/dev/git-add.sh index 1a6f337..39c94eb 100755 --- a/dev/git-add.sh +++ b/dev/git-add.sh @@ -3,16 +3,10 @@ # # works together with git pre-push.sh and ADD all changed files since last push -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 -# magic to ensure that we're always inside the root of our application, -# no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -if [ "$GIT_DIR" != "" ] ; then - cd "$GIT_DIR/.." || exit 1 -else - printf "Sorry, no git repository %s\n" "$(pwd)" && exit 1 -fi +#shellcheck disable=SC1090 +source "${0%/*}/dev.inc.sh" [ ! -f .git/.lastcommit ] && printf "No previous commit or hooks not installed, use \"git add\" instead ... Abort\n" && exit @@ -31,7 +25,9 @@ for file in ${FILES} do [ -d "${file}" ] && continue printf "%s" "${file} " - git add "$file" done printf " - Done.\n" +# stay with "." for (re)moved files! +git add . + diff --git a/dev/hooks/post-commit.sh b/dev/hooks/post-commit.sh index e8ad88c..e988e64 100755 --- a/dev/hooks/post-commit.sh +++ b/dev/hooks/post-commit.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############ # NOTE: you MUST run install-hooks.sh again when updating this file! @@ -7,7 +7,7 @@ # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +cd "${GIT_DIR}/.." || exit 1 export HOOKDIR="dev/hooks" LASTPUSH='.git/.lastcommit' diff --git a/dev/hooks/pre-commit.sh b/dev/hooks/pre-commit.sh index 8880d05..8b44725 100755 --- a/dev/hooks/pre-commit.sh +++ b/dev/hooks/pre-commit.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############ # NOTE: you MUST run install-hooks.sh again when updating this file! @@ -7,7 +7,7 @@ # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +cd "${GIT_DIR}/.." || exit 1 export HOOKDIR="dev/hooks" LASTPUSH='.git/.lastpush' @@ -32,9 +32,9 @@ set +f FILES="$(find ./* -name '*.sh' | grep -v -e 'DIST\/' -e 'STANDALONE\/' -e 'JSON.sh')" set -f FILES="${FILES} $(sed '/^#/d' <"dev/shellcheck.files")" -if [ "$FILES" != "" ]; then +if [ "${FILES}" != "" ]; then # shellcheck disable=SC2086 - shellcheck -x ${FILES} || exit 1 + shellcheck -o all -e SC2249,SC2154 -x ${FILES} || exit 1 printf " OK\n............................\n" else # something went wrong diff --git a/dev/hooks/pre-push.sh b/dev/hooks/pre-push.sh index 26f4658..710c411 100755 --- a/dev/hooks/pre-push.sh +++ b/dev/hooks/pre-push.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############ # NOTE: you MUST run install-hooks.sh again when updating this file! @@ -7,7 +7,7 @@ # magic to ensure that we're always inside the root of our application, # no matter from which directory we'll run script GIT_DIR=$(git rev-parse --git-dir) -cd "$GIT_DIR/.." || exit 1 +cd "${GIT_DIR}/.." || exit 1 export HOOKDIR="dev/hooks" LASTPUSH='.git/.lastpush' diff --git a/dev/inject-json.sh b/dev/inject-json.sh index f470500..0936571 100644 --- a/dev/inject-json.sh +++ b/dev/inject-json.sh @@ -7,7 +7,7 @@ # # Usage: source inject-json.sh # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############################################################## # download JSON.sh diff --git a/dev/install-hooks.sh b/dev/install-hooks.sh index d004ec8..e4e41b4 100755 --- a/dev/install-hooks.sh +++ b/dev/install-hooks.sh @@ -1,16 +1,10 @@ #!/usr/bin/env bash # this has to run once atfer git clone # and every time we create new hooks -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 -# magic to ensure that we're always inside the root of our application, -# no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -if [ "$GIT_DIR" != "" ] ; then - cd "$GIT_DIR/.." || exit 1 -else - printf "Sorry, no git repository %s\n" "$(pwd)" && exit 1 -fi +#shellcheck disable=SC1090 +source "${0%/*}/dev.inc.sh" HOOKDIR="dev/hooks" @@ -19,7 +13,7 @@ for hook in pre-commit post-commit pre-push do rm -f "${GIT_DIR}/hooks/${hook}" if [ -f "${HOOKDIR}/${hook}.sh" ]; then - printf "%s"" $hook" + printf "%s"" ${hook}" ln -s "../../${HOOKDIR}/${hook}.sh" "${GIT_DIR}/hooks/${hook}" fi done diff --git a/dev/make-distribution.sh b/dev/make-distribution.sh index f67a85b..226cd28 100755 --- a/dev/make-distribution.sh +++ b/dev/make-distribution.sh @@ -7,25 +7,20 @@ # # Options: --notest - skip tests # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############################################################## -# magic to ensure that we're always inside the root of our application, -# no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -if [ "$GIT_DIR" != "" ] ; then - [[ "$GIT_DIR" != "/"* ]] && GIT_DIR="${PWD}/${GIT_DIR}" - cd "$GIT_DIR/.." || exit 1 -else - printf "Sorry, no git repository %s\n" "$(pwd)" && exit 1 -fi +#shellcheck disable=SC1090 +source "${0%/*}/dev.inc.sh" VERSION="$(git describe --tags | sed -e 's/-[0-9].*//' -e 's/v//')" DISTNAME="telegram-bot-bash" DISTDIR="./DIST/${DISTNAME}" -DISTFILES="bashbot.rc bashbot.sh commands.sh mycommands.sh mycommands.sh.clean bin doc examples scripts modules addons LICENSE README.md README.txt README.html" -DISTMKDIR="data-bot-bash logs bin bin/logs" +DISTMKDIR="data-bot-bash logs bin bin/logs addons" + +DISTFILES="bashbot.sh commands.sh mycommands.sh.clean bin doc examples scripts modules LICENSE README.md README.txt README.html" +DISTFILESDIST="mycommands.sh mycommands.conf bashbot.rc $(echo "addons/"*.sh)" # run tests first! for test in $1 dev/all-test*.sh @@ -47,6 +42,7 @@ cp -r ${DISTFILES} "${DISTDIR}" cd "${DISTDIR}" || exit 1 printf "Create directories\n" +# shellcheck disable=SC2250 for dir in $DISTMKDIR do [ ! -d "${dir}" ] && mkdir "${dir}" @@ -54,15 +50,15 @@ done # do not overwrite on update printf "Create .dist files\n" -for file in mycommands.sh bashbot.rc addons/*.sh +for file in ${DISTFILESDIST} do [ "${file}" = "addons/*.sh" ] && continue - mv "${file}" "${file}.dist" + cp "${BASE_DIR}/${file}" "${file}.dist" done # inject JSON.sh into distribution # shellcheck disable=SC1090 -source "$GIT_DIR/../dev/inject-json.sh" +source "${BASE_DIR}/dev/inject-json.sh" # make html doc printf "Create html doc\n" @@ -73,13 +69,13 @@ source "../../dev/make-html.sh" cd .. || exit 1 printf "Create dist archives\n" # shellcheck disable=SC2046 -zip -rq - "${DISTNAME}" --exclude $(cat "$GIT_DIR/../dev/${0##*/}.exclude") >"${DISTNAME}-${VERSION}.zip" -tar --exclude-ignore="$GIT_DIR/../dev/${0##*/}.exclude" -czf "${DISTNAME}-${VERSION}.tar.gz" "${DISTNAME}" +zip -rq - "${DISTNAME}" --exclude $(cat "${BASE_DIR}/dev/${0##*/}.exclude") >"${DISTNAME}-${VERSION}.zip" +tar --exclude-ignore="${BASE_DIR}/dev/${0##*/}.exclude" -czf "${DISTNAME}-${VERSION}.tar.gz" "${DISTNAME}" printf "%s Done!\n" "$0" # shellcheck disable=SC2086 -ls -ld ${DISTNAME}-${VERSION}.* +ls -ld "${DISTNAME}-${VERSION}".* # an empty DEBUG.log is created ... :-( -rm -f "$GIT_DIR/../test/"*.log +rm -f "${BASE_DIR}/test/"*.log diff --git a/dev/make-distribution.sh.exclude b/dev/make-distribution.sh.exclude index e93a029..2a21666 100644 --- a/dev/make-distribution.sh.exclude +++ b/dev/make-distribution.sh.exclude @@ -1,5 +1,7 @@ data-bot-bash/* JSON.awk +bashbot.rc +mycommands.sh awk-patch.sh *.jssh* botacl diff --git a/dev/make-html.sh b/dev/make-html.sh index c8be7e8..f3cc5f8 100644 --- a/dev/make-html.sh +++ b/dev/make-html.sh @@ -7,7 +7,7 @@ # # Usage: source make-hmtl # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ############################################################## # check for correct dir @@ -28,16 +28,17 @@ else cp README.html html/index.html # convert *.md files in doc to *.hmtl in html find doc -iname "*.md" -type f -exec sh -c\ - 'printf "."; pandoc -s -f commonmark -M "title=Bashobot Documentation - ${0%.md}.html" "${0}" -o "./html/$(basename ${0%.md}.html)"' {} \; + 'printf "."; pandoc -s -f commonmark -M "title=Bashobot Documentation - ${0%.md}.html" "$0" -o "./html/$(basename ${0%.md}.html)"' {} \; # html for examples dir if [ -d "examples" ]; then EXAMPLES="examples" # add to final conversion job find examples -iname "*.md" -type f -exec sh -c\ - 'printf "."; pandoc -s -f commonmark -M "title=Bashobot Documentation - ${0%.md}.html" "${0}" -o "${0%.md}.html"' {} \; + 'printf "."; pandoc -s -f commonmark -M "title=Bashobot Documentation - ${0%.md}.html" "$0" -o "${0%.md}.html"' {} \; fi # final: convert links from *.md to *.html + # shellcheck disable=SC2248 find README.html html ${EXAMPLES} -iname "*.html" -type f -exec sh -c\ - 'sed -i -E "s/href=\"(\.\.\/)*doc\//href=\"\1html\//g;s/href=\"(.*).md(#.*)*\"/href=\"\1.html\"/g" ${0}' {} \; + 'sed -i -E "s/href=\"(\.\.\/)*doc\//href=\"\1html\//g;s/href=\"(.*).md(#.*)*\"/href=\"\1.html\"/g" $0' {} \; printf " Done!\n" fi fi diff --git a/dev/make-standalone.sh b/dev/make-standalone.sh index edcf64b..a9f9e69 100755 --- a/dev/make-standalone.sh +++ b/dev/make-standalone.sh @@ -6,38 +6,43 @@ # Description: # even after make-distribution.sh bashbot is not self contained as it was in the past. # +# Options: --notest +# # If you your bot is finished you can use make-standalone.sh to create the # the old all-in-one bashbot: bashbot.sh and commands.sh only! # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ################################################################### -# magic to ensure that we're always inside the root of our application, -# no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -if [ "$GIT_DIR" != "" ] ; then - [[ "$GIT_DIR" != "/"* ]] && GIT_DIR="${PWD}/${GIT_DIR}" - cd "$GIT_DIR/.." || exit 1 -else - [ ! -f "bashbot.sh" ] && printf "bashbot.sh not found in %s\n" " $(pwd)" && exit 1 -fi +#shellcheck disable=SC1090 +source "${0%/*}/dev.inc.sh" +[ ! -f "bashbot.sh" ] && printf "bashbot.sh not found in %s\n" " $(pwd)" && exit 1 #DISTNAME="telegram-bot-bash" -DISTDIR="./STANDALONE/${DISTNAME}" -DISTFILES="bashbot.sh bashbot.rc commands.sh mycommands.sh dev/obfuscate.sh modules scripts logs LICENSE README.* doc botacl botconfig.jssh" +DISTDIR="./STANDALONE" +DISTMKDIR="data-bot-bash logs bin bin/logs addons" +DISTFILES="bashbot.sh bashbot.rc commands.sh mycommands.sh dev/obfuscate.sh modules bin scripts LICENSE README.* doc botacl botconfig.jssh $(echo "addons/"*.sh)" # run pre_commit on files -dev/hooks/pre-commit.sh +[ "$1" != "--notest" ] && dev/hooks/pre-commit.sh # create dir for distribution and copy files +printf "Create directories and copy files\n" mkdir -p "${DISTDIR}" 2>/dev/null + # shellcheck disable=SC2086 cp -r ${DISTFILES} "${DISTDIR}" 2>/dev/null cd "${DISTDIR}" || exit 1 +# shellcheck disable=SC2250 +for dir in $DISTMKDIR +do + [ ! -d "${dir}" ] && mkdir "${dir}" +done + # inject JSON.sh into distribution # shellcheck disable=SC1090 -source "$GIT_DIR/../dev/inject-json.sh" +source "${BASE_DIR}/dev/inject-json.sh" ####################### # here the magic starts @@ -66,7 +71,7 @@ printf "\n... create unified bashbot.sh\n" { # first head of bashbot.sh - sed -n '0,/for modules in/ p' bashbot.sh | head -n -3 + sed -n '0,/for module in/ p' bashbot.sh | head -n -3 # then mycommands from first non comment line on printf '\n##############################\n# bashbot modules starts here ...\n' @@ -95,7 +100,7 @@ chmod +x bashbot.sh.min # make html doc printf "Create html doc\n" #shellcheck disable=SC1090 -source "$GIT_DIR/../dev/make-html.sh" +source "${BASE_DIR}/dev/make-html.sh" printf "%s Done!\n" "$0" diff --git a/dev/obfuscate.sh b/dev/obfuscate.sh index 2db4016..b921e13 100755 --- a/dev/obfuscate.sh +++ b/dev/obfuscate.sh @@ -2,7 +2,7 @@ # # joke hack to obfuscate bashbot.min.sh # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # shellcheck disable=SC2028,SC2016,SC1117 infile="bashbot.sh" diff --git a/dev/shellcheck.files b/dev/shellcheck.files index 0f99ebf..f43ba31 100644 --- a/dev/shellcheck.files +++ b/dev/shellcheck.files @@ -1,4 +1,4 @@ # list of additional files to check from shellcheck -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 bashbot.rc mycommands.sh.clean diff --git a/dev/version.sh b/dev/version.sh index eb759d4..5090119 100755 --- a/dev/version.sh +++ b/dev/version.sh @@ -1,6 +1,6 @@ #!/bin/bash # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # shellcheck disable=SC2016 # # Easy Versioning in git: @@ -34,14 +34,8 @@ # run this script to (re)place Version number in files # -# magic to ensure that we're always inside the root of our application, -# no matter from which directory we'll run script -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) -if [ "$GIT_DIR" != "" ] ; then - cd "$GIT_DIR/.." || exit 1 -else - printf "Sorry, no git repository %s\n" "$(pwd)" && exit 1 -fi +#shellcheck disable=SC1090 +source "${0%/*}/dev.inc.sh" unset IFS # set -f # if you are paranoid use set -f to disable globbing @@ -52,16 +46,28 @@ printf "Update to version %s ...\n" "${VERSION}" FILES="$(find ./*)" [ "$1" != "" ] && FILES="$*" -for file in $FILES +# autogenerate REMADME.html REMADE.txt +if [[ "${FILES}" == *"README.md"* ]]; then + FILES+=" README.html README.txt" + type -f pandoc >/dev/null && pandoc -s -f commonmark -M "title=Bashbot README" README.md >README.html + cat "doc/bashbot.ascii" >"README.txt" + if [ -r "README.html" ] && type -f html2text >/dev/null; then + # convert html links to text [link] + sed -E 's/([^<#]+)<\/a>/\2 [\1]/' >README.txt + else + type -f fold >/dev/null && fold -s -w 90 README.md >>README.txt + fi +fi + +# change version string in given files +for file in ${FILES} do [ ! -f "${file}" ] && continue #[ "${file}" == "version" ] && continue printf "%s" " ${file}" >&2 sed -i 's/^#### $$VERSION$$.*/#### \$\$VERSION\$\$ '"${VERSION}"'/' "${file}" done -# try to compile README.txt -printf " README.txt" >&2 -type -f pandoc >/dev/null && pandoc -s -f commonmark -M "title=Bashbot README" README.md >README.html -fold -s README.md >README.txt + printf " done.\n" diff --git a/doc/0_install.md b/doc/0_install.md index 20a1f90..199e604 100644 --- a/doc/0_install.md +++ b/doc/0_install.md @@ -2,7 +2,7 @@ ## Check bash installation -There may systems where bash seems to be installed but it is not, e.g. embedded systems, or where bash is to old. +There may systems where bash seems to be installed but it is not (_e.g. embedded systems_) or where bash is to old. Bashbot has some builtin checks but it may better to check before installing bashbot. Run the following commands to see if your bash looks ok ... @@ -15,7 +15,7 @@ if which bash; then echo "bash seems available..."; else echo "NO bash"; fi bash -c 'if eval "a[1]=1"; then echo "Shell support arrays..."; else echo "Shell has NO arrays"; fi' # check for bash version by feature -bash -c 'if [ "$(echo -e "\u1111")" != "\u1111" ]; then echo "Bash version ok ..."; else echo "Bash version may to old ..."; fi' +bash -c 'if [ "$(LANG=C.UTF-8 echo -e "\u1111")" != "\u1111" ]; then echo "Bash version ok ..."; else echo "Bash version may to old ..."; fi' # display bash version, must be greater than 4.3 bash --version | grep "bash" @@ -23,58 +23,60 @@ bash --version | grep "bash" ## Install bashbot -1. Go to the directory you want to install bashbot, e.g. - * your $HOME directory (install and run with your user-ID) - * /usr/local if you want to run as service -2. [Download latest release zip / tar archive from github](https://github.com/topkecleon/telegram-bot-bash/releases/latest) and extract all files. -3. Change into the directory `telegram-bot-bash` -4. Copy `mycommands.sh.dist` or `mycommands.sh.clean` to `mycommands.sh` -5. Run `./bashbot.sh init` to setup the environment and enter your Bots token given by botfather. +Installing bashbot is very simple: Download and extract the installation archive. -Edit `mycommands.sh` to fit your needs. +1. Choose a directory to install bashbot (_e.g.your HOME or /usr/local_) +2. Download [latest release zip / tar archive](https://github.com/topkecleon/telegram-bot-bash/releases/latest) and extract all files. +3. Change into the directory `telegram-bot-bash` +4. Copy `mycommands.conf.dist` `mycommands.conf` +4. Copy `mycommands.sh.dist` or `mycommands.sh.clean` to `mycommands.sh` +5. Run `./bashbot.sh init`\* to setup the environment and enter your Bots token given by botfather. + +Edit config in `mycommands.conf` and commands in `mycommands.sh` to fit your need. Now your Bot is ready to start ... -**If you are new to Bot development read [Bots: An introduction for developers](https://core.telegram.org/bots)** +*If you are new to Bot development read [Bots: An introduction for developers](https://core.telegram.org/bots)* +\* _Run with sudo if you want to run bashbot from different user, e.g. from `bashbot.rc`._ ### Update bashbot -**Important: all files including `mycommands.sh` may overwritten, make a backup!** +Update bashbot is almost identical to installing bashbot: Download and extract the installation archive. -1. Go to the directory where you have installed bashbot, e.g. - * your $HOME directory - * /usr/local -2. [Download latest release zip / tar archive from github](https://github.com/topkecleon/telegram-bot-bash/releases/latest) +**Important: All files may overwritten, make a backup!** + +1. Go to the directory where bashbot is installed (_e.g.$HOME/telegram-bot-bash or /usr/local/telegram-bot-bash_) +2. Download [latest release zip / tar archive](https://github.com/topkecleon/telegram-bot-bash/releases/latest) 3. Stop all running instances of bashbot `./bashbot.sh stop` -4. Extract all files to your existing bashbot dir -5. Run `sudo ./bashbot.sh init` to setup your environment after the update +4. Change to parent directory of bashbot installation and extract all files from archive. +5. Run `./bashbot.sh init`\* to setup your environment after the update +6. Restart your bot `./bashbot.sh start` -If you modified `commands.sh` move your changes to `mycommands.sh`, this avoids overwriting your commands on every update. - -Now you can restart your bashbot instances. +`mycommands.conf` and `mycommands.sh` will not overwritten, this avoids losing your bot config and commands on updates. *Note*: If you are updating from a pre-1.0 version, update to [Version 1.20](https://github.com/topkecleon/telegram-bot-bash/releases/tags/v1.20) first! -### Use JSON.awk (beta) +### Use JSON.awk [JSON.awk](https://github.com/step-/JSON.awk) is an awk port of `JSON.sh`, it provides the same functionality but is 5 times faster. -Most systems with awk can use `JSON.awk` as drop in replacement +On most systems you can use `JSON.awk` with system default awk installation. ( [gnu awk, posix awk, mawk, busybox akw](https://github.com/step-/JSON.awk#compatibility-with-awk-implementations) ). +After you have checked that `JSON.awk.dist` is working on your system copy it to `JSON.awk` and (re)start bashbot. + BSD and MacOS users must install `gnu awk` and adjust the shebang, see below -After you have checked that 'JSON.awk.dist' is working correct on your system copy it to `JSON.awk` and (re)start bashbot. - -Note: If you are not using the zip / tar archive, you must install `JSON.awk` manually into the same directory as 'JSON.sh`: +*Note*: To install or update `JSON.awk` manually execute the following commands in the directory `JSON.sh/`: wget https://cdn.jsdelivr.net/gh/step-/JSON.awk/JSON.awk wget https://cdn.jsdelivr.net/gh/step-/JSON.awk/tool/patch-for-busybox-awk.sh bash patch-for-busybox-awk.sh + chmod +x JSON.awk ### Install bashbot from git repo -Installation and Updates should be done using the zip / tar archives provided on github to avoid +Installation and updates should be done using the zip / tar archives provided on github to avoid problems and not overwriting your bot config and `mycommands.sh`. Nevertheless you can install or update bashbot from a git repo, see next chapter ... @@ -82,7 +84,8 @@ Nevertheless you can install or update bashbot from a git repo, see next chapter ### Create Installation / Update archives -To install or update bashbot from git repo execute `dev/make-distribution.sh`, this creates the archives and set up bashbot to run in `DIST/telegram.bot-bash`. +To install or update bashbot from git repo execute `dev/make-distribution.sh`. +This creates the installation archives in `DIST/` and a ready to run test installation in `DIST/telegram.bot-bash`. *Note:* You should be familiar with `git`. @@ -92,10 +95,10 @@ To install or update bashbot from git repo execute `dev/make-distribution.sh`, t 4. Run ` dev/make-distribution.sh` (_add --notest to skip tests_) 5. Change to dir `DIST/` -Use the archives created in `DIST/` to install or update bashbot as described above. +Use the installation archives to install or update bashbot as described above. To run a test bot, e.g. while development or testing latest changes, you can use the bashbot installation provided in `DIST/telegram-bot-bash`. -To update the test installation, e.g. after git pull, local changes or switch master/develop, run `dev/make-distrubition.sh` again. +To update the test installation (_after git pull, local changes or switch master/develop_) run `dev/make-distribution.sh` again. ### Note for BSD and MacOS @@ -104,32 +107,30 @@ To update the test installation, e.g. after git pull, local changes or switch ma see e.g. [Install Bash on Mac](http://macappstore.org/bash/) **On BSD and MacOS** I recommend to install gnu coreutils and include them in your PATH -environment variable before running bashbot, e.g. the gnu versions of sed, grep, find ... +environment variable before running bashbot, e.g. the gnu versions of sed, grep, find, awk ... -On BSD and MacOS you must adjust the shebang line of the scripts ```bashbot.sh``` and ```json.sh``` to point to to the correct bash -or use the script: ```examples/bash2env *.sh */*.sh``` to convert them for you. +On BSD and MacOS you must adjust the shebang line of the scripts `bashbot.sh` and `json.sh` to point to to the correct bash +or use the script: `examples/bash2env *.sh */*.sh` to convert them for you. -Bashbot will stay with /bin/bash shebang, as using a fixed path is more secure than the portable /usr/bin/env variant, see -[Security Considerations](../README.md#Security-Considerations) +Bashbot will stay with `#!/bin/bash` shebang, as using a fixed path is IMHO more secure than the portable '!/usr/bin/env bash` variant. -I considered to make bashbot BSD sed compatible, but much of the bashbot "magic" relies on -(gnu) sed features, e.g. alternation ```|```, non printables ```\n\t\<``` or repeat ```?+``` pattern, not supported by BSD sed. - -BSD/MacOS sed compatibility will result in a rewrite of all grep/sed commands with an uncertain outcome, +Compatibility with BSD/MacOS will result in a rewrite of all grep/sed commands with an uncertain outcome, see [BSD/MacOS vs. GNU sed](https://riptutorial.com/sed/topic/9436/bsd-macos-sed-vs--gnu-sed-vs--the-posix-sed-specification) to get an impression how different they are. ### Notes on Changes +#### Config moved to mycommands.conf + +From Version 1.30 on config for new bots is moved to `mycommands.conf`. + #### Support for update from pre-1.0 removed -From Version 1.21 on updating from a pre-1.0 version is no more supported! +From Version 1.21 on updating from a pre-1.0 version (_no \*.jssh config_) is no more supported! You must update to [Version 1.20](https://github.com/topkecleon/telegram-bot-bash/releases/tags/v1.20) first! - - #### [Next Create Bot](1_firstbot.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/1_firstbot.md b/doc/1_firstbot.md index 4a67e48..173c936 100644 --- a/doc/1_firstbot.md +++ b/doc/1_firstbot.md @@ -65,5 +65,5 @@ group. This step is up to you actually. #### [Prev Installation](0_install.md) #### [Next Getting started](2_usage.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/2_usage.md b/doc/2_usage.md index e8a39e3..6ec08bf 100644 --- a/doc/2_usage.md +++ b/doc/2_usage.md @@ -19,8 +19,10 @@ Have FUN! ``` . ├── mycommands.sh # THIS is your bot, place logic and commands here! +├── mycommands.conf # place your bot config and bot messages here! │ -├── mycommands.sh.clean # copy to "mycommands.sh" if you start developing your bot +├── mycommands.conf.dist # copy to "mycommands.conf" if not exist +├── mycommands.sh.clean # copy to "mycommands.sh" to start developing a new bot ├── mycommands.sh.dist # example bot, also used for testing bashbot internally │ ├── count.jssh # count bashbot usage in jssh key-value store @@ -33,6 +35,7 @@ Have FUN! ├── bin # ready to use scripts, use `scriptname --help` for help │   ├── send_message.sh # send message to given chat │   ├── edit_message.sh # replace given message id in given chat +│   ├── delete_message.sh # delete given message id in given chat │   ├── send_broadcast.sh # send message to all known chats │   ├── send_file.sh # send file to given chat │   ├── bashbot_stats.sh # does what it says ... @@ -138,7 +141,8 @@ Note: to get help about a script in bin/ run `scriptname.sh --help` ---- ## Receive data -Evertime a Message is received, you can read incoming data using the following variables: +Evertime a Telegram update is received, you can read incoming data using the following variables: +In case you need other update values, the array `UPD` contains complete Telegram response. ### Regular Messages @@ -252,14 +256,19 @@ they contain the following variables only: * `${iQUERY[LAST_NAME]}`: User's last name -### Send Message Results +## Send data / get response -BOTSENT is set on every send_xxx action and only valid until next send action. For more on message results see. -[Advanced Usage](3_advanced.md) +After every `send_xxx` `get_xxx` call the array BOTSENT contains the most important values from Telegram response. +In case you need other response values , the array `UPD` contains complete Telegram response. + +### BOTSENT array * `$BOTSENT`: This array contains the parsed results from the last transmission to telegram. * `${BOTSENT[OK]}`: contains the string `true`: after a successful transmission * `${BOTSENT[ID]}`: Message ID of sent message, image, file etc., if OK is true + * `${BOTSENT[FILE_ID]}`: unique identifier returned for an uploaded file or URL + * `${BOTSENT[FILE_TYPE]}`: file type: photo, audio, video, sticker, voice, document + ## Usage of bashbot functions @@ -314,9 +323,10 @@ send_message "${CHAT[ID]}" "lol" "safe" #### Send files, locations, keyboards. -To send images, videos, voice files, photos etc. use the `send_photo`function (remember to change the safety Regex @ line 14 of command.sh to allow sending files only from certain directories): +To send local files or URL's (photo, video, voice, sticker, documents) use the `send_file` function. ```bash -send_file "${CHAT[ID]}" "/home/user/doge.jpg" "Lool" +send_file "${CHAT[ID]}" "/home/user/dog.jpg" "Lool" "photo" +send_file "${CHAT[ID]}" "https://images-na.ssl-images-amazon.com/images/I/81DQ0FpoSNL._AC_SL1500_.jpg" ``` To send custom keyboards use the `send_keyboard`function: ```bash @@ -341,5 +351,5 @@ send_action "${CHAT[ID]}" "action" #### [Prev Create Bot](1_firstbot.md) #### [Next Advanced Usage](3_advanced.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/3_advanced.md b/doc/3_advanced.md index efa507f..8ba9cf4 100644 --- a/doc/3_advanced.md +++ b/doc/3_advanced.md @@ -62,6 +62,7 @@ You must use the function `user_is_allowed` to check if a user has the capabilit # GLOBAL commands start here, only edit messages '/start'*) user_is_botadmin "${USER[ID]}" && send_markdown_message "${CHAT[ID]}" "You are *BOTADMIN*." + # true if: user is botadmin, user is group admin, user is allowed if user_is_allowed "${USER[ID]}" "start" "${CHAT[ID]}" ; then bot_help "${CHAT[ID]}" else @@ -171,7 +172,7 @@ All output of the script will be sent to the user, to stop a background job use: ```bash kill_back "${CHAT[ID]}" "jobname" ``` -You can also suspend and resume currently running background jobs from outside bashbot, e.g. in your startup schripts: +You can also suspend and resume currently running background jobs from outside bashbot, e.g. in your startup scripts: ```bash ./bashbot.sh suspendback ./bashbot.sh resumeback @@ -301,5 +302,5 @@ Note: If you disable automatic retry, se above, you disable also connection prob #### [Prev Getting started](2_usage.md) #### [Next Expert Use](4_expert.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/4_expert.md b/doc/4_expert.md index 19a4171..f15d390 100644 --- a/doc/4_expert.md +++ b/doc/4_expert.md @@ -10,12 +10,12 @@ two bytes for encoding and covers almost all `Latin` alphabets, also `Greek`, `C #### Setting up your Environment In general `bash` and `GNU` utitities are UTF-8 aware if you to setup your environment -and your scripts accordingly: +and your scripts accordingly (_locale setting_): 1. Your Terminal and Editor must support UTF-8: Set Terminal and Editor locale to UTF-8, eg. in `Settings/Configuration` select UTF-8 (Unicode) as Charset. -2. Set `Shell` environment to UTF-8 in your `.profile` and your scripts. The usual settings are: +2. Set `Shell` locale environment to UTF-8 in your `.profile` and your scripts. The usual settings are: ```bash export 'LC_ALL=C.UTF-8' @@ -31,11 +31,67 @@ export 'LANGUAGE=de_DE.UTF-8' ```bash export 'LC_ALL=en_US.UTF-8' export 'LANG=de_en_US.UTF-8' -export 'LANGUAGE=den_US.UTF-8' +export 'LANGUAGE=en_US.UTF-8' ``` -3. make sure your bot scripts use the correct settings, eg. include the lines above at the beginning of your scripts +3. make sure your bot scripts use the correct settings, eg. include the lines above at the beginning of your scripts -To display all available locales on your system run `locale -a | more`. [Gentoo Wiki](https://wiki.gentoo.org/wiki/UTF-8) + +#### Known locale pitfalls + +##### Missing C locale + +Even required by POSIX standard some systems (e.g. Manjaro Linux) has `C` and `C.UTF-8` locale not installed. +If bashbot display a warning about missing locale you must install `C` and `C.UTF-8` locale. + +If you don't know what locales are installed on your sytsem use `locale -a` to display them. +[Gentoo Wiki](https://wiki.gentoo.org/wiki/UTF-8). + + +##### Character classes + +In ASCII times it was clear `[:lower:]` and `[a-z]` means ONLY the lowercase letters `[abcd...xyz]`. +With the introduction of locales, character classes and ranges now contain all characters fitting the class definition. + +This means with a Latin UTF-8 locale `[:lower:]` and `[a-z]` contains also e.g. `á ø ü` etc, +see [Unicode Latin lowercase letters](https://www.fileformat.info/info/unicode/category/Ll/list.htm) + +If that's ok for your script you're fine, but many scripts rely on the idea of ASCII ranges and may produce undesired results. + +```bash +# try with different locales ... +# new bash to not change your current locale! +bash +lower="abcö" + +echo "$LC_ALL $LC_COLLATE" +[[ "$lower" =~ ^[a-z]+$ ]] && echo "Ups, $lower is all lower case!" || echo "OK, not lower case" + +LC_ALL="en_US.UTF-8" +[[ "$lower" =~ ^[a-z]+$ ]] && echo "Ups, $lower is all lower case!" || echo "OK, not lower case" + +LC_ALL="C" +[[ "$lower" =~ ^[a-z]+$ ]] && echo "Ups, $lower is all lower case!" || echo "OK, not lower case" +``` + +There are three solutions: + +1. list exactly the characters you want: `[abcd...]` +2. instruct bash to use `C` locale for ranges: `shopt -s "globasciiranges"` +3. use `LC_COLLATE` to change behavior of all programs: `export LC_COLLATE=C` + + +To work independent of language and bash settings bashbot uses solution 1.: Own "ranges" if an exact match is mandatory: + +```bash +azazaz='abcdefghijklmnopqrstuvwxyz' # a-z :lower: +AZAZAZ='ABCDEFGHIJKLMNOPQRSTUVWXYZ' # A-Z :upper: +o9o9o9='0123456789' # 0-9 :digit: +azAZaz="${azazaz}${AZAZAZ}" # a-zA-Z :alpha: +azAZo9="${azAZaz}${o9o9o9}" # a-zA-z0-9 :alnum: + +# e.g. characters allowed for key in key/value pairs +JSSH_KEYOK="[-${azAZo9},._]" +``` #### Bashbot UTF-8 Support Bashbot handles all messages transparently, regardless of the charset in use. The only exception is when converting from JSON data to strings. @@ -45,11 +101,11 @@ The Emoticons ` 😁 😘 ❤️ 😊 👍 ` are encoded as: ` \uD83D\uDE01 \uD8 **This "mixed" JSON encoding needs special handling and can not decoded from** `echo -e` or `printf '%s\\n'` -Bbashbot uses an internal, pure bash implementation which is well tested now, even there may some corner cases*. +Bashbot uses an internal, pure bash implementation which is well tested now, even there may some corner cases*. ### Run as other user or system service -Bashbot is desingned to run manually by the user who installed it. Nevertheless it's possible to run it by an other user-ID, as a system service or scheduled from cron. This is recommended if you want to bashbot run as a service. +Bashbot is designed to run manually by the user who installed it. Nevertheless it's possible to run it by an other user-ID, as a system service or scheduled from cron. This is recommended if you want to bashbot run as a service. Setup the environment for the user you want to run bashbot and enter desired username, e.g. nobody : ```bash @@ -98,7 +154,7 @@ To use bashbot as a system service include a working `bashbot.rc` in your init s An example crontab is provided in `examples/bashbot.cron`. - If you are running bashbot with your user-ID, copy the examples lines to your crontab and remove username `nobody`. -- if you run bashbot as an other user or a system service edit `examples/bashbot.cron` to fit your needs and replace username `nobody` with the username you want to run bashbot. copy the modified file to `/etc/cron.d/bashbot` +- if you run bashbot as an other user or a system service edit `examples/bashbot.cron` to fit your needs and replace username `nobody` with the username you want to run bashbot. Copy the modified file to `/etc/cron.d/bashbot` ### Use bashbot from CLI and scripts @@ -175,10 +231,10 @@ User or Chat you are in. See [Send Messages](2_usage.md#sending-messages). *Examples:* You can test this by sending messages to yourself: ```bash -# fist Hello World +# first Hello World send_normal_message "$(getConfigKey "botadmin")" "Hello World! This is my first message" -# now with some markdown and HTML +# now with some markdown and HTML send_markdown_message "$(getConfigKey "botadmin")" '*Hello World!* _This is my first markdown message_' send_html_message "$(getConfigKey "botadmin")" 'Hello World! This is my first HTML message' send_keyboard "$(getConfigKey "botadmin")" 'Do you like it?' '[ "Yep" , "No" ]' @@ -202,10 +258,10 @@ This section describe how you can customize bashbot to your needs by setting env #### Change file locations -In standard setup bashbot is self containing, this means you can place 'telegram-bot-bash' any location +In standard setup bashbot is self containing, this means you can place 'telegram-bot-bash' any location and run it from there. All files - program, config, data etc - will reside in 'telegram-bot-bash'. -If you want to have other locations for config, data etc, define and export the following environment variables. +If you want to have other locations for config, data etc, define and export the following environment variables. **Note: all specified directories and files must exist or running 'bashbot.sh' will fail.** ##### BASHBOT_ETC @@ -323,7 +379,7 @@ BASHBOT_TIMEOUT to a numeric value between 1 and 999. Any non numeric or negativ ##### BASHBOT_SLEEP Instead of polling permanently or with a fixed delay, bashbot offers a simple adaptive polling. -If messages are received bashbot polls with no dealy. If no messages are available bashbot add 100ms delay +If messages are received bashbot polls with no delay. If no messages are available bashbot add 100ms delay for every poll until the maximum of BASHBOT_SLEEP ms. ```bash unset BASHBOT_SLEEP # 5000ms (default) @@ -378,5 +434,5 @@ for every poll until the maximum of BASHBOT_SLEEP ms. #### [Prev Advanced Use](3_advanced.md) #### [Next Best Practice](5_practice.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/5_practice.md b/doc/5_practice.md index dd39426..85e9f1c 100644 --- a/doc/5_practice.md +++ b/doc/5_practice.md @@ -160,5 +160,5 @@ The second warning is about an unused variable, this is true because in our exam #### [Prev Best Practice](5_practice.md) #### [Next Functions Reference](6_reference.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/6_reference.md b/doc/6_reference.md index cca5abd..463e13d 100644 --- a/doc/6_reference.md +++ b/doc/6_reference.md @@ -115,23 +115,36 @@ The main use case for send_message is to process the output of interactive chats ### File, Album, Location, Venue, Keyboard - ##### send_file -send_file can send different type's of files, e.g. photos, stickers, audio, media, etc. -[see Telegram API documentation](https://core.telegram.org/bots/api#sending-files). +send_file can send local files, URL's or file_id's as different filex types (_e.g. photo video sticker_) -It's recommended to use __absolute path names__ (_starting with `/`_), as relative path names are threated as __relative to UPLOADDIR__ `data-bot-bash/upload`! +*usage:* send_file "${CHAT[ID]}" "file/URL/file_id" "caption" ["type"] -For security reasons the following restrictions apply: +URL's must start with `http://` or `https://` and remote server must send an appropriate media type. +A file_id must start with `file_id://`, all other file names are threated as local files. +If Telegram accepts the file `BOTSENT[FILE_ID]` and `BOTSENT[FILE_TYPE]` are set. -- absolute path name must match the __shell regex__ `FILE_REGEX` (_not file glob_) +Argument "type" is optional, if not given `send_file` detects file type by the file extension. +if file/URL has no extension `photo` is assumed. Unknown types and extensions are send as type `document` + +Supported file types are: photo (_png jpg jpeg gif pic_) audio (_mp3 flac_) sticker (_webp_) video (_mp4_) voice (_ogg_) or document. + +It's recommended to use __absolute path names__ for local files (_starting with `/`_), as relative path names are threated as __relative to UPLOADDIR__ `data-bot-bash/upload`! + +For security reasons the following restrictions apply to local files: + +- absolute path name must match the __shell regex__ `FILE_REGEX` +- relative path name is threated as relative to `UPLOADDIR` (_default: data-bot-bash/upload_) - path must not start with `./` and not contain `../` -*usage:* send_file "${CHAT[ID]}" "file" "caption" *example:* ```bash -# recommended: absolute path +# send picture from web +send_file "${CHAT[ID]}" "https://dealz.rrr.de/assets/images/rbofd-1.gif" "My Bot" "photo" +send_file "${CHAT[ID]}" "https://images-na.ssl-images-amazon.com/images/I/81DQ0FpoSNL._AC_SL1500_.jpg" + +# local file recommended: absolute path send_file "${CHAT[ID]}" "/home/user/dog.jpg" "My Dog" # relative to UPLOADDIR: data-bot-bash/upload/dog.jpg @@ -201,7 +214,7 @@ _keyboard_numpad ---- ##### send_button -*usage:* send_button "chat-id" "message" "text" "URL" +*usage:* send_button "$CHAT[ID]" "message" "text" "URL" *alias:* _button "text" "URL" @@ -210,6 +223,12 @@ _keyboard_numpad send_button "${CHAT[ID]}" "MAKE MONEY FAST!!!" "Visit my Shop" "https://dealz.rrr.de" ``` +##### send_sticker +`send_sticker` sends a sticker using a `file_id` to send a sticker that exists on the Telegram servers. + +*usage:* send_sticker "$CHAT[ID]" "file_id" + + ##### send_inline_keyboard Even its called keyboard, this function is different from send_keyboard. The main difference is that it's only possible to specify URL buttons, no Text Buttons and the Buttons must be an Array of Buttons as specified for @@ -240,6 +259,7 @@ send_inline_keyboard "${CHAT[ID]}" "" '[{"text":"b 1", url"":"u 1"}, {"text":"b Edit a message means replace the content of the message in place. The message stay on the same position in the chat and keep the same message id. +If new message is the same than current message Telegram return error 400 with description "Bad Request: chat message is not modified" There is no need to use the same format when replace a message, e.g. a message sent with `send_normal_message` can be replaced with `edit_markdown_message` or `edit_html_message` and vice versa. @@ -299,6 +319,67 @@ saved-id="${BOTSENT[ID]}" edit_html_message "${CHAT[ID]}" "${saved-id}" "this is html text" ``` +##### edit_message_caption +`edit_message_caption` changes the caption of a message (photo, audio, video, document) in the given chat. + +*usage:* edit_message_caption "${CHAT[ID]}" "MESSAGE-ID" "caption" + + +--- + +### Manage Group +To use the following functions the bot must have administrator status in the chat / group + +##### set_chat_title +`set_chat_title` sets a new chat title. If new title is the same than current title Telegram return error 400 +with description "Bad Request: chat title is not modified" + +*usage:* set_chat_title "${CHAT[ID]}" "new chat title" + + +##### set_chat_description +`set_chat_description` sets a new description title. If new description is the same than current description Telegram return error 400 +with description "Bad Request: chat description is not modified" + +*usage:* set_chat_description "${CHAT[ID]}" "new chat description" + + +##### new_chat_invite +`new_chat_invite` generate a new invite link for a chat; any previously generated link is revoked. +Returns the new invite link as String on success. + +*usage:* new_chat_invite "${CHAT[ID]}" + + +##### delete_chat_photo + +*usage:* delete_chat_photo "${CHAT[ID]}" + + +##### pin_chat_message +# $1 chat, $2 message_id +`pin_chat_message` add a message to the list of pinned messages in a chat. + +*usage:* pin_chat_message "${CHAT[ID]}" "message_id" + + +##### unpin_chat_message +`unpin_chat_message` remove a message from the list of pinned messages in a chat. + +*usage:* unpin_chat_message "${CHAT[ID]}" "message_id" + + +##### unpinall_chat_message +`unpinall_chat_message` clear the list of pinned messages in a chat. + +*usage:* unpinall_chat_message "${CHAT[ID]}" + + +##### delete_chat_stickers +`delete_chat_stickers` deletes a group sticker set from a supergroup. + +*usage:* delete_chat_stickers "${CHAT[ID]}" + ---- @@ -390,9 +471,10 @@ fi *See also [Chat Member](https://core.telegram.org/bots/api/#chatmember)* ##### user_is_allowed -Bashbot supports User Access Control, see [Advanced Usage](3_advanced.md) +`uers_is_allowed` checks if: user id botadmin, user is group admin or user is allowed to execute action.. +Allowed actions are configured as User Access Control rules, see [Advanced Usage](3_advanced.md) -*usage:* user_is_allowed "${USER[ID]}" "what" "${CHAT[ID]}" +*usage:* user_is_allowed "${USER[ID]}" "action" "${CHAT[ID]}" *example:* ```bash @@ -1187,5 +1269,5 @@ The name of your bot is available as bash variable "$ME", there is no need to ca #### [Prev Best Practice](5_practice.md) #### [Next Notes for Developers](7_develop.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/7_develop.md b/doc/7_develop.md index eb12beb..ec96dc5 100644 --- a/doc/7_develop.md +++ b/doc/7_develop.md @@ -1,7 +1,7 @@ #### [Home](../README.md) ## Notes for bashbot developers -This section is about help and best practices for new bashbot developers. The main focus on is creating new versions of bashbot, modules and addons, not on develop your individual bot. Nevertheless the information provided here should help your bot development also. +This section is about help and best practices for new bashbot developers. The main focus on is creating new versions of bashbot, modules and addons, not on developing your individual bot. Nevertheless the information provided here should also help you with your bot development. If you want to provide fixes or new features [fork bashbot on github](https://help.github.com/en/articles/fork-a-repo) and provide changes as [pull request on github](https://help.github.com/en/articles/creating-a-pull-request). @@ -11,21 +11,20 @@ If you want to get error messages (and more) start bashbot `./bashbot.sh startb you can the change the level of verbosity of the debug argument: ``` - "debug" all output is redirected to "DEBUG.log", in addition every incoming message is logged in "MESSAGE.log" and "INLINE.log" - "xdebug" same as debug plus set bash option '-x' to log any executed command in "DEBUG.log" - - use the command tail to watch your bot live, e.g. "tail -f DEBUG.log", to obtain more information place set -x; set +x in your code. + "debug" all output is redirected to `DEBUG.log`, in addition every incoming message is logged in `MESSAGE.log` and `INLINE.log` + "xdebug" same as debug plus set bash option '-x' to log any executed command in `DEBUG.log` ``` -``` - sometimes its useful to watch the bot live in the terminal: +Use the command `tail` to watch your bot live, e.g. `tail -f DEBUG.log`. To obtain more information place `set -x; ... set +x` around suspected code. +Sometimes it's useful to watch the bot live in the terminal: + +``` "debugx" debug output and errors are sent to terminal - "xdebugx" same as debugx plus set bash option '-x' to show any executed command - + "xdebugx" same as debugx plus set bash option '-x' to show any executed command ``` -Logging of telegram update poll is disabled by default, also in `debug` mode. To enable it without using verbose `xdebug` mode +Logging of Telegram update poll is disabled by default, also in `debug` mode. To enable it without using verbose `xdebug` mode set `BASHBOT_UPDATELOG` to an empty value (not unset) `export BASHBOT_UPDATELOG=""` ### Modules and Addons @@ -36,7 +35,7 @@ disable modules, e.g. by rename the respective module file to 'module.sh.off'. Modules must use only functions provided by 'bashbot.sh' or the module itself and should not depend on other modules or addons. The only mandatory module is 'module/sendMessage.sh'. -If a not mandatory module is used in 'bashbot.sh' or 'commands.sh', the use of `_is_function` or +If an optional module is used in 'bashbot.sh' or 'commands.sh', the use of `_is_function` or `_execute_if_function` is mandatory to catch absence of the module. **Addons** resides in `addons/*.sh.dist` and are not enabled by default. To activate an addon rename it to end with '.sh', e.g. by @@ -63,18 +62,17 @@ Note: For the same reason event function MUST return immediately! Time consuming ##### SEND RECEIVE events -An RECEIVE event is executed when a Message is received, same iQuery / Message variables are available as in commands.sh +A RECEIVE event is executed when a Message is received, same iQuery / Message variables are available as in commands.sh -* `BASHBOT_EVENT_INLINE` an inline query is received - -* BASHBOT_EVENT_MESSAGE` any of the following message types is received - * `BASHBOT_EVENT_TEXT` a message containing text is received - * `BASHBOT_EVENT_CMD` a message containing a command is received (starts with /) - * `BASHBOT_EVENT_REPLYTO` a reply to a message is received - * `BASHBOT_EVENT_FORWARD` a forwarded message is received - * `BASHBOT_EVENT_CONTACT` a contact is received - * `BASHBOT_EVENT_LOCATION` a location or a venue is received - * `BASHBOT_EVENT_FILE` a file is received +* `BASHBOT_EVENT_INLINE` an inline query is received +* `BASHBOT_EVENT_MESSAGE` any of the following message types is received +* `BASHBOT_EVENT_TEXT` a message containing text is received +* `BASHBOT_EVENT_CMD` a message containing a command is received (starts with /) +* `BASHBOT_EVENT_REPLYTO` a reply to a message is received +* `BASHBOT_EVENT_FORWARD` a forwarded message is received +* `BASHBOT_EVENT_CONTACT` a contact is received +* `BASHBOT_EVENT_LOCATION` a location or a venue is received +* `BASHBOT_EVENT_FILE` a file is received *usage*: BASHBOT_EVENT_xxx[ "unique-name" ]="callback" @@ -149,7 +147,7 @@ This means if you register an every 5 minutes callback first execution may < 5 M * x execute every x minutes * -x execute once WITHIN the next x Minutes (next 10 Minutes since start "event") -Note: If you want exact "in x minutes" use "EVENT_TIMER plus x" as time: `-(EVENT_TIMER + x)` +Note: If you want exact "in x minutes" use "EVENT_TIMER" as reference: `(EVENT_TIMER +x)` *Example:* ```bash @@ -168,8 +166,8 @@ BASHBOT_EVENT_TIMER["example_every5","5"]="example_every5min" # execute once on the next 10 minutes since start "event" BASHBOT_EVENT_TIMER["example_10min","-10"]="example_in10min" -# once in exact 10 minutes -BASHBOT_EVENT_TIMER["example_10min","$(( (EVENT_TIMER+10) * -1 ))"]="example_in10min" +# once in exact 10 minutes, note the - +BASHBOT_EVENT_TIMER["example_10min","-$(( EVENT_TIMER+10 ))"]="example_in10min" ``` @@ -182,8 +180,8 @@ You don't need all these files after you're finished with your cool new bot. Let's create a stripped down version: - delete all modules you do not need from `modules`, e.g. `modules/inline.sh` if you don't use inline queries -- delete not needed standard commands and messages from `commands.sh` -- delete not needed commands and functions from `mycommands.sh` +- delete unused standard commands and messages from `commands.sh` +- delete unused commands and functions from `mycommands.sh` - run `dev/make-standalone.sh` to create a a stripped down version of your bot Now have a look at the directory `standalone`, here you find the files `bashbot.sh` and `commands.sh` containing everything to run your bot. @@ -198,8 +196,8 @@ Now have a look at the directory `standalone`, here you find the files `bashbot. 5. give your (dev) fork a new version tag: `git tag v1.xx` 6. setup github hooks by running `dev/install-hooks.sh` -Run `dev/make-distrubition.sh` to create installation archives and a test installation in `DIST/`. -To update the test installation, e.g. after git pull, local changes or switch master/develop, run `dev/make-distrubition.sh` again. +Run `dev/make-distribution.sh` to create installation archives and a test installation in `DIST/`. +To update the test installation, e.g. after git pull, local changes or switch master/develop, run `dev/make-distribution.sh` again. Note for Debian: Debian Buster ships older versions of many utilities, pls try to install from [buster-backports](https://backports.debian.org/Instructions/) ```bash @@ -210,7 +208,7 @@ sudo apt-get -t buster-backports install git shellcheck pandoc codespell curl A typical bashbot develop loop looks as follow: 1. start developing - *change, copy, edit bashbot files ...* -2. after changing a bash sript: `shellcheck -x script.sh` +2. after changing a bash script: `shellcheck -x script.sh` 3. `dev/all-tests.sh` - *in case if errors back to 2.* 4. `dev/git-add.sh` - *check for changed files, update version string, run git add* 5. `git commit -m "COMMIT MESSAGE"; git push` @@ -254,6 +252,8 @@ data="$(cat file)" -> data="$(<"file")" DIR="$(dirname $0) -> DIR="${0%/*}" +date -> printf"%(%c)T\n" -1 # 100 times faster! + PROG="($basename $0)" -> PROG="${0##*/}* ADDME="$ADDME something to add" -> ADDME+=" something to add"" @@ -261,10 +261,38 @@ ADDME="$ADDME something to add" -> ADDME+=" something to add"" VAR="$(( 1 + 2 ))" -> (( var=1+2 )) INDEX="$(( ${INDEX} + 1 ))" -> (( INDEX++ )) - ``` For more examples see [Pure bash bible](https://github.com/dylanaraps/pure-bash-bible) +The special variable `$_` stores the expanded __last__ argument of the previous command. +This allows a nice optimisation in combination with the no-op command `:`, but be aware of `$_` pitfalls. + +```bash +# $_ example: mkdir plus cd to it +mkdir "somedir-$$" && cd "$_" # somedir-1234 (process id) + +# manipulate a variable multiple times without storing intermediate results +foo="1a23_b__x###" +... +: "${foo//[0-9]}" # a_b__x### +: "${_%%#*}" # a_b__x +bar="${_/__x/_c}" # a_b_c + + +# BE AWARE OF ... +# pitfall missing quotes: $_ is LAST arg +: ${SOMEVAR} # "String in var" $_ -> "var" +: $(<"file") # "Content of\n file" $_ -> "file" + +# pitfall test command +[ -n "$MYVAR" ] && echo "$_" # outputs "]" + +# pitfall command substitution: globbing and IFS is applied! +: "$(echo "a* is born")"# $_ -> a* is globbed even quoted! +: "$(echo "a b c")"# $_ -> "a b c" +: "$(<"file")" # "Content of\n file" $_ -> "Content of file" +``` + #### Prepare a new version After some development it may time to create a new version for the users. a new version can be in sub version upgrade, e.g. for fixes and smaller additions or a new release version for new features. To mark a new version use `git tag NEWVERSION` and run `dev/version.sh` to update all version strings. @@ -291,15 +319,15 @@ To update version in all files run 'dev/version.sh' without parameter. For a shell script running as a service it's important to be paranoid about quoting, globbing and other common problems. So it's a must to run shellchek on all shell scripts before you commit a change. this is automated by a git hook activated in Setup step 6. -To run shellcheck for a single script run `shellcheck -x script.sh`, to check all schripts run `dev/hooks/pre-commit.sh`. +To run shellcheck for a single script run `shellcheck -x script.sh`, to check all scripts run `dev/hooks/pre-commit.sh`. ### bashbot test suite -Starting with version 0.70 bashbot has a test suite. To start testsuite run `dev/all-tests.sh`. all-tests.sh will return 'SUCCESS' only if all tests pass. +Starting with version 0.70 bashbot has a test suite. To start the test suite run `dev/all-tests.sh`. all-tests.sh will return 'SUCCESS' if ALL tests pass. #### enabling / disabling tests -All tests are placed in the directory `test`. To disable a test remove the execute flag from the '*-test.sh' script, to (re)enable a test make the script executable again. +All tests are placed in the directory `test`. To disable a test remove the execute flag from the '*-test.sh' script, to (re)enable a test make the script executable again. #### creating new tests @@ -328,7 +356,7 @@ The file `ALL-tests.inc.sh` must be included from all tests and provide the test ADMINFILE="botadmin" DATADIR="data-bot-bash" -# SUCCESS NOSUCCES -> echo "${SUCCESS}" or echo "${NOSUCCESS}" +# SUCCESS NOSUCCESS -> echo "${SUCCESS}" or echo "${NOSUCCESS}" SUCCESS=" OK" NOSUCCESS=" FAILED!" @@ -358,5 +386,5 @@ fi #### [Prev Function Reference](6_reference.md) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/doc/bashbot.ascii b/doc/bashbot.ascii new file mode 100644 index 0000000..b413e56 --- /dev/null +++ b/doc/bashbot.ascii @@ -0,0 +1,22 @@ + + .. + **** + ****oooooo***** + *****ooooooooooooo***** + *****oooooooooooooooooooooo**** + ****oooooooooooooooooooooooooooooooo** + *.*oooooooooooooooooooooooooooooooooooo** + *.ooooooooooooooooooooooooooooooooo**.... + *.oooooooooooooooooooooooooooo**......... + *.oooooooooooooooooooooooo**............. + *.ooooooooooooooooooo**.................. ____ _ _ _ + *.ooooooooooooooooo*.......,............. | _ \ | | | | | | + *.ooooooooooooooooo*.....,***,........... | |_) | __ _ ___ | |__ | |__ ___ | |_ + *.ooooooooooooooooo*....o*............... | _ < / _` |/ __|| '_ \ | '_ \ / _ \ | __| + *.ooooooooooooooooo*....*o***,........... | |_) || (_| |\__ \| | | || |_) || (_) || |_ + *.*oooooooooooooooo*........o*.....oo.... |____/ \__,_||___/|_| |_||_.__/ \___/ \__| + ****ooooooooooooo*....`***....oo.....* + *****oooooooo*......*..oo.....** + ******ooo*.............* + ***o*........** + **...** diff --git a/examples/README.md b/examples/README.md index 5ac3794..1e89153 100644 --- a/examples/README.md +++ b/examples/README.md @@ -56,6 +56,6 @@ convert existing bots. **jsonDB-keybords** contains a stripped down real world example from my bot showing the usage of jsonDB to store and retrieve values plus use of keyboards in private chats. It's an extended version of mycommands.sh.dist. Messages and help are in german. -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 diff --git a/examples/background-scripts/mycommands.sh b/examples/background-scripts/mycommands.sh index 9a813e4..79bf773 100644 --- a/examples/background-scripts/mycommands.sh +++ b/examples/background-scripts/mycommands.sh @@ -6,12 +6,12 @@ export res # your additional bashbot commands ... mycommands() { - case "$MESSAGE" in + case "${MESSAGE}" in '/run_'*) myback="run_${MESSAGE#*_}" if [ -x "./${myback}.sh" ]; then checkback "${myback}" - if [ "$res" -gt 0 ] ; then + if [ "${res}" -gt 0 ] ; then send_normal_message "${CHAT[ID]}" "Start ${myback}, use /kill${myback} to stop it." background "./${myback}.sh" "${myback}" else @@ -23,7 +23,7 @@ mycommands() { myback="run_${MESSAGE#*_}" if [ -x "./${myback}.sh" ]; then checkback "${myback}" - if [ "$res" -eq 0 ] ; then + if [ "${res}" -eq 0 ] ; then killback "${myback}" send_normal_message "${CHAT[ID]}" "Stopping ${myback}, use /run_${myback} to start again." else @@ -56,7 +56,7 @@ watch_dir_loop() { echo "$(date): new file: ${newfile}" >>"$0.log" # note: loop callback must a function in the calling script! if _is_function loop_callback ; then - loop_callback "$1/$newfile" + loop_callback "$1/${newfile}" else echo "ERROR: loop_callback not found!" >&2 exit 1 @@ -94,8 +94,8 @@ output_file() { sed <<< "${1}" ' s/ *mynewlinestartshere */\n/ s/ my[a-z]\{3,15}\(start\|ends\)here.*//g - ' >"$publish$$" - cat "$publish" >>"$publish$$" + ' >"${publish}$$" + cat "${publish}" >>"${publish}$$" mv "${publish}$$" "${publish}" } # >>"$0.log" 2>&1 diff --git a/examples/background-scripts/run_diskusage.sh b/examples/background-scripts/run_diskusage.sh index eadbfbe..f5a5fbc 100755 --- a/examples/background-scripts/run_diskusage.sh +++ b/examples/background-scripts/run_diskusage.sh @@ -4,7 +4,7 @@ # # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ###### # parameters @@ -28,8 +28,8 @@ cat >/dev/null & source "./mycommands.sh" # check if $1 is a number -re='^[0-9]+$' -if [[ $1 =~ $re ]] ; then +regex='^[0-9]+$' +if [[ $1 =~ ${regex} ]] ; then SLEEP="$1" else SLEEP=100 # time between time notifications @@ -39,11 +39,11 @@ NEWLINE=$'\n' # output disk usage every $1 seconds WAIT=0 -while sleep $WAIT +while sleep "${WAIT}" do output_telegram "Current Disk usage ${NEWLINE} $(df -h / /tmp /usr /var /home)" # only for testing, delete echo line for production ... echo "Current Disk usage ${NEWLINE} $(df -h / /tmp /usr /var /home)" - WAIT="$SLEEP" + WAIT="${SLEEP}" done diff --git a/examples/background-scripts/run_filecontent.sh b/examples/background-scripts/run_filecontent.sh index 04cbfdd..67e35cb 100755 --- a/examples/background-scripts/run_filecontent.sh +++ b/examples/background-scripts/run_filecontent.sh @@ -2,7 +2,7 @@ # file: run_filename # background job to display content of all new files in WATCHDIR # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ###### # parameters @@ -40,4 +40,4 @@ loop_callback() { output_telegram "Contents of ${1}: ${NEWLINE} $(cat "${1}")" } -watch_dir_loop "$WATCHDIR" +watch_dir_loop "${WATCHDIR}" diff --git a/examples/background-scripts/run_filename.sh b/examples/background-scripts/run_filename.sh index 1b562e6..0ade91d 100755 --- a/examples/background-scripts/run_filename.sh +++ b/examples/background-scripts/run_filename.sh @@ -2,7 +2,7 @@ # file: run_filename # background job to display all new files in WATCHDIR # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ###### # parameters @@ -38,5 +38,5 @@ loop_callback() { echo "New file ${1} created in ${WATCHDIR}!" } -watch_dir_loop "$WATCHDIR" +watch_dir_loop "${WATCHDIR}" diff --git a/examples/background-scripts/run_notify.sh b/examples/background-scripts/run_notify.sh index 67cf313..b27cb64 100755 --- a/examples/background-scripts/run_notify.sh +++ b/examples/background-scripts/run_notify.sh @@ -4,7 +4,7 @@ # # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ###### # parameters @@ -25,15 +25,15 @@ unset IFS cat >/dev/null & # check if $1 is a number -re='^[0-9]+$' -if [[ $1 =~ $re ]] ; then +regex='^[0-9]+$' +if [[ "$1" =~ ${regex} ]] ; then SLEEP="$1" else SLEEP=10 # time between time notifications fi # output current time every $1 seconds -while sleep $SLEEP +while sleep "${SLEEP}" do date "+* It's %k:%M:%S o' clock ..." done diff --git a/examples/bash2env.sh b/examples/bash2env.sh index 2870fcc..b7972ae 100755 --- a/examples/bash2env.sh +++ b/examples/bash2env.sh @@ -6,7 +6,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # shellcheck disable=SC1117 -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # adjust your language setting here # https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment diff --git a/examples/bashbot-multi.sh b/examples/bashbot-multi.sh index a0b3951..c6fa3aa 100755 --- a/examples/bashbot-multi.sh +++ b/examples/bashbot-multi.sh @@ -2,14 +2,14 @@ # file. multibot.sh # description: run multiple telegram bots from one installation # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 -if [ "${2}" = "" ] || [ "${2}" = "-h" ]; then +if [ "$2" = "" ] || [ "$2" = "-h" ]; then echo "Usage: $0 botname command" exit 1 fi -BOT="${1}" +BOT="$1" [ "${#BOT}" -lt 5 ] && echo "Botname must have a minimum length of 5 characters" && exit 1 # where should the bots live? diff --git a/examples/bashbot.cron b/examples/bashbot.cron index cdf5629..0a12049 100644 --- a/examples/bashbot.cron +++ b/examples/bashbot.cron @@ -7,7 +7,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 SHELL=/bin/sh diff --git a/examples/calc.sh b/examples/calc.sh index 72f94f6..7a1ce99 100755 --- a/examples/calc.sh +++ b/examples/calc.sh @@ -11,7 +11,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ######################################################################## ###### diff --git a/examples/jsonDB-keyboard/mycommands.sh b/examples/jsonDB-keyboard/mycommands.sh index 2352974..e1739ab 100644 --- a/examples/jsonDB-keyboard/mycommands.sh +++ b/examples/jsonDB-keyboard/mycommands.sh @@ -10,7 +10,7 @@ # AUTHOR: KayM (), kay@rrr.de # DATE: 19.12.2020 19:03 # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== # shellcheck disable=SC2154 # shellcheck disable=SC2034 @@ -326,8 +326,8 @@ fi MYKEYF="$2" MINLEN="4" # check len of keys - for MYKEY in ${MYFIND}; do [ "${#MYKEY}" -lt ${MINLEN} ] && break; done - if [ "${#MYKEY}" -lt ${MINLEN} ]; then + for MYKEY in ${MYFIND}; do [ "${#MYKEY}" -lt "${MINLEN}" ] && break; done + if [ "${#MYKEY}" -lt "${MINLEN}" ]; then send_markdownv2_message "${CHAT[ID]}" "*Ein Suchbegriff ist kürzer als ${MINLEN} Zeichen!*" else MYFIND="$(create_pattern "${MYFIND}")" diff --git a/examples/notify.sh b/examples/notify.sh index 2cc2678..b709668 100755 --- a/examples/notify.sh +++ b/examples/notify.sh @@ -13,7 +13,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ######################################################################## ###### @@ -47,6 +47,6 @@ printf "Output time every %s seconds ...\n" "${SLEEP}" while true do date "+* It's %k:%M:%S o'clock ..." - sleep $SLEEP + sleep "${SLEEP}" done diff --git a/examples/question.sh b/examples/question.sh index 17479b0..8b116a3 100755 --- a/examples/question.sh +++ b/examples/question.sh @@ -10,7 +10,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 ######################################################################## ###### @@ -39,7 +39,7 @@ else fi # question with Keyboard, repeating until correct answer given -until [ "$SUCCESS" = "y" ] ;do +until [ "${SUCCESS}" = "y" ] ;do printf 'Do you like Music? mykeyboardstartshere "Yass!" , "No"\n' read -r answer <"${INPUT}" case ${answer,,} in diff --git a/examples/send-system-status/botacl b/examples/send-system-status/botacl index 0d4c3a1..8fcce22 100644 --- a/examples/send-system-status/botacl +++ b/examples/send-system-status/botacl @@ -1,7 +1,7 @@ # file: botacl # a user not listed here, will return false from 'user_is_allowed' # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # Format: # user:resource:chat diff --git a/examples/send-system-status/mycommands.sh b/examples/send-system-status/mycommands.sh index 2704143..661497b 100644 --- a/examples/send-system-status/mycommands.sh +++ b/examples/send-system-status/mycommands.sh @@ -5,7 +5,7 @@ # to show how you can customize bashbot by only editing mycommands.sh # NOTE: this is not tested, simply copied from original source and reworked! # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # # shellcheck disable=SC2154 # shellcheck disable=SC2034 @@ -38,7 +38,7 @@ mycommands() { local msg="" if user_is_botadmin "${USER[ID]}" || user_is_allowed "${USER[ID]}" "systemstatus"; then - case "$CMD" in + case "${CMD}" in '/md'*) msg="$(cat /proc/mdstat)";; '/smb'*) msg="$(smbstatus)" ;; '/se'*) msg="$(sensors | sed -r 's/\s|\)+//g' | sed -r 's/\(high=|\(min=/\//' | sed -r 's/\,crit=|\,max=/\//')";; @@ -51,14 +51,14 @@ mycommands() { '/smart'*) [ "${CMD[1]}" == "" ] && msg="example \`/smart sda\`" && return drive="$(echo "${CMD[1]}" | cut -c 1-3)" - echo "smartctl -a /dev/$drive" - msg="$(smartctl -a "/dev/$drive")" + echo "smartctl -a /dev/${drive}" + msg="$(smartctl -a "/dev/${drive}")" ;; '/df') msg="$(df -h | sed -r 's/^/\n/' | sed -r 's/\s+/\n/g')";; esac - if [ "$msg" != "" ]; then - send_normal_message "${CHAT[ID]}" "$msg" + if [ "${msg}" != "" ]; then + send_normal_message "${CHAT[ID]}" "${msg}" fi else send_normal_message "${USER[ID]}" "Sorry, you are not allowed to use this bot!" diff --git a/modules/aliases.sh b/modules/aliases.sh index a160db0..0e551a7 100644 --- a/modules/aliases.sh +++ b/modules/aliases.sh @@ -5,7 +5,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # # will be automatically sourced from bashbot @@ -23,36 +23,36 @@ _is_creator() { user_is_creator "${CHAT[ID]}" "${USER[ID]}" } _is_allowed() { - user_is_allowed "${USER[ID]}" "${1}" "${CHAT[ID]}" + user_is_allowed "${USER[ID]}" "$1" "${CHAT[ID]}" } _leave() { leave_chat "${CHAT[ID]}" } _kick_user() { - kick_chat_member "${CHAT[ID]}" "${1}" + kick_chat_member "${CHAT[ID]}" "$1" } _unban_user() { - unban_chat_member "${CHAT[ID]}" "${1}" + unban_chat_member "${CHAT[ID]}" "$1" } # easy sending of messages of messages _message() { - send_normal_message "${CHAT[ID]}" "${1}" + send_normal_message "${CHAT[ID]}" "$1" } _normal_message() { - send_normal_message "${CHAT[ID]}" "${1}" + send_normal_message "${CHAT[ID]}" "$1" } _html_message() { - send_html_message "${CHAT[ID]}" "${1}" + send_html_message "${CHAT[ID]}" "$1" } _markdown_message() { - send_markdown_message "${CHAT[ID]}" "${1}" + send_markdown_message "${CHAT[ID]}" "$1" } # easy handling of keyboards _inline_button() { - send_inline_button "${CHAT[ID]}" "" "${1}" "${2}" + send_inline_button "${CHAT[ID]}" "" "$1" "$2" } _inline_keyboard() { - send_inline_keyboard "${CHAT[ID]}" "" "${1}" + send_inline_keyboard "${CHAT[ID]}" "" "$1" } _keyboard_numpad() { send_keyboard "${CHAT[ID]}" "" '["1","2","3"],["4","5","6"],["7","8","9"],["-","0","."]' "yes" diff --git a/modules/answerInline.sh b/modules/answerInline.sh index 83534da..76ffb64 100644 --- a/modules/answerInline.sh +++ b/modules/answerInline.sh @@ -5,20 +5,19 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # will be automatically sourced from bashbot # source once magic, function named like file eval "$(basename "${BASH_SOURCE[0]}")(){ :; }" -INLINE_QUERY=$URL'/answerInlineQuery' answer_inline_query() { - answer_inline_multi "${1}" "$(shift; inline_query_compose "$RANDOM" "$@")" + answer_inline_multi "$1" "$(shift; inline_query_compose "${RANDOM}" "$@")" } answer_inline_multi() { - sendJson "" '"inline_query_id": '"${1}"', "results": ['"${2}"']' "${INLINE_QUERY}" + sendJson "" '"inline_query_id": '"$1"', "results": ['"$2"']' "${URL}/answerInlineQuery" } # $1 unique ID for answer @@ -27,76 +26,76 @@ answer_inline_multi() { # followed by the optional arguments: https://core.telegram.org/bots/api#inlinequeryresult inline_query_compose(){ local JSON="{}" - local ID="${1}" + local ID="$1" local fours last # title2Json title caption description markup inlinekeyboard - case "${2}" in + case "$2" in # user provided media "article"|"message") # article ID title message (markup description) - JSON='{"type":"article","id":"'$ID'","input_message_content": {"message_text":"'$4'"} '$(title2Json "$3" "" "$5" "$6" "$7")'}' + JSON='{"type":"article","id":"'${ID}'","input_message_content": {"message_text":"'$4'"} '$(title2Json "$3" "" "$5" "$6" "$7")'}' ;; "photo") # photo ID photoURL (thumbURL title description caption) [ -z "$4" ] && tumb="$3" - JSON='{"type":"photo","id":"'$ID'","photo_url":"'$3'","thumb_url":"'$4${tumb}'"'$(title2Json "$5" "$7" "$6" "$7" "$8")'}' + JSON='{"type":"photo","id":"'${ID}'","photo_url":"'$3'","thumb_url":"'$4${tumb}'"'$(title2Json "$5" "$7" "$6" "$7" "$8")'}' ;; "gif") # gif ID photoURL (thumbURL title caption) [ -z "$4" ] && tumb="$3" - JSON='{"type":"gif","id":"'$ID'","gif_url":"'$3'", "thumb_url":"'$4${tumb}'"'$(title2Json "$5" "$6" "$7" "$8" "$9")'}' + JSON='{"type":"gif","id":"'${ID}'","gif_url":"'$3'", "thumb_url":"'$4${tumb}'"'$(title2Json "$5" "$6" "$7" "$8" "$9")'}' ;; "mpeg4_gif") # mpeg4_gif ID mpegURL (thumbURL title caption) [ -n "$4" ] && tumb='","thumb_url":"'$4'"' - JSON='{"type":"mpeg4_gif","id":"'$ID'","mpeg4_url":"'$3'"'${tumb}$(title2Json "$5" "$6" "" "$7" "$8")'}' + JSON='{"type":"mpeg4_gif","id":"'${ID}'","mpeg4_url":"'$3'"'${tumb}$(title2Json "$5" "$6" "" "$7" "$8")'}' ;; "video") # video ID videoURL mime thumbURL title (caption) - JSON='{"type":"video","id":"'$ID'","video_url":"'$3'","mime_type":"'$4'","thumb_url":"'$5'"'$(title2Json "$6" "$7" "$8" "$9" "${10}")'}' + JSON='{"type":"video","id":"'${ID}'","video_url":"'$3'","mime_type":"'$4'","thumb_url":"'$5'"'$(title2Json "$6" "$7" "$8" "$9" "${10}")'}' ;; "audio") # audio ID audioURL title (caption) - JSON='{"type":"audio","id":"'$ID'","audio_url":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' + JSON='{"type":"audio","id":"'${ID}'","audio_url":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' ;; "voice") # voice ID voiceURL title (caption) - JSON='{"type":"voice","id":"'$ID'","voice_url":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' + JSON='{"type":"voice","id":"'${ID}'","voice_url":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' ;; "document") # document ID title documentURL mimetype (caption description) - JSON='{"type":"document","id":"'$ID'","document_url":"'$4'","mime_type":"'$5'"'$(title2Json "$3" "$6" "$7" "$8" "$9")'}' + JSON='{"type":"document","id":"'${ID}'","document_url":"'$4'","mime_type":"'$5'"'$(title2Json "$3" "$6" "$7" "$8" "$9")'}' ;; "location") # location ID lat long title - JSON='{"type":"location","id":"'$ID'","latitude":"'$3'","longitude":"'$4'","title":"'$5'"}' + JSON='{"type":"location","id":"'${ID}'","latitude":"'$3'","longitude":"'$4'","title":"'$5'"}' ;; "venue") # venue ID lat long title (address forsquare) [ -z "$6" ] && addr="$5" [ -n "$7" ] && fours=',"foursquare_id":"'$7'"' - JSON='{"type":"venue","id":"'$ID'","latitude":"'$3'","longitude":"'$4'","title":"'$5'","address":"'$6${addr}'"'${fours}'}' + JSON='{"type":"venue","id":"'${ID}'","latitude":"'$3'","longitude":"'$4'","title":"'$5'","address":"'$6${addr}'"'${fours}'}' ;; "contact") # contact ID phone first (last thumb) [ -n "$5" ] && last=',"last_name":"'$5'"' [ -n "$6" ] && tumb='","thumb_url":"'$6'"' - JSON='{"type":"contact","id":"'$ID'","phone_number":"'$3'","first_name":"'$4'"'${last}'"}' + JSON='{"type":"contact","id":"'${ID}'","phone_number":"'$3'","first_name":"'$4'"'${last}'"}' ;; # title2Json title caption description markup inlinekeyboard # Cached media stored in Telegram server "cached_photo") # photo ID file (title description caption) - JSON='{"type":"photo","id":"'$ID'","photo_file_id":"'$3'"'$(title2Json "$4" "$6" "$5" "$7" "$8")'}' + JSON='{"type":"photo","id":"'${ID}'","photo_file_id":"'$3'"'$(title2Json "$4" "$6" "$5" "$7" "$8")'}' ;; "cached_gif") # gif ID file (title caption) - JSON='{"type":"gif","id":"'$ID'","gif_file_id":"'$3'"'$(title2Json "$4" "$5" "$6" "$7" "$8" )'}' + JSON='{"type":"gif","id":"'${ID}'","gif_file_id":"'$3'"'$(title2Json "$4" "$5" "$6" "$7" "$8" )'}' ;; "cached_mpeg4_gif") # mpeg ID file (title caption) - JSON='{"type":"mpeg4_gif","id":"'$ID'","mpeg4_file_id":"'$3'"'$(title2Json "$4" "$5" "" "$6" "$7")'}' + JSON='{"type":"mpeg4_gif","id":"'${ID}'","mpeg4_file_id":"'$3'"'$(title2Json "$4" "$5" "" "$6" "$7")'}' ;; "cached_sticker") # sticker ID file - JSON='{"type":"sticker","id":"'$ID'","sticker_file_id":"'$3'"}' + JSON='{"type":"sticker","id":"'${ID}'","sticker_file_id":"'$3'"}' ;; "cached_document") # document ID title file (description caption) - JSON='{"type":"document","id":"'$ID'","document_file_id":"'$4'"'$(title2Json "$3" "$6" "$5" "$6" "$7")'}' + JSON='{"type":"document","id":"'${ID}'","document_file_id":"'$4'"'$(title2Json "$3" "$6" "$5" "$6" "$7")'}' ;; "cached_video") # video ID file title (description caption) - JSON='{"type":"video","id":"'$ID'","video_file_id":"'$3'"'$(title2Json "$4" "$6" "$5" "$7" "$8")'}' + JSON='{"type":"video","id":"'${ID}'","video_file_id":"'$3'"'$(title2Json "$4" "$6" "$5" "$7" "$8")'}' ;; "cached_voice") # voice ID file title (caption) - JSON='{"type":"voice","id":"'$ID'","voice_file_id":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' + JSON='{"type":"voice","id":"'${ID}'","voice_file_id":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' ;; "cached_audio") # audio ID file title (caption) - JSON='{"type":"audio","id":"'$ID'","audio_file_id":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' + JSON='{"type":"audio","id":"'${ID}'","audio_file_id":"'$3'"'$(title2Json "$4" "$5" "" "" "$6")'}' ;; esac diff --git a/modules/background.sh b/modules/background.sh index 5e0513b..4409616 100644 --- a/modules/background.sh +++ b/modules/background.sh @@ -6,7 +6,7 @@ # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # # shellcheck disable=SC1117,SC2059 -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # will be automatically sourced from bashbot @@ -48,9 +48,9 @@ start_back() { } restart_back() { local fifo; fifo="${DATADIR:-.}/$(procname "$1" "back-$3-")" - printf "%s: Start background job CHAT=%s JOB=%s CMD=%s\n" "$(date)" "${1}" "${fifo##*/}" "${2##*/} ${4} ${5}" >>"${UPDATELOG}" + log_message "Start background job CHAT=$1 JOB=${fifo##*/} CMD=${2##*/} $4 $5" check_back "$1" "$3" && kill_proc "$1" "back-$3-" - nohup bash -c "{ $2 \"$4\" \"$5\" \"${fifo}\" | \"${SCRIPT}\" outproc \"${1}\" \"${fifo}\"; }" &>>"${fifo}.log" & + nohup bash -c "{ $2 \"$4\" \"$5\" \"${fifo}\" | \"${SCRIPT}\" outproc \"$1\" \"${fifo}\"; }" &>>"${fifo}.log" & sleep 0.5 # give bg job some time to init } @@ -62,10 +62,10 @@ start_proc() { [ -z "$2" ] && return [ -x "${2%% *}" ] || return 1 local fifo; fifo="${DATADIR:-.}/$(procname "$1")" - printf "%s: Start interacitve script CHAT=%s JOB=%s CMD=%s\n" "$(date)" "${1}" "${fifo##*/}" "${2} ${3} ${4}" >>"${UPDATELOG}" + log_message "Start interactive script CHAT=$1 JOB=${fifo##*/} CMD=$2 $3 $4" check_proc "$1" && kill_proc "$1" mkfifo "${fifo}" - nohup bash -c "{ $2 \"$4\" \"$5\" \"$fifo\" | \"${SCRIPT}\" outproc \"${1}\" \"${fifo}\" + nohup bash -c "{ $2 \"$4\" \"$5\" \"${fifo}\" | \"${SCRIPT}\" outproc \"$1\" \"${fifo}\" rm \"${fifo}\"; [ -s \"${fifo}.log\" ] || rm -f \"${fifo}.log\"; }" &>>"${fifo}.log" & } @@ -99,7 +99,7 @@ kill_proc() { fifo="$(procname "$1" "$2")" prid="$(proclist "${fifo}")" fifo="${DATADIR:-.}/${fifo}" - printf "%s: Stop interacitve / background CHAT=%s JOB=%s\n" "$(date)" "${1}" "${fifo##*/}" >>"${UPDATELOG}" + log_message "Stop interactive / background CHAT=$1 JOB=${fifo##*/}" # shellcheck disable=SC2086 [ -n "${prid}" ] && kill ${prid} [ -s "${fifo}.log" ] || rm -f "${fifo}.log" @@ -127,7 +127,7 @@ job_control() { local BOT ADM content proc CHAT job fifo killall="" BOT="$(getConfigKey "botname")" ADM="$(getConfigKey "botadmin")" - debug_checks "Enter job_control" "${1}" + debug_checks "Enter job_control" "$1" for FILE in "${DATADIR:-.}/"*-back.cmd; do [ "${FILE}" = "${DATADIR:-.}/*-back.cmd" ] && printf "${RED}No background processes.${NN}" && break content="$(< "${FILE}")" @@ -136,7 +136,7 @@ job_control() { proc="${job#*:}" job="${job%:*}" fifo="$(procname "${CHAT}" "${job}")" - debug_checks "Execute job_control" "${1}" "${FILE##*/}" + debug_checks "Execute job_control" "$1" "${FILE##*/}" case "$1" in "resumeb"*|"backgr"*) printf "Restart Job: %s %s\n" "${proc}" " ${fifo##*/}" @@ -163,7 +163,7 @@ job_control() { # send message only onnfirst job ADM="" done - debug_checks "end job_control" "${1}" + debug_checks "end job_control" "$1" # kill all requestet. kill ALL background jobs, even not listed in data-bot-bash [ "${killall}" = "y" ] && killallproc "back-" } diff --git a/modules/chatMember.sh b/modules/chatMember.sh index 4f23645..2f9a316 100644 --- a/modules/chatMember.sh +++ b/modules/chatMember.sh @@ -5,36 +5,78 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # will be automatically sourced from bashbot # source once magic, function named like file eval "$(basename "${BASH_SOURCE[0]}")(){ :; }" -LEAVE_URL=$URL'/leaveChat' -KICK_URL=$URL'/kickChatMember' -UNBAN_URL=$URL'/unbanChatMember' -GETMEMBER_URL=$URL'/getChatMember' + +# manage chat functions ------- +# $1 chat +new_chat_invite() { + sendJson "$1" "" "${URL}/exportChatInviteLink" + [ "${BOTSENT[OK]}" = "true" ] && printf "%s\n" "${BOTSENT[RESULT]}" +} + +# $1 chat, $2 title +set_chat_title() { + sendJson "$1" '"title": "'"$2"'"' "${URL}/setChatTitle" +} + +# $1 chat, $2 title +set_chat_description() { + sendJson "$1" '"description": "'"$2"'"' "${URL}/setChatDescription" +} + +# $1 chat +delete_chat_photo() { + sendJson "$1" "" "${URL}/deleteChatPhoto" +} + +# $1 chat, $2 message_id +pin_chat_message() { + sendJson "$1" '"message_id": "'"$2"'"' "${URL}/pinChatMessage" +} + +# $1 chat, $2 message_id +unpin_chat_message() { + sendJson "$1" '"message_id": "'"$2"'"' "${URL}/unpinChatMessage" +} + +# $1 chat +unpinall_chat_message() { + sendJson "$1" "" "${URL}/unpinAllChatMessages" +} + +# $1 chat +delete_chat_stickers() { + sendJson "$1" "" "${URL}/deleteChatStickerSet" +} + +# manage chat member functions ------- +kick_chat_member() { + sendJson "$1" 'user_id: '"$2"'' "${URL}/kickChatMember" +} + +unban_chat_member() { + sendJson "$1" 'user_id: '"$2"'' "${URL}/unbanChatMember" +} + +leave_chat() { + sendJson "$1" "" "${URL}/leaveChat" +} + + +# bashbot specific functions --------- # usage: status="$(get_chat_member_status "chat" "user")" # $1 chat # $2 user get_chat_member_status() { - sendJson "$1" '"user_id":'"$2"'' "$GETMEMBER_URL" + sendJson "$1" '"user_id":'"$2"'' "${URL}/getChatMember" # shellcheck disable=SC2154 - JsonGetString '"result","status"' <<< "$res" -} - -kick_chat_member() { - sendJson "$1" 'user_id: '"$2"'' "$KICK_URL" -} - -unban_chat_member() { - sendJson "$1" 'user_id: '"$2"'' "$UNBAN_URL" -} - -leave_chat() { - sendJson "$1" "" "$LEAVE_URL" + JsonGetString '"result","status"' <<< "${res}" } user_is_creator() { @@ -62,8 +104,8 @@ user_is_admin() { user_is_botadmin() { [ -z "$1" ] && return 1 local admin; admin="$(getConfigKey "botadmin")"; [ -z "${admin}" ] && return 1 - [[ "${admin}" == "${1}" || "${admin}" == "${2}" ]] && return 0 - #[[ "${admin}" = "@*" ]] && [[ "${admin}" = "${2}" ]] && return 0 + [[ "${admin}" == "$1" || "${admin}" == "$2" ]] && return 0 + #[[ "${admin}" = "@*" ]] && [[ "${admin}" = "$2" ]] && return 0 if [ "${admin}" = "?" ]; then setConfigKey "botadmin" "${1:-?}"; return 0; fi return 1 } @@ -71,17 +113,18 @@ user_is_botadmin() { # $1 user # $2 key # $3 chat user_is_allowed() { [ -z "$1" ] && return 1 + user_is_admin "$1" && return 0 # user can do everything - grep -F -xq "$1:*:*" <"${BOTACL}" && return 0 + grep -F -xq "$1:*:*" "${BOTACL}" && return 0 [ -z "$2" ] && return 1 # user is allowed todo one action in every chat - grep -F -xq "$1:$2:*" <"${BOTACL}" && return 0 + grep -F -xq "$1:$2:*" "${BOTACL}" && return 0 # all users are allowed to do one action in every chat - grep -F -xq "ALL:$2:*" <"${BOTACL}" && return 0 + grep -F -xq "ALL:$2:*" "${BOTACL}" && return 0 [ -z "$3" ] && return 1 # user is allowed to do one action in one chat - grep -F -xq "$1:$2:$3" <"${BOTACL}" && return 0 + grep -F -xq "$1:$2:$3" "${BOTACL}" && return 0 # all users are allowed to do one action in one chat - grep -F -xq "ALL:$2:$3" <"${BOTACL}" && return 0 + grep -F -xq "ALL:$2:$3" "${BOTACL}" && return 0 return 1 } diff --git a/modules/jsonDB.sh b/modules/jsonDB.sh index 90f11f9..9c49c3c 100644 --- a/modules/jsonDB.sh +++ b/modules/jsonDB.sh @@ -5,7 +5,7 @@ # This file is public domain in the USA and all free countries. # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # # source from commands.sh to use jsonDB functions # @@ -27,6 +27,28 @@ eval "$(basename "${BASH_SOURCE[0]}")(){ :; }" # lockfile filename.flock is persistent and will be testet with flock for active lock (file open) export JSSH_LOCKNAME=".flock" +# in UTF-8 äöü etc. are part of [:alnum:] and ranges (e.g. a-z), but we want ASCII a-z ranges! +# for more information see doc/4_expert.md#Character_classes +azazaz='abcdefghijklmnopqrstuvwxyz' # a-z :lower: +AZAZAZ='ABCDEFGHIJKLMNOPQRSTUVWXYZ' # A-Z :upper: +o9o9o9='0123456789' # 0-9 :digit: +azAZaz="${azazaz}${AZAZAZ}" # a-zA-Z :alpha: +azAZo9="${azAZaz}${o9o9o9}" # a-zA-z0-9 :alnum: + +# characters allowed for key in key/value pairs +JSSH_KEYOK="[-${azAZo9},._]" + +# read string from stdin and and strip invalid characters +# $1 - invalid charcaters are replaced with first character +# or deleted if $1 is empty +jssh_stripKey() { # tr: we must escape first - in [-a-z...] + if [[ "$1" =~ ^${JSSH_KEYOK} ]]; then # tr needs [\-... + tr -c "${JSSH_KEYOK/\[-/[\\-}\r\n" "${1:0:1}" + else + tr -dc "${JSSH_KEYOK/\[-/[\\-}\r\n" + fi +} + # use flock if command exist if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then @@ -63,8 +85,8 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then # complex slow, warpper async jssh_updateDB() { # for atomic update we can't use read/writeDB - [ -z "${2}" ] && return 1 - local DB="${2}.jssh" # check in async + [ -z "$2" ] && return 1 + local DB="$2.jssh" # check in async [ ! -f "${DB}" ] && return 2 { flock -e -w 10 200; jssh_updateDB_async "$@"; } 200>"${DB}${JSSH_LOCKNAME}" } @@ -76,7 +98,7 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then alias jssh_insertDB=jssh_insertKeyDB # backward compatibility # renamed to be more consistent jssh_insertKeyDB() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 local DB; DB="$(jssh_checkDB "$3")" [ -z "${DB}" ] && return 1 [ ! -f "${DB}" ] && return 2 @@ -93,9 +115,9 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then # $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..' # medium complex slow, wrapper async jssh_deleteKeyDB() { - [ -z "${2}" ] && return 1 - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 - local DB="${2}.jssh" + [ -z "$2" ] && return 1 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 + local DB="$2.jssh" # start atomic delete here, exclusive max wait 10s { flock -e -w 10 200; jssh_deleteKeyDB_async "$@"; } 200>"${DB}${JSSH_LOCKNAME}" } @@ -105,12 +127,12 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then # $2 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..' alias jssh_getDB=jssh_getKeyDB jssh_getKeyDB() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 local DB; DB="$(jssh_checkDB "$2")" [ -z "${DB}" ] && return 1 # start atomic delete here, exclusive max wait 1s { flock -s -w 1 200 - [ -r "${DB}" ] && sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' <"${DB}" | tail -n 1 + [ -r "${DB}" ] && sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' "${DB}" | tail -n 1 } 200>"${DB}${JSSH_LOCKNAME}" } @@ -122,9 +144,9 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then # side effect: if $3 is not given, we add to end of file to be as fast as possible # complex, wrapper to async jssh_countKeyDB() { - [ -z "${2}" ] && return 1 - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 - local DB="${2}.jssh" + [ -z "$2" ] && return 1 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 + local DB="$2.jssh" # start atomic delete here, exclusive max wait 5 { flock -e -w 5 200; jssh_countKeyDB_async "$@"; } 200>"${DB}${JSSH_LOCKNAME}" } @@ -135,12 +157,12 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then # $3 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..' #no own locking, so async is the same as updatekeyDB jssh_updateKeyDB() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 - [ -z "${3}" ] && return 1 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 + [ -z "$3" ] && return 1 declare -A updARR # shellcheck disable=SC2034 updARR["$1"]="$2" - jssh_updateDB "updARR" "${3}" || return 3 + jssh_updateDB "updARR" "$3" || return 3 } # $1 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..' @@ -156,11 +178,11 @@ if [ "$(LC_ALL=C type -t "flock")" = "file" ]; then # $3 id used to identify caller # medium complex, wrapper async jssh_updateArray() { - [ -z "${2}" ] && return 1 - local DB="${2}.jssh" # name check in async + [ -z "$2" ] && return 1 + local DB="$2.jssh" # name check in async [ ! -f "${DB}" ] && return 2 declare -n ARRAY="$1" - [[ -z "${ARRAY[*]}" || "${DB}" -nt "${DB}.last${3}" ]] && touch "${DB}.last${3}" && jssh_readDB "${1}" "${2}" + [[ -z "${ARRAY[*]}" || "${DB}" -nt "${DB}.last$3" ]] && touch "${DB}.last$3" && jssh_readDB "$1" "$2" } else @@ -206,9 +228,9 @@ jssh_checkDB(){ [ -z "$1" ] && return 1 [[ "$1" = *'../.'* ]] && return 2 if [[ "$1" == "${BASHBOT_VAR:-.}"* ]] || [[ "$1" == "${BASHBOT_DATA:-.}"* ]]; then - DB="${1}.jssh" + DB="$1.jssh" else - DB="${BASHBOT_VAR:-.}/${1}.jssh" + DB="${BASHBOT_VAR:-.}/$1.jssh" fi [ "${DB}" != ".jssh" ] && printf '%s' "${DB}" } @@ -232,7 +254,7 @@ jssh_writeDB_async() { } jssh_updateDB_async() { - [ -z "${2}" ] && return 1 + [ -z "$2" ] && return 1 declare -n ARRAY="$1" [ -z "${ARRAY[*]}" ] && return 1 declare -A oldARR @@ -253,7 +275,7 @@ jssh_updateDB_async() { jssh_insertDB_async() { jssh_insertKeyDB "$@"; } jssh_insertKeyDB_async() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 local DB; DB="$(jssh_checkDB "$3")" [ -z "${DB}" ] && return 1 [ ! -f "${DB}" ] && return 2 @@ -263,7 +285,7 @@ jssh_insertKeyDB_async() { } jssh_deleteKeyDB_async() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 local DB; DB="$(jssh_checkDB "$2")" [ -z "${DB}" ] && return 1 declare -A oldARR @@ -273,14 +295,14 @@ jssh_deleteKeyDB_async() { } jssh_getKeyDB_async() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 local DB; DB="$(jssh_checkDB "$2")" [ -z "${DB}" ] && return 1 - [ -r "${DB}" ] && sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' <"${DB}" | tail -n 1 + [ -r "${DB}" ] && sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' "${DB}" | tail -n 1 } jssh_countKeyDB_async() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 local VAL DB; DB="$(jssh_checkDB "$2")" [ -z "${DB}" ] && return 1 # start atomic delete here, exclusive max wait 5 @@ -291,7 +313,7 @@ jssh_countKeyDB_async() { Array2Json "oldARR" >"${DB}" elif [ -r "${DB}" ]; then # it's append, but last one counts, its a simple DB ... - VAL="$(sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' <"${DB}" | tail -n 1)" + VAL="$(sed -n 's/\["'"$1"'"\]\t*"\(.*\)"/\1/p' "${DB}" | tail -n 1)" printf '["%s"]\t"%s"\n' "${1//,/\",\"}" "$((++VAL))" >>"${DB}" fi } @@ -302,12 +324,12 @@ jssh_countKeyDB_async() { # $3 filename (must exist!), must be relative to BASHBOT_ETC, and not contain '..' #no own locking, so async is the same as updatekeyDB jssh_updateKeyDB_async() { - [[ "$1" =~ ^[-a-zA-Z0-9,._]+$ ]] || return 3 - [ -z "${3}" ] && return 1 + [[ "$1" =~ ^${JSSH_KEYOK}+$ ]] || return 3 + [ -z "$3" ] && return 1 declare -A updARR # shellcheck disable=SC2034 updARR["$1"]="$2" - jssh_updateDB_async "updARR" "${3}" || return 3 + jssh_updateDB_async "updARR" "$3" || return 3 } jssh_clearDB_async() { @@ -321,7 +343,7 @@ function jssh_updateArray_async() { [ -z "${DB}" ] && return 1 [ ! -f "${DB}" ] && return 2 declare -n ARRAY="$1" - [[ -z "${ARRAY[*]}" || "${DB}" -nt "${DB}.last${3}" ]] && touch "${DB}.last${3}" && jssh_readDB_async "${1}" "${2}" + [[ -z "${ARRAY[*]}" || "${DB}" -nt "${DB}.last$3" ]] && touch "${DB}.last$3" && jssh_readDB_async "$1" "$2" } ############## @@ -340,13 +362,13 @@ Json2Array() { # $1 ARRAY name, must be declared with "declare -A ARRAY" before calling Array2Json() { [ -z "$1" ] && return 1 - local key val + local key declare -n ARRAY="$1" for key in "${!ARRAY[@]}" do - # in case val contains newline convert to \n - val="${ARRAY[${key}]//$'\n'/\\n}" - printf '["%s"]\t"%s"\n' "${key//,/\",\"}" "${val//\"/\\\"}" + [[ "${key}" =~ ^${JSSH_KEYOK}+$ ]] || continue + # in case value contains newline convert to \n + : "${ARRAY[${key}]//$'\n'/\\n}" + printf '["%s"]\t"%s"\n' "${key//,/\",\"}" "${_//\"/\\\"}" done } - diff --git a/modules/sendMessage.sh b/modules/sendMessage.sh index e5ef612..07f5613 100644 --- a/modules/sendMessage.sh +++ b/modules/sendMessage.sh @@ -6,7 +6,7 @@ # Elsewhere, consider it to be WTFPLv2. (wtfpl.net/txt/copying) # # shellcheck disable=SC1117 -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 # will be automatically sourced from bashbot @@ -15,19 +15,8 @@ eval "$(basename "${BASH_SOURCE[0]}")(){ :; }" # source from commands.sh to use the sendMessage functions -MSG_URL=$URL'/sendMessage' -EDIT_URL=$URL'/editMessageText' -PHO_URL=$URL'/sendPhoto' -AUDIO_URL=$URL'/sendAudio' -DOCUMENT_URL=$URL'/sendDocument' -STICKER_URL=$URL'/sendSticker' -VIDEO_URL=$URL'/sendVideo' -VOICE_URL=$URL'/sendVoice' -LOCATION_URL=$URL'/sendLocation' -VENUE_URL=$URL'/sendVenue' -ACTION_URL=$URL'/sendChatAction' -FORWARD_URL=$URL'/forwardMessage' -ALBUM_URL=$URL'/sendMediaGroup' +MSG_URL=${URL}'/sendMessage' +EDIT_URL=${URL}'/editMessageText' # # send/edit message variants ------------------ @@ -35,17 +24,17 @@ ALBUM_URL=$URL'/sendMediaGroup' # $1 CHAT $2 message send_normal_message() { - local len text; text="$(JsonEscape "${2}")" + local len text; text="$(JsonEscape "$2")" text="${text//$'\n'/\\n}" until [ -z "${text}" ]; do if [ "${#text}" -le 4096 ]; then - sendJson "${1}" '"text":"'"${text}"'"' "${MSG_URL}" + sendJson "$1" '"text":"'"${text}"'"' "${MSG_URL}" break else len=4095 [ "${text:4095:2}" != "\n" ] &&\ len="${text:0:4096}" && len="${len%\\n*}" && len="${#len}" - sendJson "${1}" '"text":"'"${text:0:${len}}"'"' "${MSG_URL}" + sendJson "$1" '"text":"'"${text:0:${len}}"'"' "${MSG_URL}" text="${text:$((len+2))}" fi done @@ -53,48 +42,53 @@ send_normal_message() { # $1 CHAT $2 message send_markdown_message() { - _format_message_url "${1}" "${2}" ',"parse_mode":"markdown"' "${MSG_URL}" + _format_message_url "$1" "$2" ',"parse_mode":"markdown"' "${MSG_URL}" } # $1 CHAT $2 message send_markdownv2_message() { - _markdownv2_message_url "${1}" "${2}" ',"parse_mode":"markdownv2"' "${MSG_URL}" + _markdownv2_message_url "$1" "$2" ',"parse_mode":"markdownv2"' "${MSG_URL}" } # $1 CHAT $2 message send_html_message() { - _format_message_url "${1}" "${2}" ',"parse_mode":"html"' "${MSG_URL}" + _format_message_url "$1" "$2" ',"parse_mode":"html"' "${MSG_URL}" } # $1 CHAT $2 msg-id $3 message edit_normal_message() { - _format_message_url "${1}" "${3}" ',"message_id":'"${2}"'' "${EDIT_URL}" + _format_message_url "$1" "$3" ',"message_id":'"$2"'' "${EDIT_URL}" } # $1 CHAT $2 msg-id $3 message edit_markdown_message() { - _format_message_url "${1}" "${3}" ',"message_id":'"${2}"',"parse_mode":"markdown"' "${EDIT_URL}" + _format_message_url "$1" "$3" ',"message_id":'"$2"',"parse_mode":"markdown"' "${EDIT_URL}" } # $1 CHAT $2 msg-id $3 message edit_markdownv2_message() { - _markdownv2_message_url "${1}" "${3}" ',"message_id":'"${2}"',"parse_mode":"markdownv2"' "${EDIT_URL}" + _markdownv2_message_url "$1" "$3" ',"message_id":'"$2"',"parse_mode":"markdownv2"' "${EDIT_URL}" } # $1 CHAT $2 msg-id $3 message edit_html_message() { - _format_message_url "${1}" "${3}" ',"message_id":'"${2}"',"parse_mode":"html"' "${EDIT_URL}" + _format_message_url "$1" "$3" ',"message_id":'"$2"',"parse_mode":"html"' "${EDIT_URL}" +} + +# $1 chat $2 mesage_id, $3 caption +edit_message_caption() { + sendJson "$1" '"message_id":'"$2"',"caption":"'"$3"'"' "${URL}/editMessageCaption" } # internal function, send/edit formatted message with parse_mode and URL # $1 CHAT $2 message $3 action $4 URL _format_message_url(){ - local text; text="$(JsonEscape "${2}")" + local text; text="$(JsonEscape "$2")" text="${text//$'\n'/\\n}" [ "${#text}" -ge 4096 ] && log_error "Warning: html/markdown message longer than 4096 characters, message is rejected if formatting crosses 4096 border." until [ -z "${text}" ]; do - sendJson "${1}" '"text":"'"${text:0:4096}"'"'"${3}"'' "${4}" + sendJson "$1" '"text":"'"${text:0:4096}"'"'"$3"'' "$4" text="${text:4096}" done } @@ -102,13 +96,13 @@ _format_message_url(){ # internal function, send/edit markdownv2 message with URL # $1 CHAT $2 message $3 action $4 URL _markdownv2_message_url() { - local text; text="$(JsonEscape "${2}")" + local text; text="$(JsonEscape "$2")" text="${text//$'\n'/\\n}" [ "${#text}" -ge 4096 ] && log_error "Warning: markdownv2 message longer than 4096 characters, message is rejected if formatting crosses 4096 border." # markdown v2 needs additional double escaping! - text="$(sed -E -e 's|([_|~`>+=#{}()!.-])|\\\1|g' <<< "$text")" + text="$(sed -E -e 's|([_|~`>+=#{}()!.-])|\\\1|g' <<< "${text}")" until [ -z "${text}" ]; do - sendJson "${1}" '"text":"'"${text:0:4096}"'"'"${3}"'' "${4}" + sendJson "$1" '"text":"'"${text:0:4096}"'"'"$3"'' "$4" text="${text:4096}" done } @@ -121,61 +115,67 @@ _markdownv2_message_url() { send_keyboard() { if [[ "$3" != *'['* ]]; then old_send_keyboard "${@}"; return; fi local text='"text":"'"Keyboard:"'"' - if [ -n "${2}" ]; then - text="$(JsonEscape "${2}")" + if [ -n "$2" ]; then + text="$(JsonEscape "$2")" text='"text":"'"${text//$'\n'/\\n}"'"' fi local one_time=', "one_time_keyboard":true' && [ -n "$4" ] && one_time="" - sendJson "${1}" "${text}"', "reply_markup": {"keyboard": [ '"${3}"' ] '"${one_time}"'}' "$MSG_URL" - # '"text":"$2", "reply_markup": {"keyboard": [ ${3} ], "one_time_keyboard": true}' + sendJson "$1" "${text}"', "reply_markup": {"keyboard": [ '"$3"' ] '"${one_time}"'}' "${MSG_URL}" + # '"text":"$2", "reply_markup": {"keyboard": [ $3 ], "one_time_keyboard": true}' } # $1 CHAT $2 message $3 remove remove_keyboard() { local text='"text":"'"remove custom keyboard ..."'"' - if [ -n "${2}" ]; then - text="$(JsonEscape "${2}")" + if [ -n "$2" ]; then + text="$(JsonEscape "$2")" text='"text":"'"${text//$'\n'/\\n}"'"' fi - sendJson "${1}" "${text}"', "reply_markup": {"remove_keyboard":true}' "$MSG_URL" + sendJson "$1" "${text}"', "reply_markup": {"remove_keyboard":true}' "${MSG_URL}" # delete message if no message or $3 not empty - [[ -z "${2}" || -n "${3}" ]] && delete_message "${1}" "${BOTSENT[ID]}" "nolog" + [[ -z "$2" || -n "$3" ]] && delete_message "$1" "${BOTSENT[ID]}" "nolog" #JSON='"text":"$2", "reply_markup": {"remove_keyboard":true}' } # $1 CHAT $2 message $3 keyboard send_inline_keyboard() { - local text; text='"text":"'$(JsonEscape "${2}")'"'; [ -z "${2}" ] && text='"text":"'"Keyboard:"'"' - sendJson "${1}" "${text}"', "reply_markup": {"inline_keyboard": [ '"${3}"' ]}' "$MSG_URL" + local text; text='"text":"'$(JsonEscape "$2")'"'; [ -z "$2" ] && text='"text":"'"Keyboard:"'"' + sendJson "$1" "${text}"', "reply_markup": {"inline_keyboard": [ '"$3"' ]}' "${MSG_URL}" # JSON='"text":"$2", "reply_markup": {"inline_keyboard": [ $3->[{"text":"text", "url":"url"}]<- ]}' } # $1 CHAT $2 message $3 button text $4 URL send_button() { - send_inline_keyboard "${1}" "${2}" '[ {"text":"'"$(JsonEscape "${3}")"'", "url":"'"${4}"'"}]' + send_inline_keyboard "$1" "$2" '[ {"text":"'"$(JsonEscape "$3")"'", "url":"'"$4"'"}]' +} + +# $1 chat, $2 file_id on telegram server +send_sticker() { + sendJson "$1" '"sticker": "'"$2"'"' "${URL}/sendSticker" } -if [ -z "${BASHBOT_WGET}" ] && _exists curl ; then -# there are no checks if URL or ID exists -# $1 chat $3 ... $n URL or ID +# only curl can send files ... +if detect_curl ; then + # there are no checks if URL or ID exists + # $1 chat $3 ... $n URL or ID send_album(){ - [ -z "${1}" ] && return 1 - [ -z "${3}" ] && return 2 # minimum 2 files - local CHAT JSON IMAGE; CHAT="${1}"; shift + [ -z "$1" ] && return 1 + [ -z "$3" ] && return 2 # minimum 2 files + local CHAT JSON IMAGE; CHAT="$1"; shift for IMAGE in "$@" do [ -n "${JSON}" ] && JSON+="," JSON+='{"type":"photo","media":"'${IMAGE}'"}' done # shellcheck disable=SC2086 - res="$("${BASHBOT_CURL}" -s -k ${BASHBOT_CURL_ARGS} "${ALBUM_URL}" -F "chat_id=${CHAT}"\ + res="$("${BASHBOT_CURL}" -s -k ${BASHBOT_CURL_ARGS} "${URL}/sendMediaGroup" -F "chat_id=${CHAT}"\ -F "media=[${JSON}]" | "${JSONSHFILE}" -s -b -n 2>/dev/null )" sendJsonResult "${res}" "send_album (curl)" "${CHAT}" "$@" [[ -z "${SOURCE}" && -n "${BASHBOT_EVENT_SEND[*]}" ]] && event_send "album" "$@" & } else send_album(){ - log_error "Sorry, wget Album upload not yet implemented" + log_error "Sorry, wget Album upload not implemented" BOTSENT[OK]="false" [[ -z "${SOURCE}" && -n "${BASHBOT_EVENT_SEND[*]}" ]] && event_send "album" "$@" & } @@ -183,88 +183,98 @@ fi UPLOADDIR="${BASHBOT_UPLOAD:-${DATADIR}/upload}" -# for now this can only send local files with curl! -# extend to allow send files by URL or telegram ID -send_file() { - local err - upload_file "${@}"; err="$?" - # fake Telegram response to provide error - if [ "${err}" != "0" ]; then - BOTSENT=() - BOTSENT[OK]="false" - case "$err" in - 1) BOTSENT[ERROR]="Path to file $2 contains to much '../' or starts with '.'";; - 2) BOTSENT[ERROR]="Path to file $2 does not match regex: ${FILE_REGEX} ";; - 3) if [[ "$2" == "/"* ]];then - BOTSENT[ERROR]="File not found: $2" - else - BOTSENT[ERROR]="File not found: ${UPLOADDIR}/$2" - fi;; - esac - [ -n "${BASHBOTDEBUG}" ] && log_message "Error in upload_file: ${BOTSENT[ERROR]}" - fi -} - -upload_file(){ - local CUR_URL WHAT STATUS text=$3 file="$2" - # file access checks ... - [[ "$file" = *'..'* ]] && return 1 # no directory traversal - [[ "$file" = '.'* ]] && return 1 # no hidden or relative files - if [[ "$file" = '/'* ]] ; then - [[ ! "$file" =~ ${FILE_REGEX} ]] && return 2 # absolute must match REGEX +# supports local file, URL and file_id +# $1 chat, $2 file https::// file_id:// , $3 caption, $4 extension (optional) +send_file(){ + local url what num stat err media capt file="$2" ext="$4" + capt="$(JsonEscape "$3")" + if [[ "${file}" =~ ^https*:// ]]; then + media="URL" + elif [[ "${file}" == file_id://* ]]; then + media="ID" + file="${file#file_id://}" else - file="${UPLOADDIR:-NOUPLOADDIR}/${file}" # othiers must be in UPLOADDIR + # we have a file, check file location ... + media="FILE" + [[ "${file}" = *'..'* || "${file}" = '.'* ]] && err=1 # no directory traversal + if [[ "${file}" = '/'* ]] ; then + [[ ! "${file}" =~ ${FILE_REGEX} ]] && err=2 # absolute must match REGEX + else + file="${UPLOADDIR:-NOUPLOADDIR}/${file}" # others must be in UPLOADDIR + fi + [ ! -r "${file}" ] && err=3 # and file must exits of course + # file path error, generate error response + if [ -n "${err}" ]; then + BOTSENT=(); BOTSENT[OK]="false" + case "${err}" in + 1) BOTSENT[ERROR]="Path to file $2 contains to much '../' or starts with '.'";; + 2) BOTSENT[ERROR]="Path to file $2 does not match regex: ${FILE_REGEX} ";; + 3) if [[ "$2" == "/"* ]];then + BOTSENT[ERROR]="File not found: $2" + else + BOTSENT[ERROR]="File not found: ${UPLOADDIR}/$2" + fi;; + esac + [ -n "${BASHBOTDEBUG}" ] && log_message "Error in upload_file: ${BOTSENT[ERROR]}" + return + fi + # file OK, let's continue fi - [ ! -r "$file" ] && return 3 # and file must exits of course - - local ext="${file##*.}" - case $ext in - mp3|flac) - CUR_URL="$AUDIO_URL" - WHAT="audio" - STATUS="upload_audio" - ;; - png|jpg|jpeg|gif|pic) - CUR_URL="$PHO_URL" - WHAT="photo" - STATUS="upload_photo" - ;; - webp) - CUR_URL="$STICKER_URL" - WHAT="sticker" - STATUS="upload_photo" - ;; - mp4) - CUR_URL="$VIDEO_URL" - WHAT="video" - STATUS="upload_video" - ;; - ogg) - CUR_URL="$VOICE_URL" - WHAT="voice" - STATUS="upload_audio" + # no type given, use file ext, if no ext type photo + if [ -z "${ext}" ]; then + ext="${file##*.}" + [ "${ext}" = "${file}" ] && ext="photo" + fi + # select upload URL + case "${ext}" in + photo|png|jpg|jpeg|gif|pic) + url="${URL}/sendPhoto"; what="photo"; num=",0"; stat="upload_photo" ;; - *) - CUR_URL="$DOCUMENT_URL" - WHAT="document" - STATUS="upload_document" + audio|mp3|flac) + url="${URL}/sendAudio"; what="audio"; stat="upload_audio" + ;; + sticker|webp) + url="${URL}/sendSticker"; what="sticker"; stat="upload_photo" + ;; + video|mp4) + url="${URL}/sendVideo"; what="video"; stat="upload_video" + ;; + voice|ogg) + url="${URL}/sendVoice"; what="voice"; stat="record_audio" + ;; + *) url="${URL}/sendDocument"; what="document"; stat="upload_document" ;; esac - send_action "${1}" "$STATUS" - sendUpload "$1" "${WHAT}" "${file}" "${CUR_URL}" "${text//\\n/$'\n'}" + + # show file upload to user + send_action "$1" "${stat}" + # select method to send + case "${media}" in + FILE) # send local file ... + sendUpload "$1" "${what}" "${file}" "${url}" "${capt//\\n/$'\n'}";; + + URL|ID) # send URL, file_id ... + sendJson "$1" '"'"${what}"'":"'"${file}"'","caption":"'"${capt//\\n/$'\n'}"'"' "${url}" + esac + # get file_id and file_type + if [ "${BOTSENT[OK]}" = "true" ]; then + BOTSENT[FILE_ID]="${UPD["result,${what}${num},file_id"]}" + BOTSENT[FILE_TYPE]="${what}" + fi + return 0 } -# typing for text messages, upload_photo for photos, record_video or upload_video for videos, record_audio or upload_audio for audio files, upload_document for general files, find_location for location +# $1 typing upload_photo record_video upload_video record_audio upload_audio upload_document find_location send_action() { [ -z "$2" ] && return - sendJson "${1}" '"action": "'"${2}"'"' "$ACTION_URL" & + sendJson "$1" '"action": "'"$2"'"' "${URL}/sendChatAction" & } # $1 CHAT $2 lat $3 long send_location() { [ -z "$3" ] && return - sendJson "${1}" '"latitude": '"${2}"', "longitude": '"${3}"'' "$LOCATION_URL" + sendJson "$1" '"latitude": '"$2"', "longitude": '"$3"'' "${URL}/sendLocation" } # $1 CHAT $2 lat $3 long $4 title $5 address $6 foursquard id @@ -272,7 +282,7 @@ send_venue() { local add="" [ -z "$5" ] && return [ -n "$6" ] && add=', "foursquare_id": '"$6"'' - sendJson "${1}" '"latitude": '"${2}"', "longitude": '"${3}"', "address": "'"${5}"'", "title": "'"${4}"'"'"${add}" "$VENUE_URL" + sendJson "$1" '"latitude": '"$2"', "longitude": '"$3"', "address": "'"$5"'", "title": "'"$4"'"'"${add}" "${URL}/sendVenue" } @@ -283,7 +293,7 @@ send_venue() { # $1 CHAT $2 from chat $3 from msg id forward_message() { [ -z "$3" ] && return - sendJson "${1}" '"from_chat_id": '"${2}"', "message_id": '"${3}"'' "$FORWARD_URL" + sendJson "$1" '"from_chat_id": '"$2"', "message_id": '"$3"'' "${URL}/forwardMessage" } forward() { # backward compatibility forward_message "$@" || return @@ -293,50 +303,50 @@ forward() { # backward compatibility send_message() { [ -z "$2" ] && return local text keyboard btext burl no_keyboard file lat long title address sent - text="$(sed <<< "${2}" 's/ mykeyboardend.*//;s/ *my[kfltab][a-z]\{2,13\}startshere.*//')$(sed <<< "${2}" -n '/mytextstartshere/ s/.*mytextstartshere//p')" + text="$(sed <<< "$2" 's/ mykeyboardend.*//;s/ *my[kfltab][a-z]\{2,13\}startshere.*//')$(sed <<< "$2" -n '/mytextstartshere/ s/.*mytextstartshere//p')" #shellcheck disable=SC2001 text="$(sed <<< "${text}" 's/ *mynewlinestartshere */\n/g')" text="${text//$'\n'/\\n}" [ "$3" != "safe" ] && { - no_keyboard="$(sed <<< "${2}" '/mykeyboardendshere/!d;s/.*mykeyboardendshere.*/mykeyboardendshere/')" - keyboard="$(sed <<< "${2}" '/mykeyboardstartshere /!d;s/.*mykeyboardstartshere *//;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - btext="$(sed <<< "${2}" '/mybtextstartshere /!d;s/.*mybtextstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - burl="$(sed <<< "${2}" '/myburlstartshere /!d;s/.*myburlstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//g;s/ *mykeyboardendshere.*//g')" - file="$(sed <<< "${2}" '/myfile[^s]*startshere /!d;s/.*myfile[^s]*startshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - lat="$(sed <<< "${2}" '/mylatstartshere /!d;s/.*mylatstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - long="$(sed <<< "${2}" '/mylongstartshere /!d;s/.*mylongstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - title="$(sed <<< "${2}" '/mytitlestartshere /!d;s/.*mytitlestartshere //;s/ *my[kfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" - address="$(sed <<< "${2}" '/myaddressstartshere /!d;s/.*myaddressstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + no_keyboard="$(sed <<< "$2" '/mykeyboardendshere/!d;s/.*mykeyboardendshere.*/mykeyboardendshere/')" + keyboard="$(sed <<< "$2" '/mykeyboardstartshere /!d;s/.*mykeyboardstartshere *//;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + btext="$(sed <<< "$2" '/mybtextstartshere /!d;s/.*mybtextstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + burl="$(sed <<< "$2" '/myburlstartshere /!d;s/.*myburlstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//g;s/ *mykeyboardendshere.*//g')" + file="$(sed <<< "$2" '/myfile[^s]*startshere /!d;s/.*myfile[^s]*startshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + lat="$(sed <<< "$2" '/mylatstartshere /!d;s/.*mylatstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + long="$(sed <<< "$2" '/mylongstartshere /!d;s/.*mylongstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + title="$(sed <<< "$2" '/mytitlestartshere /!d;s/.*mytitlestartshere //;s/ *my[kfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" + address="$(sed <<< "$2" '/myaddressstartshere /!d;s/.*myaddressstartshere //;s/ *my[nkfltab][a-z]\{2,13\}startshere.*//;s/ *mykeyboardendshere.*//')" } - if [ -n "$no_keyboard" ]; then - remove_keyboard "$1" "$text" + if [ -n "${no_keyboard}" ]; then + remove_keyboard "$1" "${text}" sent=y fi - if [ -n "$keyboard" ]; then - if [[ "$keyboard" != *"["* ]]; then # pre 0.60 style + if [ -n "${keyboard}" ]; then + if [[ "${keyboard}" != *"["* ]]; then # pre 0.60 style keyboard="[ ${keyboard//\" \"/\" \] , \[ \"} ]" fi - send_keyboard "$1" "$text" "$keyboard" + send_keyboard "$1" "${text}" "${keyboard}" sent=y fi - if [ -n "$btext" ] && [ -n "$burl" ]; then - send_button "$1" "$text" "$btext" "$burl" + if [ -n "${btext}" ] && [ -n "${burl}" ]; then + send_button "$1" "${text}" "${btext}" "${burl}" sent=y fi - if [ -n "$file" ]; then - send_file "$1" "$file" "$text" + if [ -n "${file}" ]; then + send_file "$1" "${file}" "${text}" sent=y fi - if [ -n "$lat" ] && [ -n "$long" ]; then - if [ -n "$address" ] && [ -n "$title" ]; then - send_venue "$1" "$lat" "$long" "$title" "$address" + if [ -n "${lat}" ] && [ -n "${long}" ]; then + if [ -n "${address}" ] && [ -n "${title}" ]; then + send_venue "$1" "${lat}" "${long}" "${title}" "${address}" else - send_location "$1" "$lat" "$long" + send_location "$1" "${lat}" "${long}" fi sent=y fi - if [ "$sent" != "y" ];then - send_text_mode "$1" "$text" + if [ "${sent}" != "y" ];then + send_text_mode "$1" "${text}" fi } diff --git a/mycommands.conf b/mycommands.conf new file mode 100644 index 0000000..e06fe09 --- /dev/null +++ b/mycommands.conf @@ -0,0 +1,100 @@ +#!/bin/bash +####################################################### +# +# File: mycommands.conf +# +# Description: place your config and messages here +# +# Usage: will be sourced from mycommands.sh +# +# License: WTFPLv2 http://www.wtfpl.net/txt/copying/ +# Author: KayM (gnadelwartz), kay@rrr.de +# Created: 09.01.2021 07:27 +# +#### $$VERSION$$ v1.30-0-g3266427 +####################################################### + +########## +# adjust your language setting here, default is C.UTF-8 +# https://github.com/topkecleon/telegram-bot-bash#setting-up-your-environment +export 'LC_ALL=C.UTF-8' +export 'LANG=C.UTF-8' +export 'LANGUAGE=C.UTF-8' + +########## +# in UTF-8 äöü etc. are part of [:alnum:] and ranges (e.g. a-z) +# for more information see doc/4_expert.md#Character_classes +# uncomment next line if you want classic ASCII ranges for [a-z] etc. +#export LC_COLLATE=C + + +########## +# edit the following lines to fit your bot usage +# use ${ME} for current bot name in messages +# Note: you must escape '_' in botname with two \ in markdown messages! + +# output of /info command +export bashbot_info='This is @'"${ME//_/\\\\_}"', the Telegram example bot written entirely in bash. +Edit commands and messages in mycommands.sh! +' + +# output of /help command (uncomment the next 2 lines +# export bashbot_help='*Available commands*: +# ' + +# Set INLINE to 1 in order to receive inline queries. +# To enable this option in your bot, send the /setinline command to @BotFather. +export INLINE="0" + +# if your bot is group admin it get commands sent to other bots +# Set MEONLY to 1 to ignore commands sent to other bots +export MEONLY="0" + +# Set to .* to allow sending files from all locations +# NOTE: this is a regex, not shell globbing! you must use a valid egex, +# '.' matches any character and '.*' matches all remaining charatcers! +# additionally you must escape special characters with '\', e.g. '\. \? \[ \*" to match them literally +export FILE_REGEX="${BASHBOT_ETC}/.*" + +# set BASHBOT_RETRY to enable retry in case of recoverable errors, e.g. throtteling +# problems with send_xxx message etc are looged to logs/ERROR.log +unset BASHBOT_RETRY +#export BASHBOT_RETRY="yes" + +# set value for adaptive sleeping while waiting for uodates in millisconds +# max slepp between polling updates 10s (default 5s) +export BASHBOT_SLEEP="10000" +# add 0.2s if no update available, up to BASHBOT_SLEEP (default 0.1s) +export BASHBOT_SLEEP_STEP="200" + +# if you want to use timer functions, set BASHBOT_START_TIMER to a not empty value +# default is to not start timer +unset BASHBOT_START_TIMER +#export BASHBOT_START_TIMER="yes" + +# set to "yes" and give your bot admin privilegs to remove service messages from groups +export SILENCER="no" + +# uncomment to remove keyboards sent from your bot +# export REMOVEKEYBOARD="yes" +# export REMOVEKEYBOARD_PRIVATE="yes" + +# uncomment to say welcome to new chat members +# export WELCOME_NEWMEMBER="yes" +WELCOME_MSG="Welcome" + +# uncomment to be informed about new/left chat members +# export REPORT_NEWMEMBER="yes" +# export REPORT_LEFTMEMBER="yes" + +# messages for admin only commands +NOTADMIN="Sorry, this command is allowed for admin or owner only" +NOTBOTADMIN="Sorry, this command is allowed for bot owner only" + +######## +# special network setup may require additional ARGS to curl +# +# example: run bashbot over TOR or SOCKS proxy +# export BASHBOT_CURL_ARGS="--socks5-hostname 127.0.0.1:9050" # TOR +# export BASHBOT_CURL_ARGS="--socks5-hostname 127.0.0.1" # regular SOCKS + diff --git a/mycommands.sh b/mycommands.sh index e5acad7..bc27071 100644 --- a/mycommands.sh +++ b/mycommands.sh @@ -1,83 +1,30 @@ #!/bin/bash -######### +####################################################### # -# files: mycommands.sh.dist +# File: mycommands.sh.dist # # this is an out of the box test and example file to show what's possible in mycommands.sh # # #### if you start to develop your own bot, use the clean version of this file: # #### mycommands.clean # +# Usage: will be executed when a bot command is received +# +# License: WTFPLv2 http://www.wtfpl.net/txt/copying/ +# Author: KayM (gnadelwartz), kay@rrr.de +# +#### $$VERSION$$ v1.30-0-g3266427 +####################################################### # shellcheck disable=SC1117 -#### $$VERSION$$ v1.21-0-gc85af77 -# -# uncomment the following lines to overwrite info and help messages -# use ${ME} for current bot name in messages -# Note: you must escape '_' in botname with two \ in markdown messages! -export bashbot_info='This is @'"${ME//_/\\\\_}"', the Telegram example bot written entirely in bash. -Edit commands and messages in mycommands.sh! -' -# export bashbot_help='*Available commands*: -#' -export res="" +#################### +# Config has moved to bashbot.conf +# shellcheck source=./commands.sh +[ -r "${BASHBOT_ETC:-.}/mycommands.conf" ] && source "${BASHBOT_ETC:-.}/mycommands.conf" "$1" -# Set INLINE to 1 in order to receive inline queries. -# To enable this option in your bot, send the /setinline command to @BotFather. -export INLINE="0" - -# if your bot is group admin it get commands sent to other bots -# Set MEONLY to 1 to ignore commands sent to other bots -export MEONLY="0" - -# Set to .* to allow sending files from all locations -# NOTE: this is a regex, not shell globbing! you must use a valid egex, -# '.' matches any character and '.*' matches all remaining charatcers! -# additionally you must escape special characters with '\', e.g. '\. \? \[ \*" to match them literally -export FILE_REGEX="${BASHBOT_ETC}/.*" - -# set BASHBOT_RETRY to enable retry in case of recoverable errors, e.g. throtteling -# problems with send_xxx message etc are looged to logs/ERROR.log -unset BASHBOT_RETRY -#export BASHBOT_RETRY="yes" - -# set value for adaptive sleeping while waiting for uodates in millisconds -# max slepp between polling updates 10s (default 5s) -export BASHBOT_SLEEP="10000" -# add 0.2s if no update available, up to BASHBOT_SLEEP (default 0.1s) -export BASHBOT_SLEEP_STEP="200" - -# if you want to use timer functions, set BASHBOT_START_TIMER to a not empty value -# default is to not start timer -unset BASHBOT_START_TIMER -#export BASHBOT_START_TIMER="yes" - -# set to "yes" and give your bot admin privilegs to remove service messages from groups -export SILENCER="no" - -# uncomment to remove keyboards sent from your bot -# export REMOVEKEYBOARD="yes" -# export REMOVEKEYBOARD_PRIVATE="yes" - -# uncomment to say welcome to new chat members -# export WELCOME_NEWMEMBER="yes" -WELCOME_MSG="Welcome" - -# uncomment to be informed about new/left chat members -# export REPORT_NEWMEMBER="yes" -# export REPORT_LEFTMEMBER="yes" - -# messages for admin only commands -NOTADMIN="Sorry, this command is allowed for admin or owner only" -NOTBOTADMIN="Sorry, this command is allowed for bot owner only" - -######## -# special network setup may require additional ARGS to curl -# -# example: run bashbot over TOR or SOCKS proxy -# export BASHBOT_CURL_ARGS="--socks5-hostname 127.0.0.1:9050" # TOR -# export BASHBOT_CURL_ARGS="--socks5-hostname 127.0.0.1" # regular SOCKS +################## +# let's go ... if [ "$1" = "startbot" ];then ################### # this section is processed on startup @@ -177,41 +124,45 @@ else case "${MESSAGE}" in ################## # example commands, replace thm by your own + '/unpin'*) # unpin all messages if (bot)admin or allowed for user + user_is_allowed "${USER[ID]}" "unpin" "${CHAT[ID]}" &&\ + unpinall_chat_messages "${CHAT[ID]}" + ;; '/echo'*) # example echo command - send_normal_message "${CHAT[ID]}" "$MESSAGE" + send_normal_message "${CHAT[ID]}" "${MESSAGE}" ;; '/question'*) # start interactive questions checkproc - if [ "$res" -gt 0 ] ; then + if [ "${res}" -gt 0 ] ; then startproc "examples/question.sh" || send_normal_message "${CHAT[ID]}" "Can't start question." else - send_normal_message "${CHAT[ID]}" "$MESSAGE already running ..." + send_normal_message "${CHAT[ID]}" "${MESSAGE} already running ..." fi ;; '/cancel'*) # cancel interactive command checkproc - if [ "$res" -gt 0 ] ;then + if [ "${res}" -gt 0 ] ;then killproc && send_normal_message "${CHAT[ID]}" "Command canceled." else send_normal_message "${CHAT[ID]}" "No command is currently running." fi ;; '/run_notify'*) # start notify background job - myback="notify"; checkback "$myback" - if [ "$res" -gt 0 ] ; then - background "examples/notify.sh 60" "$myback" || send_normal_message "${CHAT[ID]}" "Can't start notify." + myback="notify"; checkback "${myback}" + if [ "${res}" -gt 0 ] ; then + background "examples/notify.sh 60" "${myback}" || send_normal_message "${CHAT[ID]}" "Can't start notify." else - send_normal_message "${CHAT[ID]}" "Background command $myback already running ..." + send_normal_message "${CHAT[ID]}" "Background command ${myback} already running ..." fi ;; '/stop_notify'*) # kill notify background job - myback="notify"; checkback "$myback" - if [ "$res" -eq 0 ] ; then - killback "$myback" - send_normal_message "${CHAT[ID]}" "Background command $myback canceled." + myback="notify"; checkback "${myback}" + if [ "${res}" -eq 0 ] ; then + killback "${myback}" + send_normal_message "${CHAT[ID]}" "Background command ${myback} canceled." else - send_normal_message "${CHAT[ID]}" "No background command $myback is currently running.." + send_normal_message "${CHAT[ID]}" "No background command ${myback} is currently running.." fi ;; @@ -247,15 +198,15 @@ else ;; "2"*) # two photos answer_inline_multi "${iQUERY[ID]}" " - $(inline_query_compose "$RANDOM" "photo" "https://avatars.githubusercontent.com/u/13046303"), - $(inline_query_compose "$RANDOM" "photo" "https://avatars.githubusercontent.com/u/4593242") + $(inline_query_compose "${RANDOM}" "photo" "https://avatars.githubusercontent.com/u/13046303"), + $(inline_query_compose "${RANDOM}" "photo" "https://avatars.githubusercontent.com/u/4593242") " ;; "3"*) # three photos answer_inline_multi "${iQUERY[ID]}" " - $(inline_query_compose "$RANDOM" "photo" "https://avatars.githubusercontent.com/u/13046303"), - $(inline_query_compose "$RANDOM" "photo" "https://avatars.githubusercontent.com/u/4593242") - $(inline_query_compose "$RANDOM" "photo" "https://avatars.githubusercontent.com/u/102707") + $(inline_query_compose "${RANDOM}" "photo" "https://avatars.githubusercontent.com/u/13046303"), + $(inline_query_compose "${RANDOM}" "photo" "https://avatars.githubusercontent.com/u/4593242") + $(inline_query_compose "${RANDOM}" "photo" "https://avatars.githubusercontent.com/u/102707") " ;; @@ -264,7 +215,7 @@ else local avatar=("https://avatars.githubusercontent.com/u/13046303" "https://avatars.githubusercontent.com/u/4593242" "https://avatars.githubusercontent.com/u/102707" "https://avatars.githubusercontent.com/u/6460407") answer_inline_multi "${iQUERY[ID]}" " $(for photo in ${avatar[*]} ; do - printf "%s\n" "${sep}"; inline_query_compose "$RANDOM" "photo" "${photo}" "${photo}"; sep="," + printf "%s\n" "${sep}"; inline_query_compose "${RANDOM}" "photo" "${photo}" "${photo}"; sep="," done) " ;; @@ -283,7 +234,7 @@ else # $1 current date, $2 from where the function was called, $3 ... $n optional information my_debug_checks() { # example check because my bot created a wrong file - [ -f ".jssh" ] && printf "%s: %s\n" "${1}" "Ups, found file \"${PWD:-.}/.jssh\"! ==========" + [ -f ".jssh" ] && printf "%s: %s\n" "$1" "Ups, found file \"${PWD:-.}/.jssh\"! ==========" } # called when bashbot send_xxx command failed because we can not connect to telegram @@ -303,10 +254,10 @@ else local image result sep="" count="1" result="$(wget --user-agent 'Mozilla/5.0' -qO - "https://images.search.yahoo.com/search/images?p=$1" | sed 's/"${LOGFILE}" <>"${OUTPUTFILE}" + send_message "123456" "${line}" >>"${OUTPUTFILE}" set +x; set -e printf "." done < "${INPUTFILE}" 2>>"${LOGFILE}" -[ -d "$ALLOW" ] && rm -rf "$ALLOW" +[ -d "${ALLOW}" ] && rm -rf "${ALLOW}" printf " done.\n" diff --git a/test/d-user_is-test.sh b/test/d-user_is-test.sh index 5cee920..9d2802e 100755 --- a/test/d-user_is-test.sh +++ b/test/d-user_is-test.sh @@ -10,7 +10,7 @@ # LICENSE: WTFPLv2 http://www.wtfpl.net/txt/copying/ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== # include common functions and definitions diff --git a/test/e-env-test.sh b/test/e-env-test.sh index 17fc8df..f9e80dc 100755 --- a/test/e-env-test.sh +++ b/test/e-env-test.sh @@ -10,7 +10,7 @@ # LICENSE: WTFPLv2 http://www.wtfpl.net/txt/copying/ # AUTHOR: KayM (gnadelwartz), kay@rrr.de # -#### $$VERSION$$ v1.21-0-gc85af77 +#### $$VERSION$$ v1.30-0-g3266427 #=============================================================================== # include common functions and definitions @@ -53,7 +53,7 @@ mkdir "${BASHBOT_VAR}/${DATADIR}" # run bashbot first time with init "${BASHBOT_BIN}/bashbot.sh" init >"${LOGFILE}" <