diff --git a/.gitignore b/.gitignore index b6e4761..2fa4571 100644 --- a/.gitignore +++ b/.gitignore @@ -1,129 +1,4 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ +.token.sh +log +util_test.sh +bot.log diff --git a/README.md b/README.md index 343cd80..e3d325a 100644 --- a/README.md +++ b/README.md @@ -1 +1,85 @@ -# fosscu-telegram-bot \ No newline at end of file +# FOSSCU-K Telegram Bot + +FOSSCU-K Bot is a Telegram bot written in Bash that interacts with GitHub to fetch and display organization members, open issues, and pull requests. Additionally, it provides general-purpose functionalities like fetching user information, downloading profile pictures, and some fun features like calculating IQ for fun. + +## Features + +- List organization members +- List open issues +- List pull requests +- Display help message with available commands +- Fetch user information +- Download profile pictures +- Fun features like calculating IQ, replace words in msgs, shuffle words etc +- Logging facilities for debugging and monitoring + +## Commands + +- `/start`: Display the help message +- `/issues`: Fetch and display open issues +- `/prs`: Fetch and display pull requests +- `/members`: Fetch and display organization members + +## Installation + +1. **Clone the Repository** + + ```bash + git clone https://github.com/ksauraj/fosscu-bot.git + cd fosscu-bot + ``` + +2. **Set Up Telegram Bot** + + - Create a new bot using [BotFather](https://core.telegram.org/bots#botfather) on Telegram and obtain the bot token. + +3. **Set Up GitHub Token** + + - Create a personal access token on GitHub with the necessary permissions to read organization members, repositories, issues, and pull requests. + +4. **Run Initialization Script** + + Execute the `init.sh` script to interactively set up the bot: + + ```bash + chmod +x init.sh bot.sh + ./init.sh + ``` + + Follow the prompts to input your Telegram bot token, GitHub token, GitHub organization name, and Telegram chat ID. + +## Usage + +1. **Run the Bot** + + ```bash + ./bot.sh + ``` + +2. **Interact with the Bot** + + - Send `/start` to display the help message. + - Send `/issues` to fetch and display open issues. + - Send `/prs` to fetch and display pull requests. + - Send `/members` to fetch and display organization members. + +## Directory Structure + +- `init.sh`: Interactive setup script for the bot. +- `utils.sh`: Contains Telegram functions and utilities. +- `bot.sh`: Main script to run the bot. +- `bot.log`: File where log files are stored. + +## Additional Features + +- **User Information**: Fetch details about a user. +- **Profile Picture Download**: Download and send a user's profile picture. +- **Fun Features**: Fun feature to calculate IQ, Shuffle Words, Replace Words and others for entertainment purposes. + +## Contributing + +Feel free to submit issues and enhancement requests. + +## Inspiration & Credits +`util.sh` was ported from [here](https://github.com/ksauraj/telegram-bash-bot/blob/master/util.sh) which was written by @ksauraj and @Hakimi0804 from scratch. + diff --git a/bot.sh b/bot.sh new file mode 100644 index 0000000..279c4ac --- /dev/null +++ b/bot.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +#Clear & Clean terminal before starting +clear +rm -f bot.log + +# Source core utils +source .token.sh +source util.sh + +# Source functions +source bot/all_replace.sh +source bot/calc.sh +source bot/choice.sh +source bot/github.sh +source bot/info.sh +source bot/iq.sh +source bot/log_dump.sh +source bot/neofetch.sh +source bot/pfp.sh +source bot/purge.sh +source bot/replace.sh +source bot/reset_log.sh +source bot/round.sh +source bot/start.sh +source bot/shuffle.sh +source bot/weath.sh +source bot/bot_util.sh +source bot/shell.sh + +log -i tgbot "STARTING BOT" + + +# Defining constan# Initialize update +update_init + +## While loop +while true; do + # Refresh stuff + update + [ "$RET_MSG_TEXT" ] && log -v tgbot "Message received: $RET_MSG_TEXT" | tee -a log + [ "$RET_MSG_TEXT" ] && log -v tgbot " -> Chat title: $RET_CHAT_TITLE" | tee -a log & + [ "$RET_MSG_TEXT" ] && log -v tgbot " -> Chat id: $RET_CHAT_ID" | tee -a log & + RET_LOWERED_MSG_TEXT=$(tr '[:upper:]' '[:lower:]' <<<"$RET_MSG_TEXT") + + case $RET_LOWERED_MSG_TEXT in + + '/start'*) start | tee -a log ;; + '.all_replace'*) all_replace | tee -a log ;; + '.calc'*) calc | tee -a log ;; + '.choice'*) choice | tee -a log ;; + '.iq'*) iq | tee -a log ;; + '.info'*) info | tee -a log ;; + '/issues'*) send_open_issues ;; + '.neofetch'*) neo_fetch | tee -a log ;; + '.pfp'*) pfp | tee -a log ;; + '.replace'*) replace | tee -a log ;; + '.round'*) round_msg | tee -a log;; + '.weath'*) weath | tee -a log ;; + '.log'*) log_dump ;; + '/members'*) send_members ;; + '/prs'*) send_open_pull_requests ;; + '.reset_log'*) reset_log ;; + '.shuffle'*) shuffle ;; + '.purge'*) purge ;; + '.restart'*) bot_util::restart ;; + '.update'*) bot_util::update ;; + '.shell'*) shell ;; + esac + + unset RET_MSG_TEXT RET_REPLIED_MSG_ID +done + + diff --git a/bot/all_replace.sh b/bot/all_replace.sh new file mode 100644 index 0000000..7bb9365 --- /dev/null +++ b/bot/all_replace.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +all_replace() { + TRIMMED=${RET_MSG_TEXT#.all_replace } + echo "${RET_REPLIED_MSG_TEXT}" > sed.txt + log -d all_replace "sed -i "s/${TRIMMED}/g" sed.txt" + sed -i "s/${TRIMMED}/g" sed.txt + text=$(cat sed.txt) + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "${text}" + rm sed.txt +} diff --git a/bot/bot_util.sh b/bot/bot_util.sh new file mode 100644 index 0000000..6cc097b --- /dev/null +++ b/bot/bot_util.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check if the bot was restarted when sourced +if [ -n "$BOT_RESTARTED" ]; then + log -i bot_util "Bot was restarted" + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "Bot restarted." +fi + +bot_util::restart() { + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Restarting bot" + log -i bot_util "Restarting bot" + export BOT_RESTARTED=true + export RET_CHAT_ID + export SENT_MSG_ID + exec bash tgbot.sh +} + +bot_util::update() { + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Updating bot" + log -i bot_util "Updating bot" + log -v bot_util "Adding safe directory to git" + git config --global --add safe.directory /app + log -v bot_util "Running git pull" + git pull || { + git update-ref -d HEAD + git pull + } || local bot_update_error=true + + if [ "$bot_update_error" = true ]; then + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "Failed to update bot." + log -e bot_util "git pull failed" + else + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "Bot updated. It is recommended to restart the bot." + log -i "Bot updated" + log -d "git pull exited with success" + fi +} + diff --git a/bot/calc.sh b/bot/calc.sh new file mode 100644 index 0000000..355900d --- /dev/null +++ b/bot/calc.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +calc() { + TRIMMED="${RET_MSG_TEXT#.calc}" + CALCED=$(echo "$TRIMMED" | bc -l 2>&1) + if ! echo "$CALCED" | grep -q 'syntax error'; then + ROUNDED=$(round "$CALCED" 2) + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "$ROUNDED" + else + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Bruh, did you just entered nonsense, cuz bc ain't happy" + fi +} diff --git a/bot/extra/neofetch.sh b/bot/extra/neofetch.sh new file mode 100644 index 0000000..6b47bd1 --- /dev/null +++ b/bot/extra/neofetch.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +neo_fetch() { + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "This may take a while depending upon host..." + NEOFETCH_OUTPUT=$(neofetch --stdout) + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "$NEOFETCH_OUTPUT" +} diff --git a/bot/extra/pfp.sh b/bot/extra/pfp.sh new file mode 100644 index 0000000..be781ae --- /dev/null +++ b/bot/extra/pfp.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +pfp() { + if [ "${RET_REPLIED_MSGGER_ID}" != "null" ]; then + tg --getuserpfp "${RET_REPLIED_MSGGER_ID}" + tg --downloadfile "$FILE_ID" "pfp.jpg" + else + tg --getuserpfp "${MSGGER}" + tg --downloadfile "$FILE_ID" "pfp.jpg" + fi + tg --replyfile "$RET_CHAT_ID" "$RET_MSG_ID" "pfp.jpg" + rm pfp.jpg +} diff --git a/bot/extra/purge.sh b/bot/extra/purge.sh new file mode 100644 index 0000000..f8e4235 --- /dev/null +++ b/bot/extra/purge.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +purge() { + echo "Purge called" + if [ "$RET_REPLIED_MSG_ID" = null ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Reply to a message plox" + return + fi + + if ! is_admin "$RET_CHAT_ID" "$MSGGER"; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "You are not an admin" + return + elif ! is_admin "$RET_CHAT_ID" "$BOT_ID"; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "I am not an admin" + return + fi + + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Purging" + local purge_start_time=$(date +%s.%N) + log -d purge "purge ret rep $RET_REPLIED_MSG_ID" + log -d purge "purge sent $SENT_MSG_ID" + log -d purge "purge seq debug: $(seq "$RET_REPLIED_MSG_ID" $((SENT_MSG_ID - 1)))" + for message_id in $(seq "$RET_REPLIED_MSG_ID" $((SENT_MSG_ID - 1))); do # -1 because we don't want to delete the "Purging" message + log -v purge "Deleting message $message_id" + tg --delmsg "$RET_CHAT_ID" "$message_id" & + done + wait # make sure all background jobs are finished + local purge_end_time=$(date +%s.%N) + local purge_time_diff=$(echo "$purge_end_time - $purge_start_time" | bc) + + if [ -z "$(echo "$RET_LOWERED_MSG_TEXT" | tr -d '^.purge')" ]; then + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "Purged $((SENT_MSG_ID - 1 - RET_REPLIED_MSG_ID)) messages in $(round "$purge_time_diff" 2)s." + else + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "Purged $((SENT_MSG_ID - 1 - RET_REPLIED_MSG_ID)) messages in $(round "$purge_time_diff" 2)s. +Reason: $(echo "$RET_LOWERED_MSG_TEXT" | tr -d '^.purge')" + fi +} diff --git a/bot/extra/round.sh b/bot/extra/round.sh new file mode 100644 index 0000000..43c76d7 --- /dev/null +++ b/bot/extra/round.sh @@ -0,0 +1,13 @@ +#!/bin/bash +round() { + # $1 = Your number + # $2 = Amount of decimal places + FLOAT=$1 + DECIMAL_POINT=$2 + printf "%.${2:-$DECIMAL_POINT}f" "$FLOAT" +} +round_msg() { + TRIMMED="${RET_MSG_TEXT#.round}" + ROUNDED=$(round "$TRIMMED" 2) + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "$ROUNDED" +} diff --git a/bot/extra/shell.sh b/bot/extra/shell.sh new file mode 100644 index 0000000..dd5d7bb --- /dev/null +++ b/bot/extra/shell.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +shell() { + # Check if msg is empty + if [ -z "$(echo "$RET_MSG_TEXT" | sed 's/^\.shell//')" ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Give something to run" + return + fi + + local cmd=$(echo "$RET_MSG_TEXT" | sed 's/^\.shell//') + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Running command" + + log -i shell "Running command" + log -v shell "Command: $cmd" + + # TODO: Make this command evaluation run in the background + # currently it's not being done because of the poor + # specs that fly.io gives + local output + output=$(eval "$cmd" 2>&1) + local status=$? + + log -i shell "Command exited with code: $status" + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Command exited with status $status, output: +$output" +} diff --git a/bot/fun/choice.sh b/bot/fun/choice.sh new file mode 100644 index 0000000..849e6e5 --- /dev/null +++ b/bot/fun/choice.sh @@ -0,0 +1,19 @@ +#!/usr/bin/bash + +choice(){ + num=$(shuf -i 1-25 -n 1) + + if [ "$num" -lt 5 ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "Strongly Yes" + elif [ "$num" -lt 9 ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "No" + elif [ "$num" -lt 13 ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "Partial Yes" + elif [ "$num" -lt 17 ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "Yes" + elif [ "$num" -lt 21 ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "Strongly No" + else + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "Cannot decide..." + fi +} diff --git a/bot/fun/iq.sh b/bot/fun/iq.sh new file mode 100644 index 0000000..f8dbc4e --- /dev/null +++ b/bot/fun/iq.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +iq() { + if [[ $USERNAME == 'Ksauraj' ]]; then + iq=$(shuf -i 0-8 -n1) + iq=$(expr 180 - "$iq") + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Calculating your IQ, wait plox..." + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "@${USERNAME} calculated IQ Score is ${iq}." + else + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Calculating your IQ, wait plox..." + sha=$(echo "${FIRST_NAME}" | sha1sum | grep -Eo "[[:digit:]]{2}" | head -n1) + md5=$(echo "${USERNAME}" | md5sum | grep -Eo "[[:digit:]]{2}" | head -n1) + num1=$(expr "${sha}" + "${md5}" | head -n1) + num2=$(shuf -i 0-5 -n1) + f=$(expr "${num1}" - "${num2}" ) + iq=$(expr 180 - "$f" ) + tg --editmsg "$RET_CHAT_ID" "$SENT_MSG_ID" "@${USERNAME} calculated IQ Score is ${iq}." + fi +} diff --git a/bot/fun/replace.sh b/bot/fun/replace.sh new file mode 100644 index 0000000..869d632 --- /dev/null +++ b/bot/fun/replace.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +replace() { + TRIMMED=${RET_MSG_TEXT#.replace } + echo "${RET_REPLIED_MSG_TEXT}" > sed.txt + echo "sed -i \"s/${TRIMMED}/\" sed.txt" + sed -i "s/${TRIMMED}/" sed.txt + text=$(cat sed.txt) + tg --replymsg "$RET_CHAT_ID" "$RET_REPLIED_MSG_ID" "${text}" + rm sed.txt +} diff --git a/bot/fun/shuffle.sh b/bot/fun/shuffle.sh new file mode 100644 index 0000000..e08d9fa --- /dev/null +++ b/bot/fun/shuffle.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +shuffle() { + if ( "$RET_REPLIED_MSG_TEXT" == null); + then + tg --replymarkdownv2msg "$RET_CHAT_ID" "$RET_MSG_ID" "Please reply to some message for using shuffle." + else + text=$(echo "${RET_REPLIED_MSG_TEXT}" | sed "s/ /\n/g" | shuf | tr '\n' ' ' | sed 's/[][`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + tg --replymarkdownv2msg "$RET_CHAT_ID" "$RET_MSG_ID" "${text}" + fi + } diff --git a/bot/github.sh b/bot/github.sh new file mode 100644 index 0000000..857e576 --- /dev/null +++ b/bot/github.sh @@ -0,0 +1,319 @@ +#!/bin/bash + +# Function to display usage +ORG_NAME="FOSS-Community" + +# Variables to store detailed information +declare -A MEMBERS +declare -A RECENT_PUSHES +declare -A REPOS +declare -A ISSUES +declare -A PULL_REQUESTS +declare -A TEAMS + +# Function to fetch members +fetch_members() { + echo "Fetching members of the organization..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/orgs/$ORG_NAME/members") + echo "Result: $result" + for member in $(echo "$result" | jq -r '.[] | @base64'); do + local login=$(echo "$member" | base64 --decode | jq -r '.login') + local id=$(echo "$member" | base64 --decode | jq -r '.id') + MEMBERS["$login"]="$id" + done + echo "Members array: ${MEMBERS[@]}" +} + +# Function to fetch recent pushes (events) +fetch_recent_pushes() { + echo "Fetching recent pushes..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/orgs/$ORG_NAME/events") + echo "Result: $result" + echo "$result" | jq -c '.[] | select(.type=="PushEvent")' | while read -r event; do + local repo_name=$(echo "$event" | jq -r '.repo.name') + local pusher=$(echo "$event" | jq -r '.actor.login') + local timestamp=$(echo "$event" | jq -r '.created_at') + local event_url=$(echo "$event" | jq -r '.url') + RECENT_PUSHES["$repo_name"]="$pusher | $timestamp | $event_url" + done + echo "Recent pushes array:" + for repo in "${!RECENT_PUSHES[@]}"; do + echo "- $repo: ${RECENT_PUSHES[$repo]}" + done +} + +# Function to fetch repository details +fetch_repos() { + echo "Fetching repositories..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/orgs/$ORG_NAME/repos?per_page=100") + echo "Result: $result" + echo "$result" | jq -c '.[]' | while read -r repo; do + local name=$(echo "$repo" | jq -r '.name') + local description=$(echo "$repo" | jq -r '.description // "No description"') + local language=$(echo "$repo" | jq -r '.language // "No language specified"') + REPOS["$name"]="$description | $language" + done + echo "Repositories array:" + for repo in "${!REPOS[@]}"; do + echo "- $repo: ${REPOS[$repo]}" + done +} + +# Function to fetch pull requests +fetch_pull_requests() { + echo "Fetching pull requests..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/search/issues?q=org:$ORG_NAME+type:pr&per_page=100") + echo "Result: $result" + local count=$(echo "$result" | jq -r '.total_count') + echo "Total pull requests found: $count" + if [[ $count -gt 0 ]]; then + local index=0 + while read -r pr; do + local repo_name=$(echo "$pr" | jq -r '.repository_url | split("/")[-1]') + local title=$(echo "$pr" | jq -r '.title') + local state=$(echo "$pr" | jq -r '.state') + local created_at=$(echo "$pr" | jq -r '.created_at') + local pr_url=$(echo "$pr" | jq -r '.html_url') + + PULL_REQUESTS["$repo_name:$title"]="$state | $created_at | $pr_url" + ((index++)) + done < <(echo "$result" | jq -c '.items[]' | head -10) + + #Bro needs some serious debugging :-(( + #echo "Pull requests array:" + #for pr_key in "${!PULL_REQUESTS[@]}"; do + #echo "- $pr_key: ${PULL_REQUESTS[$pr_key]}" + #done + else + echo "No pull requests found." + fi +} + +# Function to fetch teams +fetch_teams() { + echo "Fetching teams..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/orgs/$ORG_NAME/teams?per_page=100") + echo "Result: $result" + echo "$result" | jq -c '.[]' | while read -r team; do + local name=$(echo "$team" | jq -r '.name') + local description=$(echo "$team" | jq -r '.description // "No description"') + local members_count=$(echo "$team" | jq -r '.members_count') + TEAMS["$name"]="$description | $members_count members" + done + echo "Teams array:" + for team in "${!TEAMS[@]}"; do + echo "- $team: ${TEAMS[$team]}" + done +} + +send_fetch_message() { + local message="$1" + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Fetching latest info... Keep Patience" +} + + +# Function to send formatted Telegram messages +send_telegram_message() { + local message="$1" + tg --editmarkdownv2msg "$RET_CHAT_ID" "$SENT_MSG_ID" "$message" +} + +# Function to format and send member details +# Function to format and send member details +send_members() { + send_fetch_message + fetch_members + local message="*Members:*" + for login in "${!MEMBERS[@]}"; do + local member_id="${MEMBERS[$login]}" + # Escape characters such as ( and ) with a preceding '\' + local escaped_login=$(echo "$login" | sed 's/[][`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + message+=" +\\- [$escaped_login](https://github.com/$escaped_login)" + done + echo "$message" + send_telegram_message "$message" +} + + +# Function to format and send recent pushes +send_recent_pushes() { + fetch_recent_pushes + local message="*Recent Pushes:*\n" + for repo in "${!RECENT_PUSHES[@]}"; do + local details="${RECENT_PUSHES[$repo]}" + local pusher=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()-_=+{}\\|;:",<.>/?'"'"']/\\&/g') + local timestamp=$(echo "$details" | cut -d '|' -f 2) + local event_url=$(echo "$details" | cut -d '|' -f 3) + message+="-$repo: Pusher - $pusher, Timestamp - $timestamp, [Event]($event_url)\n" + done + echo "Sending recent pushes message: $message" + send_telegram_message "$message" +} + +# Function to format and send repository details +send_repos() { + fetch_repos + local message="*Repositories:*\n" + for repo in "${!REPOS[@]}"; do + local details="${REPOS[$repo]}" + local description=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()-_=+{}\\|;:",<.>/?'"'"']/\\&/g') + local language=$(echo "$details" | cut -d '|' -f 2) + message+="-$repo: $description, Language: $language\n" + done + echo "Sending repositories message: $message" + send_telegram_message "$message" +} + +# Function to format and send recent pushes +send_recent_pushes() { + fetch_recent_pushes + local message="*Recent Pushes:*\n" + for repo in "${!RECENT_PUSHES[@]}"; do + local details="${RECENT_PUSHES[$repo]}" + local pusher=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()-_=+{}\\|;:",<.>/?'"'"']/\\&/g') + local timestamp=$(echo "$details" | cut -d '|' -f 2) + local event_url=$(echo "$details" | cut -d '|' -f 3) + message+="- $repo: Pusher - $pusher, Timestamp - $timestamp, [Event]($event_url)\n" + done + echo "$message" + send_telegram_message "$message" +} + +# Function to format and send pull requests +send_pull_requests() { + fetch_pull_requests + local message="*Pull Requests:*" + for pr in "${!PULL_REQUESTS[@]}"; do + local details="${PULL_REQUESTS[$pr]}" + local repo_name=$(echo "$pr" | cut -d ':' -f 1 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local pr_title=$(echo "$pr" | cut -d ':' -f 2 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local state=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local created_at=$(echo "$details" | cut -d '|' -f 2 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local pr_url=$(echo "$details" | cut -d '|' -f 3 | sed 's/^ //g') + message+=" + +\\- FOSS\-Community\/**${repo_name}** +[${pr_title}]($pr_url): State \\- ${state}, Created at \\- __${created_at}__" + done + echo "$message" + send_telegram_message "$message" +} + + + +# Function to format and send team details +send_teams() { + fetch_teams + local message="*Teams:*\n" + for team in "${!TEAMS[@]}"; do + local details="${TEAMS[$team]}" + local description=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()-_=+{}\\|;:",<.>/?'"'"']/\\&/g') + local members_count=$(echo "$details" | cut -d '|' -f 2) + message+="-$team: Description - $description, Members count - $members_count\n" + done + echo "Sending teams message: $message" + send_telegram_message "$message" +} + +# Function to fetch open pull requests with author name +fetch_open_pull_requests() { + echo "Fetching open pull requests..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/search/issues?q=org:$ORG_NAME+type:pr+state:open&per_page=100") + echo "Result: $result" + local count=$(echo "$result" | jq -r '.total_count') + echo "Total open pull requests found: $count" + if [[ $count -gt 0 ]]; then + local index=0 + while read -r pr; do + local repo_name=$(echo "$pr" | jq -r '.repository_url | split("/")[-1]') + local title=$(echo "$pr" | jq -r '.title') + local state=$(echo "$pr" | jq -r '.state') + local created_at=$(echo "$pr" | jq -r '.created_at') + local pr_url=$(echo "$pr" | jq -r '.html_url') + local author=$(echo "$pr" | jq -r '.user.login') + + PULL_REQUESTS["$repo_name:$title"]="$state | $created_at | $pr_url | $author" + ((index++)) + done < <(echo "$result" | jq -c '.items[]' | head -10) + else + echo "No open pull requests found." + fi + echo "Pull requests array: ${PULL_REQUESTS[@]}" +} + +# Function to format and send open pull requests +send_open_pull_requests() { + send_fetch_message + fetch_open_pull_requests + local message="*Open Pull Requests:*" + for pr in "${!PULL_REQUESTS[@]}"; do + local details="${PULL_REQUESTS[$pr]}" + local repo_name=$(echo "$pr" | cut -d ':' -f 1 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local pr_title=$(echo "$pr" | cut -d ':' -f 2 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local state=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local created_at=$(echo "$details" | cut -d '|' -f 2 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local pr_url=$(echo "$details" | cut -d '|' -f 3 | sed 's/^ //g') + local author=$(echo "$details" | cut -d '|' -f 4 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + message+=" + +\\- *FOSS\\-Community\\/${repo_name}* +[${pr_title}](${pr_url}) +Created at \\- _${created_at}_ +Author \\- *${author}*" + done + echo "$message" + send_telegram_message "$message" +} + + +# Function to fetch open issues with author name +fetch_open_issues() { + echo "Fetching open issues..." + local result=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/search/issues?q=org:$ORG_NAME+type:issue+state:open&per_page=100") + echo "Result: $result" + local count=$(echo "$result" | jq -r '.total_count') + echo "Total open issues found: $count" + if [[ $count -gt 0 ]]; then + local index=0 + while read -r issue; do + local repo_name=$(echo "$issue" | jq -r '.repository_url | split("/")[-1]') + local title=$(echo "$issue" | jq -r '.title') + local state=$(echo "$issue" | jq -r '.state') + local created_at=$(echo "$issue" | jq -r '.created_at') + local issue_url=$(echo "$issue" | jq -r '.html_url') + local author=$(echo "$issue" | jq -r '.user.login') + + ISSUES["$repo_name:$title"]="$state | $created_at | $issue_url | $author" + ((index++)) + done < <(echo "$result" | jq -c '.items[]' | head -10) + else + echo "No open issues found." + fi + echo "Issues array: ${ISSUES[@]}" +} + +# Function to format and send open issues +send_open_issues() { + send_fetch_message + fetch_open_issues + local message="*Open Issues:*" + for issue in "${!ISSUES[@]}"; do + local details="${ISSUES[$issue]}" + local repo_name=$(echo "$issue" | cut -d ':' -f 1 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local issue_title=$(echo "$issue" | cut -d ':' -f 2 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local state=$(echo "$details" | cut -d '|' -f 1 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local created_at=$(echo "$details" | cut -d '|' -f 2 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + local issue_url=$(echo "$details" | cut -d '|' -f 3 | sed 's/^ //g') + local author=$(echo "$details" | cut -d '|' -f 4 | sed 's/[][`~!@#$%^&*()_=+{}\\|;:",<.>?/-]/\\&/g') + message+=" + +\\- *FOSS\\-Community\\/${repo_name}*: +[${issue_title}](${issue_url}) +Created at \\- _${created_at}_ +Author \\- *${author}*" + done + echo "$message" + send_telegram_message "$message" +} + diff --git a/bot/info.sh b/bot/info.sh new file mode 100644 index 0000000..b078d88 --- /dev/null +++ b/bot/info.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +info() { + if [ "${RET_REPLIED_MSGGER_ID}" != "null" ]; then + USERNAME=$(echo "$USERNAME" | sed 's/[`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + FIRST_NAME=$( echo "$FIRST_NAME" | sed 's/[`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + LAST_NAME=$(echo "$LAST_NAME" | sed 's/[`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + RET_REPLIED_MSGGER_FIRST_NAME=$(echo "$RET_REPLIED_MSGGER_FIRST_NAME" | sed 's/[`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + RET_REPLIED_MSGGER_LAST_NAME=$(echo "$RET_REPLIED_MSGGER_LAST_NAME" | sed 's/[`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + RET_REPLIED_MSGGER_USERNAME=$(echo "$RET_REPLIED_MSGGER_USERNAME" | sed 's/[`~!@#$%^&*()-_=+{}\|;:",<.>/?'"'"']/\\&/g') + tg --replymarkdownv2msg "$RET_CHAT_ID" "$RET_MSG_ID" "Chat ID \: \`${RET_CHAT_ID}\` +Message ID \: \`${RET_MSG_ID}\` +Chat Type \: \`${RET_CHAT_TYPE}\` + +User Name \: \@${USERNAME} +First Name \: \`${FIRST_NAME}\` +Last Name \: \`${LAST_NAME}\` +User ID \: \`${MSGGER}\` + +Replied user Username \: \@${RET_REPLIED_MSGGER_USERNAME} +Replied user First Name \: \`${RET_REPLIED_MSGGER_FIRST_NAME}\` +Replied user Last Name \: \`${RET_REPLIED_MSGGER_LAST_NAME}\` +Replied user ID \: \`${RET_REPLIED_MSGGER_ID}\`" + else + tg --replymarkdownv2msg "$RET_CHAT_ID" "$RET_MSG_ID" "Chat ID \: \`${RET_CHAT_ID}\` +Chat Type \: \`${RET_CHAT_TYPE}\` + +User Name \: \@${USERNAME} +First Name \: \`${FIRST_NAME}\` +Last Name \: \`${LAST_NAME}\` +User ID \: \`${MSGGER}\`" + fi +} diff --git a/bot/log_dump.sh b/bot/log_dump.sh new file mode 100644 index 0000000..851f9bd --- /dev/null +++ b/bot/log_dump.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +log_dump() { + if ! is_botowner; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Only owner can use this command." + return + fi + + local split_msg=($RET_LOWERED_MSG_TEXT) # Purposefully unquoted + local switch=${split_msg[1]} + local logtype=${split_msg[2]} + local tempfile=$(mktemp) + local tempfile_result=$(mktemp) + + # Check if --exclude or --only is in msg + local switch_exclude=false + local switch_only=false + if [ "${switch}" = --exclude ]; then + switch_exclude=true + log -d log_dump "switch_exclude set to true" + elif [ "${switch}" = --only ]; then + switch_only=true + log -d log_dump "switch_only set to true" + fi + + if [ "$switch_exclude" = true ] && [ "$switch_only" = true ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "--exclude and --only cannot be used together." + return + fi + + # Make sure logtype is not empty after --exclude / --only + if [ "$switch_only" = true ] || [ "$switch_only" = true ]; then + if [ -z "$logtype" ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Missing log type after $switch" + return + fi + fi + + if [ -z "$switch" ]; then + tg --replyfile "$RET_CHAT_ID" "$RET_MSG_ID" bot.log + return + fi + + # Return log according to the switches + local IFS=, + local invalid_logtype=() + local ltype + cp "$LOG_FNAME" "$tempfile" + for lt in $logtype; do + ltype=$(log::getLogType "$lt") + + # Store invalid log type + if [ "$?" != 0 ]; then + invalid_logtype+=("$lt") + log -w log_dump "User passed invalid log type: $lt" + continue + fi + + if [ "$switch_exclude" = true ]; then + sed -i "/$ltype:/d" "$tempfile" + log -v log_dump "Excluding log type: $lt" + elif [ "$switch_only" = true ]; then + grep "$ltype:" "$LOG_FNAME" >>"$tempfile_result" + sort -n "$tempfile_result" -o "$tempfile" + log -v log_dump "Only-Including log type: $lt" + fi + done + + { + tg --replyfile "$RET_CHAT_ID" "$RET_MSG_ID" "$tempfile" + rm -f "$tempfile" "$tempfile_result" + } & + + # Let the user know about invalid log type provided (if any). + [ -n "$invalid_logtype" ] && tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Warning: Invalid log type was provided: ${invalid_logtype[*]}" +} diff --git a/bot/reset_log.sh b/bot/reset_log.sh new file mode 100644 index 0000000..4d4ae1b --- /dev/null +++ b/bot/reset_log.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +reset_log() { + if is_botowner; then + rm log > /dev/null 2>&1 + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Log handler reset succesfully." + else + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Only owner can use this command." + fi +} diff --git a/bot/start.sh b/bot/start.sh new file mode 100644 index 0000000..e677aef --- /dev/null +++ b/bot/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash +start() { + tg --replymarkdownv2msg "$RET_CHAT_ID" "$RET_MSG_ID" "This bot is managed by **FOSSCU-K** team\." +} diff --git a/bot/weath.sh b/bot/weath.sh new file mode 100644 index 0000000..3d72b06 --- /dev/null +++ b/bot/weath.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +weath() { + TRIMMED=${RET_MSG_TEXT#.weath} + local key=$(echo "${WEATH_API_KEY}" | tr -d '\n' | wc -c) + if [ "$key" -lt 50 ]; then + log -e weath "Api key is wrong or not found. Please add api key in .token.sh" + else + if [ -z "${TRIMMED}" ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "Please provide a legit location." + else + location=${RET_MSG_TEXT#.weath } + RESULT=$(curl -s --request GET \ + --url "https://yahoo-weather5.p.rapidapi.com/weather?location=${location}&format=json&u=c" \ + --header 'x-rapidapi-host: yahoo-weather5.p.rapidapi.com' \ + --header "x-rapidapi-key: ${WEATH_API_KEY}") + echo $RESULT | jq . + local test=$(echo "$RESULT" | jq '.location') + if [ "$test" == "null" ]; then + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "We only accept location that are present on earth. Are you sure the given location exist on earth?" + else + local city=$(echo "$RESULT" | jq '.location | .city') + local woeid=$(echo "$RESULT" | jq '.location | .woeid') + local country=$(echo "$RESULT" | jq '.location | .country') + local region=$(echo "$RESULT" | jq '.location | .region') + local timezone_id=$(echo "$RESULT" | jq '.location | .timezone_id') + local speed=$(echo "$RESULT" | jq '.current_observation | .wind | .speed') + local humidity=$(echo "$RESULT" | jq '.current_observation | .atmosphere | .humidity') + local visibility=$(echo "$RESULT" | jq '.current_observation | .atmosphere | .visibility') + local pressure=$(echo "$RESULT" | jq '.current_observation | .atmosphere | .pressure') + local sunrise=$(echo "$RESULT" | jq '.current_observation | .astronomy | .sunrise') + local sunset=$(echo "$RESULT" | jq '.current_observation | .astronomy | .sunset') + local low_1=$(echo "$RESULT" | jq '.forecasts[].low' | sed '1!d') + local low_2=$(echo "$RESULT" | jq '.forecasts[].low' | sed '2!d') + local low_3=$(echo "$RESULT" | jq '.forecasts[].low' | sed '3!d') + local high_1=$(echo "$RESULT" | jq '.forecasts[].high' | sed '1!d') + local high_2=$(echo "$RESULT" | jq '.forecasts[].high' | sed '2!d') + local high_3=$(echo "$RESULT" | jq '.forecasts[].high' | sed '3!d') + local day_1=$(echo "$RESULT" | jq '.forecasts[].day' | sed '1!d') + local day_2=$(echo "$RESULT" | jq '.forecasts[].day' | sed '2!d') + local day_3=$(echo "$RESULT" | jq '.forecasts[].day' | sed '3!d') + local weather_1=$(echo "$RESULT" | jq '.forecasts[].text' | sed '1!d') + { + echo "╭⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + echo "│ Results For ${city}, ${region}, ${country}" + echo "│⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + echo "│ Timezone : ${timezone_id}" + echo "│ Wind Speed : ${speed}km/h" + echo "│ Humididty : ${humidity}%" + echo "│ Visibility : ${visibility}%" + echo "│ Pressure : ${pressure} Hg" + echo "│⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + echo "│ Day : ${day_1}" + echo "│ Min Temperature : ${low_1}°C" + echo "│ Min Temperature : ${high_1}°C" + echo "│ Weather : ${weather_1}" + echo "│ Sunrise : ${sunrise}" + echo "│ Sunset : ${sunset}" + echo "│⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + echo "│ Forecasts for next 2 days" + echo "│" + echo "│ Min Temp. Max Temp." + echo "│ DAY 1 ${low_2}°C ${high_2}°C" + echo "│ DAY 2 ${low_3}°C ${high_3}°C" + echo "╰⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" + } > weath.txt + sed -i s/\"//g weath.txt + text=$(cat weath.txt) + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "${text}" + rm weath.txt + fi + fi + fi +} diff --git a/init.sh b/init.sh new file mode 100644 index 0000000..fee7b84 --- /dev/null +++ b/init.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +pr_blue() { + echo -e "\e[1;34m$*\e[0m" +} + +TOKEN="" +BOT_OWNER_ID="" +WEATH_API_KEY="" +SYNTAX_CHECK="True" +INTERACTIVE="True" + +rm .token.sh +touch .token.sh + +if [ "$INTERACTIVE" == "True" ]; then + read -rsp "Enter Bot Token : " TOKEN + echo + echo "#!/bin/bash" >> .token.sh + echo TOKEN="$TOKEN" >> .token.sh + read -rp "Enter Owner ID : " BOT_OWNER_ID + echo BOT_OWNER_ID="$BOT_OWNER_ID" >> .token.sh + pr_blue "Get API KEY for .weath from https://rapidapi.com/apishub/api/yahoo-weather5" + read -rsp "Enter API KEY FOR .weath :" WEATH_API_KEY + echo + echo WEATH_API_KEY="$WEATH_API_KEY" >> .token.sh + read -rsp "Enter GitHub Token :" GITHUB_TOKEN + echo + echo GITHUB_TOKEN="$GITHUB_TOKEN" >> .token.sh + + read -rp "Enable Syntax Checking [True/False] : " SYNTAX_CHECK +else + echo "Using Non-Interactive Mode." + { + echo "#!/bin/bash" + echo TOKEN="$TOKEN" + echo WEATH_API_KEY="$WEATH_API_KEY" + echo BOT_OWNER_ID="$BOT_OWNER_ID" + echo GITHUB_TOKEN="$GITHUB_TOKEN" + } >> .token.sh +fi + + +if [[ "$SYNTAX_CHECK" == "True" ]]; then + + SCRIPTS=$(find -iname '*.sh') + echo "$0" + for script in $SCRIPTS; do + [ "$script" = "$0" ] && continue + pr_blue "LINT (SHELLCHECK): $script" + shellcheck -ax "$script" + done + + read -r -n1 -t5 -p "Proceed with shfmt?[y/N]: " SHFMT_PROMPT + + [ "$SHFMT_PROMPT" = "y" ] && { + echo + for script in $SCRIPTS; do + pr_blue "LINT (SHFMT): $script" + shfmt -w "$script" + done + } +else + pr_blue Skipping Syntax Checking. +fi + +read -r -n1 -t20 -p "Start Bot? [y/N]: " START_BOT +[ "$START_BOT" = "y" ] && { +bash bot.sh +} diff --git a/util.logging.sh b/util.logging.sh new file mode 100644 index 0000000..30c7a0b --- /dev/null +++ b/util.logging.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +readonly LOG_FNAME=bot.log +readonly LOG_DATEFMT="+%d-%m-%y %H:%M:%S.%N" + +log::getLogType() { + case $1 in + -v|--verbose|verbose) echo V ;; + -d|--debug|debug) echo D ;; + -i|--info|info) echo I ;; + -w|--warn|warn) echo W ;; + -e|--error|error) echo E ;; + -f|--fatal|fatal) echo F ;; + *) return 1 ;; + esac +} + +log() { + # Argument to pass: + # $1 - log type, ex. -d / --debug + # $2 - tag name (script/function name recommended) + # $3 - Texts + + # Warn if called with incorrect amount of argument passed + if [ "$#" -ne 3 ]; then + log -w log "Logger was called with incorrect amount of args:" + log -w log "arg1: $1" + log -w log "arg2: $2" + log -w log "arg3: $3" + #return 1 + fi + + local arg=$1 + local logtag=$2 + local logtype + shift 2 + + echo "$(log::getLogType $arg): [$(date "$LOG_DATEFMT" | sed 's/......$//')] ($logtag) $*" | tee -a "$LOG_FNAME" +} diff --git a/util.sh b/util.sh new file mode 100644 index 0000000..a581121 --- /dev/null +++ b/util.sh @@ -0,0 +1,338 @@ +#!/bin/bash +source .token.sh +source util.logging.sh + +if [ -z "$TOKEN" ]; then + log -f util "Cannot load token, exiting" + exit 1 +fi + +readonly API="https://api.telegram.org/bot$TOKEN" + +log -i util "Getting my ID" +readonly BOT_ID=$(curl -s $API/getMe | jq .result.id) +log -i util "ID Acquired" + +tg() { + case $1 in + --sendmsg) + shift + local CHAT_ID=$1 + local MSG=$2 + local RESULT=$(curl -s "$API/sendMessage" -d "chat_id=$CHAT_ID" -d "text=$MSG") + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --sendmsghtml) + shift + local CHAT_ID=$1 + local MSG=$2 + local RESULT=$(curl -s "$API/sendMessage" --form-string "chat_id=$CHAT_ID" --form-string "text=$MSG" --form-string "parse_mode=HTML" --form-string "disable_web_page_preview=True" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --editmsg | --editmarkdownv2msg) + local PARAM=$1 + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local NEW_TEXT=$3 + if [[ "$PARAM" =~ "--editmarkdownv2msg" ]]; then + curl -s "$API/editMessageText" -d "chat_id=$CHAT_ID" -d "message_id=$MSG_ID" -d "text=$NEW_TEXT" -d "parse_mode=MarkdownV2" | jq . + else + curl -s "$API/editMessageText" -d "chat_id=$CHAT_ID" -d "message_id=$MSG_ID" -d "text=$NEW_TEXT" | jq . + fi + ;; + --editmsgurl) + local PARAM=$1 + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local NEW_TEXT=$3 + curl -s "$API/editMessageText" -F "chat_id=$CHAT_ID" -F "message_id=$MSG_ID" -F "text=$NEW_TEXT" | jq . + ;; + --editmsghtml) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local MSG=$3 + local RESULT=$(curl -s "$API/editMessageText" --form-string "chat_id=$CHAT_ID" --form-string "message_id=$MSG_ID" --form-string "text=$MSG" --form-string "parse_mode=HTML" --form-string "disable_web_page_preview=True" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --sendmarkdownv2msg) + shift + local CHAT_ID=$1 + local MSG=$2 + local RESULT=$(curl -s "$API/sendMessage" -d "chat_id=$CHAT_ID" -d "parse_mode=MarkdownV2" -d "text=$MSG") + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --replymsg) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local MSG=$3 + local RESULT=$(curl -s "$API/sendMessage" -d "chat_id=$CHAT_ID" -d "reply_to_message_id=$MSG_ID" -d "text=$MSG" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --replyfile) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local MSG=$3 + local RESULT=$(curl -s "$API/sendDocument" -F "chat_id=$CHAT_ID" -F "reply_to_message_id=$MSG_ID" -F "document=@\"$MSG\"" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --replymsghtml) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local MSG=$3 + local RESULT=$(curl -s "$API/sendMessage" --form-string "chat_id=$CHAT_ID" --form-string "reply_to_message_id=$MSG_ID" --form-string "text=$MSG" --form-string "parse_mode=HTML" --form-string "disable_web_page_preview=True" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --replymsgmarkdown) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local MSG=$3 + local RESULT=$(curl -s "$API/sendMessage" -d "chat_id=$CHAT_ID" -d "reply_to_message_id=$MSG_ID" --data-urlencode "text=$MSG" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo $RESULT | jq . + ;; + --replymarkdownv2msg) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local MSG=$3 + local RESULT=$(curl -s "$API/sendMessage" -d "chat_id=$CHAT_ID" -d "reply_to_message_id=$MSG_ID" -d "text=$MSG" -d "parse_mode=MarkdownV2" | jq .) + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + echo "$RESULT" | jq . + ;; + --delmsg) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + curl -s "$API/deleteMessage" -d "chat_id=$CHAT_ID" -d "message_id=$MSG_ID" | jq . + ;; + --sendsticker | --replysticker) + local PARAM=$1 + shift + local CHAT_ID=$1 + local FILE_ID=$2 + if [[ "$PARAM" =~ "--replysticker" ]]; then + local MSG_ID=$2 + local FILE_ID=$3 + curl -s "$API/sendSticker" -d "chat_id=$CHAT_ID" -d "sticker=$FILE_ID" -d "reply_to_message_id=$MSG_ID" | jq . + else + curl -s "$API/sendSticker" -d "chat_id=$CHAT_ID" -d "sticker=$FILE_ID" | jq . + fi + ;; + --fwdmsg | --cpmsg) + local PARAM=$1 # Save this to check for --cpmsg + shift + local FROM=$1 + local TO=$2 + local MSG_ID=$3 + if [ "$PARAM" = "--cpmsg" ]; then + local MODE=copyMessage + else + local MODE=forwardMessage + fi + curl -s "$API/$MODE" -d "from_chat_id=$FROM" -d "chat_id=$TO" -d "message_id=$MSG_ID" + ;; + --pinmsg) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + curl -s "$API/pinChatMessage" -d "chat_id=$CHAT_ID" -d "message_id=$MSG_ID" + ;; + --unpinmsg) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + curl -s "$API/unpinChatMessage" -d "chat_id=$CHAT_ID" -d "message_id=$MSG_ID" + ;; + --getuserpfp) #pfp -> Profile Photo + shift + local USER_ID=$1 + local RESULT=$(curl -s "$API/getUserProfilePhotos" -d "user_id=$USER_ID" | jq . ) + echo $RESULT | jq . + FILE_ID=$(echo "$RESULT" | jq -r '.result.photos[0][-1].file_id') + FILE_PATH=$(echo "$RESULT" | jq -r '.result.photos[0][-1].file_path') + ;; + # --downloadfile -> OUTPUT_FILE should be complete name of file including MIME type / extension. + --downloadfile) + shift + local FILE_ID=$1 + local OUTPUT_FILE=$2 + local RESULT=$(curl -s "$API/getFile" -d "file_id=$FILE_ID" | jq .) + echo $RESULT | jq . + FILE_ID=$(echo "$RESULT" | jq -r '.result.file_id') + FILE_PATH=$(echo "$RESULT" | jq -r '.result.file_path') + curl -s "https://api.telegram.org/file/bot$TOKEN/$FILE_PATH" -o $OUTPUT_FILE + ;; + --ban) + shift + local CHAT_ID=$1 + local USER_ID=$2 + curl -s $API/banChatMember -F "chat_id=$CHAT_ID" -F "user_id=$USER_ID" | jq . + ;; + --unban) + shift + local CHAT_ID=$1 + local USER_ID=$2 + curl -s $API/unbanChatMember -F "chat_id=$CHAT_ID" -F "user_id=$USER_ID" -F only_if_banned=true | jq . + ;; + --sendphoto) + shift + local CHAT_ID=$1 + local PHOTO=$2 + local CAPTION=$3 + local RESULT=$(curl -s "$API/sendPhoto" -F "chat_id=$CHAT_ID" -F "photo=@\"$PHOTO\"" -F "caption=$CAPTION" | jq . ) + echo $RESULT | jq . + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + ;; + --replyphoto) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local PHOTO=$3 + local CAPTION=$4 + local RESULT=$(curl -s "$API/sendPhoto" -F "chat_id=$CHAT_ID" -F "reply_to_message_id=$MSG_ID" -F "photo=@\"$PHOTO\"" -F "caption=$CAPTION" | jq . ) + echo $RESULT | jq . + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + ;; + #--sendaudiofile -> File must have the correct MIME type (e.g., audio/mp3 ) + --sendaudiofile) + shift + local CHAT_ID=$1 + local AUDIO=$2 + local CAPTION=$3 + local RESULT=$(curl -s "$API/sendAudio" -F "chat_id=$CHAT_ID" -F "audio=@\"$AUDIO\"" -F "caption=$CAPTION" | jq . ) + echo $RESULT | jq . + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + ;; + #--sendvoicefile -> The file must have the type audio/ogg and be no more than 1MB in size. 1-20MB voice notes will be sent as files. + --sendvoicefile) + shift + local CHAT_ID=$1 + local VOICE=$2 + local CAPTION=$3 + local RESULT=$(curl -s "$API/sendVoice" -F "chat_id=$CHAT_ID" -F "audio=@\"$VOICE\"" -F "caption=$CAPTION" | jq . ) + echo $RESULT | jq . + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + ;; + --sendpreviewvideo) + shift + local CHAT_ID=$1 + local VIDEO=$2 + local resolution=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$VIDEO" ) + local width=$(echo $resolution | cut -d 'x' -f 1) + local height=$(echo $resolution | cut -d 'x' -f 2) + local RESULT=$(curl -s $API/sendVideo -F "chat_id=$CHAT_ID" -F "video=@\"$VIDEO\"" -F "width=$width" -F "height=$height" | jq . ) + echo $RESULT | jq . + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + ;; + --replypreviewvideo) + shift + local CHAT_ID=$1 + local MSG_ID=$2 + local VIDEO=$3 + local resolution=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$VIDEO" ) + local width=$(echo $resolution | cut -d 'x' -f 1) + local height=$(echo $resolution | cut -d 'x' -f 2) + local RESULT=$(curl -s $API/sendVideo -F "chat_id=$CHAT_ID" -F "reply_to_message_id=$MSG_ID" -F "video=@\"$VIDEO\"" -F "width=$width" -F "height=$height" | jq . ) + echo $RESULT | jq . + SENT_MSG_ID=$(echo "$RESULT" | jq '.result | .message_id') + ;; + esac +} + +update() { + FETCH=$(curl -s "$API/getUpdates" -d "offset=$UPDATE_ID" -d "timeout=60" | jq '.result[]' 2>/dev/null) + if [ -n "$FETCH" ]; then + UPDATE_ID=$((UPDATE_ID + 1)) + + # IDs + RET_MSG_ID=$(echo "$FETCH" | jq '.message.message_id') + RET_CHAT_ID=$(echo "$FETCH" | jq '.message.chat.id') + RET_CHAT_TYPE=$(echo "$FETCH" | jq -r '.message.chat.type') + RET_CHAT_TITLE=$(echo "$FETCH" | jq -r '.message.chat.title') + MSGGER=$(echo "$FETCH" | jq '.message.from.id') + RET_FILE_ID=$(echo "$FETCH" | jq -r '.message.document.file_id') + + # Strings + RET_MSG_TEXT=$(echo "$FETCH" | jq -r '.message.text') + FIRST_NAME=$(echo "$FETCH" | jq -r '.message.from.first_name') + LAST_NAME=$(echo "$FETCH" | jq -r '.message.from.last_name') + USERNAME=$(echo "$FETCH" | jq -r '.message.from.username') + + # Replies + RET_REPLIED_MSG_ID=$(echo "$FETCH" | jq '.message.reply_to_message.message_id') + RET_REPLIED_MSGGER_ID=$(echo "$FETCH" | jq '.message.reply_to_message.from.id') + RET_REPLIED_MSGGER_FIRST_NAME=$(echo "$FETCH" | jq -r '.message.reply_to_message.from.first_name') + RET_REPLIED_MSGGER_LAST_NAME=$(echo "$FETCH" | jq -r '.message.reply_to_message.from.last_name') + RET_REPLIED_MSGGER_USERNAME=$(echo "$FETCH" | jq -r '.message.reply_to_message.from.username') + RET_REPLIED_MSG_TEXT=$(echo "$FETCH" | jq -r '.message.reply_to_message.text') + RET_REPLIED_FILE_ID=$(echo "$FETCH" | jq -r '.message.reply_to_message.document.file_id') + + # Stickers + STICKER_EMOJI=$(echo "$FETCH" | jq -r '.message.sticker.emoji') + STICKER_FILE_ID=$(echo "$FETCH" | jq -r '.message.sticker.file_id') + STICKER_PACK_NAME=$(echo "$FETCH" | jq -r '.message.sticker.set_name') + fi +} + +update_init() { + until [ -n "$UPDATE_ID" ]; do + UPDATE_ID=$(($(curl -s "$API/getUpdates" -d "offset=-1" | jq '.result[].update_id') + 1)) + done +} + +is_botowner() { + [ "$MSGGER" = "$BOT_OWNER_ID" ] && return 0 + return 1 +} + +err_not_botowner() { + tg --replymsg "$RET_CHAT_ID" "$RET_MSG_ID" "You are not allowed to use this command." +} + +# contains: returns true if an element is present in an array +# (kang from fish) +contains() { + local element=$1 + shift + local array=("$@") + + for elem in "${array[@]}"; do + if [ "$elem" = "$element" ]; then + return 0 + fi + done + + return 1 +} + +# is_admin: return 0 if all passed users are admin, otherwise return 1 +is_admin() { + local chat_id=$1 + shift + local user_id=("$@") + local everyone_is_admin=true + local chat_admins=$(curl -s "$API/getChatAdministrators" -d "chat_id=$chat_id" | jq .result[].user.id) + chat_admins=($chat_admins) # Convert to an array + for user in "${user_id[@]}"; do + if ! contains "$user" "${chat_admins[@]}"; then + return 1 # This user is not admin, thus not everyone is admin + fi + done + + # The loop finished successfully, which means everyone is admin + return 0 +}