Compare commits
39
Commits
9031b6ec40
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8566dfdde | ||
|
|
b22d16e364 | ||
|
|
710c570c8d | ||
|
|
f43905b760 | ||
|
|
e2702cbfe8 | ||
|
|
f38fcd8939 | ||
|
|
bd24e53a3c | ||
|
|
05b11aafc8 | ||
|
|
8cac1a7da4 | ||
|
|
d4922f8772 | ||
|
|
a22905f689 | ||
|
|
c85a7520ce | ||
|
|
5a060342a4 | ||
|
|
dfca0593f5 | ||
|
|
ee3e081c3c | ||
|
|
712faa876c | ||
|
|
c53d0ce4ed | ||
|
|
4d87558e0f | ||
|
|
890d2c47a4 | ||
|
|
78f82d64e5 | ||
|
|
49e3de14a5 | ||
|
|
55665db5bb | ||
|
|
31b745d048 | ||
|
|
55776e31cd | ||
|
|
35c64cea14 | ||
|
|
71a6aa71ca | ||
|
|
ed9f9acb11 | ||
|
|
82053e8131 | ||
|
|
954593258c | ||
|
|
aed1a85b20 | ||
|
|
c5c265d3a0 | ||
|
|
659dfd551c | ||
|
|
de05b885c2 | ||
|
|
1df3e08bee | ||
|
|
dddd444e4d | ||
|
|
a472c4dc06 | ||
|
|
14fbc7f827 | ||
|
|
305ad6cffb | ||
|
|
7540e57c08 |
@@ -141,3 +141,5 @@ cython_debug/
|
|||||||
# Pycharm
|
# Pycharm
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
prod.toml
|
||||||
|
log/
|
||||||
@@ -5,8 +5,9 @@ name = "pypi"
|
|||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
ts3 = "*"
|
ts3 = "*"
|
||||||
python-dotenv = "*"
|
|
||||||
pycoingecko = "*"
|
pycoingecko = "*"
|
||||||
|
toml = "*"
|
||||||
|
requests = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
|||||||
Generated
+25
-25
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "dea9fe66fc91a3adf33caec6ce10635546f9e81ec613bc3bbba14c257d57029b"
|
"sha256": "31ecf8cb0f57cd41d5c094347ce8878eddd6f611790259ee671f31d6620a1bee"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -18,26 +18,26 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
|
||||||
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
|
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||||
],
|
],
|
||||||
"version": "==2021.5.30"
|
"version": "==2021.10.8"
|
||||||
},
|
},
|
||||||
"charset-normalizer": {
|
"charset-normalizer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
|
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||||
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
|
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3'",
|
"markers": "python_version >= '3'",
|
||||||
"version": "==2.0.6"
|
"version": "==2.0.12"
|
||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||||
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
|
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3'",
|
"markers": "python_version >= '3'",
|
||||||
"version": "==3.2"
|
"version": "==3.3"
|
||||||
},
|
},
|
||||||
"pycoingecko": {
|
"pycoingecko": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -47,21 +47,21 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.0"
|
"version": "==2.2.0"
|
||||||
},
|
},
|
||||||
"python-dotenv": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
|
|
||||||
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.19.0"
|
|
||||||
},
|
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
||||||
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
"index": "pypi",
|
||||||
"version": "==2.26.0"
|
"version": "==2.27.1"
|
||||||
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||||
|
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.10.2"
|
||||||
},
|
},
|
||||||
"ts3": {
|
"ts3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@@ -73,11 +73,11 @@
|
|||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
|
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
|
||||||
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
"sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
"version": "==1.26.7"
|
"version": "==1.26.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {}
|
"develop": {}
|
||||||
|
|||||||
@@ -3,73 +3,93 @@ TBD
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = "Lukas Mahler"
|
__author__ = "Lukas Mahler"
|
||||||
__version__ = "0.2.0"
|
__version__ = "1.0.0"
|
||||||
__date__ = "23.09.2021"
|
__date__ = "14.04.2022"
|
||||||
__email__ = "m@hler.eu"
|
__email__ = "m@hler.eu"
|
||||||
__status__ = "Development"
|
__status__ = "Development"
|
||||||
|
|
||||||
# Default
|
# Default
|
||||||
import os
|
import sys
|
||||||
import json
|
import time
|
||||||
from time import sleep
|
import os.path
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
import ts3
|
import ts3
|
||||||
import dotenv # python-dotenv
|
|
||||||
|
|
||||||
# Self
|
# Self
|
||||||
import gecko
|
from src import util, gecko, commands
|
||||||
import util
|
|
||||||
|
|
||||||
|
|
||||||
class MyTeamspeakBot:
|
class TSbot:
|
||||||
|
|
||||||
def __init__(self, conf):
|
def __init__(self, conf, log):
|
||||||
|
"""
|
||||||
|
Create a new instance, connect to the server via telnet and
|
||||||
|
start the event loop function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
# Initialize from config
|
||||||
|
self.host = conf["Connection"]["host"]
|
||||||
|
self.port = conf["Connection"]["port"]
|
||||||
|
self.sid = conf["Connection"]["sid"]
|
||||||
|
self.user = conf["Authentication"]["user"]
|
||||||
|
self.pwd = conf["Authentication"]["pwd"]
|
||||||
|
self.sgid_admin = conf["Groups"]["admins"]
|
||||||
|
self.sgid_mute = conf["Groups"]["mute"]
|
||||||
|
self.crypto = conf["Misc"]["crypto"]
|
||||||
|
self.nickname = conf["Misc"]["nickname"]
|
||||||
|
self.whitelisted = conf["Misc"]["whitelisted"]
|
||||||
|
|
||||||
self.host = conf["host"]
|
# Initialize self
|
||||||
self.port = conf["port"]
|
self.log = log
|
||||||
self.user = conf["user"]
|
|
||||||
self.pwd = conf["pwd"]
|
|
||||||
self.sid = conf["sid"]
|
|
||||||
self.nickname = conf["name"]
|
|
||||||
|
|
||||||
self.gecko = gecko.GeckoAPI()
|
|
||||||
self.myid = None
|
self.myid = None
|
||||||
self.running = True
|
self.running = True
|
||||||
self.intro = "<Keep this chat open to use commands>"
|
self.version = __version__
|
||||||
|
self.started = time.time()
|
||||||
|
self.gecko = gecko.GeckoAPI()
|
||||||
|
self.crypto_update = self.started - 1800
|
||||||
|
|
||||||
print(f"* Trying to connect to: {self.host}:{self.port}")
|
self.pipeOut(f"Trying to connect to: {self.host}:{self.port}")
|
||||||
|
|
||||||
|
# Starting the Connection
|
||||||
with ts3.query.TS3Connection(self.host, self.port) as self.bot:
|
with ts3.query.TS3Connection(self.host, self.port) as self.bot:
|
||||||
self.bot.login(client_login_name=self.user, client_login_password=self.pwd)
|
try:
|
||||||
self.bot.use(sid=self.sid)
|
self.bot.login(client_login_name=self.user, client_login_password=self.pwd)
|
||||||
|
self.bot.use(sid=self.sid)
|
||||||
|
except ts3.query.TS3QueryError:
|
||||||
|
self.pipeOut("Invalid login credentials, please check your '.toml' file.", lvl="CRITICAL")
|
||||||
try:
|
try:
|
||||||
self.bot.clientupdate(client_nickname=self.nickname)
|
self.bot.clientupdate(client_nickname=self.nickname)
|
||||||
except ts3.query.TS3QueryError:
|
except ts3.query.TS3QueryError as e:
|
||||||
|
self.pipeOut(e, lvl="WARNING")
|
||||||
pass
|
pass
|
||||||
print(f"* Successfully connected as: {self.nickname}")
|
|
||||||
|
|
||||||
# Start the Bot
|
self.pipeOut(f"Successfully connected as: {self.nickname}")
|
||||||
|
|
||||||
|
# Start the Bot loop
|
||||||
self.loop()
|
self.loop()
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
"""
|
"""
|
||||||
|
Subscribe to event types, ping bot admins and start the event loop.
|
||||||
|
Every time an event is triggered, the bot will identify its type and
|
||||||
|
execute the appropriate function.
|
||||||
|
|
||||||
|
The loop can be stopped setting self.running to False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Find my client id
|
# Find the instances client id
|
||||||
me = self.bot.clientfind(pattern=self.nickname)
|
me = self.bot.clientfind(pattern=self.nickname)
|
||||||
if len(me) == 1:
|
if len(me) == 1:
|
||||||
self.myid = me[0]["clid"]
|
self.myid = me[0]["clid"]
|
||||||
else:
|
else:
|
||||||
raise ValueError("x Can't find my own client id.")
|
self.pipeOut("Can't find my own client id. terminating...", lvl="critical")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
''' if you want to move the Bot to a certain channel (instead of the defualt channel, you can do: '''
|
''' if you want to move the Bot to a certain channel (instead of the default channel, you can do: '''
|
||||||
# self.bot.clientmove(clid=self.myid,cid=129)
|
# self.bot.clientmove(clid=self.myid,cid=129)
|
||||||
|
|
||||||
|
# ----------- SUBSCRIBE TO EVENTS -------------
|
||||||
# Subscribe to a server movement events
|
# Subscribe to a server movement events
|
||||||
self.bot.servernotifyregister(event="server")
|
self.bot.servernotifyregister(event="server")
|
||||||
|
|
||||||
@@ -85,23 +105,31 @@ class MyTeamspeakBot:
|
|||||||
# Subscribe to channel movement events
|
# Subscribe to channel movement events
|
||||||
# self.bot.servernotifyregister(event="channel", id_=0)
|
# self.bot.servernotifyregister(event="channel", id_=0)
|
||||||
|
|
||||||
# Start the timer for auto-updating crypto channels
|
if not self.whitelisted:
|
||||||
channelname = f"{'[cspacerBTC]Bitcoin:':<33}" + f"{self.gecko.getSymbol('BTC', decimal=0):>5}€"
|
time.sleep(5)
|
||||||
btc_timer = util.maketimer(60, self.editChannelname(200, channelname))
|
|
||||||
btc_timer.start()
|
|
||||||
channelname = f"{'[cspacerETH]Ethereum:':<30}" + f"{self.gecko.getSymbol('ETH', decimal=0):>5}€"
|
|
||||||
eth_timer = util.maketimer(60, self.editChannelname(201, channelname))
|
|
||||||
eth_timer.start()
|
|
||||||
|
|
||||||
# Notify connected admins
|
# Notify connected admins
|
||||||
self.notifyAdmin()
|
commands.notifyAdmin(self)
|
||||||
|
|
||||||
# ----------- LOOP HERE -------------
|
# ----------- LOOP HERE -------------
|
||||||
while self.running:
|
while self.running:
|
||||||
# self.bot.send_keepalive()
|
# self.bot.send_keepalive()
|
||||||
print("* Waiting for a new Event...")
|
self.pipeOut(f"Waiting for a new Event...", lvl="debug")
|
||||||
self.bot.version()
|
self.bot.version()
|
||||||
|
|
||||||
|
# Auto-update crypto price channels every 30 minutes
|
||||||
|
if self.crypto:
|
||||||
|
if self.crypto_update + 1800 < time.time():
|
||||||
|
self.crypto_update = time.time()
|
||||||
|
|
||||||
|
commands.ticker(self, "")
|
||||||
|
|
||||||
|
# Hijacked for WoW
|
||||||
|
commands.rwf(self)
|
||||||
|
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# This method blocks, but we must sent the keepalive message at
|
# This method blocks, but we must sent the keepalive message at
|
||||||
# least once in 5 minutes to avoid the sever side idle client
|
# least once in 5 minutes to avoid the sever side idle client
|
||||||
@@ -110,20 +138,33 @@ class MyTeamspeakBot:
|
|||||||
|
|
||||||
except ts3.query.TS3TimeoutError:
|
except ts3.query.TS3TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# try else!
|
||||||
else:
|
else:
|
||||||
event_type = event.event
|
event_type = event.event
|
||||||
print(f"* Got Event | length={len(event[0])} | {event_type}")
|
self.pipeOut(f"Got Event | length={len(event[0])} | {event_type}")
|
||||||
|
|
||||||
# Client Connect
|
# Client connected
|
||||||
if event_type == "notifycliententerview":
|
if event_type == "notifycliententerview":
|
||||||
print(f"* Client [{event[0]['client_nickname']}] connected.")
|
|
||||||
|
if 'client_nickname' in event[0]:
|
||||||
|
displayname = event[0]['client_nickname']
|
||||||
|
elif 'clid' in event[0]:
|
||||||
|
displayname = event[0]['clid']
|
||||||
|
else:
|
||||||
|
self.pipeOut(event[0])
|
||||||
|
# displayname = "Unresolved"
|
||||||
|
continue # can't resolve no clid
|
||||||
|
|
||||||
|
self.pipeOut(f"Client [{displayname}] connected.")
|
||||||
|
|
||||||
# Check if the connector is a ServerQuery or not
|
# Check if the connector is a ServerQuery or not
|
||||||
if not self.isqueryclient(event[0]["client_unique_identifier"]):
|
if not self.isqueryclient(event[0]["clid"]):
|
||||||
print(f"* {event[0]}")
|
self.pipeOut(f"* {event[0]}", lvl="debug")
|
||||||
# Check if the connector is an Admin
|
# Check if the connector is an Admin
|
||||||
if self.isadmin(event[0]["client_database_id"]):
|
if self.isadmin(event[0]["client_database_id"]):
|
||||||
self.bot.sendtextmessage(targetmode=1, target=event[0]["clid"], msg=self.intro)
|
msg = "<Keep this chat open to use commands>"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=event[0]["clid"], msg=msg)
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -131,96 +172,127 @@ class MyTeamspeakBot:
|
|||||||
|
|
||||||
# Client disconnected
|
# Client disconnected
|
||||||
elif event_type == "notifyclientleftview":
|
elif event_type == "notifyclientleftview":
|
||||||
print(f"* Clientid [{event[0]['clid']}] disconnected.")
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Text Message
|
if 'client_nickname' in event[0]:
|
||||||
|
displayname = event[0]['client_nickname']
|
||||||
|
else:
|
||||||
|
displayname = event[0]['clid']
|
||||||
|
|
||||||
|
self.pipeOut(f"Clientid [{displayname}] disconnected.")
|
||||||
|
|
||||||
|
# New text message
|
||||||
elif event_type == "notifytextmessage":
|
elif event_type == "notifytextmessage":
|
||||||
msg = event[0]["msg"]
|
msg = event[0]["msg"].replace("\n", " ")
|
||||||
invkr = event[0]["invokername"]
|
invkr_name = event[0]["invokername"]
|
||||||
invkr_id = event[0]["invokerid"]
|
invkr_id = event[0]["invokerid"]
|
||||||
print(f'* From: "{invkr}" | Message: "{msg}"')
|
invkr_uid = event[0]["invokeruid"]
|
||||||
self.lookupcommand(msg, invkr_id)
|
invkr_dbid = self.bot.clientgetdbidfromuid(cluid=invkr_uid)[0]["cldbid"]
|
||||||
|
permission_level = 1 if self.isadmin(invkr_dbid) else 0
|
||||||
|
|
||||||
|
invoker = {'name': invkr_name,
|
||||||
|
'id': invkr_id,
|
||||||
|
'uid': invkr_uid,
|
||||||
|
'dbid': invkr_dbid}
|
||||||
|
|
||||||
|
self.pipeOut(f'Message | From: "{invkr_name}" | '
|
||||||
|
f'Permissionlevel: {permission_level} | Content: "{msg}"')
|
||||||
|
|
||||||
|
if msg.startswith("."):
|
||||||
|
self.lookupcommand(msg, invoker, permission_level)
|
||||||
|
|
||||||
|
# Unknown Event
|
||||||
else:
|
else:
|
||||||
print(f"* Unknown Event: {event.__dict__}")
|
self.pipeOut(f"Unknown Event: {event.__dict__}", lvl="warning")
|
||||||
|
|
||||||
def stop(self, invkr_id):
|
# Class Utility --------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def pipeOut(self, msg, lvl="INFO", reprint=True):
|
||||||
|
"""
|
||||||
|
All output pipes through this function.
|
||||||
|
It's possible to print the output to console [set reprint to True]
|
||||||
|
or run in silent log mode. [set reprint to False]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
lvl = lvl.upper()
|
||||||
|
lvln = int(getattr(util.logging, lvl))
|
||||||
|
self.log.log(lvln, msg)
|
||||||
|
|
||||||
|
if reprint and lvln >= self.log.getEffectiveLevel():
|
||||||
|
print(f"[{time.strftime('%H:%M:%S')}]{f'[{lvl}]':10s} {msg}")
|
||||||
|
|
||||||
|
if not self.whitelisted:
|
||||||
|
if msg == "error id 524: client is flooding":
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
if lvl == "CRITICAL":
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
def stop(self, invoker):
|
||||||
"""
|
"""
|
||||||
msg = "I'm out, bye bye!"
|
This stops the bot instance by invalidating the event loop.
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=msg)
|
"""
|
||||||
|
|
||||||
|
msg = "Shutdown, bye bye!"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=msg)
|
||||||
|
self.pipeOut(msg)
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
def notifyAdmin(self):
|
|
||||||
clients = self.bot.clientlist()
|
|
||||||
clients = [client for client in clients if client["client_type"] != "1"]
|
|
||||||
for client in clients:
|
|
||||||
cldbid = client["client_database_id"]
|
|
||||||
clid = client["clid"]
|
|
||||||
if self.isadmin(cldbid):
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=clid, msg=self.intro)
|
|
||||||
sleep(1) # This can be removed if the Query Client is Whitelisted
|
|
||||||
|
|
||||||
def kickall(self, msg):
|
def kickall(self, msg):
|
||||||
"""
|
"""
|
||||||
|
TODO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
clients = self.bot.clientlist()
|
clients = self.bot.clientlist()
|
||||||
clients = [client["clid"] for client in clients if client["client_type"] != "1"]
|
clients = [client["clid"] for client in clients if client["client_type"] != "1"]
|
||||||
for clid in clients:
|
for clid in clients:
|
||||||
try:
|
self.bot.clientpoke(msg=msg, clid=clid)
|
||||||
self.bot.clientpoke(msg=msg, clid=clid)
|
# TODO
|
||||||
# TODO
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def poke(self, msg=None, num=10, delay=0.2, usr='all'):
|
def poke(self, msg=None, n=1, delay=0.2, usr='all'):
|
||||||
"""
|
"""
|
||||||
|
ping a single or multiple users for n times including a msg.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if msg is None:
|
if msg is None:
|
||||||
msg = "-~-~-~-~-~-~-~-~-~-~-~"
|
msg = "-~-~-~-~-~ Placeholder -~-~-~-~-~-~"
|
||||||
|
|
||||||
# Get the client ids
|
# Get the client ids
|
||||||
if usr == 'all':
|
if usr == 'all':
|
||||||
clients = self.bot.clientlist()
|
clients = self.bot.clientlist()
|
||||||
clients = [client["clid"] for client in clients if client["client_type"] != "1"]
|
clients = [client["clid"] for client in clients if client["client_type"] != "1"]
|
||||||
else:
|
else:
|
||||||
clients = self.bot.clientfind(pattern=usr)
|
try:
|
||||||
clients = [client["clid"] for client in clients]
|
clients = self.bot.clientfind(pattern=usr)
|
||||||
|
clients = [client["clid"] for client in clients]
|
||||||
|
except ts3.query.TS3QueryError as e:
|
||||||
|
self.pipeOut(f"Couldnt execute poke, no client found using pattern {usr},"
|
||||||
|
f"returned error:\n{e}", lvl="ERROR")
|
||||||
|
return
|
||||||
|
|
||||||
# Break, if there's no client.
|
# Ping them
|
||||||
if not clients:
|
if len(clients) > 0:
|
||||||
return None
|
|
||||||
else:
|
|
||||||
for client in clients:
|
|
||||||
data = self.printable_clientinfo(client)
|
|
||||||
print(f"* {data}")
|
|
||||||
|
|
||||||
# Nopokeatm
|
|
||||||
# return
|
|
||||||
|
|
||||||
# Poke them
|
|
||||||
i = 0
|
|
||||||
while num == -1 or i < num:
|
|
||||||
for clid in clients:
|
for clid in clients:
|
||||||
print(f"* {clid}")
|
data = self.printable_clientinfo(clid)
|
||||||
try:
|
self.pipeOut(f"{data}", lvl="DEBUG")
|
||||||
self.bot.clientpoke(msg=msg, clid=clid)
|
|
||||||
except:
|
i = 0
|
||||||
pass
|
while n == -1 or i < n:
|
||||||
sleep(delay)
|
for clid in clients:
|
||||||
i += 1
|
try:
|
||||||
return None
|
self.bot.clientpoke(msg=msg, clid=clid)
|
||||||
|
except Exception as e:
|
||||||
|
self.pipeOut(e, lvl="WARNING")
|
||||||
|
pass
|
||||||
|
time.sleep(delay)
|
||||||
|
i += 1
|
||||||
|
|
||||||
def createChannel(self, name, permanent=False):
|
def createChannel(self, name, permanent=False):
|
||||||
"""
|
"""
|
||||||
|
Create a teamspeak channel, the channel is non persistent by default.
|
||||||
|
Set permanent to True if you want a persistant channel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if permanent:
|
if permanent:
|
||||||
new = self.bot.channelcreate(channel_name=name, channel_flag_permanent="1")
|
new = self.bot.channelcreate(channel_name=name, channel_flag_permanent="1")
|
||||||
else:
|
else:
|
||||||
@@ -230,197 +302,216 @@ class MyTeamspeakBot:
|
|||||||
|
|
||||||
def editChannelname(self, cid, name):
|
def editChannelname(self, cid, name):
|
||||||
"""
|
"""
|
||||||
|
Using a channel-id you can set the name of the given channel.
|
||||||
"""
|
This will fail if the channel name is already in use.
|
||||||
self.bot.channeledit(cid=cid, channel_name=name)
|
This will fail if the given channel name is > 40 bytes.
|
||||||
sleep(5)
|
|
||||||
|
|
||||||
def list(self, what, invkr_id):
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Check name for bytes
|
||||||
|
n = len(name)
|
||||||
|
if n > 40:
|
||||||
|
self.pipeOut(f"Can't change channelname to {name} as [{n}] exceeds the max bytes of 40.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.bot.channeledit(cid=cid, channel_name=name)
|
||||||
|
except Exception as e:
|
||||||
|
self.pipeOut(e, lvl="error")
|
||||||
|
|
||||||
|
def isqueryclient(self, clid):
|
||||||
"""
|
"""
|
||||||
if what == "channel":
|
Check if the given client-uid is a query client.
|
||||||
mydict = {}
|
|
||||||
channels = self.bot.channellist()
|
|
||||||
for channel in channels:
|
|
||||||
order = channel["channel_order"]
|
|
||||||
mydict[order] = [channel["channel_name"], channel["cid"]]
|
|
||||||
|
|
||||||
mydict = dict(sorted(mydict.items()))
|
|
||||||
msg = json.dumps(mydict)
|
|
||||||
|
|
||||||
elif what == "clients":
|
|
||||||
msg = self.bot.clientlist()
|
|
||||||
# TODO
|
|
||||||
else:
|
|
||||||
msg = None
|
|
||||||
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=msg)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def isqueryclient(cluid):
|
|
||||||
"""
|
"""
|
||||||
Check if the given client-uid is a query client
|
try:
|
||||||
"""
|
client = self.bot.clientinfo(clid=clid)
|
||||||
|
except ts3.query.TS3QueryError as e:
|
||||||
# client = self.bot.clientlist(uid=uid)
|
self.pipeOut(f"ISQUERY: given clid {clid} returned: {e}", lvl="WARNING")
|
||||||
# print(client[0])
|
|
||||||
# if client[0]["client_type"] == "1":
|
|
||||||
if cluid == "ServerQuery":
|
|
||||||
print("* ISQUERY: True")
|
|
||||||
return True
|
return True
|
||||||
else:
|
try:
|
||||||
print("* ISQUERY: False")
|
if client[0]["client_type"] == "1":
|
||||||
return False
|
self.pipeOut(f"[{clid}] ISQUERY: True")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.pipeOut(f"[{clid}] ISQUERY: False")
|
||||||
|
return False
|
||||||
|
except KeyError as e:
|
||||||
|
self.pipeOut(f"Error [{e}] on client [{client.__dict__}] ")
|
||||||
|
return True # Da wahrscheinlich ein Query Client
|
||||||
|
|
||||||
def isadmin(self, cldbid):
|
def isadmin(self, cldbid):
|
||||||
"""
|
"""
|
||||||
Check if the given client-databaseid is an admin
|
Check if the given client-databaseid is an admin.
|
||||||
|
This is done by resolving the clients groups and check
|
||||||
|
if any of the groups sgid match a sgid from the sgids from the '.toml' config.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
groups = self.bot.servergroupsbyclientid(cldbid=cldbid)
|
try:
|
||||||
# [print(group["sgid"]) for group in groups]
|
groups = self.bot.servergroupsbyclientid(cldbid=cldbid)
|
||||||
for group in groups:
|
except ts3.query.TS3QueryError as e:
|
||||||
# 6 Server Admin/ 13 Operator / 15 Root
|
self.pipeOut(e, lvl="ERROR")
|
||||||
# if (group["sgid"] == "6") or (group["sgid"] == "13") or (group["sgid"] == "15"):
|
return False
|
||||||
if group["sgid"] == "15":
|
|
||||||
print("* ISADMIN: True")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
print("* ISADMIN: False")
|
self.pipeOut(str(groups.__dict__), lvl="DEBUG") # DEBUG
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
if any(sgid == group['sgid'] for sgid in self.sgid_admin):
|
||||||
|
self.pipeOut(f"[{cldbid}] ISADMIN: True")
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.pipeOut(f"[{cldbid}] ISADMIN: False")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def lookupcommand(self, msg, invkr_id):
|
def printable_clientinfo(self, clid):
|
||||||
|
"""
|
||||||
|
Generate printable clientinfo from clid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
info = self.bot.clientinfo(clid=clid)
|
||||||
|
usrd = info.__dict__
|
||||||
if msg.startswith("."):
|
|
||||||
commandstring = msg.split(" ")
|
|
||||||
command = commandstring[0]
|
|
||||||
parameter = commandstring[1:]
|
|
||||||
print(f"* command: {command} / parameter: {parameter} / invkr_id: {invkr_id}")
|
|
||||||
|
|
||||||
if command == ".annoy":
|
|
||||||
try:
|
|
||||||
target = parameter[0]
|
|
||||||
msg = parameter[1]
|
|
||||||
self.poke(msg=msg, usr=target)
|
|
||||||
except IndexError:
|
|
||||||
err = "Please use the command like this: .annoy TARGET MESSAGE"
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=err)
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif command == ".kickall":
|
|
||||||
self.kickall("test") # TODO
|
|
||||||
|
|
||||||
elif command == ".test":
|
|
||||||
cid = self.createChannel("Test")
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=cid)
|
|
||||||
|
|
||||||
elif command == ".btc":
|
|
||||||
channelname = f"{'[cspacerBTC]Bitcoin:':<33}" + f"{self.gecko.getSymbol('BTC', decimal=0):>5}€"
|
|
||||||
try:
|
|
||||||
self.editChannelname(200, channelname)
|
|
||||||
except ts3.query.TS3QueryError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif command == ".eth":
|
|
||||||
channelname = f"{'[cspacerETH]Ethereum:':<30}" + f"{self.gecko.getSymbol('ETH', decimal=0):>5}€"
|
|
||||||
try:
|
|
||||||
self.editChannelname(201, channelname)
|
|
||||||
except ts3.query.TS3QueryError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif command == ".list":
|
|
||||||
try:
|
|
||||||
self.list(parameter[0], invkr_id)
|
|
||||||
except IndexError:
|
|
||||||
err = "Please use the command like this: .list channel/clients"
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=err)
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif command == ".follow":
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
elif command == ".rename":
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
elif command == ".stop" or command == ".quit" or command == ".q":
|
|
||||||
self.stop(invkr_id)
|
|
||||||
|
|
||||||
elif command == ".pingall":
|
|
||||||
try:
|
|
||||||
msg = parameter[0]
|
|
||||||
self.poke(msg=msg)
|
|
||||||
except IndexError:
|
|
||||||
err = "Please use the command like this: .pingall MESSAGE"
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=err)
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
err = f"Unknown Command: [{command}]"
|
|
||||||
self.bot.sendtextmessage(targetmode=1, target=invkr_id, msg=err)
|
|
||||||
|
|
||||||
def printable_clientinfo(self, client):
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
usrd = {}
|
|
||||||
info = self.bot.clientinfo(clid=client)
|
|
||||||
temp = info._data[0].split()
|
|
||||||
for t1 in temp:
|
|
||||||
t2 = t1.decode("utf-8")
|
|
||||||
t3 = t2.split("=")
|
|
||||||
try:
|
|
||||||
t3[1]
|
|
||||||
except Exception:
|
|
||||||
t3.append("None")
|
|
||||||
pass
|
|
||||||
|
|
||||||
usrd[t3[0]] = t3[1]
|
|
||||||
|
|
||||||
return usrd
|
return usrd
|
||||||
|
|
||||||
def checkgrp(self):
|
def clid2cldbid(self, clid):
|
||||||
|
"""
|
||||||
|
Convert a given clid to a cldbid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
cluid = self.bot.clientgetuidfromclid(clid=clid)[0]["cluid"]
|
||||||
|
cldbid = self.bot.clientgetdbidfromuid(cluid=cluid)[0]["cldbid"]
|
||||||
|
|
||||||
|
return cldbid
|
||||||
|
|
||||||
|
def identifytarget(self, target):
|
||||||
|
"""
|
||||||
|
Try to find clid from nickname.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
clients = self.bot.clientlist()
|
try:
|
||||||
clients_cldbid = [client["client_database_id"] for client in clients if client["client_type"] != "1"]
|
clients = self.bot.clientfind(pattern=target)
|
||||||
clients = self.bot.clientlist(groups=True)
|
clids = [client["clid"] for client in clients]
|
||||||
clients_groups = [client["client_servergroups"] for client in clients if client["client_type"] != "1"]
|
if len(clids) == 1:
|
||||||
|
return clids[0]
|
||||||
|
else:
|
||||||
|
self.pipeOut(f"Found multiple matching clients using pattern {target}:{clids}")
|
||||||
|
return
|
||||||
|
|
||||||
print(f"* {clients_groups}")
|
except ts3.query.TS3QueryError as e:
|
||||||
|
self.pipeOut(f"No client found using pattern [{target}], "
|
||||||
|
f"returned error:\n{e}", lvl="ERROR")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Commands -------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def lookupcommand(self, msg, invoker, permission_level):
|
||||||
|
"""
|
||||||
|
Every message starting with '.' gets passed and evaluated in this function.
|
||||||
|
Commands in this function are sorted alphabetically.
|
||||||
|
Parameters in brackets are optional.
|
||||||
|
|
||||||
|
Currently only 2 permission levels are implemented [0 = Everyone, 1 = Admins]
|
||||||
|
|
||||||
|
Command Parameter 1 Parameter 2 Permissionlevel
|
||||||
|
--------------------------------------------------------------------------------------
|
||||||
|
.admin / .notifyAdmin 0
|
||||||
|
.annoy target message 1
|
||||||
|
.follow target 1
|
||||||
|
.help / .h 0
|
||||||
|
.info clid 0
|
||||||
|
.kickall message 1
|
||||||
|
.list channel/clients/commands/groups 1
|
||||||
|
.mute target 1
|
||||||
|
.pingall message 1
|
||||||
|
.rename nickname 1
|
||||||
|
.rmb amount 0
|
||||||
|
.roll 0
|
||||||
|
.rwf 0
|
||||||
|
.stop / .quit / .q 1
|
||||||
|
.test 1
|
||||||
|
.ticker (symbol) 0
|
||||||
|
.unmute target 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
commandstring = msg.split(" ")
|
||||||
|
command = commandstring[0]
|
||||||
|
parameter = commandstring[1:]
|
||||||
|
self.pipeOut(f"command: {command} | parameter: {parameter} | "
|
||||||
|
f"from: {invoker['name']} | permissionlevel: {permission_level}")
|
||||||
|
|
||||||
|
if command == ".admin" or command == ".notifyAdmin":
|
||||||
|
commands.notifyAdmin(self)
|
||||||
|
|
||||||
|
elif command == ".annoy" and permission_level > 0:
|
||||||
|
commands.annoy(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".follow" and permission_level > 0:
|
||||||
|
commands.follow(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".help" or command == ".h":
|
||||||
|
commands.help(self, invoker)
|
||||||
|
|
||||||
|
elif command == ".info":
|
||||||
|
commands.info(self, invoker)
|
||||||
|
|
||||||
|
elif command == ".kickall" and permission_level > 0:
|
||||||
|
self.kickall("test") # TODO
|
||||||
|
|
||||||
|
elif command == ".list" and permission_level > 0:
|
||||||
|
commands.list(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".mute" and permission_level > 0:
|
||||||
|
commands.mute(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".pingall" and permission_level > 0:
|
||||||
|
commands.pingall(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".rename" and permission_level > 0:
|
||||||
|
commands.rename(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".rmb":
|
||||||
|
commands.rmb(self, invoker, parameter)
|
||||||
|
|
||||||
|
elif command == ".roll":
|
||||||
|
commands.roll(self, invoker)
|
||||||
|
|
||||||
|
elif command == ".rwf":
|
||||||
|
commands.rwf(self)
|
||||||
|
|
||||||
|
elif command == ".stop" or command == ".quit" or command == ".q" and permission_level > 0:
|
||||||
|
commands.quit(self, invoker)
|
||||||
|
|
||||||
|
elif command == ".test" and permission_level > 0:
|
||||||
|
cid = self.createChannel("Test")
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=cid)
|
||||||
|
|
||||||
|
elif command == ".ticker":
|
||||||
|
commands.ticker(self, parameter)
|
||||||
|
|
||||||
|
elif command == ".unmute" and permission_level > 0:
|
||||||
|
commands.unmute(self, invoker, parameter)
|
||||||
|
|
||||||
|
else:
|
||||||
|
err = f"Unknown Command: [{command}]"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Load Dotenv
|
# Start logger
|
||||||
dotenv_file = dotenv.find_dotenv()
|
logpath = os.path.dirname(os.path.abspath(__file__)) + r"/log"
|
||||||
if not dotenv_file:
|
log = util.setupLogger(logpath)
|
||||||
raise FileNotFoundError("could not locate .env file")
|
|
||||||
dotenv.load_dotenv(dotenv_file)
|
|
||||||
dotend_keys = dotenv.dotenv_values()
|
|
||||||
|
|
||||||
if not {"_HOST", "_PORT", "_USER", "_PWD", "_SID"} <= dotend_keys.keys():
|
# Log unhandled exception
|
||||||
raise ValueError("missing keys in your .env file.")
|
sys.excepthook = util.unhandledException
|
||||||
|
|
||||||
# Config
|
# Load toml config
|
||||||
conf = dict(host=os.getenv("_HOST"),
|
conf = util.getConf("prod.toml")
|
||||||
port=os.getenv("_PORT"),
|
|
||||||
user=os.getenv("_USER"),
|
# Change loglevel from config
|
||||||
pwd=os.getenv("_PWD"),
|
util.changeLevel(log, conf['Log']['level'])
|
||||||
sid=os.getenv("_SID"),
|
|
||||||
name=os.getenv("_NAME"))
|
|
||||||
|
|
||||||
# Start the Bot Instance
|
# Start the Bot Instance
|
||||||
abot = MyTeamspeakBot(conf)
|
TSbot(conf, log)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+343
@@ -0,0 +1,343 @@
|
|||||||
|
"""
|
||||||
|
TBD
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Lukas Mahler"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__date__ = "14.04.2022"
|
||||||
|
__email__ = "m@hler.eu"
|
||||||
|
__status__ = "Development"
|
||||||
|
|
||||||
|
|
||||||
|
# Default
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
import ts3
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Self
|
||||||
|
from src import util
|
||||||
|
|
||||||
|
|
||||||
|
def annoy(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not len(parameter) != 1:
|
||||||
|
err = "Please use the command like this: .annoy TARGET MESSAGE"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
target = parameter[0]
|
||||||
|
msg = parameter[1]
|
||||||
|
self.poke(n=10, msg=msg, usr=target)
|
||||||
|
|
||||||
|
|
||||||
|
def follow(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
TODO sticky folgen
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .follow TARGET"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
target = parameter[0]
|
||||||
|
clid = self.identifytarget(target)
|
||||||
|
cid = self.bot.clientinfo(clid=int(clid))[0]["cid"]
|
||||||
|
self.bot.clientmove(clid=self.myid, cid=cid)
|
||||||
|
|
||||||
|
|
||||||
|
def help(self, invoker):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Find all functions in this submodule
|
||||||
|
cmds = [f[0] for f in inspect.getmembers(sys.modules[__name__], inspect.isfunction)]
|
||||||
|
msg = f"List of commands:\n---------------------\n.{f'{chr(10)}.'.join(cmds)}"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def info(self, invoker):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg = f"Runtime: {util.getRuntime(self.started)}\n" \
|
||||||
|
f"Version: {self.version}"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def list(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
Message the invoker of the function either a list of channels, clients, commands or server groups.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .list channel/clients/commands/groups/"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
what = parameter[0]
|
||||||
|
mydict = {}
|
||||||
|
|
||||||
|
if what == "channel":
|
||||||
|
channels = self.bot.channellist()
|
||||||
|
for channel in channels:
|
||||||
|
order = channel["channel_order"]
|
||||||
|
mydict[order] = [channel["channel_name"], channel["cid"]]
|
||||||
|
|
||||||
|
mydict = dict(sorted(mydict.items()))
|
||||||
|
msg = json.dumps(mydict)
|
||||||
|
|
||||||
|
elif what == "clients":
|
||||||
|
clients = self.bot.clientlist()
|
||||||
|
for client in clients:
|
||||||
|
mydict[client["client_nickname"]] = {"clid": client["clid"], "cldbid": client["client_database_id"]}
|
||||||
|
mydict = dict(sorted(mydict.items()))
|
||||||
|
msg = json.dumps(mydict)
|
||||||
|
|
||||||
|
elif what == "commands":
|
||||||
|
help(self, invoker)
|
||||||
|
return
|
||||||
|
|
||||||
|
elif what == "groups":
|
||||||
|
groups = self.bot.servergrouplist()
|
||||||
|
for group in groups:
|
||||||
|
mydict[group["name"]] = {"sgid": group["sgid"], "sortid": group["sortid"]}
|
||||||
|
msg = json.dumps(mydict)
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = f"The parameter [{what}] is not supported."
|
||||||
|
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def mute(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
Assign the mute group to a user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .mute TARGET"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
target = parameter[0]
|
||||||
|
clid = self.identifytarget(target)
|
||||||
|
cldbid = self.clid2cldbid(clid)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.bot.servergroupaddclient(sgid=self.sgid_mute, cldbid=cldbid)
|
||||||
|
except ts3.query.TS3QueryError as e:
|
||||||
|
err = f"Failed to add to group: [{e}]"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def pingall(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .pingall MESSAGE"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = parameter[0]
|
||||||
|
self.poke(msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def notifyAdmin(self):
|
||||||
|
"""
|
||||||
|
Message every available admin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
clients = self.bot.clientlist()
|
||||||
|
clients = [client for client in clients if client["client_type"] != "1"]
|
||||||
|
for client in clients:
|
||||||
|
cldbid = client["client_database_id"]
|
||||||
|
clid = client["clid"]
|
||||||
|
if self.isadmin(cldbid):
|
||||||
|
msg = "<Keep this chat open to use commands>"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=clid, msg=msg)
|
||||||
|
|
||||||
|
if not self.whitelisted:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def quit(self, invoker):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stop(invoker)
|
||||||
|
|
||||||
|
|
||||||
|
def rename(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
Rename the Bot to the given Nickname
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .rename NICKNAME"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.nickname = parameter[0]
|
||||||
|
self.bot.clientupdate(client_nickname=self.nickname)
|
||||||
|
|
||||||
|
|
||||||
|
def rmb(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
Convert rmb (Chinese Yuan) to eur (Euro)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .rmb AMOUNT"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
url = "https://www.floatrates.com/daily/cny.json"
|
||||||
|
req = requests.get(url)
|
||||||
|
|
||||||
|
if req.status_code != 200:
|
||||||
|
self.pipeOut(req.status_code, lvl="WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = json.loads(req.text)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "eur" not in data:
|
||||||
|
self.pipeOut(data, lvl="WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
conversion_rate = float(data['eur']['inverseRate'])
|
||||||
|
except KeyError:
|
||||||
|
conversion_rate = 6.94 # Static
|
||||||
|
|
||||||
|
if "k" in parameter[0]:
|
||||||
|
rmb_val = float(parameter[0].replace("k", "000"))
|
||||||
|
else:
|
||||||
|
rmb_val = float(parameter[0])
|
||||||
|
|
||||||
|
eur = rmb_val / conversion_rate
|
||||||
|
msg = f"[{rmb_val:.0f}] rmb is approx. [{eur:.0f}] eur / conversion rate [{conversion_rate:.2f}]"
|
||||||
|
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def roll(self, invoker):
|
||||||
|
|
||||||
|
cid_invoker = self.bot.clientinfo(clid=int(invoker['id']))[0]["cid"]
|
||||||
|
cid_self = self.bot.clientinfo(clid=self.myid)[0]["cid"]
|
||||||
|
|
||||||
|
if cid_self != cid_invoker:
|
||||||
|
self.bot.clientmove(clid=self.myid, cid=cid_invoker)
|
||||||
|
|
||||||
|
rolled = random.randint(0, 100)
|
||||||
|
msg = f"Rolled a [{rolled:3d}] by {invoker['name']}"
|
||||||
|
self.bot.sendtextmessage(targetmode=2, target=cid_invoker, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def rwf(self):
|
||||||
|
"""
|
||||||
|
Refresh the WoW progress channel,
|
||||||
|
this is very specific to my use, you can disable this in the config.
|
||||||
|
"""
|
||||||
|
url = "https://raider.io/api/v1/raiding/progression?raid=sepulcher-of-the-first-ones&difficulty=mythic®ion=world"
|
||||||
|
req = requests.get(url)
|
||||||
|
|
||||||
|
if req.status_code != 200:
|
||||||
|
self.pipeOut(req.status_code, lvl="WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = json.loads(req.text)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "progression" not in data:
|
||||||
|
self.pipeOut(data, lvl="WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
prog_done = 0
|
||||||
|
prog_max = len(data['progression'])
|
||||||
|
|
||||||
|
for i in range(0, prog_max):
|
||||||
|
if data['progression'][i]['totalGuilds'] > 0:
|
||||||
|
prog_done = i
|
||||||
|
|
||||||
|
guilds_done = data['progression'][prog_done]['totalGuilds']
|
||||||
|
|
||||||
|
channelname = f"[cspacerC3]Sepulcher {prog_done+1}/{prog_max} ({guilds_done})"
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.editChannelname(205, channelname)
|
||||||
|
except ts3.query.TS3QueryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def ticker(self, parameter):
|
||||||
|
"""
|
||||||
|
Refresh the crypto Ticker channels,
|
||||||
|
this is very specific to my use, you can disable this in the config.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if parameter:
|
||||||
|
symbol = parameter[0].lower()
|
||||||
|
else:
|
||||||
|
symbol = "all"
|
||||||
|
|
||||||
|
if symbol in ["btc", "bitcoin", "eth", "ethereum", "all"]:
|
||||||
|
channelname = f"[cspacerC1]BTC: {self.gecko.getSymbol('bitcoin', decimal=0)} | " \
|
||||||
|
f"ETH: {self.gecko.getSymbol('ethereum', decimal=0)}"
|
||||||
|
try:
|
||||||
|
self.editChannelname(200, channelname)
|
||||||
|
except ts3.query.TS3QueryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if symbol in ["dot", "polkadot", "ada", "cardano", "all"]:
|
||||||
|
channelname = f"[cspacerC2]DOT: {self.gecko.getSymbol('polkadot', decimal=2)} | " \
|
||||||
|
f"ADA: {self.gecko.getSymbol('cardano', decimal=2)}"
|
||||||
|
try:
|
||||||
|
self.editChannelname(201, channelname)
|
||||||
|
except ts3.query.TS3QueryError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def unmute(self, invoker, parameter):
|
||||||
|
"""
|
||||||
|
Remove the mute group to a user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not parameter:
|
||||||
|
err = "Please use the command like this: .unmute TARGET"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
target = parameter[0]
|
||||||
|
clid = self.identifytarget(target)
|
||||||
|
cldbid = self.clid2cldbid(clid)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.bot.servergroupdelclient(sgid=self.sgid_mute, cldbid=cldbid)
|
||||||
|
except ts3.query.TS3QueryError as e:
|
||||||
|
err = f"Failed to remove from group: [{e}]"
|
||||||
|
self.bot.sendtextmessage(targetmode=1, target=invoker['id'], msg=err)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit()
|
||||||
+13
-5
@@ -4,11 +4,12 @@ TBD
|
|||||||
|
|
||||||
__author__ = "Lukas Mahler"
|
__author__ = "Lukas Mahler"
|
||||||
__version__ = "0.0.1"
|
__version__ = "0.0.1"
|
||||||
__date__ = "23.09.2021"
|
__date__ = "04.02.2022"
|
||||||
__email__ = "m@hler.eu"
|
__email__ = "m@hler.eu"
|
||||||
__status__ = "Development"
|
__status__ = "Development"
|
||||||
|
|
||||||
# Imports
|
# Imports
|
||||||
|
from datetime import datetime
|
||||||
from pycoingecko import CoinGeckoAPI
|
from pycoingecko import CoinGeckoAPI
|
||||||
|
|
||||||
|
|
||||||
@@ -37,21 +38,28 @@ class GeckoAPI:
|
|||||||
|
|
||||||
return self.api.get_coins_list()
|
return self.api.get_coins_list()
|
||||||
|
|
||||||
def getSymbol(self, symbol, curr="EUR", decimal=2):
|
def getSymbol(self, symbol_id, curr="EUR", decimal=2):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
matching = [x for x in self.coinlist if x["symbol"] == symbol.lower()]
|
matching = [x for x in self.coinlist if x["id"] == symbol_id.lower()]
|
||||||
if len(matching) == 1:
|
if len(matching) == 1:
|
||||||
resp_dict = self.api.get_price(ids=matching[0]["id"], vs_currencies=curr.lower())
|
try:
|
||||||
|
resp_dict = self.api.get_price(ids=matching[0]["id"], vs_currencies=curr.lower())
|
||||||
|
except Exception as e:
|
||||||
|
with open("gecko_error.log", 'a+') as log:
|
||||||
|
log.write(f"{datetime.now():%Y-%m-%d %H:%M} | {e}\n")
|
||||||
|
return "Error"
|
||||||
price = resp_dict[matching[0]["id"]][curr.lower()]
|
price = resp_dict[matching[0]["id"]][curr.lower()]
|
||||||
if isinstance(price, int):
|
if isinstance(price, int):
|
||||||
price = float(price) # Convert to always have floats
|
price = float(price) # Convert to always have floats
|
||||||
price = f"{price:.{decimal}f}"
|
price = f"{price:.{decimal}f}"
|
||||||
return price
|
return price
|
||||||
else:
|
else:
|
||||||
return "No unique coin found"
|
print(matching)
|
||||||
|
# No unique coin found
|
||||||
|
return "Not found"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
[Connection]
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 10011
|
||||||
|
sid = 1
|
||||||
|
|
||||||
|
[Authentication]
|
||||||
|
user = "exampleuser"
|
||||||
|
pwd = "password"
|
||||||
|
|
||||||
|
[Groups]
|
||||||
|
admins = ["1"]
|
||||||
|
mute = "0"
|
||||||
|
|
||||||
|
[Misc]
|
||||||
|
nickname = "myTS3-Bot"
|
||||||
|
whitelisted = false
|
||||||
|
crypto = false
|
||||||
|
|
||||||
|
[Log]
|
||||||
|
level = "INFO"
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
"""
|
||||||
|
TBD
|
||||||
|
"""
|
||||||
|
|
||||||
|
__author__ = "Lukas Mahler"
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__date__ = "14.04.2022"
|
||||||
|
__email__ = "m@hler.eu"
|
||||||
|
__status__ = "Development"
|
||||||
|
|
||||||
|
|
||||||
|
# Default
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import os.path
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from datetime import timedelta
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
# Custom
|
||||||
|
import toml
|
||||||
|
|
||||||
|
|
||||||
|
def unhandledException(exc_type, exc_value, exc_traceback):
|
||||||
|
"""
|
||||||
|
src = https://stackoverflow.com/a/16993115/5593051
|
||||||
|
"""
|
||||||
|
|
||||||
|
if issubclass(exc_type, KeyboardInterrupt):
|
||||||
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
|
return
|
||||||
|
|
||||||
|
log.critical("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
|
||||||
|
|
||||||
|
|
||||||
|
def getConf(fname):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
if fname.endswith(".toml"):
|
||||||
|
if os.path.isfile(fname):
|
||||||
|
try:
|
||||||
|
config = toml.load(fname)
|
||||||
|
checkConf(config)
|
||||||
|
return config
|
||||||
|
except ValueError as e:
|
||||||
|
log.critical(f"The provided '.toml' is probably invalid, returned error:\n{e}")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
log.critical(f"Couldn't locate the '.toml' file [{fname}].")
|
||||||
|
log.info("Creating a new '.toml' file from template, please edit and restart.")
|
||||||
|
shutil.copy("src/template.toml", fname)
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
log.critical(f"The provided config file [{fname}] is not a '.toml' file.")
|
||||||
|
log.info("Creating a new '.toml' file from template, please edit and restart.")
|
||||||
|
shutil.copy("src/template.toml", "prod.toml")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def getRuntime(started):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
elapsed = time.time() - started
|
||||||
|
runtime = str(timedelta(seconds=elapsed)).split(".")[0] # ignore Microseconds
|
||||||
|
|
||||||
|
return runtime
|
||||||
|
|
||||||
|
|
||||||
|
def checkConf(config):
|
||||||
|
"""
|
||||||
|
TODO check if keys exist
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def setupLogger(logpath, lvl="DEBUG"):
|
||||||
|
"""
|
||||||
|
Create a rotating log in a log folder
|
||||||
|
"""
|
||||||
|
|
||||||
|
global log # Needed to log exceptions in src\util
|
||||||
|
log = logging.getLogger("mylog")
|
||||||
|
if not os.path.exists(logpath):
|
||||||
|
os.makedirs(logpath)
|
||||||
|
handler = RotatingFileHandler(logpath + r"/myTS3.log", encoding='utf-8', maxBytes=1*1024*1024, backupCount=10)
|
||||||
|
logformat = logging.Formatter("%(asctime)s %(levelname)8s %(message)s", "%Y-%m-%d %H:%M:%S")
|
||||||
|
handler.setFormatter(logformat)
|
||||||
|
log.addHandler(handler)
|
||||||
|
log.setLevel(logging.getLevelName(lvl))
|
||||||
|
|
||||||
|
return log
|
||||||
|
|
||||||
|
|
||||||
|
def changeLevel(log, lvl):
|
||||||
|
"""
|
||||||
|
Change the loglevel after creation
|
||||||
|
"""
|
||||||
|
log.setLevel(logging.getLevelName(lvl))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit()
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
"""
|
|
||||||
TBD
|
|
||||||
"""
|
|
||||||
|
|
||||||
__author__ = "Lukas Mahler"
|
|
||||||
__version__ = "0.2.0"
|
|
||||||
__date__ = "23.09.2021"
|
|
||||||
__email__ = "m@hler.eu"
|
|
||||||
__status__ = "Development"
|
|
||||||
|
|
||||||
|
|
||||||
# Default
|
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
class MyTimer(threading.Timer):
|
|
||||||
def run(self):
|
|
||||||
while not self.finished.wait(self.interval):
|
|
||||||
self.function(*self.args, **self.kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def maketimer(cooldown, func):
|
|
||||||
timer = MyTimer(cooldown, func)
|
|
||||||
timer.daemon = True
|
|
||||||
return timer
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
exit()
|
|
||||||
Reference in New Issue
Block a user