Skip to content

Commit

Permalink
Merge branch 'release/3.4.0-a1'
Browse files Browse the repository at this point in the history
  • Loading branch information
aussig committed Jan 28, 2024
2 parents 1f5eda5 + 9925dd6 commit c71d34e
Show file tree
Hide file tree
Showing 28 changed files with 19,429 additions and 249 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: enhancement
labels: bug
assignees: ''

---
Expand Down
21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Change Log

## v3.4.0-a1 - 2024-01-28

### New Features:

* Discord webhooks completely re-worked. Now, instead of a single, fixed webhook for each type of Discord post, there is a fully flexible table of webhooks which you can set up any way you like - a single webhook for all Discord posts; a webhook for each type of Discord post; multiple webhooks for each type, or any combination of these. As one example, this would allow you to send your BGS reports to multiple Discord servers if you wish.
* The system title and a link to the Inara page for the system are now shown at the top of every activity panel.

### Changes:

* Heading styles have been standardised across all windows. And headings are now purple, yay!
* URL link styles have been standardised across all windows.
* When posting CMDR info to Discord, now include how you interacted with them, colour coded.

### Bug Fixes:

* Thargoid vessel types in mission reports were still showing if they were 0. These are now omitted.
* Fix error when fetching carrier data when carrier has no sell orders.


## v3.3.0 - 2023-12-09

### New Features:
Expand All @@ -20,7 +39,7 @@

* Fix (another) crash in code that detects drop from supercruise at megaships.

### API Changes ([v1.3](https://studio-ws.apicur.io/sharing/281a84ad-dca9-42da-a08b-84e4b9af1b7e)):
### API Changes ([v1.3](https://studio-ws.apicur.io/sharing/d352797e-c40e-4f91-bcd8-773a14f40fc0)):

* `/events` endpoint: All localised fields are now stripped before sending. i.e. fields who's name ends with `_Localised`.
* `/activities` endpoint: Added `banshee` to `systems/[system]/twkills`.
Expand Down
22 changes: 9 additions & 13 deletions bgstally/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class Activity:
factions with their activity
"""

def __init__(self, bgstally, tick: Tick = None, discord_bgs_messageid: str = None):
def __init__(self, bgstally, tick: Tick = None):
"""
Instantiate using a given Tick
"""
Expand All @@ -100,8 +100,7 @@ def __init__(self, bgstally, tick: Tick = None, discord_bgs_messageid: str = Non
self.tick_id: str = tick.tick_id
self.tick_time: datetime = tick.tick_time
self.tick_forced: bool = False
self.discord_bgs_messageid: str = discord_bgs_messageid
self.discord_tw_messageid: str = None
self.discord_webhook_data:dict = {} # key = webhook uuid, value = dict containing webhook data
self.discord_notes: str = ""
self.dirty: bool = False
self.systems: dict = {}
Expand Down Expand Up @@ -1289,7 +1288,7 @@ def _generate_tw_system_text(self, system: dict, discord: bool):
if (system_station['passengers']['sum'] > 0):
system_text += f" 🧍 x {green(system_station['passengers']['sum'], fp=fp)} - {green(system_station['passengers']['count'], fp=fp)} missions\n"
if (sum(x['sum'] for x in system_station['massacre'].values())) > 0:
system_text += f" 💀 (missions): " + self._build_tw_vessels_text(system_station['massacre'], discord) + f"- {green((sum(x['count'] for x in system_station['massacre'].values())), fp=fp)} missions\n"
system_text += f" 💀 (missions): " + self._build_tw_vessels_text(system_station['massacre'], discord) + f" - {green((sum(x['count'] for x in system_station['massacre'].values())), fp=fp)} missions\n"
if (system_station['reactivate'] > 0):
system_text += f" 🛠️ x {green(system_station['reactivate'], fp=fp)} missions\n"

Expand Down Expand Up @@ -1335,7 +1334,7 @@ def _build_tw_vessels_text(self, tw_data: dict, discord: bool) -> str:

if isinstance(v, dict): value = int(v.get('sum', 0))
else: value = int(v)
if v == 0: continue
if value == 0: continue

if not first: text += ", "
text += f"{red(label, fp=fp)} x {green(value, fp=fp)}"
Expand Down Expand Up @@ -1363,8 +1362,7 @@ def _as_dict(self):
'tickid': self.tick_id,
'ticktime': self.tick_time.strftime(DATETIME_FORMAT_ACTIVITY),
'tickforced': self.tick_forced,
'discordmessageid': self.discord_bgs_messageid,
'discordtwmessageid': self.discord_tw_messageid,
'discordwebhookdata': self.discord_webhook_data,
'discordnotes': self.discord_notes,
'systems': self.systems}

Expand All @@ -1376,10 +1374,9 @@ def _from_dict(self, dict: Dict):
self.tick_id = dict.get('tickid')
self.tick_time = datetime.strptime(dict.get('ticktime'), DATETIME_FORMAT_ACTIVITY)
self.tick_forced = dict.get('tickforced', False)
self.discord_bgs_messageid = dict.get('discordmessageid')
self.discord_tw_messageid = dict.get('discordtwmessageid')
self.discord_notes = dict.get('discordnotes')
self.systems = dict.get('systems')
self.discord_webhook_data = dict.get('discordwebhookdata', {})
self.discord_notes = dict.get('discordnotes', "")
self.systems = dict.get('systems', {})



Expand Down Expand Up @@ -1421,12 +1418,11 @@ def __deepcopy__(self, memo):
setattr(result, 'tick_id', self.tick_id)
setattr(result, 'tick_time', self.tick_time)
setattr(result, 'tick_forced', self.tick_forced)
setattr(result, 'discord_bgs_messageid', self.discord_bgs_messageid)
setattr(result, 'discord_tw_messageid', self.discord_tw_messageid)
setattr(result, 'discord_notes', self.discord_notes)
setattr(result, 'megaship_pat', self.megaship_pat)

# Deep copied items
setattr(result, 'systems', deepcopy(self.systems, memo))
setattr(result, 'discord_webhook_data', deepcopy(self.discord_webhook_data, memo))

return result
11 changes: 5 additions & 6 deletions bgstally/activitymanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ def new_tick(self, tick: Tick, forced: bool) -> bool:
new_activity.tick_id = tick.tick_id
new_activity.tick_time = tick.tick_time
new_activity.tick_forced = forced
new_activity.discord_bgs_messageid = None
new_activity.discord_tw_messageid = None
new_activity.discord_webhook_data = {}
new_activity.discord_notes = ""
new_activity.clear_activity(self.bgstally.mission_log)
self.activity_data.append(new_activity)
Expand All @@ -104,14 +103,14 @@ def _load(self):

# Handle legacy data if it exists - parse and migrate to new format
filepath = path.join(self.bgstally.plugin_dir, FILE_LEGACY_PREVIOUSDATA)
if path.exists(filepath): self._convert_legacy_data(filepath, Tick(self.bgstally), config.get_str('XDiscordPreviousMessageID')) # Fake a tick for previous legacy - we don't have tick_id or tick_time
if path.exists(filepath): self._convert_legacy_data(filepath, Tick(self.bgstally)) # Fake a tick for previous legacy - we don't have tick_id or tick_time
filepath = path.join(self.bgstally.plugin_dir, FILE_LEGACY_CURRENTDATA)
if path.exists(filepath): self._convert_legacy_data(filepath, self.bgstally.tick, config.get_str('XDiscordCurrentMessageID'))
if path.exists(filepath): self._convert_legacy_data(filepath, self.bgstally.tick)

self.activity_data.sort(reverse=True)


def _convert_legacy_data(self, filepath: str, tick: Tick, discord_bgs_messageid: str):
def _convert_legacy_data(self, filepath: str, tick: Tick):
"""
Convert a legacy activity data file to new location and format.
"""
Expand All @@ -122,7 +121,7 @@ def _convert_legacy_data(self, filepath: str, tick: Tick, discord_bgs_messageid:
remove(filepath)
return

activity = Activity(self.bgstally, tick, discord_bgs_messageid)
activity = Activity(self.bgstally, tick)
activity.load_legacy_data(filepath)
activity.save(path.join(self.bgstally.plugin_dir, FOLDER_ACTIVITYDATA, activity.get_filename()))
self.activity_data.append(activity)
Expand Down
3 changes: 3 additions & 0 deletions bgstally/bgstally.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from bgstally.ui import UI
from bgstally.updatemanager import UpdateManager
from bgstally.utils import get_by_path
from bgstally.webhookmanager import WebhookManager
from config import appversion, config

TIME_WORKER_PERIOD_S = 60
Expand Down Expand Up @@ -79,6 +80,7 @@ def plugin_start(self, plugin_dir: str):
self.market:Market = Market(self)
self.request_manager:RequestManager = RequestManager(self)
self.api_manager:APIManager = APIManager(self)
self.webhook_manager:WebhookManager = WebhookManager(self)
self.update_manager:UpdateManager = UpdateManager(self)
self.ui:UI = UI(self)

Expand Down Expand Up @@ -302,6 +304,7 @@ def save_data(self):
self.state.save()
self.fleet_carrier.save()
self.api_manager.save()
self.webhook_manager.save()


def new_tick(self, force: bool, uipolicy: UpdateUIPolicy):
Expand Down
31 changes: 22 additions & 9 deletions bgstally/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ class UpdateUIPolicy(Enum):
IMMEDIATE = 1
LATER = 2


class DiscordChannel(Enum):
BGS = 0
CMDR_INFORMATION = 1
FLEETCARRIER_MATERIALS = 2
FLEETCARRIER_OPERATIONS = 3
THARGOIDWAR = 4
# Discord channels
# Subclassing from str as well as Enum means json.load and json.dump work seamlessly
class DiscordChannel(str, Enum):
BGS = 'BGS'
CMDR_INFORMATION = 'CMDR-info'
FLEETCARRIER_MATERIALS = 'FC-mats'
FLEETCARRIER_OPERATIONS = 'FC-ops'
THARGOIDWAR = 'TW'


class MaterialsCategory(Enum):
Expand Down Expand Up @@ -65,14 +66,26 @@ class RequestMethod(Enum):
OPTIONS = 'options'


class CmdrInteractionReason(int, Enum):
SCANNED = 0
FRIEND_REQUEST_RECEIVED = 1
INTERDICTED_BY = 2
KILLED_BY = 3
MESSAGE_RECEIVED = 4
TEAM_INVITE_RECEIVED = 5


DATETIME_FORMAT_JOURNAL = "%Y-%m-%dT%H:%M:%SZ"
FILE_SUFFIX = ".json"
FOLDER_ASSETS = "assets"
FOLDER_DATA = "otherdata"
FOLDER_BACKUPS = "backups"
FOLDER_UPDATES = "updates"
FONT_HEADING:tuple = ("Helvetica", 11, "bold")
FONT_TEXT:tuple = ("Helvetica", 11)
FONT_HEADING_1:tuple = ("Helvetica", 13, "bold")
FONT_HEADING_2:tuple = ("Helvetica", 11, "bold")
FONT_TEXT:tuple = ("Helvetica", 11, "normal")
FONT_TEXT_BOLD:tuple = ("Helvetica", 11, "bold")
FONT_TEXT_UNDERLINE:tuple = ("Helvetica", 11, "underline")
FONT_TEXT_BOLD_UNDERLINE:tuple = ("Helvetica", 11, "bold underline")
FONT_SMALL:tuple = ("Helvetica", 9, "normal")
COLOUR_HEADING_1 = "#A300A3"
111 changes: 66 additions & 45 deletions bgstally/discord.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
from datetime import datetime

from requests import Response
Expand All @@ -19,69 +20,89 @@ def __init__(self, bgstally):
self.bgstally = bgstally


def post_plaintext(self, discord_text:str, previous_messageid:str, channel:DiscordChannel, callback:callable):
def post_plaintext(self, discord_text:str, webhooks_data:dict|None, channel:DiscordChannel, callback:callable):
"""
Post plain text to Discord
"""
webhook_url = self._get_webhook(channel)
if not self._is_webhook_valid(webhook_url): return

utc_time_now = datetime.utcnow().strftime(DATETIME_FORMAT)
data:dict = {'channel': channel, 'callback': callback, 'webhook_url': webhook_url} # Data that's carried through the request queue and back to the callback

if previous_messageid == "" or previous_messageid == None:
# No previous post
if discord_text == "": return

discord_text += f"```ansi\n{blue(f'Posted at: {utc_time_now} | {self.bgstally.plugin_name} v{str(self.bgstally.version)}')}```"
url = webhook_url
payload:dict = {'content': discord_text, 'username': self.bgstally.state.DiscordUsername.get(), 'embeds': []}

self.bgstally.request_manager.queue_request(url, RequestMethod.POST, payload=payload, callback=self._request_complete, data=data)
else:
# Previous post
if discord_text != "":
discord_text += f"```ansi\n{green(f'Updated at: {utc_time_now} | {self.bgstally.plugin_name} v{str(self.bgstally.version)}')}```"
url = f"{webhook_url}/messages/{previous_messageid}"
# Start with latest webhooks from manager. Will contain True / False for each channel. Copy dict so we don't affect the webhook manager data.
webhooks:dict = deepcopy(self.bgstally.webhook_manager.get_webhooks_as_dict(channel))

for webhook in webhooks.values():
webhook_url:str = webhook.get('url')
if not self._is_webhook_valid(webhook_url): return

# Get the previous state for this webhook's uuid from the passed in data, if it exists. Default to the state from the webhook manager
specific_webhook_data:dict = {} if webhooks_data is None else webhooks_data.get(webhook.get('uuid', ""), webhook)

utc_time_now:str = datetime.utcnow().strftime(DATETIME_FORMAT)
data:dict = {'channel': channel, 'callback': callback, 'webhookdata': specific_webhook_data} # Data that's carried through the request queue and back to the callback

# Fetch the previous post ID, if present, from the webhook data for the channel we're posting in. May be the default True / False value
previous_messageid:str = specific_webhook_data.get(channel, None)

if previous_messageid == "" or previous_messageid == None or previous_messageid == True or previous_messageid == False:
# No previous post
if discord_text == "": return

discord_text += f"```ansi\n{blue(f'Posted at: {utc_time_now} | {self.bgstally.plugin_name} v{str(self.bgstally.version)}')}```"
url:str = webhook_url
payload:dict = {'content': discord_text, 'username': self.bgstally.state.DiscordUsername.get(), 'embeds': []}

self.bgstally.request_manager.queue_request(url, RequestMethod.PATCH, payload=payload, callback=self._request_complete, data=data)
self.bgstally.request_manager.queue_request(url, RequestMethod.POST, payload=payload, callback=self._request_complete, data=data)
else:
url = f"{webhook_url}/messages/{previous_messageid}"
# Previous post
if discord_text != "":
discord_text += f"```ansi\n{green(f'Updated at: {utc_time_now} | {self.bgstally.plugin_name} v{str(self.bgstally.version)}')}```"
url:str = f"{webhook_url}/messages/{previous_messageid}"
payload:dict = {'content': discord_text, 'username': self.bgstally.state.DiscordUsername.get(), 'embeds': []}

self.bgstally.request_manager.queue_request(url, RequestMethod.PATCH, payload=payload, callback=self._request_complete, data=data)
else:
url:str = f"{webhook_url}/messages/{previous_messageid}"

self.bgstally.request_manager.queue_request(url, RequestMethod.DELETE, callback=self._request_complete, data=data)
self.bgstally.request_manager.queue_request(url, RequestMethod.DELETE, callback=self._request_complete, data=data)


def post_embed(self, title:str, description:str, fields:list, previous_messageid:str, channel:DiscordChannel, callback:callable):
def post_embed(self, title:str, description:str, fields:list, webhooks_data:dict|None, channel:DiscordChannel, callback:callable):
"""
Post an embed to Discord
"""
webhook_url = self._get_webhook(channel)
if not self._is_webhook_valid(webhook_url): return
# Start with latest webhooks from manager. Will contain True / False for each channel. Copy dict so we don't affect the webhook manager data.
webhooks:dict = deepcopy(self.bgstally.webhook_manager.get_webhooks_as_dict(channel))

for webhook in webhooks.values():
webhook_url:str = webhook.get('url')
if not self._is_webhook_valid(webhook_url): return

data:dict = {'channel': channel, 'callback': callback, 'webhook_url': webhook_url} # Data that's carried through the request queue and back to the callback
# Get the previous state for this webhook's uuid from the passed in data, if it exists. Default to the state from the webhook manager
specific_webhook_data:dict = {} if webhooks_data is None else webhooks_data.get(webhook.get('uuid', ""), webhook)

if previous_messageid == "" or previous_messageid == None:
# No previous post
if fields is None or fields == []: return
data:dict = {'channel': channel, 'callback': callback, 'webhookdata': specific_webhook_data} # Data that's carried through the request queue and back to the callback

embed:dict = self._get_embed(title, description, fields, False)
url:str = webhook_url
payload:dict = {'content': "", 'username': self.bgstally.state.DiscordUsername.get(), 'embeds': [embed]}
# Fetch the previous post ID, if present, from the webhook data for the channel we're posting in. May be the default True / False value
previous_messageid:str = specific_webhook_data.get(channel, None)

self.bgstally.request_manager.queue_request(url, RequestMethod.POST, payload=payload, params={'wait': 'true'}, callback=self._request_complete, data=data)
else:
# Previous post
if fields is not None and fields != []:
embed:dict = self._get_embed(title, description, fields, True)
url:str = f"{webhook_url}/messages/{previous_messageid}"
if previous_messageid == "" or previous_messageid == None or previous_messageid == True or previous_messageid == False:
# No previous post
if fields is None or fields == []: return

embed:dict = self._get_embed(title, description, fields, False)
url:str = webhook_url
payload:dict = {'content': "", 'username': self.bgstally.state.DiscordUsername.get(), 'embeds': [embed]}

self.bgstally.request_manager.queue_request(url, RequestMethod.PATCH, payload=payload, callback=self._request_complete, data=data)
self.bgstally.request_manager.queue_request(url, RequestMethod.POST, payload=payload, params={'wait': 'true'}, callback=self._request_complete, data=data)
else:
url = f"{webhook_url}/messages/{previous_messageid}"
# Previous post
if fields is not None and fields != []:
embed:dict = self._get_embed(title, description, fields, True)
url:str = f"{webhook_url}/messages/{previous_messageid}"
payload:dict = {'content': "", 'username': self.bgstally.state.DiscordUsername.get(), 'embeds': [embed]}

self.bgstally.request_manager.queue_request(url, RequestMethod.PATCH, payload=payload, callback=self._request_complete, data=data)
else:
url = f"{webhook_url}/messages/{previous_messageid}"

self.bgstally.request_manager.queue_request(url, RequestMethod.DELETE, callback=self._request_complete, data=data)
self.bgstally.request_manager.queue_request(url, RequestMethod.DELETE, callback=self._request_complete, data=data)


def _request_complete(self, success:bool, response:Response, request:BGSTallyRequest):
Expand All @@ -102,10 +123,10 @@ def _request_complete(self, success:bool, response:Response, request:BGSTallyReq
callback:callable = request.data.get('callback')
if callback:
if request.method == RequestMethod.DELETE:
callback(request.data.get('channel'), "")
callback(request.data.get('channel'), request.data.get('webhookdata'), "")
else:
response_json:dict = response.json()
callback(request.data.get('channel'), response_json.get('id', ""))
callback(request.data.get('channel'), request.data.get('webhookdata'), response_json.get('id', ""))


def _get_embed(self, title:str, description:str, fields:list, update:bool) -> dict:
Expand Down
Loading

0 comments on commit c71d34e

Please sign in to comment.