Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Plain Text to Embed #20

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
217 changes: 128 additions & 89 deletions discord_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,110 +25,148 @@ def escape_discord_formatting_characters(text: str):


def format_game(game):
global gameTTL
ended = time.time() - game['last_seen'] >= gameTTL
if ended:
text = '~~' + game['id'].upper() + '~~'
else:
text = '**' + game['id'].upper() + '**'
match game['type']:
case 'DRTL':
text += ' <:diabloico:760201452957335552>'
case 'DSHR':
text += ' <:diabloico:760201452957335552> (spawn)'
case 'HRTL':
text += ' <:hellfire:766901810580815932>'
case 'HSHR':
text += ' <:hellfire:766901810580815932> (spawn)'
case 'IRON':
text += ' Ironman'
case 'MEMD':
text += ' <:one_ring:1061898681504251954>'
case 'DRDX':
text += ' <:diabloico:760201452957335552> X'
case 'DWKD':
text += ' <:mod_wkd:1097321063077122068> modDiablo'
case 'HWKD':
text += ' <:mod_wkd:1097321063077122068> modHellfire'
case _:
text += ' ' + game['type']

text += ' ' + game['version']

match game['tick_rate']:
case 20:
text += ''
case 30:
text += ' Fast'
case 40:
text += ' Faster'
case 50:
text += ' Fastest'
case _:
text += ' speed: ' + str(game['tick_rate'])

match game['difficulty']:
case 0:
text += ' Normal'
case 1:
text += ' Nightmare'
case 2:
text += ' Hell'

embed = {
"type": "rich",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change all double-quotes to single-quotes. It's more consistent with the rest of the file.

"title": game['id'].upper(),
"description": "",
"color": 0x00ff00,
"fields": [],
"thumbnail": {
"url": "",
"height": 0,
"width": 0
},
"footer": {
"text": f"Duration: {format_time_delta(round((time.time() - game['first_seen']) / 60))}"
}
}

# Checking if the game is ended to adjust border color
if time.time() - game['last_seen'] >= gameTTL:
embed["color"] = 0xff0000 # red for closed games
embed["title"] = "Game Closed"

# Mapping 4-letter code to game title
game_titles = {
'DRTL': 'DevilutionX',
'DSHR': 'DevilutionX',
'HRTL': 'DevilutionX',
'HSHR': 'DevilutionX',
'IRON': 'Ironman',
'MEMD': 'Middle Earth',
'DRDX': 'DiabloX',
'DWKD': 'wkdmod',
'HWKD': 'wkdmod',
'LTDR': 'Lord of Terror',
'LTDS': 'Lord of Terror',
'LTHR': 'Lord of Terror',
'LTHS': 'Lord of Terror'
}

# Setting the author field to game title with version
embed["author"]["name"] = f"{game_titles.get(game['type'], 'Unknown Game')} {game['version']}"

# Players
embed["fields"].append({
"name": "Players",
"value": ', '.join([name for name in game['players']]),
"inline": False
})

# Difficulty
difficulties = ["Normal", "Nightmare", "Hell"]
difficulty_value = difficulties[game['difficulty']] if 0 <= game['difficulty'] < len(
difficulties) else "Unknown"
embed["fields"].append({
"name": "Difficulty",
"value": difficulty_value,
"inline": True
})

# Game Speed
tick_rate_mapping = {
20: "Normal",
30: "Fast",
40: "Faster",
50: "Fastest"
}
speed = tick_rate_mapping.get(game['tick_rate'], f"Custom ({game['tick_rate']})")
embed["fields"].append({
"name": "Speed",
"value": speed,
"inline": True
})

# Game Options
diablo_game_codes = {'DRTL', 'DSHR', 'IRON', 'MEMD', 'DWKD', 'LTDR', 'LTDS'}
attributes = []
if game['run_in_town']:
attributes.append('Run in Town')
if game['full_quests']:
attributes.append('Quests')
if game['theo_quest'] and game['type'] != 'DRTL':
if game['theo_quest'] and game['type'] not in diablo_game_codes:
attributes.append('Theo Quest')
if game['cow_quest'] and game['type'] != 'DRTL':
if game['cow_quest'] and game['type'] not in diablo_game_codes:
attributes.append('Cow Quest')
if game['friendly_fire']:
attributes.append('Friendly Fire')

if len(attributes) != 0:
text += ' ('
text += ', '.join(attributes)
text += ')'
if attributes:
embed["fields"].append({
"name": "Game Options",
"value": ', '.join(attributes),
"inline": False
})

diablo_retail_url = 'https://user-images.githubusercontent.com/68359262/272125642-d3363701-9b6e-4eff-b43e-11b9a0b58813.png'
diablo_shareware_url = 'https://user-images.githubusercontent.com/68359262/272125649-076eb58c-f587-4b16-822f-7d9b56559c69.png'
hellfire_retail_url = 'https://user-images.githubusercontent.com/68359262/272125658-3fc00693-e6dc-4273-81e8-803dbf9f5d93.png'
hellfire_shareware_url = 'https://user-images.githubusercontent.com/68359262/272125665-17805298-1400-49aa-a6a3-ac80ba5767ee.png'
Comment on lines +121 to +124
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel it would be better to upload the images to the repo and reference those URLs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does AJenbo have to do that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add the files to your PR and preemptively determine what the URLs would be once merged. Or break it into two PRs so the graphics can be added first.

# Thumbnail based on game type
game_type_icons = {
'DRTL': diablo_retail_url, # DevilutionX Diablo Retail
'DSHR': diablo_shareware_url, # DevilutionX Diablo Shareware
'HRTL': hellfire_retail_url, # DevilutionX Hellfire Retail
'HSHR': hellfire_shareware_url, # DevilutionX Hellfire Shareware

'IRON': diablo_retail_url, # Ironman Diablo Retail (Sixcy)
# MISSING: Ironman Diablo Shareware (Sixcy)
# MISSING: Ironman Hellfire Retail (Sixcy)
# MISSING: Ironman Hellfire Shareware (Sixcy)

'MEMD': diablo_retail_url, # Middle Earth Diablo Retail (DakkJaniels)

'DRDX': diablo_retail_url, # DiabloX Diablo Retail (ikonomov)
# MISSING: DiabloX Diablo Shareware (ikonomov)
# MISSING: DiabloX Hellfire Retail (ikonomov)
# MISSING: DiabloX Hellfire Shareware (ikonomov)

'DWKD': diablo_retail_url, # wkdmod Diablo Retail (wkdgmr)
# MISSING: wkdmod Diablo Shareware (wkdgmr)
'HWKD': hellfire_retail_url, # wkdmod Hellfire Retail (wkdgmr)
# MISSING: wkdmod Hellfire Shareware (wkdgmr)

'LTDR': diablo_retail_url, # Lord of Terror Diablo Retail (kphoenix)
'LTDS': diablo_shareware_url, # Lord of Terror Diablo Shareware (kphoenix)
'LTHR': hellfire_retail_url, # Lord of Terror Hellfire Retail (kphoenix)
'LTHS': hellfire_shareware_url, # Lord of Terror Hellfire Shareware (kphoenix)
}
embed["thumbnail"]["url"] = game_type_icons.get(game['type'], "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the empty string would cause an exception. Since the thumbnail is optional, we should remove it completely if the URL would be invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use a placeholder graphic. Maybe the retail diablo icon, or something else as a placeholder for unknown game id

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, I would avoid using an icon we're already using. However, I would caution against making unknown game types recognizable. Players will use the icon to differentiate between game types, but unknown game types would all share the same icon. Maybe a question mark would be appropriate, but I think no icon is perfectly reasonable.

But honestly, for all we know, the unknown game type could be an attacker trying to confuse or crash the bot. The main thing is to avoid exceptions that would compromise the main loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll come up with something good


return embed

text += '\nPlayers: **' + '**, **'.join([escape_discord_formatting_characters(name) for name in game['players']]) + '**'
text += '\nStarted: <t:' + str(round(game['first_seen'])) + ':R>'
if ended:
text += '\nEnded after: `' + format_time_delta(round((time.time() - game['first_seen']) / 60)) + '`'

return text


async def update_status_message():
global current_online
async def update_game_message(game_id):
global global_channel
global global_online_list_message
if global_online_list_message is not None:
embed_data = format_game(game_list[game_id])
embed = discord.Embed.from_dict(embed_data)
if 'message' in game_list[game_id]:
try:
await global_online_list_message.delete()
await game_list[game_id]['message'].edit(embed=embed)
except discord.errors.NotFound:
pass
global_online_list_message = None
text = 'There are currently **' + str(current_online) + '** public games.'
if current_online == 1:
text = 'There is currently **' + str(current_online) + '** public game.'
assert isinstance(global_channel, discord.TextChannel)
global_online_list_message = await global_channel.send(text)


async def update_game_message(game_id):
global global_channel
text = format_game(game_list[game_id])
if 'message' in game_list[game_id]:
if game_list[game_id]['message'].content != text:
try:
await game_list[game_id]['message'].edit(content=text)
except discord.errors.NotFound:
pass
return
assert isinstance(global_channel, discord.TextChannel)
game_list[game_id]['message'] = await global_channel.send(text)
game_list[game_id]['message'] = await global_channel.send(embed=embed)


def format_time_delta(minutes):
Expand All @@ -154,8 +192,10 @@ def format_time_delta(minutes):

async def end_game_message(game_id):
if 'message' in game_list[game_id]:
embed_data = format_game(game_list[game_id])
embed = discord.Embed.from_dict(embed_data)
try:
await game_list[game_id]['message'].edit(content=format_game(game_list[game_id]))
await game_list[game_id]['message'].edit(embed=embed)
except discord.errors.NotFound:
pass

Expand Down Expand Up @@ -263,7 +303,6 @@ async def background_task():
continue

current_online = len(game_list)
await update_status_message()

activity = discord.Activity(name='Games online: '+str(current_online), type=discord.ActivityType.watching)
await client.change_presence(activity=activity)
Expand Down
Loading