diff --git a/files/acp/database/install_dev.hanashi.wsc.discord-api.php b/files/acp/database/install_dev.hanashi.wsc.discord-api.php
index fbafcba..e256252 100644
--- a/files/acp/database/install_dev.hanashi.wsc.discord-api.php
+++ b/files/acp/database/install_dev.hanashi.wsc.discord-api.php
@@ -2,12 +2,14 @@
use wcf\system\database\table\column\BigintDatabaseTableColumn;
use wcf\system\database\table\column\BlobDatabaseTableColumn;
+use wcf\system\database\table\column\IntDatabaseTableColumn;
use wcf\system\database\table\column\NotNullInt10DatabaseTableColumn;
use wcf\system\database\table\column\ObjectIdDatabaseTableColumn;
use wcf\system\database\table\column\TextDatabaseTableColumn;
use wcf\system\database\table\column\TinyintDatabaseTableColumn;
use wcf\system\database\table\column\VarcharDatabaseTableColumn;
use wcf\system\database\table\DatabaseTable;
+use wcf\system\database\table\index\DatabaseTableForeignKey;
use wcf\system\database\table\index\DatabaseTablePrimaryIndex;
use wcf\system\database\table\PartialDatabaseTable;
@@ -55,10 +57,19 @@
VarcharDatabaseTableColumn::create('publicKey')
->length(100),
NotNullInt10DatabaseTableColumn::create('botTime'),
+ IntDatabaseTableColumn::create('webhookIconID')
+ ->length(10),
])
->indices([
DatabaseTablePrimaryIndex::create()
->columns(['botID']),
+ ])
+ ->foreignKeys([
+ DatabaseTableForeignKey::create()
+ ->columns(['webhookIconID'])
+ ->referencedTable('wcf1_file')
+ ->referencedColumns(['fileID'])
+ ->onDelete('SET NULL'),
]),
// wcf1_discord_webhook
diff --git a/files/images/discord_webhook/.htaccess b/files/images/discord_webhook/.htaccess
deleted file mode 100644
index 8d2f256..0000000
--- a/files/images/discord_webhook/.htaccess
+++ /dev/null
@@ -1 +0,0 @@
-deny from all
diff --git a/files/lib/acp/form/DiscordBotAddForm.class.php b/files/lib/acp/form/DiscordBotAddForm.class.php
index a30b60d..b13910d 100644
--- a/files/lib/acp/form/DiscordBotAddForm.class.php
+++ b/files/lib/acp/form/DiscordBotAddForm.class.php
@@ -7,10 +7,10 @@
use wcf\form\AbstractFormBuilderForm;
use wcf\system\discord\DiscordApi;
use wcf\system\form\builder\container\FormContainer;
+use wcf\system\form\builder\field\FileProcessorFormField;
use wcf\system\form\builder\field\IntegerFormField;
use wcf\system\form\builder\field\PasswordFormField;
use wcf\system\form\builder\field\TextFormField;
-use wcf\system\form\builder\field\UploadFormField;
use wcf\system\form\builder\field\validation\FormFieldValidationError;
use wcf\system\form\builder\field\validation\FormFieldValidator;
@@ -110,13 +110,12 @@ protected function createForm()
->required()
->maximumLength(50)
->value(PAGE_TITLE),
- UploadFormField::create('webhookIcon')
+ FileProcessorFormField::create('webhookIconID')
+ ->objectType('dev.hanashi.wsc.discord.webhook.avatar')
->label('wcf.acp.discordBotAdd.webhookIcon')
->description('wcf.acp.discordBotAdd.webhookIcon.description')
- ->maximum(1)
- ->imageOnly()
- ->maximumFilesize(8000000)
- ->setAcceptableFiles(['image/jpeg', 'image/png', 'image/gif']),
+ ->singleFileUpload()
+ ->bigPreview(),
]),
FormContainer::create('oauth2Settings')
->label('wcf.acp.discordBotAdd.oauth2Settings')
diff --git a/files/lib/acp/form/DiscordBotEditForm.class.php b/files/lib/acp/form/DiscordBotEditForm.class.php
index c842d52..883c7ea 100644
--- a/files/lib/acp/form/DiscordBotEditForm.class.php
+++ b/files/lib/acp/form/DiscordBotEditForm.class.php
@@ -7,6 +7,7 @@
use wcf\data\discord\bot\DiscordBot;
use wcf\http\Helper;
use wcf\system\exception\IllegalLinkException;
+use wcf\system\form\builder\field\FileProcessorFormField;
class DiscordBotEditForm extends DiscordBotAddForm
{
@@ -38,4 +39,16 @@ public function readParameters()
throw new IllegalLinkException();
}
}
+
+ #[Override]
+ protected function createForm()
+ {
+ parent::createForm();
+
+ $webhookIconFormField = $this->form->getNodeById('webhookIconID');
+ \assert($webhookIconFormField instanceof FileProcessorFormField);
+ $webhookIconFormField->context([
+ 'botID' => $this->formObject->botID,
+ ]);
+ }
}
diff --git a/files/lib/bootstrap/dev.hanashi.wsc.discord-api.php b/files/lib/bootstrap/dev.hanashi.wsc.discord-api.php
index 8bad4df..725d164 100644
--- a/files/lib/bootstrap/dev.hanashi.wsc.discord-api.php
+++ b/files/lib/bootstrap/dev.hanashi.wsc.discord-api.php
@@ -5,11 +5,13 @@
use wcf\acp\page\DiscordWebhookListPage;
use wcf\event\acp\dashboard\box\PHPExtensionCollecting;
use wcf\event\acp\menu\item\ItemCollecting;
+use wcf\event\worker\RebuildWorkerCollecting;
use wcf\system\event\EventHandler;
use wcf\system\menu\acp\AcpMenuItem;
use wcf\system\request\LinkHandler;
use wcf\system\style\FontAwesomeIcon;
use wcf\system\WCF;
+use wcf\system\worker\DiscordWebhookAvatarRebuildDataWorker;
return static function (): void {
EventHandler::getInstance()->register(ItemCollecting::class, static function (ItemCollecting $event) {
@@ -68,4 +70,11 @@ static function (PHPExtensionCollecting $event) {
$event->register('sodium');
}
);
+
+ EventHandler::getInstance()->register(
+ RebuildWorkerCollecting::class,
+ static function (RebuildWorkerCollecting $event) {
+ $event->register(DiscordWebhookAvatarRebuildDataWorker::class, 0);
+ }
+ );
};
diff --git a/files/lib/data/discord/bot/DiscordBot.class.php b/files/lib/data/discord/bot/DiscordBot.class.php
index 0ac7158..0b54126 100644
--- a/files/lib/data/discord/bot/DiscordBot.class.php
+++ b/files/lib/data/discord/bot/DiscordBot.class.php
@@ -3,6 +3,7 @@
namespace wcf\data\discord\bot;
use wcf\data\DatabaseObject;
+use wcf\data\file\File;
use wcf\system\cache\builder\DiscordGuildChannelCacheBuilder;
use wcf\system\discord\DiscordApi;
@@ -25,6 +26,7 @@
* @property-read string|null $clientSecret
* @property-read string|null $publicKey
* @property-read int $botTime
+ * @property-read int|null $webhookIconID
*/
final class DiscordBot extends DatabaseObject
{
@@ -40,6 +42,8 @@ final class DiscordBot extends DatabaseObject
protected DiscordApi $discordApi;
+ protected ?File $file;
+
public function getDiscordApi(): DiscordApi
{
if (!isset($this->discordApi)) {
@@ -53,9 +57,9 @@ public function getWebhookIconUploadFileLocations(): array
{
$files = [];
- $filename = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $this->botID);
- if (\file_exists($filename)) {
- $files[] = $filename;
+ if ($this->webhookIconID !== null) {
+ $file = new File($this->webhookIconID);
+ $files[] = $file->getPathname();
}
return $files;
@@ -68,4 +72,27 @@ public function getCachedDiscordChannel()
'botToken' => $this->botToken,
]);
}
+
+ public function getWebhookAvatar(): ?File
+ {
+ if ($this->webhookIconID === null) {
+ return null;
+ }
+
+ if (!isset($this->file)) {
+ $this->file = new File($this->webhookIconID);
+ }
+
+ return $this->file;
+ }
+
+ public function getWebhookAvatarData(): ?string
+ {
+ $file = $this->getWebhookAvatar();
+ if ($file === null) {
+ return null;
+ }
+
+ return 'data:' . $file->mimeType . ';base64,' . \base64_encode(\file_get_contents($file->getPathname()));
+ }
}
diff --git a/files/lib/data/discord/bot/DiscordBotAction.class.php b/files/lib/data/discord/bot/DiscordBotAction.class.php
index b6dc1f0..16662e1 100644
--- a/files/lib/data/discord/bot/DiscordBotAction.class.php
+++ b/files/lib/data/discord/bot/DiscordBotAction.class.php
@@ -7,7 +7,6 @@
use wcf\system\cache\builder\DiscordGuildChannelsCacheBuilder;
use wcf\system\exception\AJAXException;
use wcf\system\exception\PermissionDeniedException;
-use wcf\system\file\upload\UploadFile;
use wcf\system\WCF;
/**
@@ -47,13 +46,7 @@ public function create()
unset($this->parameters['data']['useApplicationCommands']);
}
- $discordBot = parent::create();
-
- if (isset($this->parameters['webhookIcon']) && \is_array($this->parameters['webhookIcon'])) {
- $this->processWebhookIcon($discordBot->botID);
- }
-
- return $discordBot;
+ return parent::create();
}
#[Override]
@@ -67,34 +60,6 @@ public function update()
}
parent::update();
-
- foreach ($this->getObjects() as $object) {
- if (isset($this->parameters['webhookIcon']) && \is_array($this->parameters['webhookIcon'])) {
- if ($this->parameters['webhookIcon'] === '') {
- $filename = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $object->botID);
- if (\file_exists($filename)) {
- \unlink($filename);
- }
- } else {
- $this->processWebhookIcon($object->botID);
- }
- }
- }
- }
-
- #[Override]
- public function delete()
- {
- $returnValues = parent::delete();
-
- foreach ($this->getObjects() as $object) {
- $filename = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $object->botID);
- if (\file_exists($filename)) {
- \unlink($filename);
- }
- }
-
- return $returnValues;
}
#[Override]
@@ -103,23 +68,6 @@ protected function resetCache()
DiscordGuildChannelsCacheBuilder::getInstance()->reset();
}
- /**
- * verarbeitet hochgeladenes Icon
- *
- * @param mixed $botID
- * @return void
- */
- protected function processWebhookIcon(int $botID)
- {
- $iconFile = \reset($this->parameters['webhookIcon']);
- if ($iconFile instanceof UploadFile && !$iconFile->isProcessed()) {
- $filename = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $botID);
-
- \rename($iconFile->getLocation(), $filename);
- $iconFile->setProcessed($filename);
- }
- }
-
/**
* validiert die Methode getBotToken
*
diff --git a/files/lib/system/discord/WebhookHandler.class.php b/files/lib/system/discord/WebhookHandler.class.php
index a8c48cc..e4b5006 100644
--- a/files/lib/system/discord/WebhookHandler.class.php
+++ b/files/lib/system/discord/WebhookHandler.class.php
@@ -8,7 +8,6 @@
use wcf\data\discord\webhook\DiscordWebhookList;
use wcf\system\cache\runtime\DiscordBotRuntimeCache;
use wcf\system\SingletonFactory;
-use wcf\util\FileUtil;
final class WebhookHandler extends SingletonFactory
{
@@ -21,12 +20,7 @@ public function saveWebhooks(int $botID, array $channelIDs, string $usageBy)
}
$api = $bot->getDiscordApi();
- $avatar = null;
- $avatarFile = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $botID);
- if (\file_exists($avatarFile)) {
- $mimeType = FileUtil::getMimeType($avatarFile);
- $avatar = 'data:' . $mimeType . ';base64,' . \base64_encode(\file_get_contents($avatarFile));
- }
+ $avatar = $bot->getWebhookAvatarData();
$list = new DiscordWebhookList();
$list->getConditionBuilder()->add('botID = ?', [$bot->botID]);
diff --git a/files/lib/system/discord/type/WebhookChannelMultiSelectDiscordType.class.php b/files/lib/system/discord/type/WebhookChannelMultiSelectDiscordType.class.php
index 495eb4e..7f2dee2 100644
--- a/files/lib/system/discord/type/WebhookChannelMultiSelectDiscordType.class.php
+++ b/files/lib/system/discord/type/WebhookChannelMultiSelectDiscordType.class.php
@@ -6,7 +6,6 @@
use wcf\data\discord\webhook\DiscordWebhookAction;
use wcf\data\discord\webhook\DiscordWebhookList;
use wcf\system\exception\UserInputException;
-use wcf\util\FileUtil;
class WebhookChannelMultiSelectDiscordType extends ChannelMultiSelectDiscordType
{
@@ -67,12 +66,7 @@ public function validate($newValue)
if (!isset($webhookChannelIDs[$botID]) || !\in_array($channelID, $webhookChannelIDs[$botID])) {
$discordApi = $discordBots[$botID]->getDiscordApi();
- $avatar = null;
- $avatarFile = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $botID);
- if (\file_exists($avatarFile)) {
- $mimeType = FileUtil::getMimeType($avatarFile);
- $avatar = 'data:' . $mimeType . ';base64,' . \base64_encode(\file_get_contents($avatarFile));
- }
+ $avatar = $discordBots[$botID]->getWebhookAvatarData();
$response = $discordApi->createWebhook($channelID, $discordBots[$botID]->webhookName, $avatar);
if (!$response['error']) {
$action = new DiscordWebhookAction([], 'create', [
diff --git a/files/lib/system/discord/type/WebhookChannelSelectDiscordType.class.php b/files/lib/system/discord/type/WebhookChannelSelectDiscordType.class.php
index 7628c26..14db61f 100644
--- a/files/lib/system/discord/type/WebhookChannelSelectDiscordType.class.php
+++ b/files/lib/system/discord/type/WebhookChannelSelectDiscordType.class.php
@@ -6,7 +6,6 @@
use wcf\data\discord\webhook\DiscordWebhookAction;
use wcf\data\discord\webhook\DiscordWebhookList;
use wcf\system\exception\UserInputException;
-use wcf\util\FileUtil;
class WebhookChannelSelectDiscordType extends ChannelSelectDiscordType
{
@@ -63,12 +62,7 @@ public function validate($newValue, ?int $maxChannels = null)
if (!isset($webhookChannelIDs[$botID]) || !\in_array($channelID, $webhookChannelIDs[$botID])) {
$discordApi = $discordBots[$botID]->getDiscordApi();
- $avatar = null;
- $avatarFile = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $botID);
- if (\file_exists($avatarFile)) {
- $mimeType = FileUtil::getMimeType($avatarFile);
- $avatar = 'data:' . $mimeType . ';base64,' . \base64_encode(\file_get_contents($avatarFile));
- }
+ $avatar = $discordBots[$botID]->getWebhookAvatarData();
$response = $discordApi->createWebhook($channelID, $discordBots[$botID]->webhookName, $avatar);
if (!$response['error']) {
$action = new DiscordWebhookAction([], 'create', [
diff --git a/files/lib/system/file/DiscordWebhookAvatarFileProcessor.class.php b/files/lib/system/file/DiscordWebhookAvatarFileProcessor.class.php
new file mode 100644
index 0000000..5f53def
--- /dev/null
+++ b/files/lib/system/file/DiscordWebhookAvatarFileProcessor.class.php
@@ -0,0 +1,117 @@
+getBot($context);
+ if ($bot === null) {
+ return FileProcessorPreflightResult::InvalidContext;
+ }
+ }
+
+ if (!FileUtil::endsWithAllowedExtension($filename, $this->getAllowedFileExtensions($context))) {
+ return FileProcessorPreflightResult::FileExtensionNotPermitted;
+ }
+
+ return FileProcessorPreflightResult::Passed;
+ }
+
+ #[Override]
+ public function validateUpload(File $file): void
+ {
+ $imageData = @\getimagesize($file->getPathname());
+ if ($imageData === false) {
+ throw new UserInputException('file', 'noImage');
+ }
+ if ($imageData[0] < 128 || $imageData[1] < 128) {
+ throw new UserInputException('file', 'tooSmall');
+ }
+ }
+
+ #[Override]
+ public function adopt(File $file, array $context): void
+ {
+ $bot = $this->getBot($context);
+
+ if ($bot !== null) {
+ (new DiscordBotEditor($bot))->update([
+ 'webhookIconID' => $file->fileID,
+ ]);
+ }
+ }
+
+ #[Override]
+ public function canDelete(File $file): bool
+ {
+ return WCF::getSession()->getPermission('admin.discord.canManageConnection');
+ }
+
+ #[Override]
+ public function canDownload(File $file): bool
+ {
+ return WCF::getSession()->getPermission('admin.discord.canManageConnection');
+ }
+
+ #[Override]
+ public function delete(array $fileIDs, array $thumbnailIDs): void
+ {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('webhookIconID IN (?)', [$fileIDs]);
+
+ $sql = "UPDATE wcf1_discord_bot
+ SET webhookIconID = NULL
+ {$conditionBuilder}";
+ $statement = WCF::getDB()->prepare($sql);
+ $statement->execute($conditionBuilder->getParameters());
+ }
+
+ #[Override]
+ public function getObjectTypeName(): string
+ {
+ return 'dev.hanashi.wsc.discord.webhook.avatar';
+ }
+
+ #[Override]
+ public function getAllowedFileExtensions(array $context): array
+ {
+ return [
+ 'jpg',
+ 'jpeg',
+ 'png',
+ 'gif',
+ ];
+ }
+
+ #[Override]
+ public function getMaximumSize(array $context): ?int
+ {
+ return 8000000;
+ }
+
+ private function getBot(array $context): ?DiscordBot
+ {
+ $botID = $context['botID'] ?? null;
+ if ($botID === null) {
+ return null;
+ }
+
+ return DiscordBotRuntimeCache::getInstance()->getObject($botID);
+ }
+}
diff --git a/files/lib/system/worker/DiscordWebhookAvatarRebuildDataWorker.class.php b/files/lib/system/worker/DiscordWebhookAvatarRebuildDataWorker.class.php
new file mode 100644
index 0000000..027a228
--- /dev/null
+++ b/files/lib/system/worker/DiscordWebhookAvatarRebuildDataWorker.class.php
@@ -0,0 +1,57 @@
+objectList)) {
+ return;
+ }
+
+ foreach ($this->objectList as $bot) {
+ $avatarFile = \sprintf('%simages/discord_webhook/%s.png', WCF_DIR, $bot->botID);
+ if (!\file_exists($avatarFile)) {
+ continue;
+ }
+
+ $editor = new DiscordBotEditor($bot);
+
+ $file = FileEditor::createFromExistingFile(
+ $avatarFile,
+ $bot->botID . '.png',
+ 'dev.hanashi.wsc.discord.webhook.avatar'
+ );
+
+ if ($file === null) {
+ continue;
+ }
+
+ $editor->update([
+ 'webhookIconID' => $file->fileID,
+ ]);
+ }
+ }
+
+ #[Override]
+ protected function initObjectList()
+ {
+ parent::initObjectList();
+
+ $this->objectList->getConditionBuilder()->add('webhookIconID IS NULL');
+ }
+}
diff --git a/language/de.xml b/language/de.xml
index b385e6d..a38b40e 100644
--- a/language/de.xml
+++ b/language/de.xml
@@ -117,6 +117,10 @@
- Discord-Bot erledigen.]]>
+
+
+
+
diff --git a/language/en.xml b/language/en.xml
index 0cef2dd..3d618e2 100644
--- a/language/en.xml
+++ b/language/en.xml
@@ -117,6 +117,10 @@ Go the
- Discord bot
.]]>
+
+
+
+
diff --git a/objectType.xml b/objectType.xml
new file mode 100644
index 0000000..332796f
--- /dev/null
+++ b/objectType.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ dev.hanashi.wsc.discord.webhook.avatar
+ com.woltlab.wcf.file
+ wcf\system\file\DiscordWebhookAvatarFileProcessor
+
+
+
diff --git a/package.xml b/package.xml
index 6840234..beb06da 100644
--- a/package.xml
+++ b/package.xml
@@ -6,8 +6,8 @@
Discord-API
API to cummincate with Discord.
API um mit Discord zu kommunizieren.
- 2.7.0
- 2024-08-31
+ 2.7.1
+ 2024-10-03
https://creativecommons.org/publicdomain/zero/1.0/deed.en
@@ -15,7 +15,7 @@
https://hanashi.dev/
- com.woltlab.wcf
+ com.woltlab.wcf
com.woltlab.wcf
@@ -27,17 +27,25 @@
+
acp/database/install_dev.hanashi.wsc.discord-api.php
+
+
+
+
+ acp/database/install_dev.hanashi.wsc.discord-api.php
+
+
acp/database/install_dev.hanashi.wsc.discord-api.php