Skip to content

Commit d9ae2b7

Browse files
authored
Stg (#65)
2 parents e37ad17 + d2eea51 commit d9ae2b7

6 files changed

Lines changed: 231 additions & 27 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ src/pip-delete-this-directory.txt
88
**/.vs
99
**/.vscode
1010
**/.DS_Store
11+
12+
pbrVenv
13+
src/lib
14+
.env

PyBugReporter/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.0.9'
1+
__version__ = '1.0.9b1'

PyBugReporter/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
python-graphql-client~=0.4.3
1+
python-graphql-client~=0.4.3
2+
discord.py~=2.0.1

PyBugReporter/src/BugReporter.py

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
import traceback
44
from functools import wraps
5+
from PyBugReporter.src.DiscordBot import DiscordBot
56

67
from python_graphql_client import GraphqlClient
78

@@ -25,20 +26,31 @@ class BugHandler:
2526
repoName: str = ''
2627
orgName: str = ''
2728
test: bool = False
29+
useDiscord: bool = False
30+
botToken: str = ''
31+
channelId: str | int = ''
2832

29-
def __init__(self, githubKey: str, repoName: str, orgName: str, test: bool) -> None:
33+
def __init__(self, githubKey: str, repoName: str, orgName: str, test: bool, useDiscord: bool = False, botToken: str = "", channelId: str | int = "") -> None:
3034
"""Saves the given information in the BugHandler object.
3135
3236
Args:
3337
githubKey (str): the key to use to make the issue
3438
repoName (str): the name of the repo to report to
3539
orgName (str): the organization of the repo
3640
test (bool): whether or not bugs in this code should actually be reported
41+
useDiscord (bool): whether to send the bug report to Discord
42+
botToken (str): the token for the Discord bot
43+
channelId (str | int): the ID of the Discord channel to send messages to
3744
"""
3845
self.githubKey = githubKey
3946
self.repoName = repoName
4047
self.orgName = orgName
4148
self.test = test
49+
self.useDiscord = useDiscord
50+
51+
if useDiscord:
52+
self.botToken = botToken
53+
self.channelId = channelId
4254

4355
class BugReporter:
4456
"""Sends errors to their corresponding repos.
@@ -64,7 +76,7 @@ def __init__(self, repoName: str, extraInfo: bool, **kwargs) -> None:
6476
self.kwargs = kwargs
6577

6678
@classmethod
67-
def setVars(cls, githubKey: str, repoName: str, orgName: str, test: bool) -> None:
79+
def setVars(cls, githubKey: str, repoName: str, orgName: str, test: bool, useDiscord: bool = False, botToken: str = "", channelId: str = "") -> None:
6880
"""Sets the necessary variables to make bug reports.
6981
7082
Args:
@@ -73,7 +85,7 @@ def setVars(cls, githubKey: str, repoName: str, orgName: str, test: bool) -> Non
7385
orgName (str): the name of the organization
7486
test (bool): whether to run in testing mode
7587
"""
76-
cls.handlers[repoName] = BugHandler(githubKey, repoName, orgName, test)
88+
cls.handlers[repoName] = BugHandler(githubKey, repoName, orgName, test, useDiscord, botToken, channelId)
7789

7890
def __call__(self, func: callable) -> None:
7991
"""Decorator that catches exceptions and sends a bug report to the github repository.
@@ -118,26 +130,51 @@ def _handleError(self, e: Exception, repoName: str, *args, **kwargs) -> None:
118130
if self.extraInfo:
119131
description += f"\nExtra Info: {self.kwargs}"
120132

133+
# shortened description for discord if too long (shortens the error text)
134+
start = f"# {title}\n\nType: {excType}\nError text: "
135+
compress = f"{e}\nTraceback: {traceback.format_exc()}"
136+
end = f"\n\nFunction Name: {functionName}\nArguments: {args}\nKeyword Arguments: {kwargs}"
137+
if self.extraInfo:
138+
end += f"\nExtra Info: {self.kwargs}"
139+
140+
staticLength = len(start) + len(end)
141+
if staticLength > 2000:
142+
shortDescription = f"# {title}\n\n" + description[:2000 - len(f"# {title}\n\n") - 3] + "..."
143+
else:
144+
shortDescription = f"{start}{compress[:2000 - staticLength]}{end}"
145+
146+
print(f"SHORT DESCRIPTION with length {len(shortDescription)}:\n{shortDescription}")
147+
148+
121149
# Check if we need to send a bug report
122150
if not self.handlers[repoName].test:
123-
self._sendBugReport(repoName, title, description)
151+
self._sendBugReport(repoName, title, description, shortDescription)
124152

125153
print(title)
126154
print(description)
127155
raise e
128156

129-
def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str) -> None:
157+
def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str, shortErrorMessage: str) -> None:
130158
"""Sends a bug report to the Github repository.
131159
132160
Args:
133161
errorTitle (str): the title of the error
134162
errorMessage (str): the error message
135-
"""
163+
"""
164+
asyncio.run(self._sendBugReport_async(repoName, errorTitle, errorMessage, shortErrorMessage))
165+
166+
async def _sendBugReport_async(self, repoName: str, errorTitle: str, errorMessage: str, shortErrorMessage: str) -> None:
167+
"""Sends a bug report to the Github repository asynchronously.
168+
169+
Args:
170+
errorTitle (str): the title of the error
171+
errorMessage (str): the error message
172+
"""
136173
client = GraphqlClient(endpoint="https://api.github.com/graphql")
137174
headers = {"Authorization": f"Bearer {self.handlers[repoName].githubKey}"}
138175

139176
# query variables
140-
repoId = self._getRepoId(self.handlers[repoName])
177+
repoId = await self._getRepoId_async(self.handlers[repoName])
141178
bugLabel = "LA_kwDOJ3JPj88AAAABU1q15w"
142179
autoLabel = "LA_kwDOJ3JPj88AAAABU1q2DA"
143180

@@ -171,10 +208,15 @@ def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str) -> N
171208
}
172209
}
173210

174-
issueExists = self._checkIfIssueExists(self.handlers[repoName], errorTitle)
211+
issueExists = await self._checkIfIssueExists_async(self.handlers[repoName], errorTitle)
175212

176-
if (issueExists == False):
177-
result = asyncio.run(client.execute_async(query=createIssue, variables=variables, headers=headers))
213+
# Send to Discord if applicable
214+
if self.handlers[repoName].useDiscord:
215+
discordBot = DiscordBot(self.handlers[repoName].botToken, self.handlers[repoName].channelId)
216+
await discordBot.send_message(shortErrorMessage, issueExists)
217+
218+
if (not issueExists):
219+
result = await client.execute_async(query=createIssue, variables=variables, headers=headers)
178220
print('\nThis error has been reported to the Tree Growth team.\n')
179221

180222
issueId = result['data']['createIssue']['issue']['id'] # Extract the issue ID
@@ -191,20 +233,19 @@ def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str) -> N
191233
"""
192234

193235
# Replace with your actual project ID
194-
projectId = self.getProjectId(repoName, "Tree Growth Projects")
236+
projectId = await self.getProjectId_async(repoName, "Tree Growth Projects")
195237

196238
variables = {
197239
"projectId": projectId,
198240
"contentId": issueId
199241
}
200242

201243
# Execute the mutation to add the issue to the project
202-
asyncio.run(client.execute_async(query=addToProject, variables=variables, headers=headers))
244+
await client.execute_async(query=addToProject, variables=variables, headers=headers)
203245
else:
204246
print('\nOur team is already aware of this issue.\n')
205247

206-
def getProjectId(self, repoName: str, projectName: str) -> str:
207-
"""Retrieves the GitHub project ID for a specified repository and project name."""
248+
async def getProjectId_async(self, repoName: str, projectName: str) -> str:
208249
client = GraphqlClient(endpoint="https://api.github.com/graphql")
209250
headers = {"Authorization": f"Bearer {self.handlers[repoName].githubKey}"}
210251

@@ -228,7 +269,7 @@ def getProjectId(self, repoName: str, projectName: str) -> str:
228269
}
229270

230271
# Execute the query
231-
response = asyncio.run(client.execute_async(query=query, variables=variables, headers=headers))
272+
response = await client.execute_async(query=query, variables=variables, headers=headers)
232273
projects = response["data"]["repository"]["projectsV2"]["nodes"]
233274

234275
# Find the project with the matching name and return its ID
@@ -238,7 +279,7 @@ def getProjectId(self, repoName: str, projectName: str) -> str:
238279

239280
raise ValueError(f"Project '{projectName}' not found in repository '{repoName}'.")
240281

241-
def _checkIfIssueExists(self, handler: BugHandler, errorTitle: str) -> bool:
282+
async def _checkIfIssueExists_async(self, handler: BugHandler, errorTitle: str) -> bool:
242283
"""Checks if an issue already exists in the repository.
243284
244285
Args:
@@ -276,7 +317,7 @@ def _checkIfIssueExists(self, handler: BugHandler, errorTitle: str) -> bool:
276317
"labels": autoLabel,
277318
}
278319

279-
result = asyncio.run(client.execute_async(query=findIssue, variables=variables, headers=headers))
320+
result = await client.execute_async(query=findIssue, variables=variables, headers=headers)
280321
nodes = result['data']['organization']['repository']['issues']['nodes']
281322

282323
index = 0
@@ -292,7 +333,7 @@ def _checkIfIssueExists(self, handler: BugHandler, errorTitle: str) -> bool:
292333

293334
return issueExists
294335

295-
def _getRepoId(self, handler: BugHandler) -> str:
336+
async def _getRepoId_async(self, handler: BugHandler) -> str:
296337
"""Gets the repository ID.
297338
298339
Args:
@@ -318,7 +359,7 @@ def _getRepoId(self, handler: BugHandler) -> str:
318359
"name": handler.repoName
319360
}
320361

321-
repoID = asyncio.run(client.execute_async(query=getID, variables=variables, headers=headers))
362+
repoID = await client.execute_async(query=getID, variables=variables, headers=headers)
322363
return repoID['data']['repository']['id']
323364

324365
@classmethod

PyBugReporter/src/DiscordBot.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import asyncio
2+
import discord
3+
4+
HISTORY_LIMIT = 20
5+
EMOJI = "‼"
6+
7+
class DiscordBot(discord.Client):
8+
"""
9+
A simple Discord bot that forwards the bug reports to a given Discord channel.
10+
11+
Attributes:
12+
token (str): bot token
13+
channel_id (int): the ID of the channel to send messages to
14+
_message (str): message to send
15+
_alreadySent (bool): whether the message has already been sent
16+
_done_future (asyncio.Future): a future that is set when the bot is done
17+
"""
18+
def __init__(self, token: str, channelId: str | int) -> None:
19+
"""
20+
Initializes the Discord bot with the given token and channel ID.
21+
22+
Args:
23+
token (str): bot token
24+
channel_id (int): the ID of the channel to send messages to
25+
"""
26+
self.token = token
27+
self.channelId = int(channelId)
28+
self._message = None
29+
self._alreadySent = False
30+
self._doneFuture = None
31+
32+
intents = discord.Intents(emojis = True,
33+
guild_reactions = True,
34+
message_content = True,
35+
guild_messages = True,
36+
guilds = True)
37+
super().__init__(intents=intents)
38+
39+
async def send_message(self, message, alreadySent = False):
40+
"""
41+
Sends a message to the specified channel by setting the variables and starting the bot, then turning it off when finished.
42+
43+
Args:
44+
message (str): The message to send.
45+
alreadySent (bool): Whether the message has already been sent.
46+
"""
47+
self._message = message
48+
self._alreadySent = alreadySent
49+
self._doneFuture = asyncio.get_running_loop().create_future()
50+
print("Starting bot...")
51+
# Start the bot as a background task
52+
asyncio.create_task(self.start(self.token))
53+
# Wait until the message is sent and the bot is closed
54+
await self._doneFuture
55+
56+
async def on_ready(self):
57+
"""
58+
Called when the bot is ready. Also sends the message to the specified channel, or reacts if it's been sent.
59+
"""
60+
try:
61+
channel = await self.fetch_channel(self.channelId)
62+
if channel and not self._alreadySent:
63+
await channel.send(self._message)
64+
print(f"Sent message to channel {self.channelId}")
65+
elif channel and self._alreadySent:
66+
async for message in channel.history(limit=HISTORY_LIMIT):
67+
if message.content == self._message:
68+
await message.add_reaction(EMOJI)
69+
break
70+
else:
71+
print(f"Channel with ID {self.channelId} not found.")
72+
except Exception as e:
73+
print(f"Error sending message: {e}")
74+
finally:
75+
print("Shutting down bot...")
76+
await self.close()
77+
# Mark the future as done so send_message can return
78+
if self._doneFuture and not self._doneFuture.done():
79+
self._doneFuture.set_result(True)

0 commit comments

Comments
 (0)