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
.gitignore vendored
View File

@@ -2,4 +2,3 @@ response/error.txt
log.txt
.*
bot.cfg
bot.cfg

View File

@@ -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 ....

View File

@@ -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
View File

@@ -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

View File

@@ -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"
}

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
userNick="$1"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
advice=(

View File

@@ -1,2 +1,3 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
msg "$2" "$1: $@"

View File

@@ -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//&lt;/ <}"
quote="${quote//&gt;/> }"
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

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
userNick="$1"

View File

@@ -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:]'
}

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
case "${3^^}" in

View File

@@ -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:]')"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
# Add phrases in quotes to the array.

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
shift

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
douchebag=(

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
user=$1

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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')"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
cpu="$(shuf -n 1 -e HACKER NUKE SHOTGUN)"
player="$3"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[[ -f functions.sh ]] && source functions.sh
# Find the last line of the script.

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
user=$1

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
shift

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
pong="${3:-ping}"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
shift

View File

@@ -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" &

View File

@@ -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})"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
cpu="$(shuf -n 1 -e PAPER ROCK SCISSORS)"
player="$3"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
shift

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"

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( . )')"

View File

@@ -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)

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
userNick="$1"

View File

@@ -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
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 "$@"

View File

@@ -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+="&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
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

View File

@@ -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

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
# All names to match are completely lowercase.
exitName="${1%% *}"

View File

@@ -1,3 +1,4 @@
#!/usr/bin/env bash
[ -f functions.sh ] && source functions.sh
# All names to match are completely lowercase.
case "${1,,}" in

View File

@@ -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

View File

@@ -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