(v4.2.0) Added /transcripts history command

This commit is contained in:
DJj123dj
2026-05-25 10:47:28 +02:00
parent 9da066e99e
commit e7475af694
17 changed files with 191 additions and 6 deletions
+1 -1
View File
@@ -28,7 +28,7 @@
"license": "GPL-3.0-only",
"dependencies": {
"@discordjs/rest": "^2.6.1",
"@open-discord-bots/framework": "^0.5.3",
"@open-discord-bots/framework": "^0.5.4",
"@types/node": "^22.5.0",
"@types/terminal-kit": "^2.5.7",
"ansis": "^4.2.0",
+18 -4
View File
@@ -5,6 +5,7 @@ import {opendiscord, api, utilities, openticketUtils} from "../index.js"
import * as discord from "discord.js"
const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts")
const transcriptDatabase = opendiscord.databases.get("opendiscord:transcripts")
export async function registerActions(){
opendiscord.actions.add(new api.ODAction("opendiscord:create-transcript"))
@@ -127,11 +128,24 @@ export async function registerActions(){
cancel()
throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result! (2)")
}
const result = instance.result as api.ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}|{contents:string}>
await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user])
const historyData: api.ODTranscriptHistoryData = {
ticketId:result.channel.id,
ticketName:"#"+result.channel.name,
ticketCreatorId:result.user.id,
ticketCreatedDate:result.ticket.get("opendiscord:opened-on").value,
ticketDeletedDate:Date.now(),
transcriptType:(result.data && "contents" in result.data) ? "localContents" : "remoteUrl",
transcriptContents:(result.data && "contents" in result.data) ? result.data.contents : null,
transcriptUrl:(result.data && "url" in result.data) ? result.data.url : null,
}
transcriptDatabase.set("opendiscord:transcript","C:"+result.channel.id+",U:"+result.user.id,historyData)
await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,result.ticket,result.channel,result.user])
if (instance.compiler.ready){
try{
const {channelMessage,creatorDmMessage,participantDmMessage,activeAdminDmMessage,everyAdminDmMessage} = await instance.compiler.ready(instance.result)
const {channelMessage,creatorDmMessage,participantDmMessage,activeAdminDmMessage,everyAdminDmMessage} = await instance.compiler.ready(result)
//send channel message
if (transcriptConfig.data.general.enableChannel && channelMessage){
@@ -154,7 +168,7 @@ export async function registerActions(){
}else if (p.role == "participant" && transcriptConfig.data.general.enableParticipantDM && participantDmMessage){
//send participant dm message
await opendiscord.client.sendUserDm(p.user,participantDmMessage)
}else if (p.role == "admin" && transcriptConfig.data.general.enableActiveAdminDM && instance.result.success && instance.result.messages && instance.result.messages.some((msg) => msg.author.id == p.user.id) && activeAdminDmMessage){
}else if (p.role == "admin" && transcriptConfig.data.general.enableActiveAdminDM && result.success && result.messages && result.messages.some((msg) => msg.author.id == p.user.id) && activeAdminDmMessage){
//send active admin dm message
await opendiscord.client.sendUserDm(p.user,activeAdminDmMessage)
}else if (p.role == "admin" && transcriptConfig.data.general.enableEveryAdminDM && everyAdminDmMessage){
@@ -171,7 +185,7 @@ export async function registerActions(){
throw new api.ODSystemError("ODAction(ot:create-transcript) => Failed transcript compiler ready()! (see error above)")
}
}
await opendiscord.events.get("afterTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user])
await opendiscord.events.get("afterTranscriptReady").emit([opendiscord.transcripts,result.ticket,result.channel,result.user])
})
//update stats
+21
View File
@@ -1131,6 +1131,27 @@ const transcriptEmbeds = () => {
if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"})
})
)
//TRANSCRIPT HISTORY
embeds.add(new api.ODEmbed("opendiscord:transcript-history"))
embeds.get("opendiscord:transcript-history").workers.add(
new api.ODWorker("opendiscord:transcript-history",0,async (instance,params,origin) => {
const {transcriptUser,transcriptList} = params
//transcript history only supports URL (html) transcripts at the moment
const filteredList = transcriptList.filter((t) => t.transcriptType == "remoteUrl")
const renderedList = filteredList.map((t) => "- ["+t.ticketName+"]("+t.transcriptUrl+") ("+discord.time(new Date(t.ticketDeletedDate ?? 0),"R")+")").join("\n")
//TODO TRANSLATION!!!
const contents = (transcriptList.length < 1) ? "This user does not yet have any transcripts." : ((filteredList.length > 0) ? renderedList : "*⚠️ Transcript history is currently only supported with HTML Transcripts.\nText transcript history will be available soon.*")
instance.setAuthor(transcriptUser.displayName,transcriptUser.displayAvatarURL())
instance.setThumbnail(transcriptUser.displayAvatarURL())
instance.setColor(generalConfig.data.mainColor)
//TODO TRANSLATION!!!
instance.setTitle(utilities.emojiTitle("📄","Transcript History"))
instance.setDescription(contents)
})
)
}
const roleEmbeds = () => {
+10
View File
@@ -677,6 +677,16 @@ const transcriptMessages = () => {
instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-continue").build(origin,{guild,channel,user,ticket,compiler,reason}))
})
)
//TRANSCRIPT HISTORY
messages.add(new api.ODMessage("opendiscord:transcript-history"))
messages.get("opendiscord:transcript-history").workers.add(
new api.ODWorker("opendiscord:transcript-history",0,async (instance,params,origin) => {
const {guild,channel,user,transcriptUser,transcriptList} = params
instance.addEmbed(await embeds.getSafe("opendiscord:transcript-history").build(origin,{guild,channel,user,transcriptUser,transcriptList}))
instance.setEphemeral(true)
})
)
}
const roleMessages = () => {
+44
View File
@@ -0,0 +1,44 @@
///////////////////////////////////////
//TRANSCRIPTS COMMAND
///////////////////////////////////////
import {opendiscord, api, utilities, openticketUtils} from "../index.js"
import * as discord from "discord.js"
const generalConfig = opendiscord.configs.get("opendiscord:general")
const transcriptsDatabase = opendiscord.databases.get("opendiscord:transcripts")
export async function registerCommandResponders(){
//TRANSCRIPTS COMMAND RESPONDER
opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:transcripts",generalConfig.data.prefix,"transcripts"))
opendiscord.responders.commands.get("opendiscord:transcripts").workers.add([
new api.ODWorker("opendiscord:transcripts",0,async (instance,params,origin,cancel) => {
const {guild,channel,user,member} = instance
//responder checks
const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"transcripts")
if (!hasPerms) return cancel()
const isInGuild = await openticketUtils.replyIsInGuild(instance,origin)
if (!isInGuild || !guild || channel.isDMBased()) return cancel()
//fetch data
const transcriptUser = instance.options.getUser("user",true)
const transcriptList = (await transcriptsDatabase.getCategory("opendiscord:transcript") ?? [])
.filter((t) => t.value.ticketCreatorId === transcriptUser.id)
.map((t) => t.value)
.sort((a,b) => (b.ticketDeletedDate ?? 0)-(a.ticketDeletedDate ?? 0))
.slice(0,20)
await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transcript-history").build(origin,{guild,channel,user,transcriptUser,transcriptList}))
}),
new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => {
opendiscord.log(instance.user.displayName+" used the 'transcripts' command!","info",[
{key:"user",value:instance.user.username},
{key:"userid",value:instance.user.id,hidden:true},
{key:"channelid",value:instance.channel.id,hidden:true},
{key:"method",value:origin}
])
})
])
}
+14
View File
@@ -60,6 +60,20 @@ export class ODTranscriptManager<IdList extends ODTranscriptManagerIdConstraint
}
}
/**## ODTranscriptHistoryData `interface`
* The transcript data stored in the transcripts database of deleted tickets.
*/
export interface ODTranscriptHistoryData {
ticketId:string,
ticketName:string,
ticketCreatorId:string,
ticketCreatedDate:number|null,
ticketDeletedDate:number|null,
transcriptType:"localContents"|"remoteUrl",
transcriptContents:string|null,
transcriptUrl:string|null,
}
/**## ODMappedTranscriptManager `class
* A special class with types for the Open Ticket `ODTranscriptManager` class.
*/
+3 -1
View File
@@ -3,7 +3,7 @@
///////////////////////////////////////
import * as api from "@open-discord-bots/framework/api"
import { ODPermissionEmbedType } from "./permission.js"
import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../api/transcript.js"
import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult, ODTranscriptHistoryData } from "../api/transcript.js"
import { ODOption, ODRoleOption, ODSubPanelOption, ODTicketOption, ODWebsiteOption } from "../api/option.js"
import { ODTicket, ODTicketClearFilter } from "../api/ticket.js"
import { ODRole, ODRoleUpdateResult } from "../api/role.js"
@@ -125,6 +125,7 @@ export interface ODEmbedManagerIdMappings extends api.ODEmbedManagerIdConstraint
"opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"},
"opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"},
"opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<any,object|null>,reason:string|null},workers:"opendiscord:transcript-error"},
"opendiscord:transcript-history":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,transcriptUser:discord.User,transcriptList:ODTranscriptHistoryData[]},workers:"opendiscord:transcript-history"},
"opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"},
"opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"},
@@ -210,6 +211,7 @@ export interface ODMessageManagerIdMappings extends api.ODMessageManagerIdConstr
"opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"},
"opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"},
"opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<any,object|null>,reason:string|null},workers:"opendiscord:transcript-error"},
"opendiscord:transcript-history":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,transcriptUser:discord.User,transcriptList:ODTranscriptHistoryData[]},workers:"opendiscord:transcript-history"},
"opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"},
"opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"},
+2
View File
@@ -30,6 +30,7 @@ export interface ODSlashCommandManagerIdMappings extends api.ODSlashCommandManag
"opendiscord:topic":api.ODSlashCommand,
"opendiscord:priority":api.ODSlashCommand,
"opendiscord:transfer":api.ODSlashCommand,
"opendiscord:transcripts":api.ODSlashCommand,
}
/**## ODTextCommandManagerIdMappings `interface`
@@ -68,6 +69,7 @@ export interface ODTextCommandManagerIdMappings extends api.ODTextCommandManager
"opendiscord:priority-set":api.ODTextCommand,
"opendiscord:priority-get":api.ODTextCommand,
"opendiscord:transfer":api.ODTextCommand,
"opendiscord:transcripts":api.ODTextCommand,
}
/**## ODContextMenuManagerIdMappings `interface`
+16
View File
@@ -4,6 +4,7 @@
import * as api from "@open-discord-bots/framework/api"
import { ODTicketJson } from "../api/ticket.js"
import { ODOptionJson } from "../api/option.js"
import { ODTranscriptHistoryData } from "../api/transcript.js"
/**## ODDatabaseManagerIdMappings `interface`
* A list of all available IDs in the default `ODDatabaseManager` class in `opendiscord`.
@@ -15,6 +16,7 @@ export interface ODDatabaseManagerIdMappings extends api.ODDatabaseManagerIdCons
"opendiscord:tickets":ODTicketsDatabase,
"opendiscord:users":ODUsersDatabase,
"opendiscord:options":ODOptionsDatabase,
"opendiscord:transcripts":ODTranscriptsDatabase,
"opendiscord:message-states":ODMessageStatesDatabase,
}
@@ -58,6 +60,15 @@ export interface ODOptionsDatabaseIdMappings extends api.ODDatabaseIdConstraint
"opendiscord:used-option":ODOptionJson
}
/**## ODTranscriptsDatabaseIdMappings `interface`
* A list of all available IDs in the default `ODTranscriptsDatabase` class in `opendiscord`.
* It's used to generate typescript declarations for this class.
*/
export interface ODTranscriptsDatabaseIdMappings extends api.ODDatabaseIdConstraint {
"opendiscord:transcript":ODTranscriptHistoryData,
}
/////////////////////////////
////// MAPPED MANAGERS //////
/////////////////////////////
@@ -92,6 +103,11 @@ export class ODUsersDatabase extends api.ODFormattedJsonDatabase<ODUsersDatabase
*/
export class ODOptionsDatabase extends api.ODFormattedJsonDatabase<ODOptionsDatabaseIdMappings> {}
/**## ODTranscriptsDatabase `class
* A special class with types for the Open Ticket `database/transcripts.json` database file
*/
export class ODTranscriptsDatabase extends api.ODFormattedJsonDatabase<ODTranscriptsDatabaseIdMappings> {}
/**## ODMessageStatesDatabase `class
* A special class with types for the Open Ticket `database/states.json` database file
*/
+1
View File
@@ -90,6 +90,7 @@ export interface ODAdvancedHelpMenuCategoryIdMappings extends api.ODHelpMenuCate
"opendiscord:autodelete-enable":api.ODHelpMenuCommandComponent,
"opendiscord:topic-set":api.ODHelpMenuCommandComponent,
"opendiscord:priority-set":api.ODHelpMenuCommandComponent,
"opendiscord:transcripts":api.ODHelpMenuCommandComponent,
}
/**## ODExtraHelpMenuCategoryIdMappings `interface`
+1
View File
@@ -30,6 +30,7 @@ export interface ODCommandResponderManagerIdMappings extends api.ODCommandRespon
"opendiscord:topic":{origin:"slash"|"text",params:{},workers:"opendiscord:topic"|"opendiscord:logs"},
"opendiscord:priority":{origin:"slash"|"text",params:{},workers:"opendiscord:priority"|"opendiscord:logs"},
"opendiscord:transfer":{origin:"slash"|"text",params:{},workers:"opendiscord:transfer"|"opendiscord:logs"},
"opendiscord:transcripts":{origin:"slash"|"text",params:{},workers:"opendiscord:transcripts"|"opendiscord:logs"},
"opendiscord:autoclose":{origin:"slash"|"text",params:{},workers:"opendiscord:autoclose"|"opendiscord:logs"},
"opendiscord:autodelete":{origin:"slash"|"text",params:{},workers:"opendiscord:autodelete"|"opendiscord:logs"},
+1
View File
@@ -15,6 +15,7 @@ export interface ODTaskManagerIdMappings extends api.ODTaskManagerIdConstraint {
"opendiscord:option-database-cleaner":api.ODTask,
"opendiscord:user-database-cleaner":api.ODTask,
"opendiscord:ticket-database-cleaner":api.ODTask,
"opendiscord:transcript-database-cleaner":api.ODTask,
"opendiscord:panel-auto-update":api.ODTask,
"opendiscord:ticket-saver":api.ODTask,
"opendiscord:blacklist-saver":api.ODTask,
+35
View File
@@ -637,6 +637,25 @@ export async function loadAllSlashCommands(){
}
]
}))
//TRANSCRIPTS
if (allowedCommands.includes("transcripts")) commands.add(new api.ODSlashCommand("opendiscord:transcripts",{
type:act.ChatInput,
name:"transcripts",
//TODO TRANSLATION!!!
description:"View ticket transcript history of a user.",
contexts:[discord.InteractionContextType.Guild],
integrationTypes:[discord.ApplicationIntegrationType.GuildInstall],
options:[
{
name:"user",
//TODO TRANSLATION!!!
description:"The user to view.",
type:acot.User,
required:true
}
]
}))
}
export async function loadAllTextCommands(){
@@ -1211,6 +1230,22 @@ export async function loadAllTextCommands(){
}
]
}))
//TRANSCRIPTS
if (allowedCommands.includes("transcripts")) commands.add(new api.ODTextCommand("opendiscord:transcripts",{
name:"transcripts",
prefix,
dmPermission:false,
guildPermission:true,
allowBots:false,
options:[
{
name:"user",
type:"user",
required:true
}
]
}))
}
export async function loadAllContextMenus(){
+2
View File
@@ -10,6 +10,7 @@ export async function loadAllDatabases(){
opendiscord.databases.add(defaultTicketsDatabase)
opendiscord.databases.add(defaultUsersDatabase)
opendiscord.databases.add(defaultOptionsDatabase)
opendiscord.databases.add(defaultTranscriptsDatabase)
opendiscord.databases.add(defaultMessageStatesDatabase)
}
@@ -52,4 +53,5 @@ export const defaultStatsDatabase = new api.ODStatsDatabase("opendiscord:stats",
export const defaultTicketsDatabase = new api.ODTicketsDatabase("opendiscord:tickets","tickets.json",defaultTicketFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/")
export const defaultUsersDatabase = new api.ODUsersDatabase("opendiscord:users","users.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/")
export const defaultOptionsDatabase = new api.ODOptionsDatabase("opendiscord:options","options.json",defaultOptionFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/")
export const defaultTranscriptsDatabase = new api.ODTranscriptsDatabase("opendiscord:transcripts","transcripts.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/")
export const defaultMessageStatesDatabase = new api.ODMessageStatesDatabase("opendiscord:message-states","states.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/")
+8
View File
@@ -283,5 +283,13 @@ export async function loadAllHelpMenuComponents(){
textOptions:[{name:"priority",optional:false},{name:"reason",optional:true}],
slashOptions:[{name:"priority",optional:false},{name:"reason",optional:true}]
}))
if (allowedCommands.includes("transcripts")) advanced.add(new api.ODHelpMenuCommandComponent("opendiscord:transcripts",0,{
textName:prefix+"transcripts",
textDescription:"View ticket transcript history of a user.", //TODO TRANSLATION!!!
slashName:"/transcripts",
slashDescription:"View ticket transcript history of a user.", //TODO TRANSLATION!!!
textOptions:[{name:"user",optional:false}],
slashOptions:[{name:"user",optional:false}]
}))
}
}
+13
View File
@@ -7,6 +7,7 @@ const userDatabase = opendiscord.databases.get("opendiscord:users")
const ticketDatabase = opendiscord.databases.get("opendiscord:tickets")
const statsDatabase = opendiscord.databases.get("opendiscord:stats")
const optionDatabase = opendiscord.databases.get("opendiscord:options")
const transcriptsDatabase = opendiscord.databases.get("opendiscord:transcripts")
const mainServer = opendiscord.client.mainServer
export async function loadAllTasks(){
@@ -248,6 +249,18 @@ export async function loadDatabaseCleanersTasks(){
}
})
}))
//TRANSCRIPT DATABASE CLEANER
opendiscord.tasks.add(new api.ODTask("opendiscord:transcript-database-cleaner",8,async () => {
//preserve max 20 transcripts per user (async)
const userCount: Map<string,number> = new Map()
for (const {key,value:transcript} of (await transcriptsDatabase.getCategory("opendiscord:transcript") ?? []).sort((a,b) => (b.value.ticketDeletedDate ?? 0)-(a.value.ticketDeletedDate ?? 0))){
const currentAmount = userCount.get(transcript.ticketCreatorId) ?? 0
userCount.set(transcript.ticketCreatorId,currentAmount+1)
if (currentAmount > 20) transcriptsDatabase.delete("opendiscord:transcript",key)
}
}))
}
export async function loadPanelAutoUpdateTasks(){
+1
View File
@@ -715,6 +715,7 @@ const main = async () => {
await (await import("./commands/topic.js")).registerCommandResponders()
await (await import("./commands/priority.js")).registerCommandResponders()
await (await import("./commands/transfer.js")).registerCommandResponders()
await (await import("./commands/transcripts.js")).registerCommandResponders()
}
await opendiscord.events.get("onCommandResponderLoad").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions])
await opendiscord.events.get("afterCommandRespondersLoaded").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions])