Compare commits

...

5 Commits

Author SHA1 Message Date
Stef Dunlap
cb2d5aa868 Kill off any forked processes on timeout
Previously we bwrap-ed the whole our.rb script. In this commit we switch
it so that our.rb is run outside of bwrap, but every user command it
executes is done inside bwrap. This allows us to use bwrap's
"--die-with-parent" (along with "--unshare-pid") to kill off any forked
processes when the parent processes is killed due to a timeout.
2023-02-11 17:39:31 -05:00
Stef Dunlap
fb94656f49 Timeout commands if they run for longer than three seconds 2023-02-11 17:39:31 -05:00
Stef Dunlap
e6d32c5cab Add persistent storage in CMDS_DIR/data 2023-02-11 17:39:31 -05:00
Stef Dunlap
47425fdd85 Customizable start up params 2023-02-11 17:39:31 -05:00
Stef Dunlap
3ffba8f981 initial dzwdz commit 2023-02-11 17:39:31 -05:00
34 changed files with 445 additions and 0 deletions

3
.envrc Normal file
View File

@ -0,0 +1,3 @@
export OUR_NICK=your
export OUR_CHANNELS='#bots'
export OUR_CMDS_DIR=/town/our

1
boy Normal file
View File

@ -0,0 +1 @@
nyaa~~

BIN
cmds/.tw.un~ Executable file

Binary file not shown.

6
cmds/arguments Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
echo -n "\$1 => " ; test -z "$1" && echo -n "<empty> | " || echo -n "$1 | "
echo -n "\$2 => " ; test -z "$2" && echo -n "<empty> | " || echo -n "$2 | "
echo -n "\$3 => " ; test -z "$3" && echo -n "<empty> | " || echo -n "$3 | "
echo -n "\$4 => " ; test -z "$4" && echo -n "<empty> | " || echo -n "$4 | "

9
cmds/count Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
DIR=$(dirname -- $0)
DATA="$DIR/data/count"
old_number=$(cat "$DATA")
new_number=$((old_number + 1))
echo $new_number > $DATA
echo $new_number

2
cmds/data/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
cmds/echo Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo $1

16
cmds/fruit Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
import random
from io import StringIO
base = 0x1f345
nSides = 14
rounds = 14
fp = StringIO()
for i in range(rounds):
roll = random.randint(0, nSides)
chcr = base + roll
fp.write(chr(chcr))
print(fp.getvalue())

2
cmds/greet Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo hi $2 from $3

6
cmds/help Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env python3
import sys
if len(sys.argv) > 1 and sys.argv[1] == "writing":
print("you can add new scripts to our by placing executable files in the /town/our directory. Make sure they have the +x permission. programs are passed these arguments by the bot: the arguments given in the message (our/[program] [args]), the user ID, and the channel it was run from.")
else:
print("the our bot allows smol programs written by townies to be used by typing \"our/[program]\" from IRC. See also \"our/help writing\"")

2
cmds/howami Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo "You're doing great."

17
cmds/jikan Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env hy3
(import [datetime [datetime]])
(setv fullwidth (str.maketrans
(dfor [i ch] (enumerate "") [(str i) ch])))
(setv weekdays "日月火水木金土") (comment 曜日)
(defn ctime []
(setv d (datetime.now))
(-> (.format "{}年{}月{}日 {:02d}{:02d}{:02d}"
d.year d.month d.day d.hour d.minute d.second)
(.translate fullwidth)))
(print (ctime))

4
cmds/ls Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
DIR=$(dirname -- $0)
find $DIR/* -maxdepth 1 -perm -111 -type f -printf "%f "
echo

2
cmds/qotd Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
nc localhost 17 -W 1

3
cmds/rev Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
echo "$1"|rev

35
cmds/roll Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# A simple dice rolling app
# Written by xaphania 2022-04-19
# v0.1.our
# modified for use with the our irc bot 2022-04-21
# yes this probably sucks. please send your flames to xaphania@tilde.town
import random
import sys
try:
dstring = sys.argv[1]
except:
dstring = "1d6"
try:
numDice = int(dstring.split("d",1)[0])
numFace = int(dstring.split("d",1)[1])
except:
print(f"{dstring} is not a valid dice format")
exit()
diceList=[]
while numDice>0:
diceList.append(random.randint(1,numFace))
numDice-=1
total = sum(diceList)
avg = total / len(diceList)
high = max(diceList)
low = min(diceList)
print (f"Result: {diceList} | Total: {total} Average: {avg} Highest: {high} Lowest: {low}")

BIN
cmds/room Executable file

Binary file not shown.

89
cmds/room.go Normal file
View File

@ -0,0 +1,89 @@
package main
import (
"fmt"
"math/rand"
"time"
)
func randStr(s []string) string {
return s[rand.Intn(len(s))]
}
func airQuality() string {
return randStr([]string{
"moist",
"warm",
"humid",
"cool",
"dry",
"electric",
"dusty",
"hazy",
"smoky",
"clear",
})
}
func wallType() string {
return randStr([]string{
"bronze",
"mossy stone",
"wet stone",
"copper",
"tile",
"hard wood",
"stained wood",
"purple",
"light blue",
"yellowed",
"vine choked",
"art hung",
})
}
func lightQuality() string {
return randStr([]string{
"dimly lit",
"brightly lit",
"faintly lit",
"covered in dancing shadows",
"dappled",
})
}
func lightSource() string {
return randStr([]string{
"candle light",
"bioluminescent moss",
"bioluminescent fungus",
"lantern light",
"flickering monitors",
"a cracked LCD",
"a fireplace",
"a torch",
"sconces",
})
}
func aroma() string {
return randStr([]string{
"lavender",
"cloves",
"sulphur",
"baking cookies",
"dirt",
"wet soil",
"cut grass",
"wet dog",
"cinnamon",
"swamp",
"moss",
})
}
func main() {
rand.Seed(time.Now().UTC().UnixNano())
fmt.Printf("the air is %s. %s walls are %s by %s. it smells faintly of %s.\n",
airQuality(), wallType(), lightQuality(), lightSource(), aroma())
}

3
cmds/rot13 Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
echo "$1"|rot13

2
cmds/sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
sh -c "$1"

11
cmds/time Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
#echo "arguments are 1:$1 2:$2 3:$3 4:$4"
args="$1";
user="$2";
channel="$3";
nick="$(echo "$user"|cut -d "!" -f 1)"
args_lower="$(echo "$args"|tr [:upper:] [:lower:])"
case "$args_lower" in
("to shine") echo "yo, $nick, 🌟💎shine bright like a diamond!💎🌟";;
esac

6
cmds/timefor Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
echo $2
ip=$(finger $1 | egrep -o -m 1 '([0-9]{1,3}\.){3}[0-9]{1,3}')
tz=$(curl -s "https://ipapi.co/$ip/timezone")
TZ=$tz date -R

5
cmds/tw Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
user="$2"
nick="$(echo "$user"|cut -d "!" -f 1)"
/home/jmjl/bin/tw -u $nick $1 | sed -z 's/\n/ - /g'
# /home/jmjl/bin/tw -u $nick $1

2
cmds/uptime Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
uptime

61
cmds/weather Executable file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
######################################################
# Takes a location, e.g. "New York" as an argument #
# and returns the current weather conditions. Uses #
# the weather data API courtesy of metaweather.com #
######################################################
######################################################
# Written by ~xaphania 2022-04-22 #
# For use on tilde.town with the 'our' IRC bot #
# Please send comments or suggestions via town mail #
# to xaphania@tilde.town #
# v0.1 #
# TO DO - customise weather strings, add emoji? #
# TO DO - more features: detailed reports, future #
# and past weather conditions, etc #
######################################################
import sys
import json
import urllib.request
# get argument, replace spaces with %20
search = sys.argv[1].replace(" ","%20")
# Get Where on Earth ID for specified location
searchUrl = "https://www.metaweather.com/api/location/search/?query=" + search
try:
locationData = json.loads(urllib.request.urlopen(searchUrl).read())
except:
print("Couldn't contact metaweather. Service may be down, please try later.")
exit()
try:
locationID = locationData[0]["woeid"]
except:
search = search.replace("%20"," ")
print(f"Sorry, I couldn't find location {search}")
exit()
# Get weather data for that woeid
weatherUrl = "https://www.metaweather.com/api/location/"+str(locationID)+"/"
try:
weatherData = json.loads(urllib.request.urlopen(weatherUrl).read())
except:
print("Couldn't contact metaweather. Service may be down, please try later.")
exit()
try:
state = weatherData["consolidated_weather"][0]["weather_state_name"]
temp = round(weatherData["consolidated_weather"][0]["the_temp"])
location = weatherData["title"]
parent = weatherData["parent"]["title"]
print(f"In {location}, {parent} it's {temp}°c and {state}" \
+" (Weather Data from www.metaweather.com)")
except:
print("Sorry, there was an error retreiving weather data. Please try later.")

2
cmds/whatami Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo "A human (most likely)"

2
cmds/whenami Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
date

2
cmds/whereami Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo $3

2
cmds/whoami Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo $2

2
cmds/whyami Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo "https://en.wikipedia.org/wiki/Meaning_of_life"

18
our.example.service Normal file
View File

@ -0,0 +1,18 @@
[Unit]
Description=our
After=our.service
[Service]
Type=simple
WorkingDirectory=/home/kindrobot/wrk/our
ExecStart=/home/kindrobot/wrk/our/our.rb
Environment="OUR_NICK=your"
Environment="OUR_CHANNELS=#bots"
Environment="OUR_CMDS_DIR=/town/our"
Restart=always
RestartSec=5
StartLimitInterval=60s
StartLimitBurst=3
[Install]
WantedBy=default.target

111
our.rb Executable file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env ruby
require 'open3'
require 'socket'
require 'timeout'
# configurable environment variables
nick = ENV['OUR_NICK'] || 'our'
channels = ENV['OUR_CHANNELS'] || '#tildetown,#bots'
prefix = ENV['OUR_PREFIX'] || "#{nick}/"
cmds_dir = ENV['OUR_CMDS_DIR'] || '/town/our'
module IRC
class User
attr_accessor :s
def initialize addr, port, nick
@hooks = []
@s = TCPSocket.open addr, port.to_s
s.puts "USER #{nick} fakehost whatevenisaservername :beep boop"
s.puts "NICK #{nick}"
hook do |m|
next unless m.cmd == 'PING'
raw "PING #{nick}"
end
end
def raw msg
@s.puts msg
end
def join chan
raw "JOIN #{chan}"
end
def privmsg target, msg
raw "PRIVMSG #{target} :#{msg}"
end
def hook &h
@hooks << h
end
def loop
while line = s.gets
msg = Message.new line
@hooks.each{|h| h.call(msg)}
end
end
end
class Message
attr_accessor :prefix, :cmd, :args, :raw
# TODO custom constructor
def initialize msg
msg = msg.delete_suffix "\r\n"
@raw = msg
@prefix = nil
@prefix, msg = msg[1..].split(' ', 2) if msg[0] == ':'
@cmd, msg = msg.split(' ', 2)
@args = []
while msg and not msg.empty?
if msg[0] == ':'
@args << msg[1..]
break
end
s, msg = msg.split(' ', 2)
@args << s
end
end
end
end
puts "starting"
i = IRC::User.new 'localhost', 6667, nick
channels.split(',').each { |channel| i.join channel }
i.hook do |msg|
next unless msg.cmd == 'PRIVMSG'
target, content = msg.args
next unless content.delete_prefix! prefix
cmd, args = content.split(' ', 2)
cmd = "#{cmds_dir}/#{cmd}"
args ||= ''
next unless File.exists? cmd
if not File.executable? cmd
i.privmsg target, "#{cmd} isn't executable. try chmod +x"
next
end
begin
Open3.popen2e("#{__dir__}/wrap_it.sh", cmd, args, msg.prefix, target) do |_, stdout, wait_thread|
out = nil
Timeout::timeout(3) do
out = stdout.gets # only interested in the first line of output
stdout.gets until stdout.eof? # make sure process finishes in time allotted
end
i.privmsg target, out if out
rescue Timeout::Error
Process.kill("KILL", wait_thread.pid)
i.privmsg target, "[our.rb] command timed out"
end
rescue Exception => e
i.privmsg target, "[our.rb] #{e.to_s}"
end
next true
end
i.loop

2
passwd Normal file
View File

@ -0,0 +1,2 @@
root:x:0:0:root:/root:/bin/ed
weed:x:420:666:lmao:/weed:/bin/mount.ext4

15
wrap_it.sh Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
DIR=$(dirname -- $0)
OUR_CMDS_DIR=${OUR_CMDS_DIR:-/town/our}
/usr/bin/bwrap \
--unshare-all \
--ro-bind / / \
--bind "$OUR_CMDS_DIR/data" "$OUR_CMDS_DIR/data" \
--share-net \
--dev /dev \
--tmpfs /tmp \
--unshare-pid \
--die-with-parent \
"$@"