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

Ticket 446: Add subject to ticket thread in Discord #6

Merged
merged 7 commits into from
Feb 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Using #446 for testing the addition of a sync lock practically
Paul Philion committed Feb 10, 2024
commit 5eb97223fb0bf95f3d1c1fa67811c2f23e36342d
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ To enable netbot on your Discord server, you need to generate a valid `DISCORD_T
4. In the OAuth2/URL Generator section, generate a URL for a **bot** with **Administrator** permissions.
5. That URL will look something like:
https://discord.com/api/oauth2/authorize?client_id=[client-id]&permissions=8&scope=bot
6. Open a browser with that URL and and login.
6. Open a browser with that URL and login.
7. You'll be presented with a window to add your bot to any server you have admin permissions for.
8. Select your server and click OK.
4. Back in the bot setup, in the "Bot" section, under "Build-A-Bot", **Copy** the Token. This is the DISCORD_TOKEN
1 change: 1 addition & 0 deletions cog_scn.py
Original file line number Diff line number Diff line change
@@ -77,6 +77,7 @@ async def sync_thread(self, thread:discord.Thread):
@tasks.loop(minutes=1.0) # FIXME to 5.0 minutes. set to 1 min for testing
async def sync_all_threads(self):
log.info(f"sync_all_threads: starting for {self.bot.guilds}")
# LOCK acquire sync lock. - 1 lock, bot-level, to block

# get all threads
for guild in self.bot.guilds:
94 changes: 53 additions & 41 deletions netbot.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import os
import re
import logging
import asyncio
import datetime as dt

import discord
@@ -15,7 +16,7 @@


def setup_logging():
logging.basicConfig(level=logging.INFO, format="{asctime} {levelname:<8s} {name:<16} {message}", style='{')
logging.basicConfig(level=logging.DEBUG, format="{asctime} {levelname:<8s} {name:<16} {message}", style='{')
logging.getLogger("discord.gateway").setLevel(logging.WARNING)
logging.getLogger("discord.http").setLevel(logging.WARNING)
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
@@ -33,6 +34,8 @@ def __init__(self, redmine: redmine.Client):
log.info(f'initializing {self}')
intents = discord.Intents.default()
intents.message_content = True

self.lock = asyncio.Lock()

self.redmine = redmine
#guilds = os.getenv('DISCORD_GUILDS').split(', ')
@@ -95,56 +98,65 @@ async def sync_new_message(self, ticket_id: int, message: discord.Message):
Synchronize a ticket to a thread
"""
async def synchronize_ticket(self, ticket, thread:discord.Thread):
log.debug(f"ticket: {ticket.id}, thread: {thread}")
# as this is an async method call, and we don't want to lock bot-level event processing,
# we need to create a per-ticket lock to make sure the same
# TODO per-ticket lock? trying bot-level lock first
if self.lock.locked():
log.debug(f"sync locked. skipping sync in progress for ticket #{ticket.id}")
return

# start of the process, will become "last update"
timestamp = dt.datetime.now(dt.timezone.utc) # UTC
async with self.lock:
log.debug(f"sync lock acquired, syncing id: {ticket.id}, thread: {thread}")

# start of the process, will become "last update"
sync_start = dt.datetime.now(dt.timezone.utc) # UTC

last_sync = self.redmine.get_field(ticket, "sync")
if last_sync is None:
last_sync = sync_start - dt.timedelta(days=2*365) # initialize to 2 years ago

last_sync = self.redmine.get_field(ticket, "sync")
if last_sync is None:
last_sync = timestamp - dt.timedelta(days=2*365) # 2 years

log.debug(f"ticket {ticket.id} last sync: {last_sync}, age: {self.redmine.get_field(ticket, 'age')}")

notes = self.redmine.get_notes_since(ticket.id, last_sync)
log.info(f"syncing {len(notes)} notes from {ticket.id} --> {thread.name}")

for note in notes:
msg = f"> **{note.user.name}** at *{note.created_on}*\n> {note.notes}\n\n"
await thread.send(msg)

# query discord for updates to thread since last-update
# see https://docs.pycord.dev/en/stable/api/models.html#discord.Thread.history
log.debug(f"calling history with thread={thread}, after={last_sync}")
#messages = await thread.history(after=last_sync, oldest_first=True).flatten()
#for message in messages:
async for message in thread.history(after=last_sync, oldest_first=True):
# ignore bot messages!
if message.author.id != self.user.id:
# for each, create a note with translated discord user id with the update (or one big one?)
user = self.redmine.find_discord_user(message.author.name)

if user:
log.debug(f"SYNC: ticket={ticket.id}, user={user.login}, msg={message.content}")
self.redmine.append_message(ticket.id, user.login, message.content)
else:
log.warning(
f"synchronize_ticket - unknown discord user: {message.author.name}, skipping message")
else:
log.debug(f"No new discord messages found since {last_sync}")
log.debug(f"ticket {ticket.id} last sync: {last_sync}")

# get the new notes from the ticket
notes = self.redmine.get_notes_since(ticket.id, last_sync)
log.info(f"syncing {len(notes)} notes from {ticket.id} --> {thread.name}")

for note in notes:
# Write the note to the discord thread
msg = f"> **{note.user.name}** at *{note.created_on}*\n> {note.notes}\n\n"
await thread.send(msg)

# query discord for updates to thread since last-update
# see https://docs.pycord.dev/en/stable/api/models.html#discord.Thread.history
log.debug(f"calling history with thread={thread}, after={last_sync}")
async for message in thread.history(after=last_sync, oldest_first=True):
# ignore bot messages!
if message.author.id != self.user.id:
# for each, create a note with translated discord user id with the update (or one big one?)
user = self.redmine.find_discord_user(message.author.name)

if user:
log.debug(f"SYNC: ticket={ticket.id}, user={user.login}, msg={message.content}")
self.redmine.append_message(ticket.id, user.login, message.content)
else:
log.info(
f"synchronize_ticket - unknown discord user: {message.author.name}, skipping message")
else:
log.debug(f"No new discord messages found since {last_sync}")

# update the SYNC timestamp
self.redmine.update_syncdata(ticket.id, dt.datetime.now(dt.timezone.utc)) # fresh timestamp, instead of 'timestamp'
log.info(f"completed sync for {ticket.id} <--> {thread.name}")

# update the SYNC timestamp
self.redmine.update_syncdata(ticket.id, dt.datetime.now(dt.timezone.utc)) # fresh timestamp, instead of 'timestamp'
log.info(f"completed sync for {ticket.id} <--> {thread.name}")

async def on_application_command_error(self, ctx: discord.ApplicationContext, error: discord.DiscordException):
"""Bot-level error handler"""
if isinstance(error, commands.CommandOnCooldown):
await ctx.respond("This command is currently on cooldown!")
else:
log.error(f"{error}", exc_info=True)
log.error(f"{ctx} - {error}", exc_info=True)
#raise error # Here we raise other errors to ensure they aren't ignored
await ctx.respond(f"{error}")
await ctx.respond(f"Error processing your request: {error}")


def main():