forked from rebane2001/txnor-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bcedb3b
commit 26c7c27
Showing
10 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
# txnor-server | ||
Server software running on txnor.com for the Discord s/e/x "hack". | ||
|
||
`txnor.nginx` is an nginx site configuration. | ||
|
||
Note: You'll have to supply your own impact.ttf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
from PIL import Image, ImageFont, ImageDraw | ||
from cairosvg import svg2png | ||
import random | ||
import re | ||
import io | ||
|
||
import chess | ||
import chess.engine | ||
import chess.svg | ||
|
||
# Start the stockfish engine | ||
engine = chess.engine.SimpleEngine.popen_uci("stockfish") | ||
|
||
valid_move_pattern = re.compile("^[a-h][0-8][a-h][0-8]q?$") | ||
valid_move_pattern_open = re.compile("[a-h][0-8][a-h][0-8]q?") | ||
|
||
board_colors = { | ||
"square light": "#FFFFFF", | ||
"square dark": "#5865F2", | ||
"square light lastmove": "#57F287", | ||
"square dark lastmove": "#57F287", | ||
} | ||
|
||
""" | ||
The implementation of python-chess and stockfish in this file was rushed and isn't very good. | ||
Please refer to the actual documentation if you wish to use them in your own project. | ||
""" | ||
|
||
|
||
def process_url(url): | ||
"""Generate moves from a URL.""" | ||
url = url.lower() | ||
moves = valid_move_pattern_open.findall(url)[::-1] | ||
|
||
# Generate a seed from the URL | ||
seed = 0 | ||
seed_str = url.split("/")[-1] | ||
if "vieag" in seed_str or "vixag" in seed_str: | ||
seed_str = "" | ||
for c in seed_str: | ||
seed = (seed + ord(c)) % 1024 | ||
|
||
return generate_state(moves, seed) | ||
|
||
|
||
def generate_state(moves, seed): | ||
"""Generate a game state from the moves.""" | ||
|
||
# Create a new board and game | ||
board = chess.Board() | ||
game_id = random.random() | ||
result = None | ||
|
||
for move in moves: | ||
try: | ||
# Just skip invalid moves | ||
if not valid_move_pattern.match(move): | ||
continue | ||
if chess.Move.from_uci(move) not in board.legal_moves: | ||
continue | ||
|
||
# Add player move | ||
board.push(chess.Move.from_uci(move)) | ||
if board.is_game_over(): | ||
break | ||
|
||
# Add stockfish move | ||
# The way the engine is configured/seeded is bad, it is only left this way to keep older play URLs intact | ||
result = engine.play(board, chess.engine.Limit(depth=1024 + seed, nodes=1024 + seed), game=game_id) | ||
result = result.move | ||
board.push(result) | ||
if board.is_game_over(): | ||
break | ||
except Exception as e: | ||
# If a move errors, we just skip over the move | ||
print(e) | ||
continue | ||
|
||
# Generate an SVG of the board | ||
svg = chess.svg.board(board, lastmove=result, colors=board_colors, size=300) | ||
board_png = io.BytesIO() | ||
svg2png(bytestring=svg, write_to=board_png) | ||
|
||
# Generate an empty image | ||
base = Image.new("RGBA", (395, 300), (0, 0, 0, 0)) | ||
img = Image.open(board_png).convert("RGBA") | ||
|
||
# Generate an empty image | ||
base.paste(img, (0, 0), img) | ||
|
||
# Draw moves list as text | ||
draw = ImageDraw.Draw(base) | ||
font = ImageFont.truetype("impact.ttf", 24) | ||
draw.text((323, 3), "\n".join([str(stack).upper() for stack in board.move_stack[::-1]]), (255, 255, 255), font=font, | ||
stroke_width=2, stroke_fill=(0, 0, 0)) | ||
|
||
# Draw example move text | ||
font = ImageFont.truetype("impact.ttf", 14) | ||
draw.text((304, 260), "example move:", (255, 255, 255), font=font, stroke_width=2, stroke_fill=(0, 0, 0)) | ||
font = ImageFont.truetype("impact.ttf", 18) | ||
draw.text((304, 276), "s/g/$&b1c3", (255, 255, 255), font=font, stroke_width=2, stroke_fill=(0, 0, 0)) | ||
|
||
# If the game is over, draw the outcome | ||
if board.is_game_over(): | ||
outcome = board.outcome() | ||
font = ImageFont.truetype("impact.ttf", 96) | ||
if outcome.winner is None: | ||
draw.text((4, 84), "wtf draw?", (255, 255, 255), font=font, stroke_width=4, stroke_fill=(0, 0, 0)) | ||
elif outcome.winner == chess.WHITE: | ||
draw.text((46, 84), "u win", (255, 255, 255), font=font, stroke_width=4, stroke_fill=(0, 0, 0)) | ||
elif outcome.winner == chess.BLACK: | ||
draw.text((33, 84), "u lose", (255, 255, 255), font=font, stroke_width=4, stroke_fill=(0, 0, 0)) | ||
|
||
# Return the image to the client | ||
out = io.BytesIO() | ||
base.save(out, "PNG") | ||
return out.getvalue() | ||
|
||
# You can quit the engine like this: | ||
# engine.quit() |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
from PIL import Image, ImageFont, ImageDraw, ImageEnhance | ||
import requests | ||
import random | ||
import web | ||
import io | ||
import re | ||
|
||
# Chess | ||
import chessgame | ||
|
||
# Configure the app | ||
app = web.application(('(.*)', 'SexHack'), globals()) | ||
web.config.debug = False | ||
host = "127.0.0.1" | ||
port = 8000 | ||
|
||
# Load images into memory | ||
with open("img/sex_default.png", "rb") as f: | ||
default_img = f.read() | ||
with open("img/sex_base_game.png", "rb") as f: | ||
base_img = f.read() | ||
with open("img/6969th.png", "rb") as f: | ||
six_nine = f.read() | ||
|
||
# User-Agent for Tenor requests, it's nice to tell them who you are | ||
headers = { | ||
'User-Agent': 'A Discord Bot by an unknown person using github.com/rebane2001/txnor-server', | ||
} | ||
|
||
|
||
class SexHack: | ||
@staticmethod | ||
def default_response(): | ||
"""Return default image""" | ||
return default_img | ||
|
||
@staticmethod | ||
def pony(): | ||
"""Return an image from Derpibooru""" | ||
r = requests.get( | ||
"https://derpibooru.org/api/v1/json/search/images?" | ||
"q=pony,score.gte:1000,aspect_ratio:1,safe&sf=random&per_page=1", | ||
headers=headers, timeout=3) | ||
medium = r.json()["images"][0]["representations"]["medium"] | ||
content = requests.get(medium, headers=headers, timeout=3).content | ||
return content | ||
|
||
@staticmethod | ||
def math_challenge(): | ||
"""Return a random math challenge""" | ||
# Create new image | ||
img = Image.new("RGBA", (450, 135), (255, 255, 255, 255)) | ||
|
||
# Draw text on the image | ||
draw = ImageDraw.Draw(img) | ||
font = ImageFont.truetype("impact.ttf", 32) | ||
draw.text((450 / 2, 10), f"Math challenge (99% fail):", (255, 255, 255), font=font, anchor="ma", | ||
stroke_width=2, stroke_fill=(0, 0, 0)) | ||
|
||
# Generate a random math challenge | ||
num_a = random.randint(2, 6) | ||
num_b = int(random.randint(3, 12) * num_a) | ||
num_c = random.randint(3, 24) | ||
|
||
# Draw the math challenge as text | ||
font = ImageFont.truetype("impact.ttf", 64) | ||
draw.text((450 / 2, 50), f"{num_b} / {num_a} + {num_c}", (255, 255, 255), font=font, anchor="ma", | ||
stroke_width=2, stroke_fill=(0, 0, 0)) | ||
|
||
# Return the image to the client | ||
out = io.BytesIO() | ||
img.save(out, "PNG") | ||
return out.getvalue() | ||
|
||
@staticmethod | ||
def generate_img(name): | ||
"""Generate s/e/x or double s/e/x image""" | ||
url = f"https://tenor.com/view/{name[6:]}" | ||
r = requests.get(url, headers=headers, timeout=3) | ||
search = re.search(r'<meta class="dynamic" name="twitter:image" content="https://c.tenor.com/([^"]*)">', | ||
r.text) | ||
img_url = f"https://c.tenor.com/{search.group(1)}".replace("AAC/", "AAe/") | ||
img_raw = requests.get(img_url, stream=True, headers=headers, timeout=3).raw | ||
|
||
img = Image.open(img_raw).resize((350, 185)).convert("RGB") | ||
|
||
draw = ImageDraw.Draw(img) | ||
font = ImageFont.truetype("impact.ttf", 32) | ||
draw.text((350 / 2, 0), "WTF DISCORD SEX", (255, 255, 255), font=font, anchor="ma", stroke_width=2, | ||
stroke_fill=(0, 0, 0)) | ||
draw.text((350 / 2, 185), "??? WTF ??? SEX ???", (255, 255, 255), font=font, anchor="md", stroke_width=2, | ||
stroke_fill=(0, 0, 0)) | ||
|
||
# JPEGify the image slightly | ||
jpg = io.BytesIO() | ||
img.save(jpg, "JPEG", quality=10) | ||
img = Image.open(jpg).resize((190, 135)).convert("RGBA") | ||
|
||
# Apply the GIF thumbnail to the template | ||
base = Image.open(io.BytesIO(base_img)) | ||
base.paste(img, (5, 159)) | ||
|
||
double_sex = name[3] == "x" | ||
if double_sex: | ||
# Draw text | ||
draw = ImageDraw.Draw(base) | ||
font = ImageFont.truetype("impact.ttf", 36) | ||
draw.text((394 / 2, -10), "ULTRA DOUBLE SEX", (255, 255, 255), font=font, anchor="ma", stroke_width=2, | ||
stroke_fill=(0, 0, 0)) | ||
draw.text((394 / 2, 295), "ACTIVATED ??? HOW", (255, 255, 255), font=font, anchor="md", stroke_width=2, | ||
stroke_fill=(0, 0, 0)) | ||
|
||
# Add a red-ish background | ||
bg = Image.new("RGBA", base.size, (35, 0, 0, 255)) | ||
bg.paste(base, (0, 0), base) | ||
|
||
# JPEGify and saturate the image | ||
jpg = io.BytesIO() | ||
bg.convert("RGB").resize((300, 200)).save(jpg, "JPEG", quality=50) | ||
base = Image.open(jpg).convert("RGB") | ||
base = ImageEnhance.Color(base).enhance(10.5).resize((395, 300)) | ||
|
||
# Return the image to the client | ||
out = io.BytesIO() | ||
base.save(out, "PNG") | ||
return out.getvalue() | ||
|
||
def handle_request(self, name): | ||
try: | ||
# 6969th winner image (disable for chess) | ||
if random.randint(0, 6969) == 6969 and "ag" not in name: | ||
web.header('Cache-Control', 'no-store') | ||
return six_nine | ||
|
||
# Math challenge | ||
if re.search(r'^/(math|math_?challenge)/?[A-Za-z0-9_-]*$', name): | ||
web.header('Cache-Control', 'no-store') | ||
return self.math_challenge() | ||
|
||
# /viqw/ | ||
if re.search(r'^/vi(q|questrian?)w/[A-Za-z0-9_-]*$', name): | ||
web.header('Cache-Control', 'no-store') | ||
return self.pony() | ||
|
||
# Enable caching from this point onward | ||
web.header('Cache-Control', 'public, max-age=86400') | ||
|
||
# /vieag/ and /vixag/ | ||
if re.search(r'^/vi[ex]ag[A-Za-z0-9]*/[A-Za-z0-9_-]*$', name): | ||
return chessgame.process_url(name) | ||
|
||
# /view/ and /vixw/ | ||
if re.search(r'^/vi[ex]w/[A-Za-z0-9_-]*$', name): | ||
return self.generate_img(name) | ||
except Exception as e: | ||
"""We catch Exceptions because we want to show the default image instead of an error page""" | ||
print(e) | ||
|
||
def GET(self, name): | ||
"""Handle a request""" | ||
web.header('Content-type', 'image/png') | ||
# Return default image if one was not generated | ||
return self.handle_request(name) or self.default_response() | ||
|
||
|
||
if __name__ == "__main__": | ||
web.httpserver.runsimple(app.wsgifunc(), (host, port)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:16m inactive=24h max_size=8g; | ||
|
||
server { | ||
server_name txnor.com c.txnor.com; | ||
|
||
charset UTF-8; | ||
root /var/www/txnor.com; | ||
|
||
location @fallback { | ||
if ($http_user_agent ~* "(Intel Mac OS X 11.6; rv:92.0|Discord)") { | ||
rewrite ^ /discord_sex.png break; | ||
} | ||
} | ||
|
||
location / { | ||
# Cache | ||
proxy_buffering on; | ||
proxy_cache STATIC; | ||
proxy_cache_valid 200 1d; | ||
proxy_cache_use_stale error timeout invalid_header updating | ||
http_500 http_502 http_503 http_504; | ||
|
||
# Request from Discord | ||
if ($http_user_agent ~* "(Intel Mac OS X 11.6; rv:92.0|Discord)") { | ||
error_page 502 = @fallback; | ||
proxy_pass http://127.0.0.1:8000; | ||
} | ||
|
||
# Request from anyone else | ||
if ($http_user_agent !~* "(Intel Mac OS X 11.6; rv:92.0|Discord)") { | ||
return 301 https://twitter.com/rebane2001/status/1521538992524644352; | ||
} | ||
} | ||
listen 443 ssl; | ||
|
||
# Certificate stuff here | ||
} | ||
|
||
server { | ||
if ($host = txnor.com) { | ||
return 301 https://$host$request_uri; | ||
} | ||
|
||
listen 80 ; | ||
server_name txnor.com; | ||
} |