A few updates, cleanup updated weather module.

This commit is contained in:
Storm Dragon
2025-10-24 17:14:52 -04:00
parent 221e14a85a
commit 3669e07a9a
43 changed files with 794 additions and 153 deletions
-1
View File
@@ -2,4 +2,3 @@ response/error.txt
log.txt log.txt
.* .*
bot.cfg bot.cfg
bot.cfg
+18 -2
View File
@@ -3,7 +3,23 @@ bash-irc-bot
A simple, modular IRC bot written in bash A simple, modular IRC bot written in bash
throw "modules" into /modules/module-name/module-name.sh ## Setup
1. Run the bot once to generate your config file:
```bash
./bot.sh
```
2. Edit `bot.cfg` with your IRC server, channel, and bot settings
3. Run the bot again:
```bash
./bot.sh
```
## Modules
Throw "modules" into /modules/module-name/module-name.sh
and they will be loaded up during runtime. and they will be loaded up during runtime.
call modules with botname: test arg1 arg2 .... Call modules with botname: test arg1 arg2 ....
+5 -2
View File
@@ -11,10 +11,13 @@ leave=true
# Path to log file # Path to log file
log="log.txt" log="log.txt"
nick="storm_bot" nick="storm_bot"
port=6667 port=6697
# Use SSL/TLS for IRC connection (true/false)
# Port 6697 is standard for SSL, 6667 for plain text
useSSL=true
# Message to give when the bot logs off the server # Message to give when the bot logs off the server
quitMessage="$nick vanishes in a sudden flash of brilliant lightning." quitMessage="$nick vanishes in a sudden flash of brilliant lightning."
server="irc.talkabout.cf" server="irc.libera.chat"
# format=username hostname servername :realname # format=username hostname servername :realname
user="$nick ${server%.} $server :$nick" user="$nick ${server%.} $server :$nick"
# auto rejoin if kicked from channel? # auto rejoin if kicked from channel?
+107 -25
View File
@@ -5,6 +5,19 @@ if [ "$(whoami)" = "root" ]; then
exit 1 exit 1
fi fi
# Check if bot.cfg exists, if not create it from example
if [[ ! -f "bot.cfg" ]]; then
if [[ -f "bot.cfg.example" ]]; then
echo "bot.cfg not found. Creating from bot.cfg.example..."
cp "bot.cfg.example" "bot.cfg"
echo "Please edit bot.cfg to configure your bot (server, channel, nick, etc.)."
exit 0
else
echo "Neither bot.cfg nor bot.cfg.example found."
exit 1
fi
fi
# Load required files. # Load required files.
for i in "bot.cfg" "functions.sh" ; do for i in "bot.cfg" "functions.sh" ; do
if [[ -f "$i" ]]; then if [[ -f "$i" ]]; then
@@ -23,6 +36,21 @@ export ignoreList
export nick export nick
export quitMessage export quitMessage
# Check for critical dependencies needed by the bot core
coreDependencies=("socat" "tail" "shuf" "grep" "sed" "tr" "cut" "date")
missingCore=()
for dep in "${coreDependencies[@]}"; do
if ! command -v "$dep" &> /dev/null; then
missingCore+=("$dep")
fi
done
if [[ ${#missingCore[@]} -gt 0 ]]; then
echo "ERROR: Missing critical dependencies: ${missingCore[*]}"
echo "Please install these packages before running the bot."
exit 1
fi
# Function called on exit to remove the temporary input file. # Function called on exit to remove the temporary input file.
rm_input() { rm_input() {
if [[ -f "$input" ]]; then if [[ -f "$input" ]]; then
@@ -30,19 +58,66 @@ rm_input() {
fi fi
} }
# Function to trim log file to prevent unbounded growth
# Keeps log between 800-1000 lines by trimming oldest entries when limit reached
trim_log() {
local logFile="$1"
local maxLines=1000
local trimTo=800
# Only check if log file exists and is readable
if [[ ! -f "$logFile" ]]; then
return 0
fi
# Count lines efficiently
local lineCount
lineCount=$(wc -l < "$logFile" 2>/dev/null || echo 0)
# If over limit, trim to keep most recent entries
if [[ $lineCount -gt $maxLines ]]; then
tail -n "$trimTo" "$logFile" > "${logFile}.tmp" && mv "${logFile}.tmp" "$logFile"
fi
}
# Trap exiting ffrom the program to remove the temporary input file. # Trap exiting ffrom the program to remove the temporary input file.
trap rm_input EXIT trap rm_input EXIT
# Set up the connection. # Reconnection loop - keeps bot connected even if connection drops
echo -e "Session started $(date "+%I:%M%p%n %A, %B %d, %Y").\n\nTo gracefully exit, make sure you are in the allow list and send the command exit to the bot.\n\n" | tee "$log" reconnectDelay=10
echo "NICK $nick" > "$input" while true; do
echo "USER $user" >> "$input" # Set up the connection.
echo "JOIN #$channel" >> "$input" echo -e "Session started $(date "+%I:%M%p%n %A, %B %d, %Y").\n\nTo gracefully exit, make sure you are in the allow list and send the command exit to the bot.\n\n" | tee -a "$log"
echo "NICK $nick" > "$input"
echo "USER $user" >> "$input"
echo "JOIN #$channel" >> "$input"
# The main loop of the program where we watch for output from irc. # The main loop of the program where we watch for output from irc.
tail -f "$input" | telnet "$server" "$port" | while read -r result ; do # Use SSL if enabled, otherwise plain TCP
if [[ "${useSSL,,}" == "true" ]]; then
socatAddress="SSL:${server}:${port},verify=0"
else
socatAddress="TCP:${server}:${port}"
fi
# Counter for log trimming (check every 50 messages)
logTrimCounter=0
tail -f "$input" | socat -,ignoreeof "$socatAddress" | while read -r result ; do
# Strip carriage return from IRC protocol (CRLF line endings)
result="${result%$'\r'}"
# Sanitize control characters for logging (prevent log injection)
logSanitized="${result//[$'\001'-$'\037'$'\177']/}"
# log the session # log the session
echo "$(date "+[$dateFormat]") $result" >> "$log" echo "$logSanitized [$(date "+$dateFormat")]" >> "$log"
# Periodically trim log to prevent unbounded growth
((logTrimCounter++))
if [[ $logTrimCounter -ge 50 ]]; then
trim_log "$log"
logTrimCounter=0
fi
# do things when you see output # do things when you see output
case "$result" in case "$result" in
# Handle nick changes # Handle nick changes
@@ -51,7 +126,7 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
originalNick="${result#:}" originalNick="${result#:}"
originalNick="${originalNick%%!*}" originalNick="${originalNick%%!*}"
# If the old nick was in the ignore list, update it. # If the old nick was in the ignore list, update it.
if [[ "${originalNick}" =~ [${ignoreList}] ]]; then if [[ "${originalNick}" =~ ^($ignoreList)$ ]]; then
export ignoreList="${ignoreList/${originalNick}/${result#:*:}}" export ignoreList="${ignoreList/${originalNick}/${result#:*:}}"
fi fi
;; ;;
@@ -83,11 +158,10 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
from="${result#*#}" from="${result#*#}"
from="#$from" from="#$from"
if [ "$who" = "$nick" ]; then if [ "$who" = "$nick" ]; then
continue continue
fi fi
echo "MODE #$channel +o $who" | tee -a "$input"
if [ "${greet^^}" = "TRUE" ]; then if [ "${greet^^}" = "TRUE" ]; then
set -f set -f
./triggers/greet/greet.sh "$who" "$from" ./triggers/greet/greet.sh "$who" "$from"
set +f set +f
fi fi
@@ -109,7 +183,7 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
;; ;;
# run when a private message is seen # run when a private message is seen
*"PRIVMSG "[[:alnum:]-_]*) *"PRIVMSG "[[:alnum:]-_]*)
echo "$result" >> "$log" echo "$logSanitized" >> "$log"
who="${result%%!*}" who="${result%%!*}"
who="${who:1}" who="${who:1}"
from="${who%!*}" from="${who%!*}"
@@ -117,12 +191,13 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
command="${command//# /}" command="${command//# /}"
will="${command#* }" will="${command#* }"
command="${command%% *}" command="${command%% *}"
if [[ "$from" =~ $allowList ]]; then if [[ "$from" =~ ^($allowList)$ ]]; then
if command -v "./modules/${command% *}/${command% *}.sh" ; then if command -v "./modules/${command% *}/${command% *}.sh" ; then
echo "Calling module ./modules/${command% *}/${command% *}/${command% *}.sh \"$who\" \"$from\" $will" >> "$log" willSanitized="${will//[$'\001'-$'\037'$'\177']/}"
echo "Calling module ./modules/${command% *}/${command% *}/${command% *}.sh \"$who\" \"$from\" $willSanitized" >> "$log"
# Disable wildcards # Disable wildcards
set -f set -f
./modules/${command% *}/${command% *}.sh "$who" "#$channel" $will "./modules/${command% *}/${command% *}.sh" "$who" "#$channel" "$will"
# Enable wildcards # Enable wildcards
set +f set +f
else else
@@ -134,7 +209,6 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
;; ;;
# run when a message is seen # run when a message is seen
*PRIVMSG*) *PRIVMSG*)
echo "$result" >> "$log"
who="${result%%!*}" who="${result%%!*}"
who="${who:1}" who="${who:1}"
from="${result#*#}" from="${result#*#}"
@@ -144,28 +218,31 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
# Call link trigger if msg contains a link: # Call link trigger if msg contains a link:
if [[ "$result" =~ .*http://|https://|www\..* ]]; then if [[ "$result" =~ .*http://|https://|www\..* ]]; then
set -f set -f
echo "Calling link.sh with \"$who\" \"$from\" \"$result\"" >> "$log" echo "Calling link.sh with \"$who\" \"$from\" \"$logSanitized\"" >> "$log"
./triggers/link/link.sh "$who" "$from" "$result" ./triggers/link/link.sh "$who" "$from" "$result"
set -f set -f
# Although this calls modules, it triggers on text other than the bot's nick # Although this calls modules, it triggers on text other than the bot's nick
# To make sure that modules are only called when they are supposed to be, had to combine string monipulation with regexp. # To make sure that modules are only called when they are supposed to be, had to combine string monipulation with regexp.
elif [[ "${result#:*PRIVMSG*:}" =~ ^[${botCaller}][a-zA-Z0-9_].* ]]; then elif [[ "${result#:*PRIVMSG*:}" =~ ^[${botCaller}][a-zA-Z0-9_].* ]]; then
echo "DEBUG: Matched bot caller pattern" >> "$log"
command="${result#*:[[:punct:]]}" command="${result#*:[[:punct:]]}"
command="${command//# /}" command="${command//# /}"
will="${command#* }" will="${command#* }"
command="${command%% *}" command="${command%% *}"
if command -v "./modules/${command% *}/${command% *}.sh" ; then willSanitized="${will//[$'\001'-$'\037'$'\177']/}"
echo "Calling module ./modules/${command% *}/${command% *}/${command% *}.sh \"$who\" \"$from\" $will" >> "$log" echo "DEBUG: command='$command' will='$willSanitized'" >> "$log"
if command -v "./modules/${command% *}/${command% *}.sh" &>/dev/null ; then
echo "Calling module ./modules/${command% *}/${command% *}.sh \"$who\" \"$from\" $willSanitized" >> "$log"
# Disable wildcards # Disable wildcards
set -f set -f
./modules/${command% *}/${command% *}.sh "$who" "$from" $will "./modules/${command% *}/${command% *}.sh" "$who" "$from" "$will"
# Enable wildcards # Enable wildcards
set +f set +f
else else
./modules/say/say.sh "$who" "$from" "$who: $(shuf -n1 "response/error.txt")" ./modules/say/say.sh "$who" "$from" "$who: $(shuf -n1 "response/error.txt")"
fi fi
else else
if ! [[ "$who" =~ $ignoreList ]]; then if ! [[ "$who" =~ ^($ignoreList)$ ]]; then
set -f set -f
./triggers/keywords/keywords.sh "$who" "$from" "$result" ./triggers/keywords/keywords.sh "$who" "$from" "$result"
set +f set +f
@@ -173,9 +250,14 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
fi fi
;; ;;
*) *)
echo "$result" >> "$log" echo "$logSanitized" >> "$log"
;; ;;
esac esac
done
# If we reach here, the connection was dropped
echo "Connection lost. Reconnecting in $reconnectDelay seconds... [$(date "+$dateFormat")]" | tee -a "$log"
sleep "$reconnectDelay"
done done
rm_input rm_input
+19
View File
@@ -4,6 +4,25 @@ if [[ -z "$input" ]]; then
input="$(mktemp .XXXXXX)" input="$(mktemp .XXXXXX)"
fi fi
# Check if required dependencies are available
# Usage: check_dependencies "command1" "command2" ...
# Returns 0 if all found, 1 if any missing
check_dependencies() {
local missing=()
local cmd
for cmd in "$@"; do
if ! command -v "$cmd" &> /dev/null; then
missing+=("$cmd")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo "Missing dependencies: ${missing[*]}"
return 1
fi
return 0
}
close_bot() { close_bot() {
echo -en "QUIT :${quitMessage}\r\n" >> "$input" echo -en "QUIT :${quitMessage}\r\n" >> "$input"
} }
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
userNick="$1" userNick="$1"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
advice=( advice=(
+1
View File
@@ -1,2 +1,3 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
msg "$2" "$1: $@" msg "$2" "$1: $@"
+15 -2
View File
@@ -1,12 +1,25 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("curl" "w3m" "grep" "sed" "iconv")
set -f set -f
quote="$(curl -s http://bash.org/?random)" quote="$(curl -s --connect-timeout 5 --max-time 10 http://bash.org/?random)"
if [[ -z "$quote" ]]; then
msg "$2" "$1: Sorry, couldn't fetch a bash.org quote right now."
set +f
exit 1
fi
quote="$(echo "$quote" | grep -m 1 -A 10 '"Permanent link to this quote."')" quote="$(echo "$quote" | grep -m 1 -A 10 '"Permanent link to this quote."')"
quote="${quote#*class=\"qt\">}" quote="${quote#*class=\"qt\">}"
quote="${quote//&lt;/ <}" quote="${quote//&lt;/ <}"
quote="${quote//&gt;/> }" quote="${quote//&gt;/> }"
quote="${quote%%<p class*}" quote="${quote%%<p class*}"
quote="$(echo "$quote" | w3m -dump -T text/html)" quote="$(echo "$quote" | w3m -dump -T text/html)"
msg "$2" "$(echo "$quote" | iconv -f utf-8 -t ascii)" if [[ ${#quote} -lt 5 ]]; then
msg "$2" "$1: Sorry, couldn't parse the bash.org quote."
else
msg "$2" "$(echo "$quote" | iconv -f utf-8 -t ascii)"
fi
set +f set +f
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
userNick="$1" userNick="$1"
+10
View File
@@ -1,5 +1,15 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("calc")
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
msg "$2" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
calculate() { calculate() {
calc -- "$*" | tr -d '[:space:]' calc -- "$*" | tr -d '[:space:]'
} }
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
case "${3^^}" in case "${3^^}" in
+10 -5
View File
@@ -1,12 +1,17 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
units="$(command -v units)" # Dependencies required by this module
dependencies=("units")
shift shift
chan="$1" chan="$1"
shift shift
if [ -z "$units" ]; then
msg "$chan" "I do not have access to units." # Check dependencies before running
else if ! check_dependencies "${dependencies[@]}"; then
msg "$chan" "$($units -v ${*#* } | head -n1 | tr -d '[:space:]')" msg "$chan" "$1: This module requires: ${dependencies[*]}"
exit 1
fi fi
msg "$chan" "$(units -v ${*#* } | head -n1 | tr -d '[:space:]')"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Add phrases in quotes to the array. # Add phrases in quotes to the array.
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
shift shift
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
douchebag=( douchebag=(
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
user=$1 user=$1
+10
View File
@@ -1,5 +1,15 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("curl" "w3m" "grep" "iconv")
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
msg "$2" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
fml="$(curl -Ls --connect-timeout 5 https://fmylife.com/random | grep -m1 -A1 '<a class="article-link" href="/article/.*>' | tail -1 | w3m -dump -T text/html | iconv -f utf-8 -t ascii | tr '[:space:]' ' ')" fml="$(curl -Ls --connect-timeout 5 https://fmylife.com/random | grep -m1 -A1 '<a class="article-link" href="/article/.*>' | tail -1 | w3m -dump -T text/html | iconv -f utf-8 -t ascii | tr '[:space:]' ' ')"
if [[ ${#fml} -gt 10 ]]; then if [[ ${#fml} -gt 10 ]]; then
+11 -6
View File
@@ -1,12 +1,17 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
fortune="$(command -v fortune)" # Dependencies required by this module
dependencies=("fortune")
target="${3#fortune}" target="${3#fortune}"
if [ -z "$fortune" ]; then # Check dependencies before running
msg "$2" "I do not have access to fortune." if ! check_dependencies "${dependencies[@]}"; then
else msg "$2" "$1: This module requires: ${dependencies[*]}"
fortuneText="$($fortune -a -e -s -n 512 $target || echo "No fortunes found.")" exit 1
fi
fortuneText="$(fortune -a -e -s -n 512 $target || echo "No fortunes found.")"
fortuneText="$(echo "$fortuneText" | tr '[:space:]' ' ' | sed -e 's/"/\"/g')" fortuneText="$(echo "$fortuneText" | tr '[:space:]' ' ' | sed -e 's/"/\"/g')"
msg "$2" "$fortuneText" msg "$2" "$fortuneText"
fi
+16 -1
View File
@@ -1,4 +1,19 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("curl")
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
msg "$2" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
type="$(shuf -n1 -e "off" "you" "donut" "shakespeare" "linus" "king" "chainsaw" "madison")" type="$(shuf -n1 -e "off" "you" "donut" "shakespeare" "linus" "king" "chainsaw" "madison")"
msg "$2" "$(curl -s -H "Accept: text/plain" "https://foaas.com/${type}/$3/$nick")" response="$(curl -s --connect-timeout 5 --max-time 10 -H "Accept: text/plain" "https://foaas.com/${type}/$3/$nick")"
if [[ -z "$response" || ${#response} -lt 5 ]]; then
msg "$2" "$1: Sorry, the FOAAS service is unavailable right now."
else
msg "$2" "$response"
fi
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
output="$1: The modules I have are:" output="$1: The modules I have are:"
output="$output $(find modules/ -type d | sort -d | tr '[:space:]' ' ' | sed -e 's#modules/##g')" output="$output $(find modules/ -type d | sort -d | tr '[:space:]' ' ' | sed -e 's#modules/##g')"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
cpu="$(shuf -n 1 -e HACKER NUKE SHOTGUN)" cpu="$(shuf -n 1 -e HACKER NUKE SHOTGUN)"
player="$3" player="$3"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[[ -f functions.sh ]] && source functions.sh [[ -f functions.sh ]] && source functions.sh
# Find the last line of the script. # Find the last line of the script.
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
user=$1 user=$1
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
shift shift
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
pong="${3:-ping}" pong="${3:-ping}"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
shift shift
+48 -11
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
userNick="$1" userNick="$1"
@@ -7,20 +8,56 @@ shift
time="$1" time="$1"
shift shift
# Validate time argument is provided
if [[ -z "$time" ]]; then
msg "$chan" "$userNick: Please provide a time (e.g., 30s, 5m, 1h)."
exit 0
fi
# Normalize time format
if ! [[ "$time" =~ ^[0-9]+[HhMmSs]$ ]]; then if ! [[ "$time" =~ ^[0-9]+[HhMmSs]$ ]]; then
time="${time}s" time="${time}s"
fi fi
# Validate time is numeric
if ! [[ "${time%[HhMmSs]}" =~ ^[0-9]+$ ]]; then if ! [[ "${time%[HhMmSs]}" =~ ^[0-9]+$ ]]; then
reminderMessage="Times must be numeric (seconds)." msg "$chan" "$userNick: Time must be numeric (e.g., 30s, 5m, 1h)."
msg "$chan" "$userNick: $reminderMessage" exit 0
else
msg "$chan" "ok, $userNick, reminder in $time."
reminderMessage="$@"
if [[ "$reminderMessage" =~ ^[Tt]ell* ]]; then
userNick="$(echo "${reminderMessage#[T|t]ell }" | cut -d ' ' -f1)"
reminderMessage="${reminderMessage#[Tt]ell }"
reminderMessage="${reminderMessage#* }"
fi
sleep $time && msg "$chan" "$userNick: $reminderMessage"&
fi fi
# Validate time is reasonable (max 24 hours)
timeValue="${time%[HhMmSs]}"
timeUnit="${time: -1}"
case "${timeUnit,,}" in
h) maxTime=24; timeInSeconds=$((timeValue * 3600)) ;;
m) maxTime=1440; timeInSeconds=$((timeValue * 60)) ;;
s) maxTime=86400; timeInSeconds=$timeValue ;;
*) maxTime=86400; timeInSeconds=$timeValue ;;
esac
if [[ $timeValue -gt $maxTime ]]; then
msg "$chan" "$userNick: Maximum time is 24 hours."
exit 0
fi
# Validate reminder message is provided
if [[ -z "$*" ]]; then
msg "$chan" "$userNick: Please provide a reminder message."
exit 0
fi
msg "$chan" "ok, $userNick, reminder in $time."
reminderMessage="$@"
# Handle 'tell' syntax for targeting other users
if [[ "$reminderMessage" =~ ^[Tt]ell[[:space:]]+ ]]; then
targetNick="$(echo "${reminderMessage#[T|t]ell }" | cut -d ' ' -f1)"
# Validate nick doesn't contain invalid characters
if [[ "$targetNick" =~ ^[a-zA-Z0-9_\[\]\{\}\\|\`\^\-]+$ ]]; then
userNick="$targetNick"
reminderMessage="${reminderMessage#[Tt]ell }"
reminderMessage="${reminderMessage#* }"
fi
fi
sleep "$timeInSeconds" && msg "$chan" "$userNick: $reminderMessage" &
+29 -4
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
roll_dice() { roll_dice() {
@@ -30,8 +31,32 @@ roll_dice() {
} }
rollString="${3##* }" rollString="${3##* }"
if ! [[ "$rollString" =~ ^[1-9][0-9]*[dD][1-9][0-9]*$ ]]; then
msg "$2" "${1}: Usage is roll #d# where # is greater than 0." # Validate roll string is provided
else if [[ -z "$rollString" ]]; then
msg "$2" "$(roll_dice ${rollString})" msg "$2" "${1}: Usage is roll #d# (e.g., 2d6, 1d20)"
exit 0
fi fi
# Validate roll string format
if ! [[ "$rollString" =~ ^[1-9][0-9]*[dD][1-9][0-9]*$ ]]; then
msg "$2" "${1}: Invalid format. Usage: roll #d# where # is greater than 0 (e.g., 2d6, 1d20)"
exit 0
fi
# Extract count and sides for validation
count=${rollString%[dD]*}
sides=${rollString#*[dD]}
# Validate reasonable limits to prevent abuse
if [[ $count -gt 1000 ]]; then
msg "$2" "${1}: Maximum 1000 dice per roll."
exit 0
fi
if [[ $sides -gt 1000 ]]; then
msg "$2" "${1}: Maximum 1000 sides per die."
exit 0
fi
msg "$2" "$(roll_dice ${rollString})"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
cpu="$(shuf -n 1 -e PAPER ROCK SCISSORS)" cpu="$(shuf -n 1 -e PAPER ROCK SCISSORS)"
player="$3" player="$3"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
shift shift
-22
View File
@@ -1,22 +0,0 @@
[ -f functions.sh ] && source functions.sh
# Sex adapted to the stormbot.
# The C version is available from: http://spatula.net/software/sex/
# Please use responsibly, disable where inappropriate
#set up random parts of the sentence
faster="$(shuf -n 1 -e "\"Let the games begin!\"" "\"Sweet Jesus!\"" "\"Not that!\"" "\"At last!\"" "\"Land o' Goshen!\"" "\"Is that all?\"" "\"Cheese it, the cops!\"" "\"I never dreamed it could be\"" "\"If I do, you won't respect me!\"" "\"Now!\"" "\"Open sesame!\"" "\"EMR!\"" "\"Again!\"" "\"Faster!\"" "\"Harder!\"" "\"Help!\"" "\"Fuck me harder!\"" "\"Is it in yet?\"" "\"You aren't my father!\"" "\"Doctor, that's not *my* shou\"" "\"No, no, do the goldfish!\"" "\"Holy Batmobile, Batman!\"" "\"He's dead, he's dead!\"" "\"Take me, Robert!\"" "\"I'm a Republican!\"" "\"Put four fingers in!\"" "\"What a lover!\"" "\"Talk dirty, you pig!\"" "\"The ceiling needs painting,\"" "\"Suck harder!\"" "\"The animals will hear!\"" "\"Not in public!\"")"
said="$( shuf -n 1 -e "bellowed" "yelped" "croaked" "growled" "panted" "moaned" "grunted" "laughed" "warbled" "sighed" "ejaculated" "choked" "stammered" "wheezed" "squealed" "whimpered" "salivated" "tongued" "cried" "screamed" "yelled" "said")"
fadj="$(shuf -n 1 -e "saucy" "wanton" "unfortunate" "lust-crazed" "nine-year-old" "bull-dyke" "bisexual" "gorgeous" "sweet" "nymphomaniacal" "large-hipped" "freckled" "forty-five year old" "white-haired" "large-boned" "saintly" "blind" "bearded" "blue-eyed" "large tongued" "friendly" "piano playing" "ear licking" "doe eyed" "sock sniffing" "lesbian" "hairy")"
female="$(shuf -n 1 -e "baggage" "hussy" "woman" "Duchess" "female impersonator" "nymphomaniac" "virgin" "leather freak" "home-coming queen" "defrocked nun" "bisexual budgie" "cheerleader" "office secretary" "sexual deviate" "DARPA contract monitor" "little matchgirl" "ceremonial penguin" "femme fatale" "bosses' daughter" "construction worker" "sausage abuser" "secretary" "Congressman's page" "grandmother" "penguin" "German shepherd" "stewardess" "waitress" "prostitute" "computer science group" "housewife")"
madjec="$(shuf -n 1 -e "thrashing" "slurping" "insatiable" "rabid" "satanic" "corpulent" "nose-grooming" "tripe-fondling" "dribbling" "spread-eagled" "orally fixated" "vile" "awesomely endowed" "handsome" "mush-brained" "tremendously hung" "three-legged" "pile-driving" "cross-dressing" "gerbil buggering" "bung-hole stuffing" "sphincter licking" "hair-pie chewing" "muff-diving" "clam shucking" "egg-sucking" "bicycle seat sniffing")"
male="$(shuf -n 1 -e "rakehell" "hunchback" "lecherous lickspittle" "archduke" "midget" "hired hand" "great Dane" "stallion" "donkey" "electric eel" "paraplegic pothead" "dirty old man" "faggot butler" "friar" "black-power advocate" "follicle fetishist" "handsome priest" "chicken flicker" "homosexual flamingo" "ex-celibate" "drug sucker" "ex-woman" "construction worker" "hair dresser" "dentist" "judge" "social worker")"
diddled="$(shuf -n 1 -e "diddled" "devoured" "fondled" "mouthed" "tongued" "lashed" "tweaked" "violated" "defiled" "irrigated" "penetrated" "ravished" "hammered" "bit" "tongue slashed" "sucked" "fucked" "rubbed" "grudge fucked" "masturbated with" "slurped")"
titadj="$(shuf -n 1 -e "alabaster" "pink-tipped" "creamy" "rosebud" "moist" "throbbing" "juicy" "heaving" "straining" "mammoth" "succulent" "quivering" "rosey" "globular" "varicose" "jiggling" "bloody" "tilted" "dribbling" "oozing" "firm" "pendulous" "muscular" "bovine")"
knockers="$(shuf -n 1 -e "globes" "melons" "mounds" "buds" "paps" "chubbies" "protuberances" "treasures" "buns" "bung" "vestibule" "armpits" "tits" "knockers" "elbows" "eyes" "hooters" "jugs" "lungs" "headlights" "disk drives" "bumpers" "knees" "fried eggs" "buttocks" "charlies" "ear lobes" "bazooms" "mammaries")"
thrust="$(shuf -n 1 -e "plunged" "thrust" "squeezed" "pounded" "drove" "eased" "slid" "hammered" "squished" "crammed" "slammed" "reamed" "rammed" "dipped" "inserted" "plugged" "augured" "pushed" "ripped" "forced" "wrenched")"
dongadj="$(shuf -n 1 -e "bursting" "jutting" "glistening" "Brobdingnagian" "prodigious" "purple" "searing" "swollen" "rigid" "rampaging" "warty" "steaming" "gorged" "trunklike" "foaming" "spouting" "swinish" "prosthetic" "blue veined" "engorged" "horse like" "throbbing" "humongous" "hole splitting" "serpentine" "curved" "steel encased" "glass encrusted" "knobby" "surgically altered" "metal tipped" "open sored" "rapidly dwindling" "swelling" "miniscule" "boney")"
dong="$(shuf -n 1 -e "intruder" "prong" "stump" "member" "meat loaf" "majesty" "bowsprit" "earthmover" "jackhammer" "ramrod" "cod" "jabber" "gusher" "poker" "engine" "brownie" "joy stick" "plunger" "piston" "tool" "manhood" "lollipop" "kidney prodder" "candlestick" "John Thomas" "arm" "testicles" "balls" "finger" "foot" "tongue" "dick" "one-eyed wonder worm" "canyon yodeler" "middle leg" "neck wrapper" "stick shift" "dong" "Linda Lovelace choker")"
twatadj="$(shuf -n 1 -e "pulsing" "hungry" "hymeneal" "palpitating" "gaping" "slavering" "welcoming" "glutted" "gobbling" "cobwebby" "ravenous" "slurping" "glistening" "dripping" "scabiferous" "porous" "soft-spoken" "pink" "dusty" "tight" "odiferous" "moist" "loose" "scarred" "weapon-less" "banana stuffed" "tire tracked" "mouse nibbled" "tightly tensed" "oft traveled" "grateful" "festering")"
twat="$(shuf -n 1 -e "swamp." "honeypot." "jam jar." "butterbox." "furburger." "cherry pie." "cush." "slot." "slit." "cockpit." "damp." "furrow." "sanctum sanctorum." "bearded clam." "continental divide." "paradise valley." "red river valley." "slot machine." "quim." "palace." "ass." "rose bud." "throat." "eye socket." "tenderness." "inner ear." "orifice." "appendix scar." "wound." "navel." "mouth." "nose." "cunt.")"
msg "$2" "$faster $said the $fadj $female as the $madjec $male $diddled her $titadj $knockers and $thrust his $dongadj $dong into her $twatadj $twat"
-13
View File
@@ -1,13 +0,0 @@
[ -f functions.sh ] && source functions.sh
sext=('( . Y . )'
'( . )( . )'
'(o)(o)'
'( + )( + )'
'(*)(*)'
'(@)(@)'
'(oYo)'
'( ^ )( ^ )'
'( . )Y( . )'
'8========3~~')
msg "$2" "$(shuf -n 1 -e '( . Y . )' '( . )( . )' '(o)(o)' '( + )( + )' '(*)(*)' '(@)(@)' '(oYo)' '( ^ )( ^ )' '( . )Y( . )')"
+10
View File
@@ -1,8 +1,18 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("clyrics")
shift shift
chan="$1" chan="$1"
shift shift
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
msg "$chan" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
#get the lyric text into a variable #get the lyric text into a variable
lyricText="$(clyrics $@ | tr '[:space:]' ' ' | tr -s ' ' | fold -s -w 384)" lyricText="$(clyrics $@ | tr '[:space:]' ' ' | tr -s ' ' | fold -s -w 384)"
i=$(echo "$lyricText" | wc -l) i=$(echo "$lyricText" | wc -l)
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
userNick="$1" userNick="$1"
+10
View File
@@ -1,5 +1,15 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("pgrep" "ps")
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
msg "$2" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
pid=$(pgrep bot.sh | head -1) pid=$(pgrep bot.sh | head -1)
uptime="$(ps -p $pid -o etime | tail -1)" uptime="$(ps -p $pid -o etime | tail -1)"
msg "$2" "$1: I have been up for ${uptime}." msg "$2" "$1: I have been up for ${uptime}."
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Alias for weather module
# Just call the weather module with all arguments
./modules/weather/weather.sh "$@"
+422 -55
View File
@@ -1,73 +1,440 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this module
dependencies=("curl" "jq" "date")
if ! check_dependencies "${dependencies[@]}"; then
msg "$2" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
set -f set -f
name="$1" name="$1"
channelName="$2" channelName="$2"
shift shift 2
shift
if [[ $# -eq 0 ]]; then # Database file for user locations and cached geocoding
msg "$channelName" "$name: Please provide a location or postcode." weatherDb="data/weather.db"
mkdir -p "$(dirname "$weatherDb")"
touch "$weatherDb"
# Weather code mapping (Open-Meteo codes to descriptions)
declare -A weatherCodes=(
[0]="Clear sky"
[1]="Mainly clear"
[2]="Partly cloudy"
[3]="Overcast"
[45]="Fog"
[48]="Rime fog"
[51]="Light drizzle"
[53]="Moderate drizzle"
[55]="Dense drizzle"
[56]="Light freezing drizzle"
[57]="Dense freezing drizzle"
[61]="Slight rain"
[63]="Moderate rain"
[65]="Heavy rain"
[66]="Light freezing rain"
[67]="Heavy freezing rain"
[71]="Slight snow fall"
[73]="Moderate snow fall"
[75]="Heavy snow fall"
[77]="Snow flurries"
[80]="Slight rain showers"
[81]="Moderate rain showers"
[82]="Heavy rain showers"
[85]="Slight snow showers"
[86]="Heavy snow showers"
[95]="Thunderstorm"
[96]="Thunderstorm with slight hail"
[99]="Thunderstorm with heavy hail"
)
# Function to get cached geocode result
get_cached_location() {
local query="$1"
local queryLower="${query,,}"
grep -i "^CACHE|${queryLower}|" "$weatherDb" 2>/dev/null | head -n1
}
# Function to get user's saved location
get_user_location() {
local user="$1"
grep "^USER|${user}|" "$weatherDb" 2>/dev/null | head -n1
}
# Function to save user location
save_user_location() {
local user="$1"
local location="$2"
local lat="$3"
local lon="$4"
local formatted="$5"
# Remove old entry if exists
if [[ -f "$weatherDb" ]]; then
grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp"
mv "${weatherDb}.tmp" "$weatherDb"
fi
# Add new entry
echo "USER|${user}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb"
}
# Function to delete user location
delete_user_location() {
local user="$1"
if [[ -f "$weatherDb" ]]; then
grep -v "^USER|${user}|" "$weatherDb" > "${weatherDb}.tmp"
mv "${weatherDb}.tmp" "$weatherDb"
fi
}
# Function to cache geocode result
cache_location() {
local query="$1"
local location="$2"
local lat="$3"
local lon="$4"
local formatted="$5"
local queryLower="${query,,}"
# Check if already cached
local cached
cached=$(get_cached_location "$query")
if [[ -z "$cached" ]]; then
echo "CACHE|${queryLower}|${location}|${lat}|${lon}|${formatted}" >> "$weatherDb"
fi
}
# Function to format location (City, State or City, Country)
format_location() {
local address="$1"
# Use jq to parse the address components
local locality
local state
local country
local countryCode
# Try various location types in order of specificity
locality=$(echo "$address" | jq -r '.city // .town // .village // .hamlet // .municipality // .suburb // empty' 2>/dev/null)
state=$(echo "$address" | jq -r '.state // empty' 2>/dev/null)
country=$(echo "$address" | jq -r '.country // empty' 2>/dev/null)
countryCode=$(echo "$address" | jq -r '.country_code // empty' 2>/dev/null)
# For US addresses, always show state if available
if [[ "$countryCode" == "us" && -n "$locality" && -n "$state" ]]; then
echo "${locality}, ${state}"
elif [[ "$countryCode" == "us" && -n "$state" ]]; then
# No locality, just show state
echo "${state}, United States"
elif [[ -n "$locality" && -n "$country" ]]; then
# International: show locality and country
echo "${locality}, ${country}"
elif [[ -n "$country" ]]; then
# Just country
echo "$country"
else
# Fallback to display_name if parsing fails
echo "$address" | jq -r '.display_name // "Unknown location"' 2>/dev/null
fi
}
# Function to geocode a location
geocode_location() {
local query="$1"
# Check cache first
local cached
local location
local lat
local lon
local formatted
cached=$(get_cached_location "$query")
if [[ -n "$cached" ]]; then
# Format: CACHE|query|location|lat|lon|formatted
location=$(echo "$cached" | cut -d'|' -f3)
lat=$(echo "$cached" | cut -d'|' -f4)
lon=$(echo "$cached" | cut -d'|' -f5)
formatted=$(echo "$cached" | cut -d'|' -f6)
# If no formatted location in cache (old format), use location
if [[ -z "$formatted" ]]; then
formatted="$location"
fi
echo "${location}|${lat}|${lon}|${formatted}"
return 0
fi
# Query Nominatim API
local url="https://nominatim.openstreetmap.org/search"
local userAgent="stormbot-weather/1.0"
local response
# URL encode the query (replace spaces with +)
local encodedQuery="${query// /+}"
response=$(curl -s --connect-timeout 5 --max-time 10 \
-H "User-Agent: ${userAgent}" \
"${url}?q=${encodedQuery}&format=json&limit=1&addressdetails=1")
if [[ -z "$response" || "$response" == "[]" ]]; then
return 1
fi
# Parse response
local fullAddress
local addressDetails
local formattedLocation
lat=$(echo "$response" | jq -r '.[0].lat // empty' 2>/dev/null)
lon=$(echo "$response" | jq -r '.[0].lon // empty' 2>/dev/null)
fullAddress=$(echo "$response" | jq -r '.[0].display_name // empty' 2>/dev/null)
addressDetails=$(echo "$response" | jq -r '.[0].address // empty' 2>/dev/null)
if [[ -z "$lat" || -z "$lon" ]]; then
return 1
fi
# Format the location nicely
formattedLocation=$(format_location "$addressDetails")
# Cache the result
cache_location "$query" "$fullAddress" "$lat" "$lon" "$formattedLocation"
echo "${fullAddress}|${lat}|${lon}|${formattedLocation}"
return 0 return 0
}
# Function to get weather data
get_weather() {
local lat="$1"
local lon="$2"
local url="https://api.open-meteo.com/v1/forecast"
local params="latitude=${lat}&longitude=${lon}"
params+="&current=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m"
params+="&daily=weather_code,temperature_2m_max,temperature_2m_min"
params+="&timezone=auto&forecast_days=3&temperature_unit=fahrenheit&wind_speed_unit=mph"
local response
response=$(curl -s --connect-timeout 5 --max-time 10 "${url}?${params}")
if [[ -z "$response" ]]; then
return 1
fi
echo "$response"
return 0
}
# Function to format weather output
format_weather() {
local weatherData="$1"
local locationName="$2"
# Parse current weather
local temp
local humidity
local windSpeed
local weatherCode
temp=$(echo "$weatherData" | jq -r '.current.temperature_2m // "N/A"')
humidity=$(echo "$weatherData" | jq -r '.current.relative_humidity_2m // "N/A"')
windSpeed=$(echo "$weatherData" | jq -r '.current.wind_speed_10m // "N/A"')
weatherCode=$(echo "$weatherData" | jq -r '.current.weather_code // 0')
local conditions="${weatherCodes[$weatherCode]:-Unknown}"
# Build message
local message="Weather for ${locationName}: ${temp}°F, ${conditions}"
if [[ "$humidity" != "N/A" ]]; then
message+=", Humidity: ${humidity}%"
fi
if [[ "$windSpeed" != "N/A" ]]; then
message+=", Wind: ${windSpeed} mph"
fi
echo "$message"
}
# Function to format forecast output
format_forecast() {
local weatherData="$1"
# Parse forecast data
local dates
dates=$(echo "$weatherData" | jq -r '.daily.time[]' 2>/dev/null)
if [[ -z "$dates" ]]; then
return 1
fi
local forecastParts=()
local i=0
while IFS= read -r dateStr; do
local minTemp
local maxTemp
local code
minTemp=$(echo "$weatherData" | jq -r ".daily.temperature_2m_min[$i] // \"N/A\"")
maxTemp=$(echo "$weatherData" | jq -r ".daily.temperature_2m_max[$i] // \"N/A\"")
code=$(echo "$weatherData" | jq -r ".daily.weather_code[$i] // 0")
# Format date to day of week
local dayName
if date --version &>/dev/null; then
# GNU date
dayName=$(date -d "$dateStr" '+%A' 2>/dev/null)
else
# BSD date (macOS)
dayName=$(date -j -f '%Y-%m-%d' "$dateStr" '+%A' 2>/dev/null)
fi
if [[ -z "$dayName" ]]; then
dayName="$dateStr"
fi
local forecastConditions="${weatherCodes[$code]:-Unknown}"
if [[ "$minTemp" != "N/A" && "$maxTemp" != "N/A" ]]; then
forecastParts+=("${dayName}: ${minTemp}°F to ${maxTemp}°F, ${forecastConditions}")
fi
((i++))
# Only show 3 days
if [[ $i -ge 3 ]]; then
break
fi
done <<< "$dates"
if [[ ${#forecastParts[@]} -gt 0 ]]; then
local IFS=" | "
echo "Forecast: ${forecastParts[*]}"
return 0
fi
return 1
}
# Main command logic
subcommand="${1:-}"
# If subcommand is the module name itself (w or weather), treat as no argument
if [[ "$subcommand" == "w" || "$subcommand" == "weather" ]]; then
subcommand=""
fi fi
location="${*}" case "$subcommand" in
set)
# Set user's location
shift
if [[ $# -eq 0 ]]; then
msg "$channelName" "$name: Usage: weather set <location>"
exit 0
fi
# Convert spaces to +. location="$*"
location="${location//[[:space:]]/+}"
# Validate location length
if [[ ${#location} -gt 100 ]]; then
msg "$channelName" "$name: Location is too long (max 100 characters)."
exit 0
fi
# c Weather condition, # Geocode the location
# C Weather condition textual name, geocodeResult=$(geocode_location "$location")
# h Humidity, if [[ $? -ne 0 || -z "$geocodeResult" ]]; then
# t Temperature (Actual), msg "$channelName" "$name: Could not find that location. Please try a different search term."
# f Temperature (Feels Like), exit 0
# w Wind, fi
# l Location,
# m Moonphase ðð,
# M Moonday,
# p precipitation (mm),
# o Probability of Precipitation,
# P pressure (hPa),
# D Dawn*,
# S Sunrise*,
# z Zenith*,
# s Sunset*,
# d Dusk*.
format="%c|%C|%h|%t|%f|%w|%l|%m|%M|%p|%o|%P|%D|%S|%z|%s|%d" # Parse result: fullAddress|lat|lon|formattedLocation
argList="${format//%/}" fullAddress=$(echo "$geocodeResult" | cut -d'|' -f1)
argList="${argList//|/ }" lat=$(echo "$geocodeResult" | cut -d'|' -f2)
weatherString="$(curl -s https://wttr.in/${location}?format="${format}")" lon=$(echo "$geocodeResult" | cut -d'|' -f3)
formattedLocation=$(echo "$geocodeResult" | cut -d'|' -f4)
i=1 # Save user location
declare -A weatherInfo save_user_location "$name" "$fullAddress" "$lat" "$lon" "$formattedLocation"
for j in $argList ; do
weatherInfo[$j]="$(echo "${weatherString}" | cut -d '|' -f $i)"
((i++))
done
# Format times to 12 hour format. msg "$channelName" "$name: Your location has been set to: ${formattedLocation}"
for i in S D z s d ; do ;;
weatherInfo[$i]="$(date '+%r' --date="${weatherInfo[$i]}")"
done
message="${weatherInfo[l]}: ${weatherInfo[t]} and ${weatherInfo[C]}" del|delete)
if [[ "${weatherInfo[t]}" == "${weatherInfo[f]}" ]]; then # Delete user's location
message+=". " delete_user_location "$name"
else msg "$channelName" "$name: Your location has been deleted."
message+=" with a real feel of ${weatherInfo[f]}. " ;;
fi
message+="Wind: ${weatherInfo[w]} "
if [[ "${weatherInfo[p]}" != "0.0mm" ]]; then
message+=" Precipitation: ${weatherInfo[p]} "
fi
if [[ -n "${weatherInfo[o]}" ]]; then
message+="Chance of precipitation ${weatherInfo[o]} "
fi
message+="Humidity: ${weatherInfo[h]}. "
message+="Sunrise: ${weatherInfo[S]}, Sunset: ${weatherInfo[s]}."
msg "$channelName" "$name: ${message//+/ }" *)
# Show weather (default action)
if [[ -n "$subcommand" ]]; then
# Weather for specified location
location="$*"
# Validate location length
if [[ ${#location} -gt 100 ]]; then
msg "$channelName" "$name: Location is too long (max 100 characters)."
exit 0
fi
# Geocode the location
geocodeResult=$(geocode_location "$location")
if [[ $? -ne 0 || -z "$geocodeResult" ]]; then
msg "$channelName" "$name: Could not find that location. Please try a different search term."
exit 0
fi
# Parse result
fullAddress=$(echo "$geocodeResult" | cut -d'|' -f1)
lat=$(echo "$geocodeResult" | cut -d'|' -f2)
lon=$(echo "$geocodeResult" | cut -d'|' -f3)
formattedLocation=$(echo "$geocodeResult" | cut -d'|' -f4)
locationDisplay="$formattedLocation"
else
# Weather for user's saved location
userLocation=$(get_user_location "$name")
if [[ -z "$userLocation" ]]; then
msg "$channelName" "$name: You have not set a location. Use 'weather set <location>' to save your location, or 'weather <location>' to check weather for a specific location."
exit 0
fi
# Parse user location: USER|name|location|lat|lon|formatted
fullAddress=$(echo "$userLocation" | cut -d'|' -f3)
lat=$(echo "$userLocation" | cut -d'|' -f4)
lon=$(echo "$userLocation" | cut -d'|' -f5)
formattedLocation=$(echo "$userLocation" | cut -d'|' -f6)
# If no formatted location stored (old format), use full address
if [[ -z "$formattedLocation" ]]; then
formattedLocation="$fullAddress"
fi
locationDisplay="$formattedLocation"
fi
# Get weather data
weatherData=$(get_weather "$lat" "$lon")
if [[ $? -ne 0 || -z "$weatherData" ]]; then
msg "$channelName" "$name: Could not retrieve weather data. Please try again later."
exit 1
fi
# Format and send current weather
weatherMessage=$(format_weather "$weatherData" "$locationDisplay")
msg "$channelName" "$name: ${weatherMessage}"
# Format and send forecast
forecastMessage=$(format_forecast "$weatherData")
if [[ $? -eq 0 && -n "$forecastMessage" ]]; then
msg "$channelName" "$name: ${forecastMessage}"
fi
;;
esac
+17 -3
View File
@@ -1,5 +1,19 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
joke="$(curl -s https://api.yomomma.info | sed -e 's/{"joke":"//' -e 's/"}$//')" # Dependencies required by this module
joke="${joke//[[:space:]]/ }" dependencies=("curl" "sed")
msg "$2" "$joke"
# Check dependencies before running
if ! check_dependencies "${dependencies[@]}"; then
msg "$2" "$1: This module requires: ${dependencies[*]}"
exit 1
fi
joke="$(curl -s --connect-timeout 5 --max-time 10 https://api.yomomma.info | sed -e 's/{"joke":"//' -e 's/"}$//')"
if [[ -z "$joke" || ${#joke} -lt 5 ]]; then
msg "$2" "$1: Sorry, couldn't fetch a yo momma joke right now."
else
joke="${joke//[[:space:]]/ }"
msg "$2" "$joke"
fi
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# All names to match are completely lowercase. # All names to match are completely lowercase.
exitName="${1%% *}" exitName="${1%% *}"
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# All names to match are completely lowercase. # All names to match are completely lowercase.
case "${1,,}" in case "${1,,}" in
+2 -1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
who="${1%!*}" who="${1%!*}"
@@ -66,7 +67,7 @@ keywords[vim]="msg \"$chan\" \"$(shuf -n1 -e \
wordList="$(echo "${@,,}" | tr '[:space:]' $'\n' | sort -u)" wordList="$(echo "${@,,}" | tr '[:space:]' $'\n' | sort -u)"
for w in ${wordList//[[:punct:]]/} ; do for w in ${wordList//[[:punct:]]/} ; do
if [[ -n "${keywords[${w,,}]}" && "$lastWordMatch" != "${keywords[${w,,}]}" ]]; then if [[ -n "${keywords[${w,,}]}" && "$lastWordMatch" != "${keywords[${w,,}]}" ]]; then
eval ${keywords[${w,,}]} eval "${keywords[${w,,}]}"
lastWordMatch="${keywords[${w,,}]}" lastWordMatch="${keywords[${w,,}]}"
fi fi
done done
+9
View File
@@ -1,5 +1,14 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh [ -f functions.sh ] && source functions.sh
# Dependencies required by this trigger
dependencies=("curl" "w3m" "sed")
# Silently skip if dependencies are missing (triggers shouldn't error in channel)
if ! check_dependencies "${dependencies[@]}" &> /dev/null; then
exit 0
fi
for l in $3 ; do for l in $3 ; do
text="${l#:}" text="${l#:}"
if [[ "${text}" =~ http://|https://|www\..* ]]; then if [[ "${text}" =~ http://|https://|www\..* ]]; then