diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a6a6b4..c977701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## v1.4.0 - 2021-09-09 + +### New features: + +* Can integrate directly with Discord to post messages to a channel, using a user-specified Discord webhook. +* Prefix positive INF with '+'. +* Mission INF is now manually editable as well as automatically updated. +* 'Select all' / 'Select none' checkbox at the top of each system to quickly enable / disable all factions for a system. +* Added 'Failed Missions' to Discord text. + +## Bug Fixes: + +* Apostrophes in faction names no longer break the colouring. + + ## v1.3.0 - 2021-09-06 ### New features: diff --git a/README.md b/README.md index 85186e7..222ce5b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ An [EDMC](https://github.com/EDCD/EDMarketConnector) plugin to count Background Based on BGS-Tally v2.0 by tezw21: [Original tezw21 BGS-Tally-v2.0 Project](https://github.com/tezw21/BGS-Tally-v2.0) -Modified by Aussi to include manual Combat Zone tracking, Discord-ready information and quick Copy to Clipboard function for the Discord text. +Modified by Aussi to include manual Combat Zone tracking, Discord-ready information, quick Copy to Clipboard function for the Discord text and posting directly to Discord. # Installation @@ -33,7 +33,7 @@ It is highly recommended that EDMC is started before ED is launched as data is r The data is shown on a pop up window when the _Latest BGS Tally_ or _Previous BGS Tally_ buttons on the EDMC main screen are clicked - data collected since the latest tick in _Latest BGS Tally_ and data from your previous play session before the latest tick in _Previous BGS Tally_. The tick time it uses is published here: https://elitebgs.app/api/ebgs/v5/ticks and the plugin displays this on the main EDMC window for reference. -The plugin also generates a nicely formatted 'Discord Ready' text string which can be copied and pasted into a Discord chat - just click the handy _Copy to Clipboard_ button at the bottom of the _Latest BGS Tally_ and _Previous BGS Tally_ windows. +The plugin also generates a nicely formatted 'Discord Ready' text string which can be copied and pasted into a Discord chat, or posted directly to a Discord channel if you wish (see 'Discord Integration' below for instructions). The plugin can be paused / restarted by un-checking / checking the _☑ Make BGS Tally Active_ checkbox in _File_ → _Settings_ → _BGS Tally_. @@ -66,9 +66,28 @@ All the above are totalled during the _Latest BGS Tally_ session and transfer to The plugin also includes manual tracking of Combat Zones (CZs). CZs are not included in Elite's Player Journal, so there is no way of automatically working out which CZs you have completed. There are fields for each category of CZ that you can manually change, and these values are incorporated into the Discord text report. +# Discord Integration + +The plugin generates Discord-ready text for copying-and-pasting manually into Discord. However, as of v1.4.0, it also now supports direct posting into Discord using a webhook. You will need to create this webhook on your Discord server first. The steps are as follows: + +1. In your Discord server, click the Cog / Gear icon ⚙ alongside the channel name. +2. In the menu that appears, click _Integrations_. +3. In the Webhooks panel, click _Create Webhook_. (If you have any webhooks already set up on the channel, instead click _View Webhooks_ and then _New Webhook_). +4. In the _Name_ field, give your webhook a name, for example 'BGS Tally' would be sensible. +5. Click _Copy Webhook URL_. +6. In EDMC, go to _File_ → _Settings_ → _BGS Tally_ and paste the Webhook URL into the _Discord Webhook URL_ field. +7. Optionally, you can also set a username that the Discord post will appear as - type this into the _Discord Post as User_ field. It would be sensible to type in your Discord username, but you can use anything you like. + +Once the connection is configured, a new button will appear on the BGS Tally windows titled _Post to Discord_. This will automatically send your report to your Discord server via the Webhook you have configured. + +If you post again within the same tick, the plugin will **update** your previous post, rather than creating a second post. + + # Your Personal Activity and Privacy -If you're concerned about the privacy of your BGS activity, note that this plugin only tracks your activity locally on your computer - it **does not send your data anywhere else**. It writes the following three files, all in the BGS-Tally folder: +If you're concerned about the privacy of your BGS activity, note that this plugin **does not send your data anywhere, unless you specifically choose to by configuring the Discord Integration** (see above for instructions). + +It writes the following three files, all in the BGS-Tally folder: 1. `Today.txt` - This contains your activity since the last tick. 2. `Yesterday.txt` - This contains your activity in your previous session before the last tick. @@ -76,7 +95,8 @@ If you're concerned about the privacy of your BGS activity, note that this plugi All three of these files use the JSON format, so can be easily viewed in a text editor or JSON viewer. -The plugin makes the following two network connections: +The plugin makes the following network connections: 1. To [EliteBGS](https://elitebgs.app/api/ebgs/v5/ticks) to grab the date and time of the lastest tick. 2. To [GitHub](https://api.github.com/repos/aussig/BGS-Tally/releases/latest) to check the version of the plugin to see whether there is a newer version available. +3. **Only if configured by you** to a specific Discord webhook on a Discord server of your choice, and only when you explicitly click the _Post to Discord_ button. diff --git a/load.py b/load.py index df116d5..c1727d6 100644 --- a/load.py +++ b/load.py @@ -16,7 +16,7 @@ from theme import theme this = sys.modules[__name__] # For holding module globals -this.VersionNo = "1.3.0" +this.VersionNo = "1.4.0" this.FactionNames = [] this.TodayData = {} this.YesterdayData = {} @@ -43,9 +43,11 @@ 'Chain_HelpFinishTheOrder_name' ] -# Plugin Preferences on settings tab -this.Status = "Active" -this.AbbreviateFactionNames = "No" +# Plugin Preferences on settings tab. These are all initialised to Variables in plugin_start3 +this.Status = None +this.AbbreviateFactionNames = None +this.DiscordWebhook = None +this.DiscordUsername = None # States that generate Conflict Zones, so we display the CZ UI for factions in these states this.CZStates = [ @@ -83,17 +85,34 @@ class CZs(Enum): GROUND_MED = 4 GROUND_LOW = 5 +class Ticks(Enum): + TICK_CURRENT = 0 + TICK_PREVIOUS = 1 + +# Subclassing from str as well as Enum means json.load and json.dump work seamlessly +class CheckStates(str, Enum): + STATE_OFF = 'No' + STATE_ON = 'Yes' + STATE_PARTIAL = 'Partial' + STATE_PENDING = 'Pending' + def plugin_prefs(parent, cmdr, is_beta): """ Return a TK Frame for adding to the EDMC settings dialog. """ frame = nb.Frame(parent) - nb.Label(frame, text="BGS Tally (modified by Aussi) v" + this.VersionNo).grid(column=0, padx=10, sticky=tk.W) - nb.Checkbutton(frame, text="Make BGS Tally Active", variable=this.Status, onvalue="Active", - offvalue="Paused").grid(padx=10, sticky=tk.W) - nb.Checkbutton(frame, text="Abbreviate Faction Names", variable=this.AbbreviateFactionNames, onvalue="Yes", - offvalue="No").grid(padx=10, sticky=tk.W) + # Make the second column fill available space + frame.columnconfigure(1, weight=1) + + nb.Label(frame, text="BGS Tally (modified by Aussi) v" + this.VersionNo).grid(columnspan=2, padx=10, sticky=tk.W) + ttk.Separator(frame, orient=tk.HORIZONTAL).grid(columnspan=2, padx=10, pady=2, sticky=tk.EW) + nb.Checkbutton(frame, text="BGS Tally Active", variable=this.Status, onvalue="Active", offvalue="Paused").grid(column=1, padx=10, sticky=tk.W) + nb.Checkbutton(frame, text="Abbreviate Faction Names", variable=this.AbbreviateFactionNames, onvalue="Yes", offvalue="No").grid(column=1, padx=10, sticky=tk.W) + nb.Label(frame, text="Discord Webhook URL").grid(column=0, padx=10, sticky=tk.W, row=5) + nb.Entry(frame, textvariable=this.DiscordWebhook).grid(column=1, padx=10, pady=2, sticky=tk.EW, row=5) + nb.Label(frame, text="Discord Post as User").grid(column=0, padx=10, sticky=tk.W, row=6) + nb.Entry(frame, textvariable=this.DiscordUsername).grid(column=1, padx=10, pady=2, sticky=tk.W, row=6) return frame @@ -136,20 +155,17 @@ def plugin_start3(plugin_dir): this.TickTime = tk.StringVar(value=config.get_str("XTickTime")) this.Status = tk.StringVar(value=config.get_str("XStatus")) this.AbbreviateFactionNames = tk.StringVar(value=config.get_str("XAbbreviate")) + this.DiscordWebhook = tk.StringVar(value=config.get_str("XDiscordWebhook")) + this.DiscordUsername = tk.StringVar(value=config.get_str("XDiscordUsername")) + this.DiscordCurrentMessageID = tk.StringVar(value=config.get_str("XDiscordCurrentMessageID")) + this.DiscordPreviousMessageID = tk.StringVar(value=config.get_str("XDiscordPreviousMessageID")) this.DataIndex = tk.IntVar(value=config.get_int("xIndex")) this.StationFaction = tk.StringVar(value=config.get_str("XStation")) response = requests.get('https://api.github.com/repos/aussig/BGS-Tally/releases/latest') # check latest version latest = response.json() this.GitVersion = latest['tag_name'] - # tick check and counter reset - response = requests.get('https://elitebgs.app/api/ebgs/v5/ticks') # get current tick and reset if changed - tick = response.json() - this.CurrentTick = tick[0]['_id'] - this.TickTime = tick[0]['time'] - if this.LastTick.get() != this.CurrentTick: - this.LastTick.set(this.CurrentTick) - this.YesterdayData = this.TodayData - this.TodayData = {} + check_tick() + return "BGS Tally" @@ -173,13 +189,32 @@ def plugin_app(parent): title2.bind("", lambda e: webbrowser.open_new("https://github.com/aussig/BGS-Tally/releases")) tk.Button(this.frame, text='Latest BGS Tally', command=display_todaydata).grid(row=1, column=0, padx=3) tk.Button(this.frame, text='Previous BGS Tally', command=display_yesterdaydata).grid(row=1, column=1, padx=3) - tk.Label(this.frame, text="Status:").grid(row=2, column=0, sticky=tk.W) + tk.Label(this.frame, text="BGS Tally Plugin Status:").grid(row=2, column=0, sticky=tk.W) tk.Label(this.frame, text="Last Tick:").grid(row=3, column=0, sticky=tk.W) this.StatusLabel = tk.Label(this.frame, text=this.Status.get()).grid(row=2, column=1, sticky=tk.W) this.TimeLabel = tk.Label(this.frame, text=tick_format(this.TickTime)).grid(row=3, column=1, sticky=tk.W) return this.frame +def check_tick(): + """ + Tick check and counter reset + """ + response = requests.get('https://elitebgs.app/api/ebgs/v5/ticks') # get current tick and reset if changed + tick = response.json() + this.CurrentTick = tick[0]['_id'] + this.TickTime = tick[0]['time'] + if this.LastTick.get() != this.CurrentTick: + this.LastTick.set(this.CurrentTick) + this.YesterdayData = this.TodayData + this.TodayData = {} + this.DiscordPreviousMessageID.set(this.DiscordCurrentMessageID.get()) + this.DiscordCurrentMessageID.set('') + return True + + return False + + def journal_entry(cmdr, is_beta, system, station, entry, state): """ Parse an incoming journal entry and store the data we need @@ -234,15 +269,8 @@ def journal_entry(cmdr, is_beta, system, station, entry, state): if entry['event'] == 'Docked': # enter system and faction named this.StationFaction.set(entry['StationFaction']['Name']) # set controlling faction name - # tick check and counter reset - response = requests.get('https://elitebgs.app/api/ebgs/v5/ticks') # get current tick and reset if changed - tick = response.json() - this.CurrentTick = tick[0]['_id'] - this.TickTime = tick[0]['time'] - if this.LastTick.get() != this.CurrentTick: - this.LastTick.set(this.CurrentTick) - this.YesterdayData = this.TodayData - this.TodayData = {} + # Check for a new tick + if check_tick(): this.TimeLabel = tk.Label(this.frame, text=tick_format(this.TickTime)).grid(row=3, column=1, sticky=tk.W) theme.update(this.frame) @@ -365,16 +393,16 @@ def human_format(num): def update_faction_data(faction_data): """ - Update data structures not present in previous versions of plugin + Update faction data structure for elements not present in previous versions of plugin """ # From < v1.2.0 to 1.2.0 if not 'SpaceCZ' in faction_data: faction_data['SpaceCZ'] = {} if not 'GroundCZ' in faction_data: faction_data['GroundCZ'] = {} # From < v1.3.0 to 1.3.0 - if not 'Enabled' in faction_data: faction_data['Enabled'] = 'Yes' + if not 'Enabled' in faction_data: faction_data['Enabled'] = CheckStates.STATE_ON -def display_data(title, data): +def display_data(title, data, tick_mode): """ Display the data window, using either latest or previous data """ @@ -390,8 +418,14 @@ def display_data(title, data): # Make the second column (faction name) fill available space tab.columnconfigure(1, weight=1) + FactionEnableCheckbuttons = [] + TabParent.add(tab, text=data[i][0]['System']) ttk.Label(tab, text="Include", font=heading_font).grid(row=0, column=0, padx=2, pady=2) + EnableAllCheckbutton = ttk.Checkbutton(tab) + EnableAllCheckbutton.grid(row=1, column=0, padx=2, pady=2) + EnableAllCheckbutton.configure(command=partial(enable_all_factions_change, EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, i)) + EnableAllCheckbutton.state(['!alternate']) ttk.Label(tab, text="Faction", font=heading_font).grid(row=0, column=1, padx=2, pady=2) ttk.Label(tab, text="State", font=heading_font).grid(row=0, column=2, padx=2, pady=2) ttk.Label(tab, text="INF", font=heading_font).grid(row=0, column=3, padx=2, pady=2) @@ -417,21 +451,24 @@ def display_data(title, data): for x in range(0, z): update_faction_data(data[i][0]['Factions'][x]) - EnableVar = tk.StringVar(value=data[i][0]['Factions'][x]['Enabled']) - EnableLabel = ttk.Checkbutton(tab, variable=EnableVar, onvalue='Yes', offvalue='No') - EnableLabel.grid(row=x + header_rows, column=0, padx=2, pady=2) + EnableCheckbutton = ttk.Checkbutton(tab) + EnableCheckbutton.grid(row=x + header_rows, column=0, padx=2, pady=2) + EnableCheckbutton.configure(command=partial(enable_faction_change, EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, i, x)) + EnableCheckbutton.state(['selected', '!alternate'] if data[i][0]['Factions'][x]['Enabled'] == CheckStates.STATE_ON else ['!selected', '!alternate']) + FactionEnableCheckbuttons.append(EnableCheckbutton) FactionName = ttk.Label(tab, text=data[i][0]['Factions'][x]['Faction']) FactionName.grid(row=x + header_rows, column=1, sticky=tk.W, padx=2, pady=2) - FactionName.bind("", partial(faction_name_clicked, EnableVar)) + FactionName.bind("", partial(faction_name_clicked, EnableCheckbutton, EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, i, x)) ttk.Label(tab, text=data[i][0]['Factions'][x]['FactionState']).grid(row=x + header_rows, column=2) - ttk.Label(tab, text=data[i][0]['Factions'][x]['MissionPoints']).grid(row=x + header_rows, column=3) + MissionPointsVar = tk.IntVar(value=data[i][0]['Factions'][x]['MissionPoints']) + ttk.Spinbox(tab, from_=-999, to=999, width=3, textvariable=MissionPointsVar).grid(row=x + header_rows, column=3, padx=2, pady=2) ttk.Label(tab, text=human_format(data[i][0]['Factions'][x]['TradeProfit'])).grid(row=x + header_rows, column=4) ttk.Label(tab, text=human_format(data[i][0]['Factions'][x]['Bounties'])).grid(row=x + header_rows, column=5) ttk.Label(tab, text=human_format(data[i][0]['Factions'][x]['CartData'])).grid(row=x + header_rows, column=6) ttk.Label(tab, text=human_format(data[i][0]['Factions'][x]['CombatBonds'])).grid(row=x + header_rows, column=7) ttk.Label(tab, text=data[i][0]['Factions'][x]['MissionFailed']).grid(row=x + header_rows, column=8) ttk.Label(tab, text=data[i][0]['Factions'][x]['Murdered']).grid(row=x + header_rows, column=9) - EnableVar.trace('w', partial(enable_faction_change, EnableVar, Discord, data, i, x)) + MissionPointsVar.trace('w', partial(mission_points_change, MissionPointsVar, Discord, data, i, x)) if (data[i][0]['Factions'][x]['FactionState'] in this.CZStates): CZSpaceLVar = tk.StringVar(value=data[i][0]['Factions'][x]['SpaceCZ'].get('l', '0')) @@ -454,16 +491,18 @@ def display_data(title, data): CZGroundMVar.trace('w', partial(cz_change, CZGroundMVar, Discord, CZs.GROUND_MED, data, i, x)) CZGroundHVar.trace('w', partial(cz_change, CZGroundHVar, Discord, CZs.GROUND_HIGH, data, i, x)) + update_enable_all_factions_checkbutton(EnableAllCheckbutton, FactionEnableCheckbuttons) + Discord.insert(tk.INSERT, generate_discord_text(data)) # Select all text and focus the field Discord.tag_add('sel', '1.0', 'end') Discord.focus() - CopyButton = ttk.Button(Form, text="Copy to Clipboard", command=partial(copy_to_clipboard, Form, Discord)) + TabParent.pack(fill=tk.BOTH, expand=1, side=tk.TOP, padx=5, pady=5) + Discord.pack(fill=tk.X, padx=5, pady=5) - TabParent.pack(fill='both', expand=1, side='top', padx=5, pady=5) - CopyButton.pack(side='bottom', padx=5, pady=5) - Discord.pack(fill='x', side='bottom', padx=5, pady=5) + ttk.Button(Form, text="Copy to Clipboard", command=partial(copy_to_clipboard, Form, Discord)).pack(side=tk.LEFT, padx=5, pady=5) + if is_webhook_valid(): ttk.Button(Form, text="Post to Discord", command=partial(post_to_discord, Form, Discord, tick_mode)).pack(side=tk.RIGHT, padx=5, pady=5) def cz_change(CZVar, Discord, cz_type, data, system_index, faction_index, *args): @@ -487,25 +526,72 @@ def cz_change(CZVar, Discord, cz_type, data, system_index, faction_index, *args) Discord.insert(tk.INSERT, generate_discord_text(data)) -def enable_faction_change(EnableVar, Discord, data, system_index, faction_index, *args): +def enable_faction_change(EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, system_index, faction_index, *args): """ - Callback (set as a variable trace) for when a Faction Enable Variable is changed + Callback for when a Faction Enable Checkbutton is changed """ - data[system_index][0]['Factions'][faction_index]['Enabled'] = EnableVar.get() + data[system_index][0]['Factions'][faction_index]['Enabled'] = CheckStates.STATE_ON if FactionEnableCheckbuttons[faction_index].instate(['selected']) else CheckStates.STATE_OFF + update_enable_all_factions_checkbutton(EnableAllCheckbutton, FactionEnableCheckbuttons) Discord.delete('1.0', 'end-1c') Discord.insert(tk.INSERT, generate_discord_text(data)) -def faction_name_clicked(EnableVar, *args): +def enable_all_factions_change(EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, system_index, *args): + """ + Callback for when the Enable All Factions Checkbutton is changed + """ + z = len(FactionEnableCheckbuttons) + for x in range(0, z): + if EnableAllCheckbutton.instate(['selected']): + FactionEnableCheckbuttons[x].state(['selected']) + data[system_index][0]['Factions'][x]['Enabled'] = CheckStates.STATE_ON + else: + FactionEnableCheckbuttons[x].state(['!selected']) + data[system_index][0]['Factions'][x]['Enabled'] = CheckStates.STATE_OFF + + Discord.delete('1.0', 'end-1c') + Discord.insert(tk.INSERT, generate_discord_text(data)) + + +def update_enable_all_factions_checkbutton(EnableAllCheckbutton, FactionEnableCheckbuttons): + """ + Update the 'Enable all factions' checkbox to the correct state based on which individual factions are enabled + """ + any_on = False + any_off = False + z = len(FactionEnableCheckbuttons) + for x in range(0, z): + if FactionEnableCheckbuttons[x].instate(['selected']): any_on = True + if FactionEnableCheckbuttons[x].instate(['!selected']): any_off = True + + if any_on == True: + if any_off == True: + EnableAllCheckbutton.state(['alternate', '!selected']) + else: + EnableAllCheckbutton.state(['!alternate', 'selected']) + else: + EnableAllCheckbutton.state(['!alternate', '!selected']) + + +def faction_name_clicked(EnableCheckbutton, EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, system_index, faction_index, *args): """ Callback when a faction name is clicked. Toggle enabled state. The EnableVar is watched, so that will automatically trigger enable_faction_change() to update data and Discord text """ - if EnableVar.get() == 'Yes': - EnableVar.set('No') - else: - EnableVar.set('Yes') + if EnableCheckbutton.instate(['selected']): EnableCheckbutton.state(['!selected']) + else: EnableCheckbutton.state(['selected']) + enable_faction_change(EnableAllCheckbutton, FactionEnableCheckbuttons, Discord, data, system_index, faction_index, *args) + + +def mission_points_change(MissionPointsVar, Discord, data, system_index, faction_index, *args): + """ + Callback (set as a variable trace) for when a mission points Variable is changed + """ + data[system_index][0]['Factions'][faction_index]['MissionPoints'] = MissionPointsVar.get() + + Discord.delete('1.0', 'end-1c') + Discord.insert(tk.INSERT, generate_discord_text(data)) def generate_discord_text(data): @@ -519,15 +605,16 @@ def generate_discord_text(data): z = len(data[i][0]['Factions']) for x in range(0, z): - if data[i][0]['Factions'][x]['Enabled'] != 'Yes': continue + if data[i][0]['Factions'][x]['Enabled'] != CheckStates.STATE_ON: continue faction_discord_text = "" - faction_discord_text += f".INF {data[i][0]['Factions'][x]['MissionPoints']}; " if data[i][0]['Factions'][x]['MissionPoints'] != 0 else "" + faction_discord_text += f".INF +{data[i][0]['Factions'][x]['MissionPoints']}; " if data[i][0]['Factions'][x]['MissionPoints'] > 0 else f".INF {data[i][0]['Factions'][x]['MissionPoints']}; " if data[i][0]['Factions'][x]['MissionPoints'] < 0 else "" faction_discord_text += f".BVs {human_format(data[i][0]['Factions'][x]['Bounties'])}; " if data[i][0]['Factions'][x]['Bounties'] != 0 else "" faction_discord_text += f".CBs {human_format(data[i][0]['Factions'][x]['CombatBonds'])}; " if data[i][0]['Factions'][x]['CombatBonds'] != 0 else "" faction_discord_text += f".Trade {human_format(data[i][0]['Factions'][x]['TradeProfit'])}; " if data[i][0]['Factions'][x]['TradeProfit'] != 0 else "" faction_discord_text += f".Expl {human_format(data[i][0]['Factions'][x]['CartData'])}; " if data[i][0]['Factions'][x]['CartData'] != 0 else "" faction_discord_text += f".Murders {data[i][0]['Factions'][x]['Murdered']}; " if data[i][0]['Factions'][x]['Murdered'] != 0 else "" + faction_discord_text += f".Fails {data[i][0]['Factions'][x]['MissionFailed']}; " if data[i][0]['Factions'][x]['MissionFailed'] != 0 else "" space_cz = build_cz_text(data[i][0]['Factions'][x].get('SpaceCZ', {}), "SpaceCZs") faction_discord_text += f"{space_cz}; " if space_cz != "" else "" ground_cz = build_cz_text(data[i][0]['Factions'][x].get('GroundCZ', {}), "GroundCZs") @@ -537,7 +624,7 @@ def generate_discord_text(data): discord_text += f"```css\n{data[i][0]['System']}\n{system_discord_text}```" if system_discord_text != "" else "" - return discord_text + return discord_text.replace("'", "") def process_faction_name(faction_name): @@ -569,14 +656,14 @@ def display_todaydata(): """ Display the latest tally data window """ - display_data("Latest BGS Tally", this.TodayData) + display_data("Latest BGS Tally", this.TodayData, Ticks.TICK_CURRENT) def display_yesterdaydata(): """ Display the previous tally data window """ - display_data("Previous BGS Tally", this.YesterdayData) + display_data("Previous BGS Tally", this.YesterdayData, Ticks.TICK_PREVIOUS) def copy_to_clipboard(Form, Discord): @@ -589,6 +676,65 @@ def copy_to_clipboard(Form, Discord): Form.update() +def post_to_discord(Form, Discord, tick_mode): + """ + Get all text from the Discord field and post it to the webhook + """ + if not is_webhook_valid(): return + + discord_text = Discord.get('1.0', 'end-1c').strip() + + # We store a historical discord message ID for the current and previous ticks, so fetch the right one + if tick_mode == Ticks.TICK_CURRENT: discord_message_id = this.DiscordCurrentMessageID + else: discord_message_id = this.DiscordPreviousMessageID + + if discord_message_id.get() == '' or discord_message_id.get() == None: + # No previous post + if discord_text != '': + url = this.DiscordWebhook.get() + response = requests.post(url=url, params={'wait': 'true'}, data={'content': discord_text, 'username': this.DiscordUsername.get()}) + if response.ok: + # Store the Message ID + response_json = response.json() + discord_message_id.set(response_json['id']) + else: + logger.error(f"Unable to create new discord post. Reason: '{response.reason}' Content: '{response.content}' URL: '{url}'") + + else: + # Previous post, amend or delete it + if discord_text != '': + url = f"{this.DiscordWebhook.get()}/messages/{discord_message_id.get()}" + response = requests.patch(url=url, data={'content': discord_text, 'username': this.DiscordUsername.get()}) + if not response.ok: + discord_message_id.set('') + logger.error(f"Unable to update previous discord post. Reason: '{response.reason}' Content: '{response.content}' URL: '{url}'") + + # Try to post new message instead + url = this.DiscordWebhook.get() + response = requests.post(url=url, params={'wait': 'true'}, data={'content': discord_text, 'username': this.DiscordUsername.get()}) + if response.ok: + # Store the Message ID + response_json = response.json() + discord_message_id.set(response_json['id']) + else: + logger.error(f"Unable to create new discord post. Reason: '{response.reason}' Content: '{response.content}' URL: '{url}'") + else: + url = f"{this.DiscordWebhook.get()}/messages/{discord_message_id.get()}" + response = requests.delete(url=url) + if response.ok: + # Clear the Message ID + discord_message_id.set('') + else: + logger.error(f"Unable to delete previous discord post. Reason: '{response.reason}' Content: '{response.content}' URL: '{url}'") + + +def is_webhook_valid(): + """ + Do a basic check on the user specified Discord webhook + """ + return this.DiscordWebhook.get().startswith('https://discord.com/api/webhooks/') + + def tick_format(ticktime): """ Format the tick date/time @@ -605,6 +751,10 @@ def save_data(): config.set('XTickTime', this.TickTime) config.set('XStatus', this.Status.get()) config.set('XAbbreviate', this.AbbreviateFactionNames.get()) + config.set('XDiscordWebhook', this.DiscordWebhook.get()) + config.set('XDiscordUsername', this.DiscordUsername.get()) + config.set('XDiscordCurrentMessageID', this.DiscordCurrentMessageID.get()) + config.set('XDiscordPreviousMessageID', this.DiscordPreviousMessageID.get()) config.set('XIndex', this.DataIndex.get()) config.set('XStation', this.StationFaction.get()) file = os.path.join(this.Dir, "Today Data.txt")