Chat replica
This commit is contained in:
parent
34c555dbd4
commit
af3ddaab9e
76
app/http/endpoints/api/transcripts/render.go
Normal file
76
app/http/endpoints/api/transcripts/render.go
Normal file
@ -0,0 +1,76 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TicketsBot/GoPanel/chatreplica"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/archiverclient"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetTranscriptRenderHandler(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
userId := ctx.Keys["userid"].(uint64)
|
||||
|
||||
// format ticket ID
|
||||
ticketId, err := strconv.Atoi(ctx.Param("ticketId"))
|
||||
if err != nil {
|
||||
ctx.JSON(400, utils.ErrorStr("Invalid ticket ID"))
|
||||
return
|
||||
}
|
||||
|
||||
// get ticket object
|
||||
ticket, err := dbclient.Client.Tickets.Get(ticketId, guildId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, gin.H{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Verify this is a valid ticket and it is closed
|
||||
if ticket.UserId == 0 || ticket.Open {
|
||||
ctx.JSON(404, utils.ErrorStr("Transcript not found"))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the user has permissions to be here
|
||||
// ticket.UserId cannot be 0
|
||||
if ticket.UserId != userId {
|
||||
hasPermission, err := utils.HasPermissionToViewTicket(guildId, userId, ticket)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
ctx.JSON(403, utils.ErrorStr("You do not have permission to view this transcript"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve ticket messages from bucket
|
||||
messages, err := utils.ArchiverClient.Get(guildId, ticketId)
|
||||
if err != nil {
|
||||
if errors.Is(err, archiverclient.ErrExpired) {
|
||||
ctx.JSON(404, utils.ErrorStr("Transcript not found"))
|
||||
} else {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Render
|
||||
payload := chatreplica.FromArchiveMessages(messages, ticketId)
|
||||
html, err := chatreplica.Render(payload)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data(200, "text/html", html)
|
||||
}
|
@ -112,6 +112,7 @@ func StartServer() {
|
||||
|
||||
// Allow regular users to get their own transcripts, make sure you check perms inside
|
||||
guildApiNoAuth.GET("/transcripts/:ticketId", rl(middleware.RateLimitTypeGuild, 10, 10*time.Second), api_transcripts.GetTranscriptHandler)
|
||||
guildApiNoAuth.GET("/transcripts/:ticketId/render", rl(middleware.RateLimitTypeGuild, 10, 10*time.Second), api_transcripts.GetTranscriptRenderHandler)
|
||||
|
||||
guildAuthApiSupport.GET("/tickets", api_ticket.GetTickets)
|
||||
guildAuthApiSupport.GET("/tickets/:ticketId", api_ticket.GetTicket)
|
||||
|
50
chatreplica/convert.go
Normal file
50
chatreplica/convert.go
Normal file
@ -0,0 +1,50 @@
|
||||
package chatreplica
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rxdn/gdl/objects/channel/message"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func FromArchiveMessages(messages []message.Message, ticketId int) Payload {
|
||||
users := make(map[string]User)
|
||||
var wrappedMessages []Message // Cannot define length because of continue
|
||||
|
||||
for _, msg := range messages {
|
||||
// If all 3 are missing, server will 400
|
||||
if msg.Content == "" && len(msg.Embeds) == 0 && len(msg.Attachments) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
wrappedMessages = append(wrappedMessages, Message{
|
||||
Id: msg.Id,
|
||||
Type: msg.Type,
|
||||
Author: msg.Author.Id,
|
||||
Time: msg.Timestamp.Unix(),
|
||||
Content: msg.Content,
|
||||
Embeds: msg.Embeds,
|
||||
Attachments: msg.Attachments,
|
||||
})
|
||||
|
||||
// Add user to entities map
|
||||
snowflake := strconv.FormatUint(msg.Author.Id, 10)
|
||||
if _, ok := users[snowflake]; !ok {
|
||||
users[snowflake] = User{
|
||||
Avatar: msg.Author.AvatarUrl(256),
|
||||
Username: msg.Author.Username,
|
||||
Discriminator: msg.Author.Discriminator,
|
||||
Badge: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Payload{
|
||||
Entities: Entities{
|
||||
Users: users,
|
||||
Channels: make(map[string]Channel),
|
||||
Roles: make(map[string]Role),
|
||||
},
|
||||
Messages: wrappedMessages,
|
||||
ChannelName: fmt.Sprintf("ticket-%d", ticketId),
|
||||
}
|
||||
}
|
44
chatreplica/proxy.go
Normal file
44
chatreplica/proxy.go
Normal file
@ -0,0 +1,44 @@
|
||||
package chatreplica
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TicketsBot/GoPanel/config"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSHandshakeTimeout: time.Second * 3, // We're not using TLS anyway
|
||||
},
|
||||
Timeout: time.Second * 3,
|
||||
}
|
||||
|
||||
func Render(payload Payload) ([]byte, error) {
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := client.Post(config.Conf.Bot.RenderServiceUrl, "application/json", bytes.NewBuffer(encoded))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println(string(encoded))
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("render service returned status code %d", res.StatusCode)
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
55
chatreplica/structs.go
Normal file
55
chatreplica/structs.go
Normal file
@ -0,0 +1,55 @@
|
||||
package chatreplica
|
||||
|
||||
import (
|
||||
"github.com/rxdn/gdl/objects/channel"
|
||||
"github.com/rxdn/gdl/objects/channel/embed"
|
||||
"github.com/rxdn/gdl/objects/channel/message"
|
||||
"github.com/rxdn/gdl/objects/user"
|
||||
)
|
||||
|
||||
type (
|
||||
Payload struct {
|
||||
Entities Entities `json:"entities"`
|
||||
Messages []Message `json:"messages"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
}
|
||||
|
||||
// Entities Snowflake -> Entity map
|
||||
Entities struct {
|
||||
Users map[string]User `json:"users"`
|
||||
Channels map[string]Channel `json:"channels"`
|
||||
Roles map[string]Role `json:"roles"`
|
||||
}
|
||||
|
||||
User struct {
|
||||
Avatar string `json:"avatar"`
|
||||
Username string `json:"username"`
|
||||
Discriminator user.Discriminator `json:"discriminator"`
|
||||
Badge *Badge `json:"badge,omitempty"`
|
||||
}
|
||||
|
||||
Channel struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
Role struct {
|
||||
Name string `json:"name"`
|
||||
Color int `json:"color"`
|
||||
}
|
||||
|
||||
Message struct {
|
||||
Id uint64 `json:"id,string"`
|
||||
Type message.MessageType `json:"type"`
|
||||
Author uint64 `json:"author,string"`
|
||||
Time int64 `json:"time"` // Unix seconds
|
||||
Content string `json:"content"`
|
||||
Embeds []embed.Embed `json:"embeds,omitempty"`
|
||||
Attachments []channel.Attachment `json:"attachments,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
type Badge string
|
||||
|
||||
const (
|
||||
BadgeBot Badge = "bot"
|
||||
)
|
@ -61,6 +61,7 @@ type (
|
||||
ObjectStore string
|
||||
AesKey string `toml:"aes-key"`
|
||||
ProxyUrl string `toml:"discord-proxy-url"`
|
||||
RenderServiceUrl string `toml:"render-service-url"`
|
||||
}
|
||||
|
||||
Redis struct {
|
||||
@ -163,6 +164,7 @@ func fromEnvvar() {
|
||||
ObjectStore: os.Getenv("LOG_ARCHIVER_URL"),
|
||||
AesKey: os.Getenv("LOG_AES_KEY"),
|
||||
ProxyUrl: os.Getenv("DISCORD_PROXY_URL"),
|
||||
RenderServiceUrl: os.Getenv("RENDER_SERVICE_URL"),
|
||||
},
|
||||
Redis: Redis{
|
||||
Host: os.Getenv("REDIS_HOST"),
|
||||
|
@ -28,6 +28,7 @@
|
||||
- PREMIUM_PROXY_KEY
|
||||
- LOG_ARCHIVER_URL
|
||||
- LOG_AES_KEY
|
||||
- RENDER_SERVICE_URL
|
||||
- REDIS_HOST
|
||||
- REDIS_PORT
|
||||
- REDIS_PASSWORD
|
||||
|
@ -1,33 +1,5 @@
|
||||
<div class="discord-container">
|
||||
<div>
|
||||
<div class="channel-header">
|
||||
<span class="channel-name">#ticket-{ticketId}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="message-container">
|
||||
{#each messages as message}
|
||||
<div class="message">
|
||||
{#if message.timestamp > epoch}
|
||||
<span class="timestamp">
|
||||
[{message.timestamp.toLocaleTimeString([], dateFormatSettings)} {message.timestamp.toLocaleDateString()}]
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<img src="https://cdn.discordapp.com/avatars/{message.author.id}/{message.author.avatar}.webp?size=256"
|
||||
class="avatar">
|
||||
<b class="username">{message.author.username}</b>
|
||||
<span class="content">{message.content}</span>
|
||||
|
||||
{#if message.attachments !== undefined && message.attachments.length > 0}
|
||||
{#each message.attachments as attachment}
|
||||
<a href="{attachment.url}" target="_blank" title="{attachment.filename}" class="attachment"><i
|
||||
class="far fa-file-alt fa-2x"></i></a>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<iframe srcdoc={html} style="border: none; width: 100%; height: 100%">
|
||||
</iframe>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
@ -41,23 +13,18 @@
|
||||
let guildId = currentRoute.namedParams.id;
|
||||
let ticketId = currentRoute.namedParams.ticketid;
|
||||
|
||||
setDefaultHeaders()
|
||||
setDefaultHeaders();
|
||||
|
||||
let messages = [];
|
||||
let epoch = new Date('2015');
|
||||
let dateFormatSettings = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
};
|
||||
let html = '';
|
||||
|
||||
async function loadData() {
|
||||
const res = await axios.get(`${API_URL}/api/${guildId}/transcripts/${ticketId}`);
|
||||
const res = await axios.get(`${API_URL}/api/${guildId}/transcripts/${ticketId}/render`);
|
||||
if (res.status !== 200) {
|
||||
errorPage(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
messages = res.data.map(message => Object.assign({}, message, {timestamp: new Date(message.timestamp)}));
|
||||
html = res.data;
|
||||
}
|
||||
|
||||
withLoadingScreen(loadData);
|
||||
|
Loading…
x
Reference in New Issue
Block a user