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

Load messages from Discord with webhook URL #91

Merged
merged 3 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
39 changes: 39 additions & 0 deletions modules/editor/EditorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Instance, SnapshotOrInstance, types } from "mobx-state-tree"
import { delay } from "../../common/state/delay"
import type { MessageData } from "../message/state/data/MessageData"
import { MessageModel } from "../message/state/models/MessageModel"
import { WebhookModel } from "../webhook/WebhookModel"

Expand All @@ -23,6 +24,44 @@ export const EditorManager = types
self.messages.push(MessageModel.create())
},

async getMessage(reference: string) {
for (const target of self.targets) {
const headers: Record<string, string> = {
"Accept": "application/json",
"Accept-Language": "en",
}

/* eslint-disable no-await-in-loop */

const [, url] = await target.getRoute(reference)
const response = await fetch(url, { method: "GET", headers })
const data = await response.json()

if (response.headers.get("X-RateLimit-Remaining") === "0") {
const retryAfter =
Number(response.headers.get("X-RateLimit-Reset-After") ?? 2) * 1000

console.log(
"Rate limited: delaying next request by",
retryAfter,
"milliseconds",
)

await delay(retryAfter)
}

/* eslint-enable no-await-in-loop */

console.log("Reference fetched", data)

if (response.ok) {
return data as MessageData
}
}

return null
},

async save() {
for (const target of self.targets) {
for (const message of self.messages) {
Expand Down
87 changes: 87 additions & 0 deletions modules/editor/message/LoadClearMessageConfirmationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useState } from "react"
import { PrimaryButton } from "../../../common/input/button/PrimaryButton"
import { SecondaryButton } from "../../../common/input/button/SecondaryButton"
import { ModalAction } from "../../../common/modal/layout/ModalAction"
import { ModalBody } from "../../../common/modal/layout/ModalBody"
import { ModalContainer } from "../../../common/modal/layout/ModalContainer"
import { ModalFooter } from "../../../common/modal/layout/ModalFooter"
import { ModalHeader } from "../../../common/modal/layout/ModalHeader"
import { ModalTitle } from "../../../common/modal/layout/ModalTitle"
import { ModalContext } from "../../../common/modal/ModalContext"
import { useRequiredContext } from "../../../common/state/useRequiredContext"
import { remove } from "../../../icons/remove"
import { Markdown } from "../../markdown/Markdown"
import { messageOf } from "../../message/helpers/messageOf"
import type { MessageLike } from "../../message/state/models/MessageModel"
import type { EditorManagerLike } from "../EditorManager"
import { InputError } from "../../../common/input/error/InputError"

export type LoadClearMessageConfirmationModalProps = {
editorManager: EditorManagerLike
message: MessageLike
}

export function LoadClearMessageConfirmationModal(
props: LoadClearMessageConfirmationModalProps,
) {
const { editorManager, message } = props

const modal = useRequiredContext(ModalContext)

const [error, setError] = useState<string | undefined>(undefined)

return (
<ModalContainer>
<ModalHeader>
<ModalTitle>Clear & Load Message</ModalTitle>
<ModalAction
icon={remove}
label="Close"
onClick={() => modal.dismiss()}
/>
</ModalHeader>
<ModalBody>
<Markdown
content={
"Loading a message link will remove all content from the current editor, if any." +
" Are you sure you want to continue? This action cannot be reverted."
}
/>
<InputError error={error} />
</ModalBody>
<ModalFooter>
<SecondaryButton onClick={() => modal.dismiss()}>
Cancel
</SecondaryButton>
<PrimaryButton
onClick={async () => {
let data
try {
data = await editorManager.getMessage(message.reference)
} catch {
// Handle later
}
if (!data) {
setError(
"The message link could not be loaded. Make sure a correct webhook URL is provided.",
)
return
}

const newMessage = messageOf(data)
newMessage.reference = message.reference

const index = editorManager.messages.indexOf(message)
const messages = [...editorManager.messages]
messages.splice(index, 1, newMessage as MessageLike)
editorManager.set("messages", messages)

modal.dismiss()
}}
>
Load
</PrimaryButton>
</ModalFooter>
</ModalContainer>
)
}
28 changes: 25 additions & 3 deletions modules/editor/message/MessageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { MessageLike } from "../../message/state/models/MessageModel"
import type { DataEditorModalProps } from "../data/DataEditorModal"
import { EditorManagerContext } from "../EditorManagerContext"
import { EmbedEditor } from "./EmbedEditor"
import { LoadClearMessageConfirmationModal } from "./LoadClearMessageConfirmationModal"
import { PrimaryContentEditor } from "./PrimaryContentEditor"

const DataEditorModal = dynamic<DataEditorModalProps>(async () =>
Expand Down Expand Up @@ -53,6 +54,16 @@ export function MessageEditor(props: MessageEditorProps) {
render: () => <DataEditorModal message={message} />,
})

const spawnLoadClearMessageModal = () =>
modalManager.spawn({
render: () => (
<LoadClearMessageConfirmationModal
editorManager={editorManager}
message={message}
/>
),
})

return useObserver(() => (
<Stack gap={16}>
<PrimaryContentEditor message={message} form={form} />
Expand Down Expand Up @@ -94,13 +105,24 @@ export function MessageEditor(props: MessageEditorProps) {
placeholder="https://discord.com/channels/..."
error={form.field("reference").error}
{...form.field("reference").inputProps}
/>
>
<PrimaryButton
disabled={
!form.field("reference").isValid || form.field("reference").isEmpty
}
onClick={() => {
spawnLoadClearMessageModal()
}}
>
Load
</PrimaryButton>
</InputField>
<Message
content={
"*When a message link is set, pressing submit or edit will edit the" +
" message sent inside of Discord. To load a message sent in Discord, use" +
" the bot's 'restore' command found in the apps section of the right" +
" click menu on any message.*"
" the 'Load' button or the bot's 'restore' command found in the apps" +
" section of the right click menu on any message.*"
}
/>
<ButtonList>
Expand Down