A few updates, cleanup updated weather module.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,4 +2,3 @@ response/error.txt
|
||||
log.txt
|
||||
.*
|
||||
bot.cfg
|
||||
bot.cfg
|
||||
|
||||
20
README.md
20
README.md
@@ -3,7 +3,23 @@ bash-irc-bot
|
||||
|
||||
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.
|
||||
|
||||
call modules with botname: test arg1 arg2 ....
|
||||
Call modules with botname: test arg1 arg2 ....
|
||||
|
||||
@@ -11,10 +11,13 @@ leave=true
|
||||
# Path to log file
|
||||
log="log.txt"
|
||||
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
|
||||
quitMessage="$nick vanishes in a sudden flash of brilliant lightning."
|
||||
server="irc.talkabout.cf"
|
||||
server="irc.libera.chat"
|
||||
# format=username hostname servername :realname
|
||||
user="$nick ${server%.} $server :$nick"
|
||||
# auto rejoin if kicked from channel?
|
||||
132
bot.sh
132
bot.sh
@@ -5,6 +5,19 @@ if [ "$(whoami)" = "root" ]; then
|
||||
exit 1
|
||||
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.
|
||||
for i in "bot.cfg" "functions.sh" ; do
|
||||
if [[ -f "$i" ]]; then
|
||||
@@ -23,6 +36,21 @@ export ignoreList
|
||||
export nick
|
||||
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.
|
||||
rm_input() {
|
||||
if [[ -f "$input" ]]; then
|
||||
@@ -30,19 +58,66 @@ rm_input() {
|
||||
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 rm_input EXIT
|
||||
|
||||
# Set up the connection.
|
||||
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"
|
||||
echo "NICK $nick" > "$input"
|
||||
echo "USER $user" >> "$input"
|
||||
echo "JOIN #$channel" >> "$input"
|
||||
# Reconnection loop - keeps bot connected even if connection drops
|
||||
reconnectDelay=10
|
||||
while true; do
|
||||
# Set up the connection.
|
||||
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.
|
||||
tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
# The main loop of the program where we watch for output from irc.
|
||||
# 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
|
||||
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
|
||||
case "$result" in
|
||||
# Handle nick changes
|
||||
@@ -51,7 +126,7 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
originalNick="${result#:}"
|
||||
originalNick="${originalNick%%!*}"
|
||||
# If the old nick was in the ignore list, update it.
|
||||
if [[ "${originalNick}" =~ [${ignoreList}] ]]; then
|
||||
if [[ "${originalNick}" =~ ^($ignoreList)$ ]]; then
|
||||
export ignoreList="${ignoreList/${originalNick}/${result#:*:}}"
|
||||
fi
|
||||
;;
|
||||
@@ -83,11 +158,10 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
from="${result#*#}"
|
||||
from="#$from"
|
||||
if [ "$who" = "$nick" ]; then
|
||||
continue
|
||||
continue
|
||||
fi
|
||||
echo "MODE #$channel +o $who" | tee -a "$input"
|
||||
if [ "${greet^^}" = "TRUE" ]; then
|
||||
set -f
|
||||
set -f
|
||||
./triggers/greet/greet.sh "$who" "$from"
|
||||
set +f
|
||||
fi
|
||||
@@ -109,7 +183,7 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
;;
|
||||
# run when a private message is seen
|
||||
*"PRIVMSG "[[:alnum:]-_]*)
|
||||
echo "$result" >> "$log"
|
||||
echo "$logSanitized" >> "$log"
|
||||
who="${result%%!*}"
|
||||
who="${who:1}"
|
||||
from="${who%!*}"
|
||||
@@ -117,12 +191,13 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
command="${command//# /}"
|
||||
will="${command#* }"
|
||||
command="${command%% *}"
|
||||
if [[ "$from" =~ $allowList ]]; then
|
||||
if [[ "$from" =~ ^($allowList)$ ]]; 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
|
||||
set -f
|
||||
./modules/${command% *}/${command% *}.sh "$who" "#$channel" $will
|
||||
set -f
|
||||
"./modules/${command% *}/${command% *}.sh" "$who" "#$channel" "$will"
|
||||
# Enable wildcards
|
||||
set +f
|
||||
else
|
||||
@@ -134,7 +209,6 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
;;
|
||||
# run when a message is seen
|
||||
*PRIVMSG*)
|
||||
echo "$result" >> "$log"
|
||||
who="${result%%!*}"
|
||||
who="${who:1}"
|
||||
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:
|
||||
if [[ "$result" =~ .*http://|https://|www\..* ]]; then
|
||||
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"
|
||||
set -f
|
||||
# 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.
|
||||
elif [[ "${result#:*PRIVMSG*:}" =~ ^[${botCaller}][a-zA-Z0-9_].* ]]; then
|
||||
echo "DEBUG: Matched bot caller pattern" >> "$log"
|
||||
command="${result#*:[[:punct:]]}"
|
||||
command="${command//# /}"
|
||||
will="${command#* }"
|
||||
command="${command%% *}"
|
||||
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 "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
|
||||
set -f
|
||||
./modules/${command% *}/${command% *}.sh "$who" "$from" $will
|
||||
set -f
|
||||
"./modules/${command% *}/${command% *}.sh" "$who" "$from" "$will"
|
||||
# Enable wildcards
|
||||
set +f
|
||||
else
|
||||
./modules/say/say.sh "$who" "$from" "$who: $(shuf -n1 "response/error.txt")"
|
||||
fi
|
||||
else
|
||||
if ! [[ "$who" =~ $ignoreList ]]; then
|
||||
if ! [[ "$who" =~ ^($ignoreList)$ ]]; then
|
||||
set -f
|
||||
./triggers/keywords/keywords.sh "$who" "$from" "$result"
|
||||
set +f
|
||||
@@ -173,9 +250,14 @@ tail -f "$input" | telnet "$server" "$port" | while read -r result ; do
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "$result" >> "$log"
|
||||
echo "$logSanitized" >> "$log"
|
||||
;;
|
||||
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
|
||||
|
||||
rm_input
|
||||
|
||||
19
functions.sh
19
functions.sh
@@ -4,6 +4,25 @@ if [[ -z "$input" ]]; then
|
||||
input="$(mktemp .XXXXXX)"
|
||||
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() {
|
||||
echo -en "QUIT :${quitMessage}\r\n" >> "$input"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
userNick="$1"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
advice=(
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
msg "$2" "$1: $@"
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
# Dependencies required by this module
|
||||
dependencies=("curl" "w3m" "grep" "sed" "iconv")
|
||||
|
||||
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="${quote#*class=\"qt\">}"
|
||||
quote="${quote//</ <}"
|
||||
quote="${quote//>/> }"
|
||||
quote="${quote%%<p class*}"
|
||||
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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
userNick="$1"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -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() {
|
||||
calc -- "$*" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
case "${3^^}" in
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
units="$(command -v units)"
|
||||
# Dependencies required by this module
|
||||
dependencies=("units")
|
||||
|
||||
shift
|
||||
chan="$1"
|
||||
shift
|
||||
if [ -z "$units" ]; then
|
||||
msg "$chan" "I do not have access to units."
|
||||
else
|
||||
msg "$chan" "$($units -v ${*#* } | head -n1 | tr -d '[:space:]')"
|
||||
|
||||
# Check dependencies before running
|
||||
if ! check_dependencies "${dependencies[@]}"; then
|
||||
msg "$chan" "$1: This module requires: ${dependencies[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg "$chan" "$(units -v ${*#* } | head -n1 | tr -d '[:space:]')"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
# Add phrases in quotes to the array.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
shift
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
douchebag=(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
user=$1
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -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:]' ' ')"
|
||||
|
||||
if [[ ${#fml} -gt 10 ]]; then
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
fortune="$(command -v fortune)"
|
||||
# Dependencies required by this module
|
||||
dependencies=("fortune")
|
||||
|
||||
target="${3#fortune}"
|
||||
|
||||
if [ -z "$fortune" ]; then
|
||||
msg "$2" "I do not have access to fortune."
|
||||
else
|
||||
fortuneText="$($fortune -a -e -s -n 512 $target || echo "No fortunes found.")"
|
||||
# Check dependencies before running
|
||||
if ! check_dependencies "${dependencies[@]}"; then
|
||||
msg "$2" "$1: This module requires: ${dependencies[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fortuneText="$(fortune -a -e -s -n 512 $target || echo "No fortunes found.")"
|
||||
fortuneText="$(echo "$fortuneText" | tr '[:space:]' ' ' | sed -e 's/"/\"/g')"
|
||||
msg "$2" "$fortuneText"
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -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")"
|
||||
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,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
output="$1: The modules I have are:"
|
||||
output="$output $(find modules/ -type d | sort -d | tr '[:space:]' ' ' | sed -e 's#modules/##g')"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
cpu="$(shuf -n 1 -e HACKER NUKE SHOTGUN)"
|
||||
player="$3"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[[ -f functions.sh ]] && source functions.sh
|
||||
|
||||
# Find the last line of the script.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
user=$1
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
shift
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
pong="${3:-ping}"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
shift
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
userNick="$1"
|
||||
@@ -7,20 +8,56 @@ shift
|
||||
time="$1"
|
||||
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
|
||||
time="${time}s"
|
||||
fi
|
||||
|
||||
# Validate time is numeric
|
||||
if ! [[ "${time%[HhMmSs]}" =~ ^[0-9]+$ ]]; then
|
||||
reminderMessage="Times must be numeric (seconds)."
|
||||
msg "$chan" "$userNick: $reminderMessage"
|
||||
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"&
|
||||
msg "$chan" "$userNick: Time must be numeric (e.g., 30s, 5m, 1h)."
|
||||
exit 0
|
||||
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" &
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
roll_dice() {
|
||||
@@ -30,8 +31,32 @@ roll_dice() {
|
||||
}
|
||||
|
||||
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."
|
||||
else
|
||||
msg "$2" "$(roll_dice ${rollString})"
|
||||
|
||||
# Validate roll string is provided
|
||||
if [[ -z "$rollString" ]]; then
|
||||
msg "$2" "${1}: Usage is roll #d# (e.g., 2d6, 1d20)"
|
||||
exit 0
|
||||
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,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
cpu="$(shuf -n 1 -e PAPER ROCK SCISSORS)"
|
||||
player="$3"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
shift
|
||||
|
||||
@@ -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"
|
||||
@@ -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( . )')"
|
||||
@@ -1,8 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
# Dependencies required by this module
|
||||
dependencies=("clyrics")
|
||||
|
||||
shift
|
||||
chan="$1"
|
||||
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
|
||||
lyricText="$(clyrics $@ | tr '[:space:]' ' ' | tr -s ' ' | fold -s -w 384)"
|
||||
i=$(echo "$lyricText" | wc -l)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
userNick="$1"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -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)
|
||||
uptime="$(ps -p $pid -o etime | tail -1)"
|
||||
msg "$2" "$1: I have been up for ${uptime}."
|
||||
|
||||
5
modules/w/w.sh
Executable file
5
modules/w/w.sh
Executable 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 "$@"
|
||||
@@ -1,73 +1,440 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -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
|
||||
|
||||
name="$1"
|
||||
channelName="$2"
|
||||
shift
|
||||
shift
|
||||
shift 2
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
msg "$channelName" "$name: Please provide a location or postcode."
|
||||
# Database file for user locations and cached geocoding
|
||||
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
|
||||
}
|
||||
|
||||
# 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+="¤t=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
|
||||
|
||||
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//[[:space:]]/+}"
|
||||
location="$*"
|
||||
|
||||
# Validate location length
|
||||
if [[ ${#location} -gt 100 ]]; then
|
||||
msg "$channelName" "$name: Location is too long (max 100 characters)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# c Weather condition,
|
||||
# C Weather condition textual name,
|
||||
# h Humidity,
|
||||
# t Temperature (Actual),
|
||||
# f Temperature (Feels Like),
|
||||
# w Wind,
|
||||
# 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*.
|
||||
# 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
|
||||
|
||||
format="%c|%C|%h|%t|%f|%w|%l|%m|%M|%p|%o|%P|%D|%S|%z|%s|%d"
|
||||
argList="${format//%/}"
|
||||
argList="${argList//|/ }"
|
||||
weatherString="$(curl -s https://wttr.in/${location}?format="${format}")"
|
||||
# Parse result: fullAddress|lat|lon|formattedLocation
|
||||
fullAddress=$(echo "$geocodeResult" | cut -d'|' -f1)
|
||||
lat=$(echo "$geocodeResult" | cut -d'|' -f2)
|
||||
lon=$(echo "$geocodeResult" | cut -d'|' -f3)
|
||||
formattedLocation=$(echo "$geocodeResult" | cut -d'|' -f4)
|
||||
|
||||
i=1
|
||||
declare -A weatherInfo
|
||||
for j in $argList ; do
|
||||
weatherInfo[$j]="$(echo "${weatherString}" | cut -d '|' -f $i)"
|
||||
((i++))
|
||||
done
|
||||
# Save user location
|
||||
save_user_location "$name" "$fullAddress" "$lat" "$lon" "$formattedLocation"
|
||||
|
||||
# Format times to 12 hour format.
|
||||
for i in S D z s d ; do
|
||||
weatherInfo[$i]="$(date '+%r' --date="${weatherInfo[$i]}")"
|
||||
done
|
||||
msg "$channelName" "$name: Your location has been set to: ${formattedLocation}"
|
||||
;;
|
||||
|
||||
message="${weatherInfo[l]}: ${weatherInfo[t]} and ${weatherInfo[C]}"
|
||||
if [[ "${weatherInfo[t]}" == "${weatherInfo[f]}" ]]; then
|
||||
message+=". "
|
||||
else
|
||||
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]}."
|
||||
del|delete)
|
||||
# Delete user's location
|
||||
delete_user_location "$name"
|
||||
msg "$channelName" "$name: Your location has been deleted."
|
||||
;;
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
joke="$(curl -s https://api.yomomma.info | sed -e 's/{"joke":"//' -e 's/"}$//')"
|
||||
joke="${joke//[[:space:]]/ }"
|
||||
msg "$2" "$joke"
|
||||
# Dependencies required by this module
|
||||
dependencies=("curl" "sed")
|
||||
|
||||
# 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,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
# All names to match are completely lowercase.
|
||||
exitName="${1%% *}"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
# All names to match are completely lowercase.
|
||||
case "${1,,}" in
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -f functions.sh ] && source functions.sh
|
||||
|
||||
who="${1%!*}"
|
||||
@@ -66,7 +67,7 @@ keywords[vim]="msg \"$chan\" \"$(shuf -n1 -e \
|
||||
wordList="$(echo "${@,,}" | tr '[:space:]' $'\n' | sort -u)"
|
||||
for w in ${wordList//[[:punct:]]/} ; do
|
||||
if [[ -n "${keywords[${w,,}]}" && "$lastWordMatch" != "${keywords[${w,,}]}" ]]; then
|
||||
eval ${keywords[${w,,}]}
|
||||
eval "${keywords[${w,,}]}"
|
||||
lastWordMatch="${keywords[${w,,}]}"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
[ -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
|
||||
text="${l#:}"
|
||||
if [[ "${text}" =~ http://|https://|www\..* ]]; then
|
||||
|
||||
Reference in New Issue
Block a user