Compare commits

...

43 Commits

Author SHA1 Message Date
97a315d2d1 fix giving rep to yourself via different nick with same account
thanks to findus for discovering this :D
2025-05-14 23:38:05 +00:00
b29cf737a7 exit if a TimeoutError occurs 2024-11-26 13:40:06 +00:00
51a37c4562 make 'is_registered' regex a raw string
to fix the following warning with python 3.12:
SyntaxWarning: invalid escape sequence '\S'
2024-06-02 15:13:59 +00:00
34a3f06d91 add socket timeout + exit if connection is lost
Fix #2
2024-01-31 01:01:35 +00:00
f92d59dd91 use socket.create_connection() for dualstack and socket timeout 2024-01-30 23:41:32 +00:00
e3cdce4f12 fix error handling during connect 2024-01-30 23:37:58 +00:00
f449305ccd fix str-concat during in except blocks 2024-01-30 22:58:08 +00:00
1b252ca2c7 Revert "fix accidental logging of understood messages"
This reverts commit e709a2ade1.
2024-01-30 22:24:52 +00:00
9aee38bc50 Revert "log unknown messages"
This reverts commit 359407187e.
2024-01-30 22:24:44 +00:00
e709a2ade1 fix accidental logging of understood messages 2024-01-30 22:19:25 +00:00
359407187e log unknown messages 2024-01-30 22:17:07 +00:00
90fc695794 nslookup: remove deletion of variable 'answer' 2023-07-17 01:35:04 +00:00
0038eecfbc nslookup: support lookup of multiple record types at once 2023-07-17 01:27:26 +00:00
7eddd9b9d7 make AAAA the default in nslookup 2023-07-17 01:11:46 +00:00
3c49d4ecd1 fix DeprecationWarning in nslookup module 2023-07-17 01:10:17 +00:00
5cbcb2e6f2 fix .nextrocket trigger 2023-07-17 01:07:44 +00:00
c92c8b368f update .help command list 2023-07-17 00:10:04 +00:00
2e71e2d6db fix FML module 2023-07-17 00:07:28 +00:00
9de4990d69 connect via ipv6 2023-07-16 23:38:37 +00:00
ef88dad4a8 set modes before joining channels 2023-07-16 23:33:19 +00:00
1259db8212 remove urlinfo module 2023-07-16 23:30:03 +00:00
eec79067d8 update readme according to recent changes 2023-07-16 23:27:02 +00:00
5676bf4735 shorten modesetting 2023-07-16 23:13:10 +00:00
b3364d99b0 bump version 2023-07-16 23:04:20 +00:00
fec0ee4b10 add missing dep on requests 2023-07-16 23:03:52 +00:00
cc274a5d96 remove unnecessary imports 2023-07-16 23:03:40 +00:00
45809ff5ec rebrand CTCP VERSION response 2023-07-16 22:49:51 +00:00
3fa1bb4c92 remove unnecessary flask dependency 2023-07-16 22:48:24 +00:00
125528c567 remove garbage collection entirely 2023-07-16 22:47:04 +00:00
a44082180c change nick to 'huan' 2023-07-16 22:42:00 +00:00
c018534c0e remove urlinfo entirely 2023-07-16 22:39:26 +00:00
bb67f98d4b add requirements.txt 2023-07-16 22:38:13 +00:00
b06a606fd7 remove tweepy and twitter related stuff 2023-07-16 22:37:59 +00:00
5ed96b0ddf remove tweepy submodule 2023-07-16 22:31:40 +00:00
c875a4a17d disable logging to file 2023-07-16 22:28:37 +00:00
2cb3eec4aa change admin nick to 'jkhsjdhjs' 2023-07-16 22:27:19 +00:00
818110e485 set MODE -x on startup 2023-07-16 22:26:44 +00:00
428814b318 disable fefe and garbage collection threads 2023-07-16 22:26:17 +00:00
a291010ae1 fix a sleep statement in the gc thread 2023-07-16 22:25:35 +00:00
e6f8dbd1e8 disable urlinfo as it can make the bot run OOM 2023-07-16 22:24:49 +00:00
7009169145 fix joining channels on startup 2023-07-16 22:24:05 +00:00
bd5621f9bd fix username escape in whois regex 2023-07-16 22:21:42 +00:00
ad96b14445 replace deprecated ssl.wrap() 2023-07-16 22:19:06 +00:00
10 changed files with 94 additions and 274 deletions

View File

@ -2,7 +2,7 @@
#### stupidly easy IRC bot written in python(3)
I wrote this bot over the course of the last year. It grew and new function were added every now and then.<br>
The code is pretty ugly and the bot has some serious problems with memory leaks and random disconnects and lacks an automatic rejoin function.
The code is pretty ugly and lacks an automatic rejoin function.
But overall it is a pretty fun thing and is heavily used on the IRC server that I am currently using (n0xy.net).
@ -15,15 +15,11 @@ But overall it is a pretty fun thing and is heavily used on the IRC server that
|.eurusd \<value\>|convert euro to usd|
|.usdeur \<value\>|convert usd to euro|
|.jn \<question\>|choose between yes or no|
|.twitter \<account\>|get the last tweet of \<account\>|
|.trump|get trumps last tweet|
|.konfuzius|get a konfuzius quote (in german from wikiquote)|
|.say \<message\>|make the bot say something (only for admin user)|
|.fml|get a random fml from fmylife.com|
|.nslookup \<domain\> (\<type\>)|make a dns lookup for \<domain\> with optional \<type\>|
##### automatic triggers
- All http links are parsed and the html title is printed out if one is found
- The bot automatically parses all twitter links and prints out the full tweet and account name.
- The blog of the german blogger "fefe" (Felix von Leitner - blog.fefe.de) is crawled every five mins. and new Posts are automatically printed out with a link to the post

228
bert.py
View File

@ -1,45 +1,33 @@
#!/usr/bin/python3 -u
import gc
import socket
import ssl
import sys
import os
from time import sleep
import launch
import tweepy
import threading
import re
import html
import json
import requests
from datetime import datetime
from datetime import timedelta
from datetime import datetime, timedelta
import konfuzius
import urlinfo
import fml
import string
import random
from flask import Flask, request
from nslookup import nslookup
import rss
version = "v0.8"
version = "v0.9"
# IRC settings
host = ""
port =
nick = "bert"
nick = "huan"
password = ""
chan = ""
fefe_chans = [""]
# Twitter settings
consumer_key = ""
consumer_secret = ""
access_token = ""
access_secret = ""
# fml endpoint
fml_url = "http://www.fmylife.com/random"
@ -50,10 +38,8 @@ y = "\x038"
r = "\x034"
g = "\x033"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
irc = ssl.wrap_socket(s)
# global variable for the socket, see connect()
irc = None
# Currency API
def get_exchange():
@ -83,11 +69,11 @@ def safeexit():
def connect(host, port):
global irc
print("Connecting to "+host+":"+str(port))
try:
irc.connect((host, port))
except Exception as err:
print("Connection failed! "+err)
context = ssl.create_default_context()
with socket.create_connection((host, port), timeout=180) as sock:
irc = context.wrap_socket(sock, server_hostname=host)
def sendRaw(data):
@ -96,26 +82,17 @@ def sendRaw(data):
def register(nick, host):
print("Registering User...")
try:
sendRaw("USER "+nick+" "+host+" "+nick+" "+nick+"\n")
except Exception as err:
print("Failed! "+err)
sendRaw("USER "+nick+" "+host+" "+nick+" "+nick+"\n")
def name(nick):
print("Setting Nickname to "+nick)
try:
sendRaw("NICK "+nick+"\n")
except Exception as err:
print("Failed! "+err)
sendRaw("NICK "+nick+"\n")
def auth(nick, password):
print("Authenticating...")
try:
sendRaw("PRIVMSG NickServ :IDENTIFY "+nick+" "+password+"\n")
except Exception as err:
print(err)
sendRaw("PRIVMSG NickServ :IDENTIFY "+nick+" "+password+"\n")
def join(chan):
@ -123,15 +100,12 @@ def join(chan):
try:
sendRaw("JOIN "+chan+"\n")
except Exception as err:
print("Failed! "+err)
print("Failed! "+str(err))
def mode(nick):
print("Setting mode +B")
try:
sendRaw("MODE "+nick+" +B\n")
except Exception as err:
print("Failed! "+err)
print("Setting modes +B-x")
sendRaw("MODE "+nick+" +B-x\n")
def part(chan):
@ -147,7 +121,7 @@ def say(chan, msg):
sendRaw("PRIVMSG "+chan+" :"+msg+"\n")
print("OK")
except Exception as err:
print("Error: "+err)
print("Error: "+str(err))
def raw(msg):
@ -162,11 +136,18 @@ def me(chan, msg):
try:
sendRaw("PRIVMSG "+chan+" :\u0001ACTION "+msg+"\u0001\n")
except Exception as err:
print("Error: "+err)
print("Error: "+str(err))
def getData():
raw = irc.recv(4096)
try:
raw = irc.recv(4096)
except TimeoutError:
print("Connection timed out!")
sys.exit(0)
if not raw:
print("Connection lost!")
sys.exit(0)
try:
data = raw.decode("UTF-8")
except:
@ -184,8 +165,8 @@ def getMessage():
def getRocket():
rocket, mission, status, delta = launch.next_launch()
rstring = "%sNext rocket: %s%s %s| %s%s %s| %sStatus: %s %s| %s%s" % (b, y, rocket, b, y, mission, b, y, status, b, y, delta)
rocket, mission, delta = launch.next_launch()
rstring = "%sNext rocket: %s%s %s| %s%s %s| %s%s" % (b, y, rocket, b, y, mission, b, y, delta)
return rstring
@ -195,19 +176,13 @@ def yon():
return answer
def url_title(url):
title = urlinfo.get_title(url).encode("UTF-8").decode("UTF-8")
return title
def is_registered(username):
try:
sendRaw("WHOIS "+str(username)+"\n")
user_info = getMessage()
regex = r"330 "+str(nick)+" "+str(username)+" [a-zA-Z0-9]+ :is logged in as"
regex = rf"330 {str(re.escape(nick))} {str(re.escape(username))} \S+ :is logged in as"
matches = re.finditer(regex, user_info, re.MULTILINE)
for match in matches:
print(match.group(0))
acc_name = match.group(0).split()[3]
return acc_name
except Exception as err:
@ -271,32 +246,26 @@ def ping(msg):
sendRaw("PONG :"+msg+"\n")
def start(host, port, nick, password, chan):
try:
connect(host, port)
sleep(2)
register(nick, host)
sleep(2)
name(nick)
sleep(6)
auth(nick, password)
sleep(2)
for chan in fefe_chans:
join(chan)
sleep(2)
mode(nick)
sleep(2)
except Exception as err:
print("FAIL: "+err)
def start(host, port, nick, password, chans):
connect(host, port)
sleep(2)
register(nick, host)
sleep(2)
name(nick)
sleep(6)
auth(nick, password)
sleep(2)
mode(nick)
sleep(2)
for chan in chans:
join(chan)
sleep(2)
def command_loop():
while True:
try:
data = getData()
with open("bert.log", "a") as f:
f.write(data)
f.close()
ircmsg = data.strip("\n\r")
if ircmsg.find("PING :") != -1:
pingstring = ircmsg.strip("PING :")
@ -314,7 +283,7 @@ def command_loop():
print("direct message received")
if msg == "\x01VERSION\x01":
print("got ctcp version request")
notice(ircnick, "\x01VERSION BertBot "+version+"\x01")
notice(ircnick, "\x01VERSION HuanBot (fork of BertBot) "+version+"\x01")
elif msg.startswith("\x01PING"):
print("got ctcp ping request")
pingstring = msg.split()[1]
@ -326,7 +295,7 @@ def command_loop():
elif msg == ".help":
print("sending help")
say(channel, "available commands: .nextrocket, .slap, .mate, .jn, .rep, .twitter, .trump, .konfuzius, .fml, .eurusd, .usdeur, .nslookup")
say(channel, "available commands: .nextrocket, .slap, .mate, .hello, .jn, .rep, .konfuzius, .fml, .eurusd, .usdeur, .nslookup")
elif msg == ".nextrocket":
try:
@ -396,14 +365,14 @@ def command_loop():
except:
reason = "None"
print("with no reason")
if user == ircnick:
say(channel, "You can't give rep to yourself. Selfish little prick!")
continue
try:
acc_name = is_registered(user)
give_acc = is_registered(ircnick)
print("found account name for "+str(user)+": "+str(acc_name))
if acc_name != None and give_acc != None:
if acc_name == give_acc:
say(channel, "You can't give rep to yourself. Selfish little prick!")
continue
give_rep(acc_name, user, give_acc, reason, channel)
elif give_acc == None:
say(channel, "You are not registered!")
@ -428,31 +397,6 @@ def command_loop():
except Exception as err:
print("Error getting rep! - "+str(err))
elif msg.startswith(".twitter"):
try:
twittername = msg.split()[1]
try:
lasttweet = api.user_timeline(screen_name=twittername, count=1, tweet_mode="extended")[0]
twit = lasttweet.full_text
user = lasttweet.user.name
text = html.unescape(re.sub(r'\n', ' ', twit))
say(channel, b+user+": "+c+text)
except Exception as err:
print("Could not get last tweet! "+err)
except:
say(channel, "You have to give me a username")
elif msg.startswith(".trump"):
try:
twittername = "realdonaldtrump"
lasttweet = api.user_timeline(screen_name=twittername, count=1, tweet_mode="extended")[0]
twit = lasttweet.full_text
user = lasttweet.user.name
text = html.unescape(re.sub(r'\n', ' ', twit))
say(channel, b+user+": "+c+text)
except Exception as err:
print("Could not get last tweet! "+err)
elif msg.startswith(".konfuzius"):
try:
quote = konfuzius.random_quote()
@ -461,7 +405,7 @@ def command_loop():
say(channel, "Error :(")
elif msg == ".quit":
if ircnick == "elmo":
if ircnick == "jkhsjdhjs":
say(channel, "Flying to mars...")
print("got quit command. exiting...")
part(channel)
@ -470,7 +414,7 @@ def command_loop():
say(channel, "You are not my master!")
elif msg.startswith(".say"):
if ircnick == "elmo":
if ircnick == "jkhsjdhjs":
try:
sayit = msg.split(" ", 1)[1]
say(channel, str(sayit))
@ -481,7 +425,7 @@ def command_loop():
say(channel, "You are not my master!")
elif msg.startswith(".debug"):
if ircnick == "elmo":
if ircnick == "jkhsjdhjs":
try:
debugmsg = msg.split(" ", 1)[1]
raw(debugmsg)
@ -491,30 +435,6 @@ def command_loop():
else:
say(channel, "You are not my master!")
elif "twitter.com" in msg:
try:
p = re.compile('twitter\.com/[A-Za-z0-9]*/status/[0-9]*')
tweet_link = p.search(msg).group(0)
status_id = tweet_link.split("/")[3]
status = api.get_status(status_id, tweet_mode="extended")
user = status.user.name
text = status.full_text
print("found twitter link\n"+user+" - "+text)
say(channel, b+user+": "+c+text)
except Exception as err:
print("twitter link invalid "+str(err))
elif urlinfo.is_url(msg) != None:
try:
url = urlinfo.is_url(msg)
ignore = ["pr0gramm", "w0bm", "f0ck", "twitter", "youtube", "youtu.be"]
if any(ign in url for ign in ignore):
pass
else:
title = urlinfo.get_title(url)
print("Found URL and got title "+str(title))
say(channel, b+"Title: "+c+title)
except Exception as err:
print("Error getting URL title - "+str(err))
elif msg == ".fml":
try:
fml_string = fml.get_fml(fml_url)
@ -530,13 +450,12 @@ def command_loop():
except:
say(channel, r+"You must specify a domain!")
continue
try:
nstype = msg_parts[2]
except:
nstype = "A"
nstype = msg_parts[2:]
if not nstype:
nstype = ["AAAA", "A"]
print("got nslookup command with domain: "+str(nsdomain)+" and type: "+str(nstype))
nsresult = nslookup(nsdomain, nstype)
nslines = nsresult.split("\n")
nslines = set(nsresult.split("\n"))
for nsline in nslines:
say(channel, y+" "+str(nsline))
except Exception as err:
@ -563,17 +482,6 @@ def fefe_check():
sleep(300)
def gc_cycle():
while True:
try:
# fire the garbage collector
gc.set_debug(gc.DEBUG_STATS)
gc.collect()
time.sleep(120)
except Exception as err:
print("Error in garbage collector! - "+str(err))
class command_thread(threading.Thread):
def __init__(self, threadID):
threading.Thread.__init__(self)
@ -590,30 +498,18 @@ class fefe_thread(threading.Thread):
print("Starting fefe thread...")
fefe_check()
class gc_thread(threading.Thread):
def __init__(self, threadID):
threading.Thread.__init__(self)
self.threadID = threadID
def run(self):
print("Starting garbage collector thread...")
gc_cycle()
def main():
start(host, port, nick, password, chan)
global api
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_secret)
api = tweepy.API(auth)
try:
start(host, port, nick, password, set(join_chans + fefe_chans))
except Exception as err:
print("FAIL: "+str(err))
sys.exit(1)
thread_command = command_thread(1)
thread_fefe = fefe_thread(2)
thread_gc = gc_thread(3)
#thread_fefe = fefe_thread(2)
thread_command.start()
sleep(2)
thread_fefe.start()
sleep(2)
thread_gc.start()
#sleep(2)
#thread_fefe.start()
if __name__ == "__main__":
gc.enable()
main()

6
fml.py
View File

@ -3,16 +3,12 @@
import requests
from bs4 import BeautifulSoup
url = "http://www.fmylife.com/random"
def get_fml(url):
r = requests.get(url)
data = r.text
r.close
soup = BeautifulSoup(data, 'html.parser')
fml = soup.find("article", class_="art-panel").find_all("a")[2].string.strip().replace("\\", "").replace(" FML", "")
fml = soup.select_one("article > a.block").string.strip().replace("\\", "").rstrip(" FML")
del soup
return fml

View File

@ -2,7 +2,6 @@
import re
import random
import json
import requests
from bs4 import BeautifulSoup

View File

@ -10,61 +10,34 @@ from string import Template
b = "\x0312"
y = "\x038"
API_URL = "https://launchlibrary.net/1.2/launch/next/1"
API_URL = "https://fdo.rocketlaunch.live/json/launches/next/1"
class launch(object):
def api_request(self):
request = urllib.request.Request(API_URL)
response = urllib.request.urlopen(request).read()
json_data = json.loads(response.decode('UTF-8'))
return json_data['launches'][0]
return json_data['result'][0]
def rocket_name(self, json_data):
try:
name = json_data['rocket']['name']
name = json_data['vehicle']['name']
except:
name = "UNKNOWN"
return name
def mission_name(self, json_data):
try:
name = json_data['missions'][0]['name']
name = json_data['name']
except:
name = "UNKNOWN"
return name
def net_raw_utc(self, json_data):
time_utc_string = json_data['net']
try:
time_utc = datetime.datetime.strptime(time_utc_string, '%B %d, %Y %H:%M:%S %Z')
except TypeError:
time_utc = datetime.datetime(*(time.strptime(time_utc_string, '%B %d, %Y %H:%M:%S %Z')[0:6]))
time_utc_string = json_data['t0']
time_utc = datetime.datetime.fromisoformat(time_utc_string)
return time_utc
def net(self, json_data):
time_utc_string = json_data['net']
try:
time_utc = datetime.datetime.strptime(time_utc_string, '%B %d, %Y %H:%M:%S %Z')
except TypeError:
time_utc = datetime.datetime(*(time.strptime(time_utc_string, '%B %d, %Y %H:%M:%S %Z')[0:6]))
time_local = pytz.utc.localize(time_utc).astimezone(pytz.timezone('Europe/Berlin'))
time = datetime.datetime.strftime(time_local, 'NET %d.%m.%Y')
return time
def get_status(self, json_data):
tbd_stat = json_data['tbdtime']
hold_stat = json_data['inhold']
go_stat = json_data['status']
if tbd_stat == 1:
status = "TBD"
elif hold_stat == 1:
status = "HOLD"
elif go_stat == 1:
status = "GO"
else:
status = "UNKNOWN"
return status
class DeltaTemplate(Template):
delimiter = "%"
@ -86,18 +59,10 @@ def next_launch():
json_data = l.api_request()
rocket = l.rocket_name(json_data)
mission = l.mission_name(json_data)
status = l.get_status(json_data)
if status != "GO":
deltastring = l.net(json_data)
else:
launch_time = l.net_raw_utc(json_data)
now = datetime.datetime.utcnow().replace(microsecond=0)
if launch_time > now:
timedelta = launch_time - now
t_string = "-"
else:
timedelta = now - launch_time
t_string = "+"
deltastring = strfdelta(timedelta, "T"+t_string+"%D:%H:%M:%S")
launch_time = l.net_raw_utc(json_data)
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
timedelta = abs(launch_time - now)
t_string = "-" if launch_time > now else "+"
deltastring = strfdelta(timedelta, "T"+t_string+"%D:%H:%M:%S")
del l
return rocket, mission, status, deltastring
return rocket, mission, deltastring

View File

@ -2,30 +2,30 @@
import dns.resolver
def nslookup(domain, typ="A"):
def nslookup(domain, types=["AAAA", "A"]):
result = ""
rcount = 0
try:
answer = dns.resolver.query(domain, typ)
for rdata in answer:
if rcount > 0:
result += "\n"
if hasattr(rdata, 'exchange'):
result += str(rdata.preference)+" "+str(rdata.exchange)
rcount += 1
else:
result += str(rdata)
rcount += 1
for typ in types:
answer = dns.resolver.resolve(domain, typ)
for rdata in answer:
if rcount > 0:
result += "\n"
if hasattr(rdata, 'exchange'):
result += str(rdata.preference)+" "+str(rdata.exchange)
rcount += 1
else:
result += str(rdata)
rcount += 1
except dns.resolver.NXDOMAIN as err:
result = str(err)
except Exception as err:
result = "Error"
print("nslookup error! - "+str(err))
del answer
return result
def main():
print(nslookup("elmo.space", "AAAA"))
print(nslookup("elmo.space"))
if __name__ == "__main__":
main()

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
pytz
beautifulsoup4~=4.11
dnspython~=2.2
feedparser~=6.0
requests~=2.0

5
rss.py
View File

@ -3,16 +3,11 @@
import feedparser
import time
from subprocess import check_output
import sys
def check():
feed_name = 'fefe'
url = 'https://blog.fefe.de/rss.xml'
#feed_name = sys.argv[1]
#url = sys.argv[2]
db = 'rss_feeds.db'
limit = 12 * 3600 * 1000

1
tweepy

Submodule tweepy deleted from 2efe385fc6

View File

@ -1,31 +0,0 @@
#!/usr/bin/python3
import re
import requests
from bs4 import BeautifulSoup
def is_url(msg):
regex = r"(https?:\/\/(?:www\.)?\S+)"
match = re.search(regex, msg)
if match:
url = match.group(1)
return url
else:
return None
def get_data(url):
r = requests.get(url)
data = r.text
r.close()
return data
def get_title(url):
data = get_data(url)
soup = BeautifulSoup(data, 'html.parser')
title = soup.title.string
del soup
return title