From 793420684e874fb599704ee2e5e656c41c81535d Mon Sep 17 00:00:00 2001 From: Ryonez Coruscare Date: Thu, 5 Sep 2019 12:33:42 +1200 Subject: [PATCH] Initial Upload --- .gitignore | 1 + .travis.yml | 33 ++++++ LICENSE | 22 ++++ Locale/cs_CZ/translations.php | 6 + Locale/da_DK/translations.php | 6 + Locale/de_DE/translations.php | 6 + Locale/es_ES/translations.php | 6 + Locale/fi_FI/translations.php | 6 + Locale/fr_FR/translations.php | 6 + Locale/hu_HU/translations.php | 6 + Locale/id_ID/translations.php | 6 + Locale/it_IT/translations.php | 6 + Locale/ja_JP/translations.php | 6 + Locale/nb_NO/translations.php | 6 + Locale/nl_NL/translations.php | 6 + Locale/pl_PL/translations.php | 6 + Locale/pt_BR/translations.php | 6 + Locale/pt_PT/translations.php | 6 + Locale/ru_RU/translations.php | 6 + Locale/sr_Latn_RS/translations.php | 6 + Locale/sv_SE/translations.php | 6 + Locale/th_TH/translations.php | 6 + Locale/tr_TR/translations.php | 6 + Locale/zh_CN/translations.php | 6 + Makefile | 5 + Notification/Discord.php | 182 +++++++++++++++++++++++++++++ Plugin.php | 55 +++++++++ README.md | 53 +++++++++ Template/config/integration.php | 11 ++ Template/project/integration.php | 11 ++ Template/user/integration.php | 11 ++ Test/PluginTest.php | 20 ++++ 32 files changed, 530 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 Locale/cs_CZ/translations.php create mode 100644 Locale/da_DK/translations.php create mode 100644 Locale/de_DE/translations.php create mode 100644 Locale/es_ES/translations.php create mode 100644 Locale/fi_FI/translations.php create mode 100644 Locale/fr_FR/translations.php create mode 100644 Locale/hu_HU/translations.php create mode 100644 Locale/id_ID/translations.php create mode 100644 Locale/it_IT/translations.php create mode 100644 Locale/ja_JP/translations.php create mode 100644 Locale/nb_NO/translations.php create mode 100644 Locale/nl_NL/translations.php create mode 100644 Locale/pl_PL/translations.php create mode 100644 Locale/pt_BR/translations.php create mode 100644 Locale/pt_PT/translations.php create mode 100644 Locale/ru_RU/translations.php create mode 100644 Locale/sr_Latn_RS/translations.php create mode 100644 Locale/sv_SE/translations.php create mode 100644 Locale/th_TH/translations.php create mode 100644 Locale/tr_TR/translations.php create mode 100644 Locale/zh_CN/translations.php create mode 100644 Makefile create mode 100644 Notification/Discord.php create mode 100644 Plugin.php create mode 100644 README.md create mode 100644 Template/config/integration.php create mode 100644 Template/project/integration.php create mode 100644 Template/user/integration.php create mode 100644 Test/PluginTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4c4ffc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.zip diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..56657ad --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: php +sudo: false +notifications: + email: false + +php: + - 7.0 + - 5.6 + +env: + global: + - PLUGIN=Slack + - KANBOARD_REPO=https://github.com/kanboard/kanboard.git + matrix: + - DB=sqlite + - DB=mysql + - DB=postgres + +matrix: + fast_finish: true + +install: + - git clone --depth 1 $KANBOARD_REPO + - ln -s $TRAVIS_BUILD_DIR kanboard/plugins/$PLUGIN + +before_script: + - cd kanboard + - phpenv config-add tests/php.ini + - composer install + - ls -la plugins/ + +script: + - phpunit -c tests/units.$DB.xml plugins/$PLUGIN/Test/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b8908f --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Original work Copyright (c) 2014-2016 Frédéric Guillot +Modified work Copyright (c) 2019 Ryonez Coruscare + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Locale/cs_CZ/translations.php b/Locale/cs_CZ/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/cs_CZ/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/da_DK/translations.php b/Locale/da_DK/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/da_DK/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/de_DE/translations.php b/Locale/de_DE/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/de_DE/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/es_ES/translations.php b/Locale/es_ES/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/es_ES/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/fi_FI/translations.php b/Locale/fi_FI/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/fi_FI/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/fr_FR/translations.php b/Locale/fr_FR/translations.php new file mode 100644 index 0000000..d0656dc --- /dev/null +++ b/Locale/fr_FR/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/hu_HU/translations.php b/Locale/hu_HU/translations.php new file mode 100644 index 0000000..d0656dc --- /dev/null +++ b/Locale/hu_HU/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/id_ID/translations.php b/Locale/id_ID/translations.php new file mode 100644 index 0000000..d0656dc --- /dev/null +++ b/Locale/id_ID/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/it_IT/translations.php b/Locale/it_IT/translations.php new file mode 100644 index 0000000..d0656dc --- /dev/null +++ b/Locale/it_IT/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/ja_JP/translations.php b/Locale/ja_JP/translations.php new file mode 100644 index 0000000..d0656dc --- /dev/null +++ b/Locale/ja_JP/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/nb_NO/translations.php b/Locale/nb_NO/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/nb_NO/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/nl_NL/translations.php b/Locale/nl_NL/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/nl_NL/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/pl_PL/translations.php b/Locale/pl_PL/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/pl_PL/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/pt_BR/translations.php b/Locale/pt_BR/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/pt_BR/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/pt_PT/translations.php b/Locale/pt_PT/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/pt_PT/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/ru_RU/translations.php b/Locale/ru_RU/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/ru_RU/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/sr_Latn_RS/translations.php b/Locale/sr_Latn_RS/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/sr_Latn_RS/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/sv_SE/translations.php b/Locale/sv_SE/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/sv_SE/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/th_TH/translations.php b/Locale/th_TH/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/th_TH/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/tr_TR/translations.php b/Locale/tr_TR/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/tr_TR/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Locale/zh_CN/translations.php b/Locale/zh_CN/translations.php new file mode 100644 index 0000000..982bfe7 --- /dev/null +++ b/Locale/zh_CN/translations.php @@ -0,0 +1,6 @@ + '', +); + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8a3cab9 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +plugin=Slack + +all: + @ echo "Build archive for plugin ${plugin} version=${version}" + @ git archive HEAD --prefix=${plugin}/ --format=zip -o ${plugin}-${version}.zip diff --git a/Notification/Discord.php b/Notification/Discord.php new file mode 100644 index 0000000..8b5ec52 --- /dev/null +++ b/Notification/Discord.php @@ -0,0 +1,182 @@ +userMetadataModel->get($user['id'], 'discord_webhook_url', $this->configModel->get('discord_webhook_url')); + $forward_attachments = $this->userMetadataModel->get($user['id'], 'forward_attachments', $this->configModel->get('forward_attachments')); + + if (! empty($webhook)) { + if ($eventName === TaskModel::EVENT_OVERDUE) { + foreach ($eventData['tasks'] as $task) { + $project = $this->projectModel->getById($task['project_id']); + $eventData['task'] = $task; + $this->sendMessage($webhook, $forward_attachments, $project, $eventName, $eventData); + } + } else { + $project = $this->projectModel->getById($eventData['task']['project_id']); + $this->sendMessage($webhook, $forward_attachments, $project, $eventName, $eventData); + } + } + } + + /** + * Send notification to a project + * + * @access public + * @param array $project + * @param string $eventName + * @param array $eventData + */ + public function notifyProject(array $project, $eventName, array $eventData) + { + $webhook = $this->projectMetadataModel->get($project['id'], 'discord_webhook_url', $this->configModel->get('discord_webhook_url')); + $forward_attachments = $this->userMetadataModel->get($user['id'], 'forward_attachments', $this->configModel->get('forward_attachments')); + + if (! empty($webhook)) { + $this->sendMessage($webhook, $forward_attachments, $project, $eventName, $eventData); + } + } + + /** + * Get message to send + * + * @access public + * @param array $project + * @param string $eventName + * @param array $eventData + * @return array + */ + public function getMessage(array $project, $forward_attachments, $eventName, array $eventData) + { + if ($this->userSession->isLogged()) { + $author = $this->helper->user->getFullname(); + $title = $this->notificationModel->getTitleWithAuthor($author, $eventName, $eventData); + } else { + $title = $this->notificationModel->getTitleWithoutAuthor($eventName, $eventData); + } + + $proj_name = isset($eventData['project_name']) ? $eventData['project_name'] : $eventData['task']['project_name']; + $task_title = $eventData['task']['title']; + $task_url = $this->helper->url->to('TaskViewController', 'show', array('task_id' => $eventData['task']['id'], 'project_id' => $project['id']), '', true); + + $attachment = ''; + + //Build Message + + $message = "[".htmlspecialchars($proj_name, ENT_NOQUOTES | ENT_IGNORE)."]\n"; + $message .= htmlspecialchars($title, ENT_NOQUOTES | ENT_IGNORE)."\n"; + + if ($this->configModel->get('application_url') !== '') + { + $message .= '📝 '.htmlspecialchars($task_title, ENT_NOQUOTES | ENT_IGNORE).''; + } + else + { + $message .= htmlspecialchars($task_title, ENT_NOQUOTES | ENT_IGNORE); + } + + // Add additional informations + + $description_events = array(TaskModel::EVENT_CREATE, TaskModel::EVENT_UPDATE, TaskModel::EVENT_USER_MENTION); + $subtask_events = array(SubtaskModel::EVENT_CREATE, SubtaskModel::EVENT_UPDATE, SubtaskModel::EVENT_DELETE); + $comment_events = array(CommentModel::EVENT_UPDATE, CommentModel::EVENT_CREATE, CommentModel::EVENT_DELETE, CommentModel::EVENT_USER_MENTION); + + if (in_array($eventName, $subtask_events)) // For subtask events + { + $subtask_status = $eventData['subtask']['status']; + $subtask_symbol = ''; + + if ($subtask_status == SubtaskModel::STATUS_DONE) + { + $subtask_symbol = '❌ '; + } + elseif ($subtask_status == SubtaskModel::STATUS_TODO) + { + $subtask_symbol = ''; + } + elseif ($subtask_status == SubtaskModel::STATUS_INPROGRESS) + { + $subtask_symbol = '🕘 '; + } + + $message .= "\n ↳ ".$subtask_symbol.' "'.htmlspecialchars($eventData['subtask']['title'], ENT_NOQUOTES | ENT_IGNORE).'"'; + } + + elseif (in_array($eventName, $description_events)) // If description available + { + if ($eventData['task']['description'] != '') + { + $message .= "\n✏️ ".'"'.htmlspecialchars($eventData['task']['description'], ENT_NOQUOTES | ENT_IGNORE).'"'; + } + } + + elseif (in_array($eventName, $comment_events)) // If comment available + { + $message .= "\n💬 ".'"'.htmlspecialchars($eventData['comment']['comment'], ENT_NOQUOTES | ENT_IGNORE).'"'; + } + + elseif ($eventName === TaskFileModel::EVENT_CREATE and $forward_attachments) // If attachment available + { + $file_path = getcwd()."/data/files/".$eventData['file']['path']; + $file_name = $eventData['file']['name']; + $is_image = $eventData['file']['is_image']; + + mkdir(sys_get_temp_dir()."/kanboard_discord_plugin"); + $attachment = sys_get_temp_dir()."/kanboard_discord_plugin/".clean($file_name); + file_put_contents($attachment, file_get_contents($file_path)); + } + + + if ($this->configModel->get('application_url') !== '') { + $message .= ' - <'; + $message .= $this->helper->url->to('TaskViewController', 'show', array('task_id' => $eventData['task']['id'], 'project_id' => $project['id']), '', true); + $message .= '|'.t('view the task on Kanboard').'>'; + } + + return array( + 'text' => $message, + 'attachment' => $attachment, + 'username' => 'Kanboard', + 'icon_url' => 'https://raw.githubusercontent.com/kanboard/kanboard/master/assets/img/favicon.png', + ); + } + + /** + * Send message to Discord + * + * @access protected + * @param string $webhook + * @param string $channel + * @param array $project + * @param string $eventName + * @param array $eventData + */ + protected function sendMessage($webhook, $forward_attachments, array $project, $eventName, array $eventData) + { + $payload = $this->getMessage($project, $forward_attachments, $eventName, $eventData); + + $this->httpClient->postJsonAsync($webhook, $payload); + } +} diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..9f7a8de --- /dev/null +++ b/Plugin.php @@ -0,0 +1,55 @@ +template->hook->attach('template:config:integrations', 'discord:config/integration'); + $this->template->hook->attach('template:project:integrations', 'discord:project/integration'); + $this->template->hook->attach('template:user:integrations', 'discord:user/integration'); + + $this->userNotificationTypeModel->setType('discord', t('Discord'), '\Kanboard\Plugin\Discord\Notification\Discord'); + $this->projectNotificationTypeModel->setType('discord', t('Discord'), '\Kanboard\Plugin\Discord\Notification\Discord'); + } + + public function onStartup() + { + Translator::load($this->languageModel->getCurrentLanguage(), __DIR__.'/Locale'); + } + + public function getPluginDescription() + { + return 'Receive notifications on Discord'; + } + + public function getPluginAuthor() + { + return 'Ryonez Coruscare'; + } + + public function getPluginVersion() + { + return '0.0.1'; + } + + public function getPluginHomepage() + { + return 'https://codelabs.alteria.xyz/ryonez/kanboard-discord'; + } + + public function getCompatibleVersion() + { + return '>=1.0.37'; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8c43d0 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +Slack plugin for Kanboard +========================= + +[![Build Status](https://travis-ci.org/kanboard/plugin-slack.svg?branch=master)](https://travis-ci.org/kanboard/plugin-slack) + +Receive Kanboard notifications on Slack. + +Author +------ + +- Frederic Guillot +- License MIT + +Requirements +------------ + +- Kanboard >= 1.0.37 + +Installation +------------ + +You have the choice between 3 methods: + +1. Install the plugin from the Kanboard plugin manager in one click +2. Download the zip file and decompress everything under the directory `plugins/Slack` +3. Clone this repository into the folder `plugins/Slack` + +Note: Plugin folder is case-sensitive. + +Configuration +------------- + +Firstly, you have to generate a new webhook url in Discord (**Server or Channel Settings > Webhooks**). + +You can define only one webhook url (**Settings > Integrations > Slack**) for each project and user. + +### Receive individual user notifications + +- Go to your user profile then choose **Integrations > Discord** +- Copy and paste the webhook url from Slack or leave it blank if you want to use the global webhook url +- Use `@username` to receive direct message to your user +- Enable Discord in your user notifications **Notifications > Discord** + +### Receive project notifications to a room + +- Go to the project settings then choose **Integrations > Discord** +- Copy and paste the webhook url from Discord or leave it blank if you want to use the global webhook url +- Enable Discord in your project notifications **Notifications > Discord** + +## Troubleshooting + +- Enable the debug mode +- All connection errors with the Discord API are recorded in the log files `data/debug.log` or syslog diff --git a/Template/config/integration.php b/Template/config/integration.php new file mode 100644 index 0000000..3bd2b3e --- /dev/null +++ b/Template/config/integration.php @@ -0,0 +1,11 @@ +

Slack

+
+ form->label(t('Webhook URL'), 'discord_webhook_url') ?> + form->text('discord_webhook_url', $values) ?> + +

+ +
+ +
+
diff --git a/Template/project/integration.php b/Template/project/integration.php new file mode 100644 index 0000000..eb434b3 --- /dev/null +++ b/Template/project/integration.php @@ -0,0 +1,11 @@ +

Slack

+
+ form->label(t('Webhook URL'), 'discord') ?> + form->text('discord_webhook_url', $values) ?> + +

+ +
+ +
+
diff --git a/Template/user/integration.php b/Template/user/integration.php new file mode 100644 index 0000000..3bd2b3e --- /dev/null +++ b/Template/user/integration.php @@ -0,0 +1,11 @@ +

Slack

+
+ form->label(t('Webhook URL'), 'discord_webhook_url') ?> + form->text('discord_webhook_url', $values) ?> + +

+ +
+ +
+
diff --git a/Test/PluginTest.php b/Test/PluginTest.php new file mode 100644 index 0000000..1c261af --- /dev/null +++ b/Test/PluginTest.php @@ -0,0 +1,20 @@ +container); + $this->assertSame(null, $plugin->initialize()); + $this->assertSame(null, $plugin->onStartup()); + $this->assertNotEmpty($plugin->getPluginName()); + $this->assertNotEmpty($plugin->getPluginDescription()); + $this->assertNotEmpty($plugin->getPluginAuthor()); + $this->assertNotEmpty($plugin->getPluginVersion()); + $this->assertNotEmpty($plugin->getPluginHomepage()); + } +}