Skip to content

Instantly share code, notes, and snippets.

@kierdavis
Created May 1, 2013 20:59
Show Gist options
  • Save kierdavis/5498334 to your computer and use it in GitHub Desktop.
Save kierdavis/5498334 to your computer and use it in GitHub Desktop.
from mc3p.plugins import MC3Plugin, msghdlr
import urllib
import urllib2
import StringIO
import sys
import os
import re
import time
import random
import threading
import pickle
import gzip
import nbt
import struct
WE_LIMIT = 1000
WORLD_SIGNAL_ITEM = 322 # Golden Apple
DATA_FILE = os.path.expanduser("~/.grabproxy.pkl")
LOG_FILE = os.path.expanduser("~/.grabproxy.log")
CHAT_COLOUR = u"c" # c is the colour I'm supposed to have
ANNOUNCE_INTERVAL = 15 * 60
BOOST_FORMAT = u"&4&o".replace(u"&", u"\xa7")
PREFIX = "&a[&cH@&a]&a"
ENTITIES = [
"arrows",
"items",
#"paintings",
"boats",
"minecarts",
"tnt",
"xp",
]
ADMINS = [
"Niklon9141", # CEO
"durrant", # CEO
"theF41_1_3N", # vCEO
"kierdavis", # H@
"HiddenSentinels", # H@
"mathonftw2", # H@
"sjonsson", # H@
"nate214", # H@
"Stef534", # A
"JokerDayz", # A
"chloesthename", # A
]
UNSTEALTHY_COMMANDS = [
"/broadcast",
"/bcast",
"/me",
]
CSEQS = {
"plotpath": [
"/b d cm",
"/b 3",
"/vr 35",
"/v 5",
"/vi 2",
],
"grass": [
"/b d mm",
"/b 3",
"/vr 3",
"/v 2",
],
"regenchunk": [
"//chunk",
"//regen",
],
"regenvert2": [
"//hpos2",
#"//expand vert",
"//expand 10 u",
"//expand 100 d",
"//regen",
"//regen",
],
}
ANNOUNCEMENTS = [
#u"House for sale in S! See the House For Sale post in General Server Discussion!",
#u"Last chance to buy my house! Ask kierdavis for info!",
#u"If you see entity spam anywhere, let an admin know ASAP!",
#u"Merry Christmas All! ~kier"
#u"Talented Foreman+ sci-fi builders needed for Project Icarus!",
#u"Want to know how to rank up? Read http://www.grabmc.com/ranks",
]
AUTOCOMPLETES = [
("/prox warp ", lambda self: self.warps.keys()),
("/prox delwarp ", lambda self: self.warps.keys()),
("/prox plot ", lambda self: self.plots.keys()),
("/prox plot del ", lambda self: self.plots.keys()),
("/prox note ", lambda self: self.notes.keys()),
("/prox delnote ", lambda self: self.notes.keys()),
("/prox boost ", lambda self: self.players),
("/prox where ", lambda self: self.players),
("/prox fall ", lambda self: self.players),
("/prox as ", lambda self: self.players),
("/prox cseq ", lambda self: CSEQS.keys()),
]
def remove_formatting(s):
return re.sub(u"\xa7.", "", s)
class Task(object):
def __init__(self, delay, f, *args, **kwds):
self.delay = delay
self.f = f
self.args = args
self.kwds = kwds
def decrement(self):
self.delay -= 1
if self.delay <= 0:
return True
return False
def __call__(self):
return self.f(*self.args, **self.kwds)
class TaskManager(object):
def __init__(self):
self.lock = threading.Lock()
self.tasks = {}
self.next_id = 1
def add(self, task):
self.lock.acquire()
key = self.next_id
self.next_id += 1
self.tasks[key] = task
self.lock.release()
return key
def get(self, key):
self.lock.acquire()
task = self.tasks[key]
self.lock.release()
return task
def remove(self, key):
self.lock.acquire()
del self.tasks[key]
self.lock.release()
def getall(self):
self.lock.acquire()
tasks = dict(self.tasks)
self.lock.release()
return tasks
class Plugin(MC3Plugin):
def init(self, args):
self.ready = False
self.tasks = TaskManager()
self.data = {}
self.stop_repeating = False
self.plot_id = None
self.selection = None
self.players = []
self.display_names = {}
self.player_worlds = {}
self.awaiting_response = None
self.ss_client = None
self.timelock = "day"
self.player_prefixes = {}
self.pos = (0, 0, 0)
self.held_item = 0
self.tpall = []
self.tpall_num = 0
self.replies = {}
self.stealth = False
self.reload_time = None
self.keepalive_time = time.time()
self.keepalive_intervals = []
self.keepalive_request = time.time()
self.keepalive_responses = []
self.join_hooks = {}
self.quit_hooks = {}
self.ping_time = None
self.boosted = []
self.vanished = False
self.tpa = False
self.bindings = {}
self.ofilters = []
self.ifilters = []
for name in dir(self):
if name.startswith("outgoing_"):
self.ofilters.append(getattr(self, name))
elif name.startswith("incoming_"):
self.ifilters.append(getattr(self, name))
if os.path.exists(DATA_FILE):
f = open(DATA_FILE, "rb")
self.data = pickle.load(f)
f.close()
self.logfile = open(LOG_FILE, "a")
self.log("----", "--------Login--------")
if len(ANNOUNCEMENTS) > 0:
self.tasks.add(Task(2 * ANNOUNCE_INTERVAL, self.announce, 0))
def announce(self, i):
self.send_to_server(u"/bcast " + ANNOUNCEMENTS[i])
i = (i + 1) % len(ANNOUNCEMENTS)
self.tasks.add(Task(2 * ANNOUNCE_INTERVAL, self.announce, i))
def destroy(self):
self.save_data()
self.logfile.close()
def save_data(self):
f = open(DATA_FILE, "wb")
pickle.dump(self.data, f)
f.close()
@property
def selections(self):
return self.data.setdefault("selections", {})
@property
def books(self):
return self.data.setdefault("books", [])
@property
def plots(self):
return self.data.setdefault("plots", {})
@property
def warps(self):
return self.data.setdefault("warps", {})
@property
def mls(self):
return self.data.setdefault("mls", {})
@property
def mail_queue(self):
return self.data.setdefault("mail_queue", [])
@property
def ml_cache(self):
return self.data.setdefault("ml_cache", [])
@property
def notes(self):
return self.data.setdefault("notes", {})
@property
def autoevac(self):
return self.data.setdefault("autoevac", True)
@autoevac.setter
def autoevac(self, v):
self.data["autoevac"] = v
def page(self, items, args, title):
items = list(items)
if len(items) == 0:
self.send_notification(u"%s: No data" % title)
return []
items = list(items)
page_num = 1
items_per_page = 10
if len(args) > 0:
try:
page_num = int(args[0])
except ValueError:
pass
start = (page_num - 1) * items_per_page
end = start + items_per_page
if start < 0 or start >= len(items):
self.send_notification(u"Error: page number out of range")
return []
total_pages = (len(items) + items_per_page - 1) / items_per_page
self.send_notification("%s (page %d/%d):" % (title, page_num, total_pages))
return items[start:end]
@msghdlr(0x00)
def handle00(self, msg, source):
if source == "client":
tasks = self.tasks.getall()
if len(tasks):
for key, task in tasks.iteritems():
if task.decrement():
self.tasks.remove(key)
task()
if len(self.mail_queue):
player, message = self.mail_queue.pop(0)
prefix = u"/mail send %s " % player
maxlen = 100 - len(prefix)
while len(message):
part, message = message[:maxlen], message[maxlen:]
self.send_to_server(prefix + part)
if len(self.mail_queue) == 0:
self.send_to_server("/ GCPROXY: Mail queue empty!")
t = time.time()
self.keepalive_intervals.append(t - self.keepalive_time)
self.keepalive_responses.append(t - self.keepalive_request)
self.keepalive_intervals = self.keepalive_intervals[-20:]
self.keepalive_responses = self.keepalive_responses[-20:]
self.keepalive_time = t
else:
self.keepalive_request = time.time()
return True
@msghdlr(0x03)
def handle03(self, msg, source):
s = msg["chat_msg"]
if source == "client":
if self.awaiting_response is not None:
f = self.awaiting_response
self.awaiting_response = None
f(s)
return False
else:
s = self.process_outgoing(s)
if s is None:
return False
else:
formatted = s
unformatted = remove_formatting(s)
for f in self.ifilters:
s = f(s, unformatted)
if s is None:
return False
elif s != formatted:
formatted = s
unformatted = remove_formatting(s)
msg["chat_msg"] = s
return True
def process_outgoing(self, s):
for f in self.ofilters:
s = f(s)
if s is None:
return None
return s
@msghdlr(0x04)
def handle04(self, msg, source):
t = msg["day_time"] % 24000
if self.timelock == "day" and t >= 12000:
self.send_notification(u"Time is %d, setting to day" % t)
self.send_to_server(u"/time day")
if self.timelock == "night" and t < 12000:
self.send_notification(u"Time is %d, setting to night" % t)
self.send_to_server(u"/time night")
return True
@msghdlr(0x0B)
def handle0B(self, msg, source):
self.pos = int(msg["x"]), int(msg["y"]), int(msg["z"])
return True
@msghdlr(0x0D)
def handle0D(self, msg, source):
self.pos = int(msg["x"]), int(msg["y"]), int(msg["z"])
if source == "server" and not self.ready:
self.ready = True
self.tasks.add(Task(3, self.on_ready))
return True
@msghdlr(0x0F)
def handle0F(self, msg, source):
if msg["dir"] == -1:
item = msg["details"]["item_id"]
if item in self.bindings:
self.send_to_server(self.process_outgoing(self.bindings[item]))
return False
return True
@msghdlr(0x10)
def handle10(self, msg, source):
self.held_item = msg["slot_id"]
return True
@msghdlr(0x46)
def handle46(self, msg, source):
if self.timelock is not None and msg["reason"] == 1:
self.send_notification(u"Rain started, setting to sun")
self.send_to_server(u"/weather sun")
return True
@msghdlr(0x67)
def handle67(self, msg, source):
if msg["slot_update"] is not None:
if msg["slot_update"]["item_id"] == 387:
data = msg["slot_update"]["nbt_data"]
f = gzip.GzipFile(fileobj=StringIO.StringIO(data))
tag = nbt.Tag.read(f)
f.close()
if "title" in tag.value and "author" in tag.value and "pages" in tag.value:
title = tag["title"].value
author = tag["author"].value
pages = [page.value for page in tag["pages"].value[1]]
key = author + ":" + re.sub(r"[^a-zA-Z0-9]+", "-", title)
for (k, _, _, p) in self.books:
if k == key and p == pages:
break
else:
self.send_notification("Book %s by %s added to index (id %d)" % (title, author, len(self.books)))
self.books.append((key, title, author, pages))
return True
@msghdlr(0xC9)
def handleC9(self, msg, source):
name = re.sub(r"\[.+?\]", "", remove_formatting(msg["name"]))
for n in ADMINS:
if n.lower().startswith(name.lower()):
name = n
break
if msg["online"] and name not in self.players:
self.players.append(name)
elif not msg["online"] and name in self.players:
self.players.remove(name)
self.display_names[name] = msg["name"]
#print msg, name
#print self.players
#print "-----"
return True
@msghdlr(0xCB)
def handleCB(self, msg, source):
if source == "client":
text = msg["text"]
matches = []
for prefix, lister in AUTOCOMPLETES:
if text.startswith(prefix):
l = lister(self)
word = text[len(prefix):]
for x in l:
if x.startswith(word):
matches.append(x)
break
else:
return True
response = u"\x00".join(map(unicode, matches))
self.to_client({"msgtype": 0xCB, "text": response})
return False
return True
#@msghdlr(0xFE)
#def handleFE(self, msg, source):
# self.server_list_ping = True
# return True
#
#@msghdlr(0xFF)
#def handleFF(self, msg, source):
# if source == "server" and self.server_list_ping:
# self.server_list_ping = False
#
# fields = msg["reason"].split("\x00")
# fields[2] = u"\xa71GrabCraft via GCProxy"
# msg["reason"] = "\x00".join(fields)
#
# return True
def incoming_socialspy(self, msg, unformatted):
m = re.match(r"([a-zA-Z0-9_]+)( ?): (/(?:r|reply) .*|/(?:msg|tell|m|whisper) [a-zA-Z0-9_]+ .*)", unformatted)
if m is not None:
src = m.group(1)
space = m.group(2)
words = m.group(3).split()
if not space:
return None
if words[0] in (u"/r", u"/reply"):
dest = "(replying)"
s = u" ".join(words[1:])
else:
dest = words[1]
s = u" ".join(words[2:])
if src == "kierdavis" or (dest.lower() in "kierdavis" and not self.vanished):
return None
else:
msg = u"\xa78[\xa77%s\xa78 -> \xa77%s\xa78]\xa7r %s" % (src, dest, s)
return msg
def incoming_filter_time_commands(self, msg, unformatted):
return None if msg.startswith(u"\xa75[Server]") else msg
def incoming_selection_info(self, msg, unformatted):
m = re.match(u"\xa7dPosition ([12]): \\(([-.0-9]+), ([-.0-9]+), ([-.0-9]+)\\)", msg)
if m is not None:
point, x, y, z = tuple(map(int, map(float, m.groups())))
if self.selection is None:
a = b = (0, 0, 0)
else:
a, b = self.selection
if point == 1:
a = (x, y, z)
else:
b = (x, y, z)
self.selection = (a, b)
return msg
def incoming_plot_id(self, msg, unformatted):
if unformatted.startswith("Plot id: "):
self.plot_id = unformatted[len("Plot id: "):]
return msg
def incoming_norandom(self, msg, unformatted):
return msg.replace(u"\xa7k", u"")
def incoming_chat(self, msg, unformatted):
m = re.match(r"\[([a-zA-Z0-9_]+?)\](.+?)([a-zA-Z0-9_]+?) : (.*?)\.", unformatted)
if m is not None:
world, tags, player, chat = m.groups()
tags = tags.strip()
self.player_worlds[player.lower()] = world
if self.autoevac:
if tags in ("[New]", "[Member]") and world in ("C2", "C3", "C4"):
self.send_notification("Automatically evacuating %s from %s" % (player, world))
self.send_to_server(u"/mv tp %s H" % player)
if player.lower() in self.boosted:
p = msg.index(chat)
msg = msg[:p] + BOOST_FORMAT + msg[p:]
#print >> sys.stderr, `msg`
p = msg.index(u"\xa76 : ")
msg = msg[:p] + u"\xa76" + remove_formatting(msg[p:])
#self.player_prefixes[player.lower()] = msg[:p].replace(u"\xa7", u"&")
self.log("CHAT", "%s: %s", player, chat)
return msg
def incoming_welcome(self, msg, unformatted):
if unformatted.startswith(u"Welcome to Grabcraft, "):
name = unformatted[len(u"Welcome to Grabcraft, "):-1]
self.tasks.add(Task(2, self.send_to_server, self.rainbow(u"Welcome, %s!" % name)))
#if "theF41_1_3N" in self.players:
#self.tasks.add(Task(4, self.send_to_server, u"/fsay theF41_1_3N Welcome %s!" % name))
#if "sjonsson" in self.players:
#self.tasks.add(Task(7, self.send_to_server, u"/fsay sjonsson welcome %s" % name))
return msg
def trigger_hooks(self, hooks, player):
player = player.lower()
if player in hooks:
for hook in hooks[player]:
self.send_to_server(hook)
del hooks[player]
def incoming_hooks(self, msg, unformatted):
m = re.match(r"\*\* ([a-zA-Z0-9_]+) (joined|left) the Game!", unformatted)
if m is not None:
player = m.group(1)
if m.group(2) == "joined":
self.log("JOIN", player)
self.trigger_hooks(self.join_hooks, player)
elif m.group(2) == "left":
self.log("QUIT", player)
self.trigger_hooks(self.quit_hooks, player)
return msg
m = re.match(r"([a-zA-Z0-9_]+) left the game.", unformatted)
if m is not None:
self.log("QUIT", m.group(1))
self.trigger_hooks(self.quit_hooks, m.group(1))
return msg
#def is_fc_duration(self, s):
# return s == "perma" or re.match(r"(\d+[smhdw])+", s)
#
#def outgoing_fc_bans(self, msg):
# if msg.startswith((u"/ban ", u"/mute ", u"/freeze ")):
# words = msg.split()
# if words[1] in (u"remove", u"check"):
# return msg
#
# index = words[1] == u"ip" and 3 or 2
#
# if not self.is_fc_duration(words[index]):
# words.insert(index, u"perma")
# msg = u" ".join(words)
#
# return msg
def outgoing_vanish(self, msg):
if msg == u"/vanish":
self.vanished = not self.vanished
return msg
def obfuscate_player(self, s):
for player in self.players:
if s.lower() in player.lower():
s = player
break
if len(s) > 4:
p = random.randrange(len(s) - 3)
s = s[p:p+4]
return s.lower()
def outgoing_plotme_rewrite(self, msg):
if msg.startswith(u"/p ") or msg == u"/p":
msg = u"/plotme" + msg[2:]
elif msg.startswith(u"/vsp ") or msg == u"/vsp":
msg = u"/p" + msg[4:]
if msg in ["/plotme auto", "/plot auto", "/plotme claim", "/plot claim"]:
self.send_notification(u"\xa74Don't forget to /prox plot add!")
return msg
def incoming_pong(self, msg, unformatted):
if msg == "_gcproxy_reload_":
t = time.time() - self.reload_time
self.send_to_server(u"/bcast Reload complete in approx. %.1f seconds" % t)
self.send_notification(u"Reload complete")
self.tasks.add(Task(3, self.on_ready))
elif msg == "_gcproxy_stat_":
t = time.time() - self.ping_time
self.send_notification(u"Ping (network): %.3fs" % t)
else:
return msg
#def outgoing_sudo(self, msg):
# if msg.startswith(u"/sudo "):
# words = msg.split()
# msg = u"/fsay %s /%s" % (words[1], u" ".join(words[2:]))
#
# return msg
def outgoing_tp(self, msg):
if msg.startswith((u"/tp ")) and not self.vanished:
self.send_to_server(u"/vanish")
self.tasks.add(Task(2, self.send_to_server, msg))
self.tasks.add(Task(5, self.send_to_server, u"/vanish"))
return None
return msg
def outgoing_cso(self, msg):
if msg.startswith(u"/cso "):
player = msg[5:]
msg = u"/sudo %s commandspy on" % player
#msg = u"/fsay %s /commandspy on" % player
return msg
def outgoing_evac(self, msg):
if msg.startswith(u"/evac "):
msg = u"/mv tp " + msg[6:] + " H"
return msg
#def incoming_reload(self, msg, unformatted):
# if re.match(r"[a-zA-Z0-9_]+: /reload", unformatted):
# self.send_to_server(u"/ping _gcproxy_reload_")
# self.reload_time = time.time()
# self.send_notification(u"Reload started")
#
# return msg
def incoming_commandspy(self, msg, unformatted):
m = re.match("(?:\\[WorldEdit\\]\\[(\d+) blocks\\])?([a-zA-Z0-9_]+): (.+)$", unformatted)
if m is not None:
num_blocks, player, command = m.groups()
self.log("CMD", "%s: %s", player, command)
return msg
def outgoing_proxy_control(self, msg):
words = msg.split(" ")
if words[0] == u"/fs":
self.send_to_server(u"/f c f")
self.tasks.add(Task(1, self.send_to_server, msg[4:]))
self.tasks.add(Task(2, self.send_to_server, u"/f c p"))
return None
elif words[0] == u"/as":
self.send_to_server(u"/f c a")
self.tasks.add(Task(1, self.send_to_server, msg[4:]))
self.tasks.add(Task(2, self.send_to_server, u"/f c p"))
return None
elif words[0] == u"/prox":
if len(words) < 2:
self.send_notification(u"Usage:")
self.send_notification(u" /prox save - Save config")
self.send_notification(u" /prox bcast <name> <message> - Broadcast")
self.send_notification(u" /prox name <name>|off - Disguise as a n00b")
self.send_notification(u" /prox fakejoin [<player>] - Fake join a player (defaulting to yourself)")
self.send_notification(u" /prox fakequit [<player>] - Fake quit a player (defaulting to yourself)")
self.send_notification(u" /prox fakeafkon [<player>] - Fake start AFK a player (defaulting to yourself)")
self.send_notification(u" /prox fakeafkoff [<player>] - Fake end AFK a player (defaulting to yourself)")
self.send_notification(u" /prox autoevac - Toggle autoevac")
self.send_notification(u" /prox stat - Stats")
self.send_notification(u" /prox bind <itemID> <command> - Binds an item to a command")
self.send_notification(u" /prox unbind <itemID> - Unbinds an item")
self.send_notification(u" /prox bindings [<page>] - Lists bindings")
self.send_notification(u" /prox tpa - Toggle auto-tpaccept (default: on)")
self.send_notification(u" /prox stealth - Toggle stealth mode")
self.send_notification(u" /prox boost <nickname> - Toggle a player's chat being boosted")
self.send_notification(u" /prox cseq <name> - Run a cseq")
self.send_notification(u" /prox repeat <command> - Repeats a command")
self.send_notification(u" /prox repeati <interval> <command> - Repeats a command with a specified interval")
self.send_notification(u" /prox norepeat - Stops repeating commands")
self.send_notification(u" /prox admins - Broadcasts list of online admins")
self.send_notification(u" /prox say - Fakes a /say command")
self.send_notification(u" /prox where <player> - Reports which world a player was last seen in")
self.send_notification(u" /prox cleanlag - Cleans up entities")
self.send_notification(u" /prox timelock [<day|night|off>] - Change timelock")
self.send_notification(u" /prox setwarp <name> - Set a warp")
self.send_notification(u" /prox delwarp <name> - Delete a warp")
self.send_notification(u" /prox warp <name> - Go to a warp")
self.send_notification(u" /prox warps [<page>] - List warps")
self.send_notification(u" /prox tpall [reset] - Teleport through all players")
self.send_notification(u" /prox fall <player> - Make a flying player fall")
self.send_notification(u" /prox addhook <j|q> <player> <command> - Add a hook")
self.send_notification(u" /prox hooks <j|q> <player> [<page>] - List hooks")
self.send_notification(u" /prox marry <player1> <player2> - Make 2 players marry")
self.send_notification(u" /prox note [<name>] [<message>] - Record a note")
self.send_notification(u" /prox delnote <name> - Delete a note")
self.send_notification(u" /prox book - Book stuff")
self.send_notification(u" /prox sel - Selection stuff")
self.send_notification(u" /prox plot - Plot stuff")
self.send_notification(u" /prox ml - Mailing list stuff")
elif words[1] == u"save":
self.save_data()
self.send_notification(u"Data saved.")
elif words[1] == u"bcast":
name = words[2]
message = u" ".join(words[3:])
return u"/af announce &6[&4%s&6] &a%s" % (name, message)
elif words[1] == u"name":
if words[2] == u"off":
self.send_to_server(u"/pex user kierdavis prefix %s" % PREFIX)
self.send_to_server(u"/pex user kierdavis suffix &c")
self.send_to_server(u"/nick off")
else:
self.send_to_server(u"/pex user kierdavis prefix &7")
self.send_to_server(u"/pex user kierdavis suffix &6")
self.send_to_server(u"/nick %s" % words[2])
elif words[1] == u"fakejoin":
player = u"kierdavis"
if len(words) > 2:
player = words[2]
self.send_to_server(u"/af announce &b[STAB]&e [%s&e] has joined." % player)
elif words[1] == u"fakequit":
player = u"kierdavis"
if len(words) > 2:
player = words[2]
self.send_to_server(u"/af announce &b[STAB]&e [%s&e] has quit." % player)
elif words[1] == u"fakeafkon":
player = u"kierdavis"
if len(words) > 2:
player = words[2]
self.send_to_server(u"/af announce &5%s&5 is now AFK" % player)
elif words[1] == u"fakeafkoff":
player = u"kierdavis"
if len(words) > 2:
player = words[2]
self.send_to_server(u"/af announce &5%s&5 is no longer AFK" % player)
elif words[1] == u"autoevac":
self.autoevac = not self.autoevac
if self.autoevac:
self.send_notification(u"Autoevac enabled")
else:
self.send_notification(u"Autoevac disabled")
elif words[1] == u"tpa":
self.tpa = not self.tpa
if self.tpa:
self.send_notification(u"Auto-tpaccept enabled")
else:
self.send_notification(u"Auto-tpaccept disabled")
elif words[1] == u"stat":
if len(self.keepalive_intervals) > 0:
kai = sum(self.keepalive_intervals) / len(self.keepalive_intervals)
self.send_notification(u"KAI (server): %.3fs (%d samples)" % (kai, len(self.keepalive_intervals)))
else:
self.send_notification(u"KAI (server): No data")
if len(self.keepalive_responses) > 0:
resptime = sum(self.keepalive_responses) / len(self.keepalive_responses)
self.send_notification(u"Response time (client): %.1fms (%d samples)" % (resptime * 1000.0, len(self.keepalive_intervals)))
else:
self.send_notification(u"Response time (client): No data")
self.ping_time = time.time()
self.send_to_server(u"/ping _gcproxy_stat_")
elif words[1] == u"bind":
if len(words) < 4:
self.send_notification(u"Usage: /prox bind <itemID> [<page>]")
else:
if words[2] == "hand":
self.send_notification(u"Binding hand item is not implemented yet!")
else:
id = int(words[2])
command = " ".join(words[3:])
self.bindings[id] = command
self.send_notification(u"Command bound!")
elif words[1] == u"unbind":
if len(words) < 3:
self.send_notification(u"Usage: /prox unbind <itemID>")
else:
id = int(words[2])
if id in self.bindings:
del self.bindings[id]
self.send_notification(u"Command unbound!")
else:
self.send_notification("That item ID is not bound!")
elif words[1] == u"bindings":
for id, command in self.page(self.bindings.items(), words[2:], "Bindings"):
self.send_notification(" %d: %s" % (id, command))
elif words[1] == u"stealth":
self.stealth = not self.stealth
if self.stealth:
self.send_notification(u"Stealth on")
else:
self.send_notification(u"Stealth off")
elif words[1] == u"boost":
if len(words) < 3:
self.send_notification(u"Usage: /prox boost <nickname>")
else:
player = words[2].lower()
if player in self.boosted:
self.boosted.remove(player)
self.send_notification(u"%s's chat is no longer boosted" % player)
else:
self.boosted.append(player)
self.send_notification(u"%s's chat is now boosted" % player)
elif words[1] == u"cseq":
if len(words) < 3:
self.send_notification(u"Usage: /prox cseq <name>")
else:
name = words[2]
if name in CSEQS:
for cmd in CSEQS[name]:
self.send_to_server(cmd)
else:
self.send_notification(u"Invalid cseq")
elif words[1] == u"repeat":
command = " ".join(words[2:])
self.tasks.add(Task(3, self.repeat, 1, command))
elif words[1] == u"repeati":
interval = int(words[2])
command = " ".join(words[3:])
self.tasks.add(Task(interval + 2, self.repeat, interval, command))
elif words[1] == u"norepeat":
self.stop_repeating = True
elif words[1] == u"admins":
admins = [x for x in self.players if x in ADMINS]
if len(admins):
self.send_to_server(u"/bcast Admins online: %s" % ", ".join(admins))
else:
self.send_to_server(u"/bcast Admins online: none")
elif words[1] == u"say":
self.send_to_server(u"&d [Server] " + " ".join(words[2:]))
#elif words[1] == u"as":
# player = words[2].lower()
# prefix = u"&b. " + self.player_prefixes[player]
# textlen = 98 - len(prefix)
#
# msg = " ".join(words[3:])
# while len(msg) > 0:
# s = msg[:textlen]
# msg = msg[textlen:]
#
# self.send_to_server(prefix + s)
elif words[1] == u"where":
player = words[2].lower()
if player in self.player_worlds:
self.send_notification(u"%s was last seen in %s" % (player, self.player_worlds[player]))
else:
self.send_notification(u"No data for %s" % player)
elif words[1] == u"cleanlag":
for entity in ENTITIES:
self.send_to_server(u"/remove %s 100000" % entity)
elif words[1] == u"timelock":
if len(words) < 3:
if self.timelock is None:
words.append("day")
else:
words.append("off")
if words[2] == "day":
self.timelock = "day"
self.send_notification(u"Time locked to day")
elif words[2] == "night":
self.timelock = "night"
self.send_notification(u"Time locked to night")
elif words[2] == "off":
self.timelock = None
self.send_notification(u"Timelock disabled")
else:
self.send_notification(u"Usage: /prox timelock [<day|night|off>]")
elif words[1] == u"setwarp":
if len(words) < 3:
self.send_notification(u"Usage: /prox setwarp <name>")
else:
name = words[2]
self.warps[name] = self.pos
self.send_notification(u"Warp '%s' set at (%d, %d, %d)" % ((name,) + self.pos))
elif words[1] == u"delwarp":
if len(words) < 3:
self.send_notification(u"Usage: /prox delwarp <name>")
else:
name = words[2]
if name in self.warps:
del self.warps[name]
self.send_notification(u"Warp '%s' deleted" % name)
else:
self.send_notification(u"Warp '%s' not found" % name)
elif words[1] == u"warp":
if len(words) < 3:
self.send_notification(u"Usage: /prox warp <name>")
else:
name = words[2]
if name in self.warps:
pos = self.warps[name]
self.send_to_server(u"/tppos %d %d %d" % pos)
else:
self.send_notification(u"Warp '%s' not found" % name)
elif words[1] == u"warps":
warps = self.warps.items()
warps.sort()
for name, pos in self.page(warps, words[2:], "Warps"):
self.send_notification(u" %s (%d, %d, %d)" % ((name,) + pos))
elif words[1] == u"tpall":
if len(self.tpall) == 0 or words[2:] == ["reset"]:
self.tpall = list(self.players)
self.tpall_num = len(self.tpall)
self.send_notification(u"Reset tpall with %d players" % self.tpall_num)
player = self.tpall.pop(0)
num = self.tpall_num - len(self.tpall)
self.send_notification(u"Player %d of %d: %s" % (num, self.tpall_num, player))
self.send_to_server(u"/tpo %s" % player)
elif words[1] == u"fall":
player = words[2]
self.send_to_server(u"/gamemode 0 %s" % player)
self.send_to_server(u"/gamemode 1 %s" % player)
elif words[1] == u"addhook":
if len(words) < 5:
self.send_notification(u"Usage: /prox addhook <j|q> <player> <command>")
return None
if words[2] == u"j":
hooks = self.join_hooks
elif words[2] == u"q":
hooks = self.quit_hooks
else:
self.send_notification(u"Usage: /prox addhook <j|q> <player> <command>")
return None
player = words[3].lower()
command = u" ".join(words[4:])
hooks.setdefault(player, []).append(command)
self.send_notification(u"Hook added to player %s" % player)
elif words[1] == u"hooks":
if len(words) < 4:
self.send_notification(u"Usage: /prox hooks <j|q> <player> [<page>]")
return None
if words[2] == u"j":
hooks = self.join_hooks
s = "Join"
elif words[2] == u"q":
hooks = self.quit_hooks
s = "Quit"
else:
self.send_notification(u"Usage: /prox hooks <j|q> <player> [<page>]")
return None
player = words[3].lower()
hooks = hooks.get(player, [])
for i, hook in self.page(enumerate(hooks), words[3:], u"%s hooks for %s" % (s, player)):
self.send_notification("%d) %s" % (i, hook))
elif words[1] == u"marry":
if len(words) < 4:
self.send_notification(u"Usage: /prox marry <player1> <player2>")
else:
player1 = words[2]
player2 = words[3]
self.send_to_server(u"/fsay %s /marry %s" % (player1, player2))
self.send_to_server(u"/fsay %s /marry accept" % (player2))
elif words[1] == u"note":
if len(words) < 3:
for name, message in self.page(sorted(self.notes.items()), words[2:], "Notes"):
self.send_notification(u" %s" % name)
elif len(words) < 4:
name = words[2]
if name in self.notes:
self.send_notification(u"%s: %s" % (name, self.notes[name]))
else:
self.send_notification(u"No note named '%s'!" % name)
else:
name = words[2]
message = u" ".join(words[3:])
self.notes[name] = message
self.send_notification(u"Note '%s' saved!" % name)
elif words[1] == u"delnote":
if len(words) < 3:
self.send_notification(u"Usage: /prox delnote <name>")
else:
name = words[2]
if name in self.notes:
del self.notes[name]
self.send_notification(u"Note '%s' deleted!" % name)
else:
self.send_notification(u"No note named '%s'!" % name)
elif words[1] == u"book":
if len(words) < 3:
self.send_notification(u"Usage:")
self.send_notification(u" /prox book list [<page>] - List books")
self.send_notification(u" /prox book read <book> [<page>] - Read a book")
elif words[2] == u"list":
for i, (_, title, author, pages) in self.page(enumerate(self.books), words[3:], "Books"):
self.send_notification(u" %d: %s by %s (%d pages)" % (i, title, author, len(pages)))
elif words[2] == u"read":
try:
booknum = int(words[3])
except (IndexError, ValueError):
self.send_notification(u"Invalid book number.")
return None
pagenum = 1
if len(words) > 4:
try:
pagenum = int(words[4])
except (IndexError, ValueError):
self.send_notification(u"Invalid page number.")
return None
(key, title, author, pages) = self.books[booknum]
if pagenum < 1 or pagenum > len(pages):
self.send_notification(u"Page number out of range.")
return None
page = pages[pagenum - 1]
self.send_notification(u"Page %d of %d" % (pagenum, len(pages)))
self.send_notification(page)
elif words[2] == u"gen":
try:
booknum = int(words[3])
except (IndexError, ValueError):
self.send_notification(u"Invalid page number.")
return None
(key, title, author, pages) = self.books[booknum]
page_tags = []
for page in pages:
page_tag = nbt.StringTag("")
page_tag.value = page
page_tags.append(page_tag)
if len(pages) == 0 or not pages[-1].startswith("Generated by GCProxy"):
note_tag = nbt.StringTag("")
note_tag.value = "Generated by GCProxy\nOriginally by %s\n" % author
page_tags.append(note_tag)
pages_tag = nbt.ListTag("pages")
pages_tag.value = (nbt.StringTag, page_tags)
tag = nbt.CompoundTag("")
tag.value = {}
tag.value["pages"] = pages_tag
buf = StringIO.StringIO()
g = gzip.GzipFile(mode="wb", fileobj=buf)
tag.write(g)
g.close()
data = buf.getvalue()
self.to_server({
"msgtype": 0xFA,
"channel": u"MC|BEdit",
"data": struct.pack(">HBHH", 386, 1, 0, len(data)) + data,
})
#self.to_client({
# "msgtype": 0x67,
# "window_id": 0,
# "slot": self.held_item + 36,
# "slot_update": {
# "item_id": 386,
# "count": 1,
# "uses": 0,
# "nbt_size": len(data),
# "nbt_data": data,
# },
#})
tag.value["title"] = nbt.StringTag("title")
tag.value["title"].value = title
tag.value["author"] = nbt.StringTag("author")
tag.value["author"].value = "kierdavis"
buf = StringIO.StringIO()
g = gzip.GzipFile(mode="wb", fileobj=buf)
tag.write(g)
g.close()
data = buf.getvalue()
self.to_server({
"msgtype": 0xFA,
"channel": u"MC|BSign",
"data": struct.pack(">HBHH", 387, 1, 0, len(data)) + data,
})
#self.to_client({
# "msgtype": 0x67,
# "window_id": 0,
# "slot": self.held_item + 36,
# "slot_update": {
# "item_id": 387,
# "count": 1,
# "uses": 0,
# "nbt_size": len(data),
# "nbt_data": data,
# },
#})
elif words[1] == u"sel":
if len(words) < 3:
self.send_notification(u"List of selections:")
selections = self.selections
for name, (a, b) in selections.iteritems():
self.send_notification(u" %s: (%d, %d, %d) (%d, %d, %d)" % (name, a[0], a[1], a[2], b[0], b[1], b[2]))
elif words[2] == u"save":
if len(words) < 4:
self.send_notification(u"Usage: /prox sel save <name>")
else:
name = words[3]
if self.selection is None:
self.send_to_server(u"//size")
self.tasks.add(Task(2, self.save_selection, name))
else:
self.save_selection(name)
elif words[2] == u"load":
if len(words) < 4:
self.send_notification(u"Usage: /prox sel load <name>")
else:
name = words[3]
self.selection = self.selections[name]
a, b = self.selection
self.send_notification(u"Loaded selection %s: (%d, %d, %d) (%d, %d, %d)" % (name, a[0], a[1], a[2], b[0], b[1], b[2]))
elif words[1] == u"plot":
if len(words) < 3:
self.send_notification(u"Plot commands:")
self.send_notification(u" /prox plot add <name> [<id>] - Add a plot")
self.send_notification(u" /prox plot del <name> - Delete a plot")
self.send_notification(u" /prox plot delall - Delete all plots")
self.send_notification(u" /prox plot list [<page>] - List plots")
self.send_notification(u" /prox plot <name> - Go to a plot")
elif words[2] == u"add":
if len(words) < 4:
self.send_notification(u"Usage: /prox plot add <id> <name>")
elif len(words) < 5:
name = words[3]
self.tasks.add(Task(2, self.add_plot, name))
return u"/plotme id"
else:
name = words[3]
id = words[4]
self.plots[name] = id
self.send_notification(u"Plot '%s' added with ID '%s'" % (name, id))
elif words[2] == u"del":
if len(words) < 4:
self.send_notification(u"Usage: /prox plot del <name>")
else:
name = words[3]
if name in self.plots:
del self.plots[name]
self.send_notification(u"Deleted plot '%s'" % name)
else:
self.send_notification(u"Plot '%s' not found" % name)
elif words[2] == u"delall":
self.send_notification("Are you sure you want to delete all plots? Say 'yes' if you want to continue or anything else to cancel.")
self.awaiting_response = self.delall
elif words[2] == u"list":
plots = self.plots.items()
plots.sort()
if len(plots) == 0:
self.send_notification(u"No plots")
else:
for name, id in self.page(plots, words[3:], "Plots"):
self.send_notification(u" %s (plot %s)" % (name, id))
else:
name = words[2]
if name in self.plots:
id = self.plots[name]
return u"/plotme tp %s" % id
else:
self.send_notification(u"Plot '%s' not found" % name)
elif words[1] == u"ml":
if len(words) < 3:
self.send_notification(u"Mailing list commands:")
self.send_notification(u" /prox ml new <name> - Create a mailing list")
self.send_notification(u" /prox ml del <name> - Delete a mailing list")
self.send_notification(u" /prox ml add <name> <players...> - Add players to a mailing list")
self.send_notification(u" /prox ml rm <name> <players...> - Remove players from a mailing list")
self.send_notification(u" /prox ml list [<page>] - List mailing lists")
self.send_notification(u" /prox ml show <name> [<page>] - Show info about a mailing list")
self.send_notification(u" /prox ml send <name> <message> - Send a message to a mailing list")
elif words[2] == u"new":
if len(words) < 4:
self.send_notification(u"Usage: /prox ml new <name>")
else:
name = words[3]
self.mls[name] = set()
self.send_notification(u"Mailing list '%s' created" % name)
elif words[2] == u"del":
if len(words) < 4:
self.send_notification(u"Usage: /prox ml del <name>")
else:
name = words[3]
if name in self.mls:
del self.mls[name]
self.send_notification(u"Mailing list '%s' deleted" % name)
else:
self.send_notification(u"Mailing list '%s' does not exist" % name)
elif words[2] == u"add":
if len(words) < 5:
self.send_notification(u"Usage: /prox ml add <name> <players...>")
else:
name = words[3]
if name in self.mls:
players = words[4:]
self.mls[name].update(players)
self.send_notification(u"%d players added to mailing list '%s' (now has %d members)" % (len(players), name, len(self.mls[name])))
else:
self.send_notification(u"Mailing list '%s' does not exist" % name)
elif words[2] == u"rm":
if len(words) < 5:
self.send_notification(u"Usage: /prox ml rm <name> <players...>")
else:
name = words[3]
if name in self.mls:
players = words[4:]
self.mls[name].difference_update(players)
self.send_notification(u"%d players removed from mailing list '%s' (now has %d members)" % (len(players), name, len(self.mls[name])))
else:
self.send_notification(u"Mailing list '%s' does not exist" % name)
elif words[2] == u"list":
for name in self.page(self.mls.keys(), words[3:], u"Mailing lists"):
self.send_notification(u" %s" % name)
elif words[2] == u"show":
if len(words) < 4:
self.send_notification(u"Usage: /prox ml show <name> [<page>]")
else:
name = words[3]
if name in self.mls:
for player in self.page(self.mls[name], words[4:], u"Mailing list '%s'" % name):
self.send_notification(u" %s" % player)
else:
self.send_notification(u"Mailing list '%s' does not exist" % name)
elif words[2] == u"send":
if len(words) < 5:
self.send_notification(u"Usage: /prox ml send <name> <message>")
else:
name = words[3]
if name in self.mls:
message = " ".join(words[4:])
self.mlsend(name, message)
else:
self.send_notification(u"Mailing list '%s' does not exist" % name)
else:
self.send_notification(u"Invalid subcommand.")
return None
return msg
def mlsend(self, name, message):
for player in self.mls[name]:
self.mail_queue.append((player, message))
n = len(self.mls[name])
self.send_to_server(u"/ GCPROXY: Mass-mailing of %d messages queued" % n)
def incoming_mlsend(self, msg, unformatted):
m = re.match(r"([a-zA-Z0-9_]+): ML:([a-zA-Z0-9_]+) (.+)", msg)
if m is not None and msg not in self.ml_cache:
self.ml_cache.append(msg)
sender = m.group(1)
name = m.group(2)
message = m.group(3)
self.mlsend(name, message)
return msg
def outgoing_mailclear(self, msg):
if msg == u"/mail clear":
for i in xrange(len(self.ml_cache)):
self.ml_cache.pop(0)
self.send_notification(u"Cleared ML cache")
return msg
def incoming_tpa(self, msg, unformatted):
if unformatted == "To deny this request, type /tpdeny." and self.tpa:
self.tasks.add(Task(3, self.send_to_server, u"/tpaccept"))
return msg
def replace_rainbow(self, m):
return self.rainbow(m.group(1))
def rainbow(self, text):
out = ""
for c in text:
out += "&%s%s" % (random.choice("1234569abcde"), c)
return out + "&r"#&" + CHAT_COLOUR
#def outgoing_commands(self, msg):
# words = msg.split()
# if words[0] in OBFUSCATED_COMMANDS:
# words[1] = self.obfuscate_player(words[1])
# msg = " ".join(words)
#
# return msg
def outgoing_formatting(self, msg):
msg = re.sub(r"@@(.+?)@@", self.replace_rainbow, msg)
if msg[0] in "!/":
return msg
out = u""
props = []
i = 0
while i < len(msg):
prop = None
if msg[i:i+2] == u"##": prop = u"k"
elif msg[i:i+2] == u"**": prop = u"l"
elif msg[i:i+2] == u"~~": prop = u"m"
elif msg[i:i+2] == u"__": prop = u"n"
elif msg[i:i+2] == u"\\\\": prop = u"o"
elif msg[i:i+2] == u"<<" and msg[i+3] == u":":
prop = msg[i+2]
i += 2
elif msg[i:i+2] == u">>":
for p in reversed(props):
if p in u"0123456789abcdefABCEF":
prop = p
break
if prop is not None:
i += 1
if prop in props:
props.remove(prop)
out += u"&r&" + CHAT_COLOUR
for prop in props:
out += u"&" + prop
else:
props.append(prop)
out += u"&" + prop
else:
out += msg[i]
i += 1
return out
def save_selection(self, name):
self.selections[name] = self.selection
a, b = self.selection
self.send_notification(u"Saved selection %s: (%d, %d, %d) (%d, %d, %d)" % (name, a[0], a[1], a[2], b[0], b[1], b[2]))
def repeat(self, interval, command):
if self.stop_repeating:
self.stop_repeating = False
self.send_notification(u"Stopped repeating: %s" % command)
return
processed_command = self.process_outgoing(command)
if processed_command is not None:
self.send_to_server(processed_command)
self.tasks.add(Task(interval, self.repeat, interval, command))
def add_plot(self, name):
if self.plot_id is None:
self.send_notification(u"No plot ID received!")
else:
(id, self.plot_id) = (self.plot_id, None)
self.plots[name] = id
self.send_notification(u"Plot '%s' added with ID '%s'" % (name, id))
def on_ready(self):
print "Initialisation commands"
self.send_to_server(u"//limit %d" % WE_LIMIT)
self.send_to_server(u"/commandspy on")
self.send_to_server(u"/f chatspy on")
self.send_to_server(u"/mail read")
def send_to_client(self, msg):
print "To client: %s" % msg.encode("utf-8")
self.to_client({"msgtype": 0x03, "chat_msg": msg})
def is_unstealthy(self, msg):
return self.stealth and (msg[0] != "/" or msg.split()[0] in UNSTEALTHY_COMMANDS)
def send_to_server(self, msg):
if self.is_unstealthy(msg):
self.send_notification(u"'%s' rejected due to stealth mode" % msg)
return
while len(msg) > 0:
part, msg = msg[:99], msg[99:]
print "To server: %s" % part.encode("utf-8")
self.to_server({"msgtype": 0x03, "chat_msg": part})
def outgoing_stealth(self, msg):
if self.is_unstealthy(msg):
self.send_notification(u"Message rejected due to stealth mode")
return None
return msg
def send_notification(self, msg):
self.send_to_client(u"\xa77[GCProxy] %s\xa7r" % msg)
def delall(self, msg):
if msg == "yes":
self.plots.clear()
self.send_notification("All plots deleted")
else:
self.send_notification("Operation aborted")
def log(self, type, format, *args):
line = "[%s] %-4s %s" % (time.asctime(), type, format % args)
self.logfile.write(line + "\n")
self.logfile.flush()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment