Compare commits
	
		
			13 Commits
		
	
	
		
			1e6ea9e51a
			...
			current
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9a4a8155e2 | |||
| 96b3f76ea2 | |||
| 91ff9625ec | |||
| 8d7bc7e096 | |||
| a4a5749789 | |||
| 94a4849d90 | |||
| 628297cae7 | |||
|   | e8f3368dc4 | ||
|   | ab8b138e0c | ||
|   | 768274f547 | ||
|   | c40603e5ad | ||
|   | f31ac862dd | ||
|   | 29d152b939 | 
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1 @@ | |||||||
| *.txt | portable_python | ||||||
| *.sgf |  | ||||||
| *.png |  | ||||||
| *.out |  | ||||||
| *.sh |  | ||||||
| __pycache__/ |  | ||||||
| rengobot_venv/ |  | ||||||
| @@ -1,8 +1,2 @@ | |||||||
| A discord bot for playing rengo games! | this is a repository of useful python scripts | ||||||
|  |  | ||||||
| # Dependencies |  | ||||||
| - sgf-render |  | ||||||
| - python-discord |  | ||||||
| - python-sgfmill |  | ||||||
|  |  | ||||||
| Make sure to run the bot in an environment with read/write permissions |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								caldav/htpasswd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								caldav/htpasswd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | username:password | ||||||
							
								
								
									
										26
									
								
								caldav/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								caldav/readme.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|  | how to make python portable | ||||||
|  |  | ||||||
|  | https://chat.mistral.ai/chat/ab946d87-22e6-47d4-a99c-e8522c077347 | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | wget https://www.python.org/ftp/python/3.13.7/Python-3.13.7.tgz | ||||||
|  | tar -xzf Python-3.13.7.tgz | ||||||
|  | rm Python-3.13.7.tgz | ||||||
|  | cd Python-3.13.7 | ||||||
|  | ./configure --enable-optimizations --with-ensurepip=install --prefix=$(pwd)/portable_python | ||||||
|  | make -j$(nproc) | ||||||
|  | make install | ||||||
|  | # now in portable_python install packages that are needed | ||||||
|  | ./portable_python/bin/pip3 install bcrypt | ||||||
|  | ./portable_python/bin/pip3 install radicale | ||||||
|  | cd .. | ||||||
|  | mv Python-3.13.7/portable_python portable_python | ||||||
|  | rm -rf Python-3.13.7  | ||||||
|  | cd .. | ||||||
|  | tar -czvf caldav.tar.gz caldav/ | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | tar -xzf caldav.tar.gz | ||||||
							
								
								
									
										34
									
								
								caldav/server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								caldav/server.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | from radicale import Application, config | ||||||
|  | from wsgiref.simple_server import make_server | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | def run_server(): | ||||||
|  |     # Load default configuration | ||||||
|  |     configuration = config.load() | ||||||
|  |  | ||||||
|  |     # Customize settings | ||||||
|  |     configuration.update({ | ||||||
|  |         "server": { | ||||||
|  |             "hosts": "0.0.0.0:5232",  # Listen on all interfaces | ||||||
|  |         }, | ||||||
|  |         | ||||||
|  |         "storage": { | ||||||
|  |             "filesystem_folder": "./calendars",  # Store calendars here | ||||||
|  |         }, | ||||||
|  |         "auth": { | ||||||
|  |             "type": "htpasswd", | ||||||
|  |             "htpasswd_filename": "./htpasswd",  # Path to htpasswd file | ||||||
|  |             "htpasswd_encryption": "autodetect",  # or "sha1", "md5" (bcrypt is most secure) | ||||||
|  |         }, | ||||||
|  |        | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     # Create and run the app | ||||||
|  |     app = Application(configuration=configuration) | ||||||
|  |     server = make_server("0.0.0.0", 5232, app) | ||||||
|  |     print("✅ Secure CalDAV server running on http://0.0.0.0:5232") | ||||||
|  |     print("🔐 Authentication required. Default user: admin / password") | ||||||
|  |     server.serve_forever() | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     run_server() | ||||||
							
								
								
									
										2
									
								
								python_website_monitor/credentials.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								python_website_monitor/credentials.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | email | ||||||
|  | password | ||||||
							
								
								
									
										120
									
								
								python_website_monitor/server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								python_website_monitor/server.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | |||||||
|  | import requests | ||||||
|  | import time | ||||||
|  | import smtplib | ||||||
|  | from email.mime.text import MIMEText | ||||||
|  | import sqlite3 | ||||||
|  | from datetime import datetime, timedelta | ||||||
|  |  | ||||||
|  | def read_credentials(file_path): | ||||||
|  |     with open(file_path, 'r') as file: | ||||||
|  |         lines = file.readlines() | ||||||
|  |         email = lines[0].strip() | ||||||
|  |         password = lines[1].strip() | ||||||
|  |     return email, password | ||||||
|  |  | ||||||
|  | def check_website(url, timeout=5): | ||||||
|  |     try: | ||||||
|  |         response = requests.get(url, timeout=timeout) | ||||||
|  |         return response.status_code == 200 | ||||||
|  |     except requests.RequestException: | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  | def send_email(subject, body, to_email, from_email, from_password, smtp_server): | ||||||
|  |     msg = MIMEText(body) | ||||||
|  |     msg['Subject'] = subject | ||||||
|  |     msg['From'] = from_email | ||||||
|  |     msg['To'] = to_email | ||||||
|  |  | ||||||
|  |     with smtplib.SMTP_SSL(smtp_server, 465) as server: | ||||||
|  |         server.login(from_email, from_password) | ||||||
|  |         server.sendmail(from_email, to_email, msg.as_string()) | ||||||
|  |  | ||||||
|  | def create_database(db_path): | ||||||
|  |     conn = sqlite3.connect(db_path) | ||||||
|  |     cursor = conn.cursor() | ||||||
|  |     cursor.execute(''' | ||||||
|  |         CREATE TABLE IF NOT EXISTS website_status ( | ||||||
|  |             id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||
|  |             timestamp DATETIME NOT NULL, | ||||||
|  |             status INTEGER NOT NULL | ||||||
|  |         ) | ||||||
|  |     ''') | ||||||
|  |     conn.commit() | ||||||
|  |     conn.close() | ||||||
|  |  | ||||||
|  | def log_website_status(db_path, status): | ||||||
|  |     conn = sqlite3.connect(db_path) | ||||||
|  |     cursor = conn.cursor() | ||||||
|  |     timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | ||||||
|  |     cursor.execute(''' | ||||||
|  |         INSERT INTO website_status (timestamp, status) | ||||||
|  |         VALUES (?, ?) | ||||||
|  |     ''', (timestamp, 1 if status else 0)) | ||||||
|  |     conn.commit() | ||||||
|  |     conn.close() | ||||||
|  |  | ||||||
|  | def get_last_status(db_path): | ||||||
|  |     conn = sqlite3.connect(db_path) | ||||||
|  |     cursor = conn.cursor() | ||||||
|  |     cursor.execute(''' | ||||||
|  |         SELECT status FROM website_status | ||||||
|  |         ORDER BY id DESC LIMIT 1 | ||||||
|  |     ''') | ||||||
|  |     result = cursor.fetchone() | ||||||
|  |     conn.close() | ||||||
|  |     return result[0] if result else None | ||||||
|  |  | ||||||
|  | def print_email_sent(status_str, timestamp): | ||||||
|  |     print(f"[{timestamp}] Email sent: Website is now {status_str}") | ||||||
|  |  | ||||||
|  | def cleanup_old_logs(db_path, days_to_keep=10): | ||||||
|  |     cutoff_date = (datetime.now() - timedelta(days=days_to_keep)).strftime('%Y-%m-%d %H:%M:%S') | ||||||
|  |     conn = sqlite3.connect(db_path) | ||||||
|  |     cursor = conn.cursor() | ||||||
|  |     cursor.execute(''' | ||||||
|  |         DELETE FROM website_status | ||||||
|  |         WHERE timestamp < ? | ||||||
|  |     ''', (cutoff_date,)) | ||||||
|  |     deleted_rows = cursor.rowcount | ||||||
|  |     conn.commit() | ||||||
|  |     conn.close() | ||||||
|  |     return deleted_rows | ||||||
|  |  | ||||||
|  | def main(): | ||||||
|  |     url = "http://petrovv.com" | ||||||
|  |     to_email = "nikola@petrovv.com" | ||||||
|  |     check_interval = 600  # 10 minutes in seconds | ||||||
|  |     credentials_file = "credentials.txt" | ||||||
|  |     db_path = "website_status.db" | ||||||
|  |     smtp_server = "mail.petrovv.com" | ||||||
|  |  | ||||||
|  |     create_database(db_path) | ||||||
|  |     from_email, from_password = read_credentials(credentials_file) | ||||||
|  |  | ||||||
|  |     last_status = None | ||||||
|  |     last_cleanup = datetime.now() | ||||||
|  |  | ||||||
|  |     while True: | ||||||
|  |         current_time = datetime.now() | ||||||
|  |         current_status = check_website(url) | ||||||
|  |         log_website_status(db_path, current_status) | ||||||
|  |  | ||||||
|  |         if last_status is None or current_status != (last_status == 1): | ||||||
|  |             status_str = "up" if current_status else "down" | ||||||
|  |             subject = f"Website Status Change: {status_str}" | ||||||
|  |             body = f"The website {url} is now {status_str} at {current_time.strftime('%Y-%m-%d %H:%M:%S')}." | ||||||
|  |             send_email(subject, body, to_email, from_email, from_password, smtp_server) | ||||||
|  |             print_email_sent(status_str, current_time.strftime('%Y-%m-%d %H:%M:%S')) | ||||||
|  |             last_status = 1 if current_status else 0 | ||||||
|  |  | ||||||
|  |         # Perform cleanup once per day | ||||||
|  |         if (current_time - last_cleanup).days >= 1: | ||||||
|  |             deleted = cleanup_old_logs(db_path, 10) | ||||||
|  |             if deleted > 0: | ||||||
|  |                 print(f"[{current_time.strftime('%Y-%m-%d %H:%M:%S')}] Cleanup: Deleted {deleted} logs older than 10 days") | ||||||
|  |             last_cleanup = current_time | ||||||
|  |  | ||||||
|  |         time.sleep(check_interval) | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
							
								
								
									
										497
									
								
								rengobot.py
									
									
									
									
									
								
							
							
						
						
									
										497
									
								
								rengobot.py
									
									
									
									
									
								
							| @@ -1,497 +0,0 @@ | |||||||
| import os |  | ||||||
| import ast |  | ||||||
| import time |  | ||||||
| from datetime import datetime, timedelta |  | ||||||
| import asyncio |  | ||||||
|  |  | ||||||
| import sgfengine |  | ||||||
|  |  | ||||||
| import discord |  | ||||||
| from discord.ext import commands |  | ||||||
|  |  | ||||||
| # We don't use fancy slash commands here. It seems there is this library for python but it looks a bit more involved. |  | ||||||
| # https://pypi.org/project/discord-py-slash-command/ |  | ||||||
|  |  | ||||||
| bot = commands.Bot(command_prefix='$', help_command=None) |  | ||||||
|  |  | ||||||
| min_time_player= timedelta(seconds=1) # in random games, min time between same player plays (default days=1) |  | ||||||
| time_to_skip= timedelta(days=1) # in queue games, how much time to wait for the next move |  | ||||||
| min_players = 2 |  | ||||||
|  |  | ||||||
| # People who can start and resign games :O |  | ||||||
| # Later we might replace this with checking for a role. |  | ||||||
| admins=[ 756220448118669463, # Young Sun |  | ||||||
|          732403731810877450, # Yeonwoo |  | ||||||
|          145294584077877249, # Mrchance |  | ||||||
|          477895596141707264  # René |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
| teachers=[ 756220448118669463, # Young Sun |  | ||||||
|            145294584077877249, # Mrchance |  | ||||||
|            732403731810877450] # Yeonwoo |  | ||||||
|  |  | ||||||
| awesome_server_id= 767835617002258443 |  | ||||||
| permitted_channel_ids= [ 875353143867752489, 885498819385634816, 878649716269785150, 881984021192671242, 892435998070431755, 892436145651216444,870604751354613770, 896111390216040540, 896112340657909820, 896112378805116978, 896112602105659442] |  | ||||||
|  |  | ||||||
| white_stone= "<:white_stone:882731089548939314>" |  | ||||||
| black_stone= "<:black_stone:882730888453046342>" |  | ||||||
|  |  | ||||||
| with open("token.txt") as f: |  | ||||||
|     token = f.readlines()[0] # Get your own token and put it in token.txt |  | ||||||
|  |  | ||||||
| format="%Y_%m_%d_%H_%M_%S_%f" |  | ||||||
|  |  | ||||||
| # The state is a list of tuples (channel_id, "queue"/"random", last_players, last_times, [black_queue, white_queue]) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def help(ctx): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     await ctx.send( |  | ||||||
|             '$help : shows this help\n\n'+ |  | ||||||
|  |  | ||||||
|             '$join : join the game in this channel\n'+ |  | ||||||
|             '$leave: leave the game in this channel\n'+ |  | ||||||
|             '$play <move>: play a move. For example, `$play Q16`. Passing is not implemented!\n'+ |  | ||||||
|             '$edit <move>: if you make a mistake in your move, you have 5 minutes to correct it with this command\n\n'+ |  | ||||||
|  |  | ||||||
|             '$sgf: get the sgf file of the game\n'+ |  | ||||||
|             '$board: shows the current board\n'+ |  | ||||||
|             '$queue: get the queue of players\n\n'+ |  | ||||||
|  |  | ||||||
|             '$newgame <queue/random/teachers> <handicap> <komi>: starts a game in this channel (admin only!)\n'+ |  | ||||||
|             '$resign <B/W>: <B/W> resigns the game in this channel. It returns its sgf file (admin only!)' |  | ||||||
|             ) |  | ||||||
|     # ctx has guild, message, author, send, and channel (?) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def play(ctx, arg): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|     guild= ctx.guild |  | ||||||
|  |  | ||||||
|     # lowest effort serialization |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     filter_state= [i for i in range(len(state))  if state[i][0] == channel_id] # This is where I should use a fancy next() |  | ||||||
|     if not filter_state: |  | ||||||
|         await ctx.send("No active game in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     i= filter_state[0] |  | ||||||
|  |  | ||||||
|     if state[i][1] in ["queue", "teachers"] and user.id not in state[i][4][0]+state[i][4][1]: |  | ||||||
|         await ctx.send("Player hasn't joined yet! Join us with `$join`") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][1] == "queue" and (len(state[i][4][0])<min_players or len(state[i][4][1]) <min_players): |  | ||||||
|         await ctx.send("Waiting for more players to join! Minimum {} per team".format(min_players)) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     colour= sgfengine.next_colour(str(channel_id)) |  | ||||||
|  |  | ||||||
|     if (state[i][1] == "queue" and user.id!= state[i][4][colour][0]) or (state[i][1]=="teachers" and ((colour==0 and user.id!=state[i][4][0][0]) or (colour==1 and user.id not in state[i][4][1]))): |  | ||||||
|         await ctx.send("It is not your turn yet!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][1] == "random": |  | ||||||
|         assert( len(state[i][2]) == len(state[i][3])) |  | ||||||
|  |  | ||||||
|         if len(state[i][2])>0 and state[i][2][-1] == user.id and (state[i][1]!="teachers" or colour=="0"): |  | ||||||
|             await ctx.send("No two consecutive moves by the same player!") |  | ||||||
|             # return |  | ||||||
|  |  | ||||||
|         for j in range(len(state[i][2])): |  | ||||||
|             if (state[i][2][j] == user.id and |  | ||||||
|                 datetime.now() - datetime.strptime(state[i][3][j],format) < min_time_player): |  | ||||||
|                 await ctx.send("At most one move per player per day!") |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     if state[i][3] != [] and datetime.now()-datetime.strptime(state[i][3][-1],format)<timedelta(seconds=4): |  | ||||||
|         return #silent error |  | ||||||
|  |  | ||||||
|     legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] |  | ||||||
|     legal_moves+=[chr(col+ord('a')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] |  | ||||||
|     if arg not in legal_moves: |  | ||||||
|         await ctx.send("I don't understand the move! Please input it in the format `$play Q16`") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         sgfengine.play_move(str(channel_id), arg, user.display_name) |  | ||||||
|     except ValueError as e: |  | ||||||
|         await ctx.send(str(e)) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     # move registered, let's do the other things |  | ||||||
|     state[i][2].append(user.id) |  | ||||||
|     state[i][3].append(datetime.now().strftime(format)) |  | ||||||
|  |  | ||||||
|     if state[i][1] == "queue": |  | ||||||
|         state[i][4][colour].pop(0) |  | ||||||
|         state[i][4][colour].append(user.id) |  | ||||||
|  |  | ||||||
|     if state[i][1] == "teachers" and colour==0: |  | ||||||
|         state[i][4][0].pop(0) |  | ||||||
|         state[i][4][0].append(user.id) |  | ||||||
|  |  | ||||||
|     file = discord.File(str(ctx.channel.id)+".png") |  | ||||||
|     if state[i][1]=="queue": |  | ||||||
|         next_player=(await guild.fetch_member(state[i][4][1-colour][0])) |  | ||||||
|         await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.mention)) |  | ||||||
|     elif state[i][1]=="teachers" and colour==1: |  | ||||||
|         next_player=(await guild.fetch_member(state[i][4][1-colour][0])) |  | ||||||
|         await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.mention)) |  | ||||||
|     elif state[i][1]=="teachers" and colour==0: |  | ||||||
|         await ctx.send(file=file, content="Teachers' turn! ⭐") |  | ||||||
|     else: |  | ||||||
|         await ctx.send(file=file) |  | ||||||
|  |  | ||||||
|     with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def edit(ctx, arg): #literally play but with less things |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     # It should wait until the queue has 4 players or so |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|     guild= ctx.guild |  | ||||||
|  |  | ||||||
|     # lowest effort serialization |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     filter_state= [i for i in range(len(state))  if state[i][0] == channel_id] |  | ||||||
|     if not filter_state: |  | ||||||
|         await ctx.send("No active game in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     i= filter_state[0] |  | ||||||
|     colour= sgfengine.next_colour(str(channel_id)) |  | ||||||
|  |  | ||||||
|     if len(state[i][2])==0 or state[i][2][-1] != user.id or datetime.now()-datetime.strptime(state[i][3][-1],format) > timedelta(minutes=5): |  | ||||||
|         await ctx.send("You cannot edit this move!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] |  | ||||||
|     legal_moves+=[chr(col+ord('a')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] |  | ||||||
|     if arg not in legal_moves: |  | ||||||
|         await ctx.send("I don't understand the move! Please input it in the format `$play Q16`") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         sgfengine.play_move(str(channel_id), arg, user.display_name, True) |  | ||||||
|     except ValueError as e: |  | ||||||
|         await ctx.send(str(e)) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     file = discord.File(str(ctx.channel.id)+".png") |  | ||||||
|     if state[i][1]=="queue": |  | ||||||
|         next_player=(await guild.fetch_member(state[i][4][colour][0])) |  | ||||||
|         await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.display_name)) |  | ||||||
|     elif state[i][1]=="teachers" and colour==1: |  | ||||||
|         next_player=(await guild.fetch_member(state[i][4][1-colour][0])) |  | ||||||
|         await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.mention)) |  | ||||||
|     elif state[i][1]=="teachers" and colour==0: |  | ||||||
|         await ctx.send(file=file, content="Teachers' turn! ⭐") |  | ||||||
|     else: |  | ||||||
|         await ctx.send(file=file) |  | ||||||
|  |  | ||||||
|     with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def board(ctx): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|     guild= ctx.guild |  | ||||||
|  |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     filter_state= [i for i in range(len(state))  if state[i][0] == channel_id] |  | ||||||
|     if not filter_state: |  | ||||||
|         await ctx.send("No active game in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     i= filter_state[0] |  | ||||||
|     colour= sgfengine.next_colour(str(channel_id)) |  | ||||||
|  |  | ||||||
|     os.system("sgf-render --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf") |  | ||||||
|  |  | ||||||
|     file = discord.File(str(ctx.channel.id)+".png") |  | ||||||
|     if state[i][1]=="queue": |  | ||||||
|         if len(state[i][4][colour]) > 0: |  | ||||||
|             next_player=(await guild.fetch_member(state[i][4][colour][0])) |  | ||||||
|             await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.display_name)) |  | ||||||
|         else: |  | ||||||
|             await ctx.send(file=file, content="Waiting for players to join!") |  | ||||||
|     if state[i][1]=="teachers": |  | ||||||
|         if colour==0: |  | ||||||
|             next_player=(await guild.fetch_member(state[i][4][colour][0])) |  | ||||||
|             await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.display_name)) |  | ||||||
|         else: |  | ||||||
|             await ctx.send(file=file, content="Teachers' turn! ⭐") |  | ||||||
|     else: |  | ||||||
|         await ctx.send(file=file) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def join(ctx): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|  |  | ||||||
|     # lowest effort serialization |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     filter_state= [i for i in range(len(state))  if state[i][0] == channel_id] |  | ||||||
|     if not filter_state: |  | ||||||
|         await ctx.send("No active game in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     i= filter_state[0] |  | ||||||
|  |  | ||||||
|     if user.id in (state[i][4][0]+state[i][4][1]): |  | ||||||
|         await ctx.send("Player already in this game!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][1] == "random": |  | ||||||
|         await ctx.send("This game has no queue! No need to join, just `$play` whenever you want :P") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     colour = 0 if len(state[i][4][0])<=len(state[i][4][1]) else 1 |  | ||||||
|     if state[i][1]=="teachers": colour= 0 |  | ||||||
|  |  | ||||||
|     state[i][4][colour].append(user.id) |  | ||||||
|  |  | ||||||
|     await ctx.send("User {} joined Team {}!".format(user.display_name, ("Black" if colour==0 else "White"))) |  | ||||||
|  |  | ||||||
|     with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def leave(ctx): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|  |  | ||||||
|     # lowest effort serialization |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     filter_state= [i for i in range(len(state))  if state[i][0] == channel_id] |  | ||||||
|     if not filter_state: |  | ||||||
|         await ctx.send("No active game in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     i= filter_state[0] |  | ||||||
|  |  | ||||||
|     if user.id not in (state[i][4][0]+state[i][4][1]): |  | ||||||
|         await ctx.send("Player not in this game!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][1] == "random": |  | ||||||
|         await ctx.send("This game has no queue! No need to leave!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     colour = 0 if (user.id in state[i][4][0]) else 1 |  | ||||||
|     state[i][4][colour].remove(user.id) |  | ||||||
|  |  | ||||||
|     await ctx.send("User {} left :(".format(user.display_name)) |  | ||||||
|  |  | ||||||
|     with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def queue(ctx): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     channel= bot.get_channel(channel_id) # thonk the order |  | ||||||
|     guild = channel.guild |  | ||||||
|  |  | ||||||
|     # lowest effort serialization |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     filter_state= [i for i in range(len(state))  if state[i][0] == channel_id] |  | ||||||
|     if not filter_state: |  | ||||||
|         await ctx.send("No active game in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     i= filter_state[0] |  | ||||||
|     colour= sgfengine.next_colour(str(channel_id)) |  | ||||||
|  |  | ||||||
|     if state[i][1] == "random": |  | ||||||
|         await ctx.send("This game has no queue! No need to join, just `$play` whenever you want :P") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][1] =="teachers": |  | ||||||
|         output="Player list for Team Black: "+black_stone+"\n" |  | ||||||
|         for j, player_id in enumerate(state[i][4][0]): |  | ||||||
|             player_name=(await guild.fetch_member(player_id)).display_name |  | ||||||
|             output+=str(j+1).rjust(3)+". "+ player_name+"\n" |  | ||||||
|         await ctx.send(output) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     output= "Player list:\n" |  | ||||||
|     if state[i][4][0]==[] and state[i][4][1] == []: |  | ||||||
|         output+="Nobody yet! Join us with `$join`" |  | ||||||
|         await ctx.send(output) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][4][0] == []: |  | ||||||
|         for j, player_id in enumerate(state[i][4][1]): |  | ||||||
|             player_name=(await guild.fetch_member(player_id)).display_name |  | ||||||
|             output+=white_stone+str(j+1).rjust(3)+". "+ player_name+"\n" |  | ||||||
|         output+="\n Team Black needs more members!" |  | ||||||
|         await ctx.send(output) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if state[i][4][1] == []: |  | ||||||
|         for j, player_id in enumerate(state[i][4][0]): |  | ||||||
|             player_name=(await guild.fetch_member(player_id)).display_name |  | ||||||
|             output+=black_stone+str(j+1).rjust(3)+". "+ player_name+"\n" |  | ||||||
|         output+="\n Team White needs more members!" |  | ||||||
|         await ctx.send(output) |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     # Which team has more members? Or in case of a tie, which team goes first? |  | ||||||
|     if len(state[i][4][colour]) > len(state[i][4][1-colour]): |  | ||||||
|         last_player = state[i][4][colour][-1] |  | ||||||
|     else: last_player= state[i][4][1-colour][-1] |  | ||||||
|  |  | ||||||
|     j=1 |  | ||||||
|     pointers=[0,0] |  | ||||||
|     while(True): |  | ||||||
|         #print(channel_id, j, pointers, colour, state[i][0], state[i][4]) |  | ||||||
|         output+= white_stone if ((colour+1) % 2 ==0)  else black_stone |  | ||||||
|         output+= str(j).rjust(3)+". " |  | ||||||
|  |  | ||||||
|         player_name= (await guild.fetch_member(state[i][4][colour][pointers[colour]])).display_name |  | ||||||
|         output+= player_name+"\n" |  | ||||||
|  |  | ||||||
|         if state[i][4][colour][pointers[colour]] == last_player: break |  | ||||||
|  |  | ||||||
|         pointers[colour] = (pointers[colour]+1) % len(state[i][4][colour]) |  | ||||||
|         colour=1-colour |  | ||||||
|  |  | ||||||
|         j+=1 |  | ||||||
|  |  | ||||||
|     if len(state[i][4][0])<min_players: |  | ||||||
|         output+="\n Team Black needs more members!" |  | ||||||
|  |  | ||||||
|     if len(state[i][4][1])<min_players: |  | ||||||
|         output+="\n Team White needs more members!" |  | ||||||
|  |  | ||||||
|     await ctx.send(output) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def sgf(ctx): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     file = discord.File(str(ctx.channel.id)+".sgf") |  | ||||||
|     await ctx.send(file=file) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def newgame(ctx, gametype, handicap=0, komi=6.5): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|  |  | ||||||
|     if user.id not in admins: |  | ||||||
|         await ctx.send("You don't have permissions for this!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if gametype not in ["queue", "random", "teachers"]: |  | ||||||
|         await ctx.send("Unrecognized game type! Please try `$newgame <queue/random/teachers>") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     # lowest effort serialization |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     if ctx.channel.id in [ ch for (ch,_,_,_,_) in state]: |  | ||||||
|         await ctx.send("A game is already active in this channel!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     sgfengine.new_game(str(ctx.channel.id), handicap, komi) |  | ||||||
|     if gametype== "teachers": |  | ||||||
|         state.append((ctx.channel.id, gametype, [], [], [[],teachers])) |  | ||||||
|     else: |  | ||||||
|         state.append((ctx.channel.id, gametype, [], [], [[],[]])) |  | ||||||
|  |  | ||||||
|     file = discord.File(str(ctx.channel.id)+".png") |  | ||||||
|     if gametype in ["queue", "teachers"]: |  | ||||||
|         await ctx.send(file=file, content="A new game has started! Join with `$join`") |  | ||||||
|     else: |  | ||||||
|         await ctx.send(file=file, content="A new game has started! Play with `$play <move>`") |  | ||||||
|  |  | ||||||
|     with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|  |  | ||||||
| @bot.command() |  | ||||||
| async def resign(ctx, arg): |  | ||||||
|     if ctx.guild.id == awesome_server_id and ctx.channel.id not in permitted_channel_ids: return |  | ||||||
|     channel_id= ctx.channel.id |  | ||||||
|     user = ctx.author |  | ||||||
|  |  | ||||||
|     if user.id not in admins: |  | ||||||
|         await ctx.send("You don't have permissions for this!") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     if arg not in ["W","B"]: |  | ||||||
|         await ctx.send("Unrecognized colour! Please try `$resign <B/W>` to resign as Black/White") |  | ||||||
|         return |  | ||||||
|  |  | ||||||
|     with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|  |  | ||||||
|     now=datetime.now() |  | ||||||
|     file_name= "rengo_"+now.strftime("%Y_%m_%d_%H_%M_%S_")+ctx.channel.name+".sgf" #remove the hour minute and second later |  | ||||||
|  |  | ||||||
|     sgfengine.resign(str(channel_id), arg, file_name) |  | ||||||
|  |  | ||||||
|     file = discord.File(file_name) |  | ||||||
|     await ctx.send(file=file, content=("Black" if arg=="W" else "White")+" wins!") |  | ||||||
|  |  | ||||||
|     state = [s for s in state if s[0]!=channel_id] |  | ||||||
|  |  | ||||||
|     with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|  |  | ||||||
| async def background_task(): |  | ||||||
|     await bot.wait_until_ready() |  | ||||||
|     print("bot ready!") |  | ||||||
|  |  | ||||||
|     guild=discord.utils.get(bot.guilds, name="Awesome Baduk") |  | ||||||
|     game=discord.Game("multiplayer Baduk! $help for command list") |  | ||||||
|     await bot.change_presence(status=discord.Status.online, activity=game) |  | ||||||
|  |  | ||||||
|     while not bot.is_closed(): |  | ||||||
|         try: |  | ||||||
|             # lowest effort serialization |  | ||||||
|             with open("state.txt") as f: state = ast.literal_eval(f.read()) |  | ||||||
|             #print(state) |  | ||||||
|  |  | ||||||
|             #TODO find who has to move, skip players accordingly, notify if any has to move |  | ||||||
|             for i in range(len(state)): |  | ||||||
|                 if state[i][3] == [] or state[i][1]=="random": continue |  | ||||||
|  |  | ||||||
|                 channel_id= state[i][0] |  | ||||||
|                 channel= bot.get_channel(channel_id) |  | ||||||
|  |  | ||||||
|                 colour = sgfengine.next_colour(str(channel_id)) |  | ||||||
|                 if state[i][1]=="teachers" and colour=="1": continue #Ask the teachers if they want a ping |  | ||||||
|  |  | ||||||
|                 last_time= datetime.strptime(state[i][3][-1],format) |  | ||||||
|                 time_left= last_time + time_to_skip-datetime.now() |  | ||||||
|  |  | ||||||
|                 if time_left < time_to_skip/3.0 and time_left > time_to_skip/3.0-timedelta(seconds=10): # Probably remove? Depends on how passive aggressive it is |  | ||||||
|                     next_user = await guild.fetch_member(state[i][4][colour][0]) |  | ||||||
|                     await channel.send("{}'s turn! Time is running up!".format(next_user.mention))#, time_left.total_seconds()/3600) ) |  | ||||||
|                 if time_left < timedelta(): |  | ||||||
|                     state[i][3][-1]= datetime.strftime(datetime.now(),format) |  | ||||||
|                     state[i][2][-1]= None |  | ||||||
|                     user_id= state[i][4][colour][0] |  | ||||||
|                     state[i][4][colour].pop(0) |  | ||||||
|                     state[i][4][colour].append(user_id) |  | ||||||
|                     next_player=(await guild.fetch_member(state[i][4][colour][0])) |  | ||||||
|                     await channel.send(content="{}'s turn! ⭐".format(next_player.mention)) |  | ||||||
|  |  | ||||||
|             with open("state.txt", "w") as f: f.write(repr(state)) |  | ||||||
|             await asyncio.sleep(10) |  | ||||||
|  |  | ||||||
|         except ConnectionResetError: |  | ||||||
|             print("Connection error") |  | ||||||
|  |  | ||||||
| bot.loop.create_task(background_task()) |  | ||||||
| bot.run(token) |  | ||||||
							
								
								
									
										7
									
								
								rengobot/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								rengobot/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | *.txt | ||||||
|  | *.sgf | ||||||
|  | *.png | ||||||
|  | *.out | ||||||
|  | *.sh | ||||||
|  | __pycache__/ | ||||||
|  | rengobot_venv/ | ||||||
							
								
								
									
										12
									
								
								rengobot/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								rengobot/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | A fork of  | ||||||
|  |  | ||||||
|  | https://github.com/ReneCareenium/rengobot | ||||||
|  |  | ||||||
|  | A discord bot for playing rengo games! | ||||||
|  |  | ||||||
|  | # Dependencies | ||||||
|  | - sgf-render | ||||||
|  | - python-discord | ||||||
|  | - python-sgfmill | ||||||
|  |  | ||||||
|  | Make sure to run the bot in an environment with read/write permissions | ||||||
							
								
								
									
										202
									
								
								rengobot/rengobot.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								rengobot/rengobot.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | |||||||
|  | import os | ||||||
|  | import ast | ||||||
|  | import time | ||||||
|  | from datetime import datetime, timedelta | ||||||
|  | import asyncio | ||||||
|  |  | ||||||
|  | import sgfengine | ||||||
|  |  | ||||||
|  | import discord | ||||||
|  | from discord.ext import commands | ||||||
|  |  | ||||||
|  | # We don't use fancy slash commands here. It seems there is this library for python but it looks a bit more involved. | ||||||
|  | # https://pypi.org/project/discord-py-slash-command/ | ||||||
|  |  | ||||||
|  | intents = discord.Intents.default() | ||||||
|  | #intents.message_content = True | ||||||
|  |  | ||||||
|  | bot = commands.Bot(command_prefix='$', help_command=None, intents=intents) | ||||||
|  |  | ||||||
|  | # People who can start and resign games :O | ||||||
|  | admins=[] | ||||||
|  | awesome_server_id= | ||||||
|  | permitted_channel_id= | ||||||
|  |  | ||||||
|  | player_id= 0 | ||||||
|  | start_time = 0 | ||||||
|  |  | ||||||
|  | with open("token.txt") as f: | ||||||
|  |     token = f.readlines()[0] # Get your own token and put it in token.txt | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def help(ctx): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |      | ||||||
|  |     await ctx.send( | ||||||
|  |             '$help : shows this help\n\n'+ | ||||||
|  |  | ||||||
|  |             '$play <move>: play a move. For example, `$play Q16`. Passing is not implemented!\n'+ | ||||||
|  |             '$edit <move>: if you make a mistake in your move\n\n'+ | ||||||
|  |  | ||||||
|  |             '$sgf: get the sgf file of the game\n'+ | ||||||
|  |             '$board: shows the current board\n'+ | ||||||
|  |  | ||||||
|  |             '$newgame <handicap> <komi>: starts a game in this channel (admin only!)\n'+ | ||||||
|  |             '$resign <B/W>: <B/W> resigns the game in this channel. It returns its sgf file (admin only!)' | ||||||
|  |             ) | ||||||
|  |     # ctx has guild, message, author, send, and channel (?) | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def play(ctx, arg): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     channel_id= ctx.channel.id | ||||||
|  |     user = ctx.author | ||||||
|  |     guild= ctx.guild | ||||||
|  |  | ||||||
|  |     global player_id | ||||||
|  |     global start_time | ||||||
|  |  | ||||||
|  |     if not os.path.exists(str(channel_id)+".sgf"): | ||||||
|  |         await ctx.send("No active game in this channel!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     if (player_id == user.id): | ||||||
|  |         await ctx.send("No two consecutive moves by the same player!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     elapsed_time = time.time() - start_time | ||||||
|  |     if elapsed_time < 1: | ||||||
|  |         await ctx.send("One second per move!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] | ||||||
|  |     legal_moves+=[chr(col+ord('a')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] | ||||||
|  |     if arg not in legal_moves: | ||||||
|  |         await ctx.send("I don't understand the move! Please input it in the format `$play Q16`") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         sgfengine.play_move(str(channel_id), arg, user.display_name) | ||||||
|  |     except ValueError as e: | ||||||
|  |         await ctx.send(str(e)) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     player_id=user.id | ||||||
|  |     start_time = time.time() | ||||||
|  |   | ||||||
|  |     file = discord.File(str(ctx.channel.id)+".png") | ||||||
|  |  | ||||||
|  |     await ctx.send(file=file) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def edit(ctx, arg): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |     channel_id= ctx.channel.id | ||||||
|  |     user = ctx.author | ||||||
|  |     guild= ctx.guild | ||||||
|  |  | ||||||
|  |     global player_id | ||||||
|  |     if (player_id == user.id): | ||||||
|  |         await ctx.send("Not the player that made the mistake!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     if not os.path.exists(str(channel_id)+".sgf"): | ||||||
|  |         await ctx.send("No active game in this channel!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     colour= sgfengine.next_colour(str(channel_id)) | ||||||
|  |  | ||||||
|  |     legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] | ||||||
|  |     legal_moves+=[chr(col+ord('a')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)] | ||||||
|  |     if arg not in legal_moves: | ||||||
|  |         await ctx.send("I don't understand the move! Please input it in the format `$play Q16`") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         sgfengine.play_move(str(channel_id), arg, user.display_name, True) | ||||||
|  |     except ValueError as e: | ||||||
|  |         await ctx.send(str(e)) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     file = discord.File(str(ctx.channel.id)+".png") | ||||||
|  |     await ctx.send(file=file) | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def board(ctx): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     channel_id= ctx.channel.id | ||||||
|  |     user = ctx.author | ||||||
|  |     guild= ctx.guild | ||||||
|  |  | ||||||
|  |     if not os.path.exists(str(channel_id)+".sgf"): | ||||||
|  |         await ctx.send("No active game in this channel!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf") | ||||||
|  |     file = discord.File(str(ctx.channel.id)+".png") | ||||||
|  |  | ||||||
|  |     await ctx.send(file=file) | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def sgf(ctx): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |      | ||||||
|  |     file = discord.File(str(ctx.channel.id)+".sgf") | ||||||
|  |     await ctx.send(file=file) | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def newgame(ctx, handicap=0, komi=6.5): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     channel_id= ctx.channel.id | ||||||
|  |     user = ctx.author | ||||||
|  |  | ||||||
|  |     if user.id not in admins: | ||||||
|  |         await ctx.send("You don't have permissions for this!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     sgfengine.new_game(str(channel_id), handicap, komi) | ||||||
|  |    | ||||||
|  |     os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf") | ||||||
|  |  | ||||||
|  |     file = discord.File(str(channel_id)+".png") | ||||||
|  |   | ||||||
|  |     await ctx.send(file=file, content="A new game has started! Play with `$play <move>`") | ||||||
|  |  | ||||||
|  | @bot.command() | ||||||
|  | async def resign(ctx, arg): | ||||||
|  |     if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id: | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     channel_id= ctx.channel.id | ||||||
|  |     user = ctx.author | ||||||
|  |  | ||||||
|  |     if user.id not in admins: | ||||||
|  |         await ctx.send("You don't have permissions for this!") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     if arg not in ["W","B"]: | ||||||
|  |         await ctx.send("Unrecognized colour! Please try `$resign <B/W>` to resign as Black/White") | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     now=datetime.now() | ||||||
|  |     file_name= "rengo_"+now.strftime("%Y_%m_%d_%H_%M_%S_")+ctx.channel.name+".sgf" | ||||||
|  |  | ||||||
|  |     sgfengine.resign(str(channel_id), arg, file_name) | ||||||
|  |  | ||||||
|  |     file = discord.File(file_name) | ||||||
|  |     await ctx.send(file=file, content=("Black" if arg=="W" else "White")+" wins!") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | print("Running") | ||||||
|  | bot.run(token) | ||||||
| @@ -26,7 +26,7 @@ def new_game(channel_id, handicap=0, komi=6.5): | |||||||
|         f.write(game.serialise()) |         f.write(game.serialise()) | ||||||
|     f.close() |     f.close() | ||||||
| 
 | 
 | ||||||
|     os.system("sgf-render --style fancy --label-sides nesw -o "+channel_id+".png -n last "+channel_id+".sgf") |     os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf") | ||||||
| 
 | 
 | ||||||
| #0 if black to play, 1 if white to play | #0 if black to play, 1 if white to play | ||||||
| def next_colour(channel_id): | def next_colour(channel_id): | ||||||
| @@ -87,7 +87,7 @@ def play_move(channel_id, messagestr, player, overwrite=False): | |||||||
|         f.write(game.serialise()) |         f.write(game.serialise()) | ||||||
|     f.close() |     f.close() | ||||||
| 
 | 
 | ||||||
|     os.system("sgf-render --style fancy --label-sides nesw -o "+channel_id+".png -n last "+channel_id+".sgf") |     os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf") | ||||||
| 
 | 
 | ||||||
| # colour is "B" if black resigns, "W" if white resigns | # colour is "B" if black resigns, "W" if white resigns | ||||||
| def resign(channel_id, colour, file_name): | def resign(channel_id, colour, file_name): | ||||||
		Reference in New Issue
	
	Block a user