├── .github ├── CODEOWNERS ├── tcdiscordwebhooks-logo-1024.png ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── git.yml │ ├── ci.yml │ └── stale.yml ├── .editorconfig ├── docker-compose.yml ├── tc-discord-webhooks-server ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── playerforcehd │ │ │ │ └── tcdiscordwebhooks │ │ │ │ ├── AppServer.java │ │ │ │ ├── discord │ │ │ │ ├── embeds │ │ │ │ │ ├── DiscordEmbedImage.java │ │ │ │ │ ├── DiscordEmbedFooter.java │ │ │ │ │ ├── DiscordEmbedField.java │ │ │ │ │ ├── DiscordAuthorEmbed.java │ │ │ │ │ ├── DiscordEmbedColor.java │ │ │ │ │ └── DiscordEmbed.java │ │ │ │ ├── DiscordWebHookPayload.java │ │ │ │ └── DiscordWebHookProcessor.java │ │ │ │ └── notificator │ │ │ │ └── DiscordNotificator.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── build-server-plugin-tc-discord-webhooks.xml │ └── test │ │ └── java │ │ └── com │ │ └── gtihub │ │ └── playerforcehd │ │ └── tcdiscordwebhooks │ │ ├── discord │ │ └── DiscordEmbedColorTest.java │ │ └── DiscordWebHookTest.java └── pom.xml ├── SECURITY.md ├── Makefile ├── .gitignore ├── teamcity-plugin.xml ├── commitlint.config.js ├── CODE_OF_CONDUCT.md ├── README.MD ├── CONTRIBUTING.md ├── pom.xml └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pascal-zarrad 2 | -------------------------------------------------------------------------------- /.github/tcdiscordwebhooks-logo-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pascal-zarrad/tc-discord-webhooks/HEAD/.github/tcdiscordwebhooks-logo-1024.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | teamcity: 5 | image: jetbrains/teamcity-server:latest 6 | volumes: 7 | - ./target/:/data/teamcity_server/datadir/plugins/ 8 | ports: 9 | - "8080:8111" 10 | 11 | teamcity_agent: 12 | image: jetbrains/teamcity-agent:latest 13 | depends_on: 14 | - teamcity 15 | links: 16 | - teamcity:teamcity 17 | environment: 18 | - SERVER_URL=teamcity:8111 19 | - AGENT_NAME="tcagent_docker" 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | What does your PR solve? If new features where added by your PR, what are the features? 3 | 4 | ### Issue relations 5 | To which issue(s) is your PR related? Use keywords (for example "Closes #1") to link your issues properly. 6 | 7 | ### Test Scenarios 8 | Describe how reviewers can verify that your changes do work from a functional perspective. 9 | 10 | ### Additional information 11 | Add here additional information if applicable or remove this section. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'C1 - enhancement' 6 | assignees: pascal-zarrad 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'C2 - bug' 6 | assignees: pascal-zarrad 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Execute command "..." 16 | 2. Type "..." 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Environment:** 26 | - OS: [e.g. Linux] 27 | - Dist: [e.g. Ubuntu] 28 | - Shell: [e.g. Bash] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | 33 | **Don't forget to add labels!** 34 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/AppServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks; 18 | 19 | public class AppServer { 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/git.yml: -------------------------------------------------------------------------------- 1 | # Checks for Git branches and commit messages to keep consistency on the repo 2 | name: Git 3 | 4 | on: ['pull_request'] 5 | 6 | jobs: 7 | # Verify that the branch name matches the format expected for contributions 8 | verify_branch_name: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check branch name 12 | uses: deepakputhraya/action-branch-name@master 13 | with: 14 | regex: '([a-z])+\/([a-z])+' 15 | allowed_prefixes: 'feature,bug,release' 16 | ignore: master,develop 17 | verify_commit_messages: 18 | runs-on: ubuntu-18.04 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | - uses: wagoid/commitlint-github-action@v2 24 | with: 25 | configFile: commitlint.config.js 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 1.x.x | :white_check_mark: | 8 | 9 | Legend: :white_check_mark: supported - :ballot_box_with_check: support on critical issues - :x: no longer supported 10 | 11 | ## Reporting a Vulnerability 12 | 13 | If you find a security issue, we advise you to _not_ create an issue on GitHub for it as 14 | it could put all TeamCity installations using TC-Discord-Webhooks on risk. 15 | Please send an e-mail to [this mail][security-mail] to report such issues. 16 | 17 | We take security issues serious and will not create an GitHub issue for any critical security issue 18 | until we have supplied a fix. All communication with the contributor of the issue will be held through e-mail 19 | until we are at a point where we can release information about the issue publicly. 20 | 21 | [security-mail]: mailto:P.Zarrad@outlook.de 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Set up JDK 1.8 11 | uses: actions/setup-java@v1 12 | with: 13 | java-version: 1.8 14 | - name: Build with Maven 15 | run: mvn -B package --file pom.xml 16 | - name: Upload build artifact 17 | uses: actions/upload-artifact@v2 18 | with: 19 | name: tc-discord-webhooks.zip 20 | path: target/tc-discord-webhooks.zip 21 | - name: Upload jacoco coverage report 22 | uses: actions/upload-artifact@v2 23 | with: 24 | name: code-coverage-report 25 | path: tc-discord-webhooks-server/target/site 26 | - name: Upload surefire report 27 | uses: actions/upload-artifact@v2 28 | with: 29 | name: code-coverage-report 30 | path: tc-discord-webhooks-server/target/surefire-reports 31 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | 2 | # Mark and close stale issues to keep issues clean 3 | name: Close stale issues and pull requests 4 | 5 | on: 6 | schedule: 7 | - cron: '0 0 * * *' 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/stale@v3.0.5 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'This issue did not have any activity in the last 60 days. It has been marked as stale and will be closed soon.' 17 | stale-pr-message: 'This pull request did not have any activity in the last 60 days. It has been marked as stale and will be closed soon.' 18 | stale-issue-label: 'S2 - stale - no activity' 19 | stale-pr-label: 'S2 - stale - no activity' 20 | exempt-issue-labels: 'S6 - in progress' 21 | exempt-pr-labels: 'S6 - in progress' 22 | days-before-stale: 60 23 | days-before-close: 7 24 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/resources/META-INF/build-server-plugin-tc-discord-webhooks.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to control the local development environment 2 | PWD=$(shell pwd) 3 | 4 | .PHONY: cache_dir 5 | cache_dir: 6 | mkdir -p m2 7 | 8 | # Build the project 9 | .PHONY: build 10 | build: cache_dir 11 | docker run -it --rm -u 1000 -v "$(PWD):/srv/tcdwbuild:delegated" -v "$(PWD)/m2":/var/maven/.m2 -w /srv/tcdwbuild -e MAVEN_CONFIG=/var/maven/.m2 maven:3.6-amazoncorretto-8 mvn clean package 12 | 13 | # Stop the local Docker stack, rebuild the project and start the local Docker stack again 14 | .PHONY: redeploy cache_dir 15 | redeploy: 16 | docker-compose stop 17 | docker run -it --rm -u 1000 -v "$(PWD):/srv/tcdwbuild:delegated" -v "$(PWD)/m2":/var/maven/.m2 -w /srv/tcdwbuild -e MAVEN_CONFIG=/var/maven/.m2 maven:3.6-amazoncorretto-8 mvn clean package 18 | docker-compose up -d 19 | 20 | # Start the local development environment 21 | .PHONY: up 22 | up: 23 | docker-compose up -d 24 | 25 | # Stop the local development environment 26 | .PHONY: stop 27 | stop: 28 | docker-compose stop 29 | 30 | # Clear the local development environment 31 | .PHONY: down 32 | down: 33 | docker-compose down 34 | 35 | # Rebuild the local development environment 36 | .PHONY: rebuild_docker 37 | rebuild_docker: 38 | docker-compose up -d --build --force-recreate 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Maven template 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | settings.xml 9 | release.properties 10 | dependency-reduced-pom.xml 11 | buildNumber.properties 12 | .mvn/timing.properties 13 | m2/ 14 | 15 | # Exclude maven wrapper 16 | !/.mvn/wrapper/maven-wrapper.jar 17 | ### JetBrains template 18 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 19 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 20 | 21 | # Entire .idea folde 22 | .idea 23 | 24 | ## File-based project format: 25 | *.iws 26 | *.iml 27 | 28 | ## Plugin-specific files: 29 | 30 | # IntelliJ 31 | /out/ 32 | 33 | gcaptchavalidator.iml 34 | 35 | # mpeltonen/sbt-idea plugin 36 | .idea_modules/ 37 | 38 | # JIRA plugin 39 | atlassian-ide-plugin.xml 40 | 41 | # Crashlytics plugin (for Android Studio and IntelliJ) 42 | com_crashlytics_export_strings.xml 43 | crashlytics.properties 44 | crashlytics-build.properties 45 | fabric.properties 46 | ### Java template 47 | *.class 48 | 49 | # BlueJ files 50 | *.ctxt 51 | 52 | # Mobile Tools for Java (J2ME) 53 | .mtj.tmp/ 54 | 55 | # Package Files # 56 | *.jar 57 | *.war 58 | *.ear 59 | 60 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 61 | hs_err_pid* 62 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/embeds/DiscordEmbedImage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord.embeds; 18 | 19 | /** 20 | * An image element for a {@link DiscordEmbed}. 21 | * This image element provides only the url attribute, due to 22 | * the fact that Discord's WebHooks do not support any of the other 23 | * attributes. 24 | * 25 | * @author Pascal Zarrad 26 | */ 27 | public class DiscordEmbedImage { 28 | 29 | /** 30 | * The url of the image to use 31 | */ 32 | private String url; 33 | 34 | public DiscordEmbedImage(String url) { 35 | this.url = url; 36 | } 37 | 38 | public DiscordEmbedImage() { 39 | } 40 | 41 | public String getUrl() { 42 | return url; 43 | } 44 | 45 | public void setUrl(String url) { 46 | this.url = url; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /teamcity-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | tc-discord-webhooks 22 | TC Discord WebHooks 23 | @Version@ 24 | A TeamCity plugin that allows the easy creation of Discord WebHooks that are used to notify users 25 | on a Discord server about the current state of builds. 26 | 27 | https://plugins.jetbrains.com/plugin/12608-tc-discord-webhooks 28 | contact@pascal-zarrad.de 29 | 30 | @VendorName@ 31 | @VendorURL@ 32 | https://raw.githubusercontent.com/pascal-zarrad/tc-discord-webhooks/develop/.github/tcdiscordwebhooks-logo-1024.png 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Configuration of commitlint to check commit message guidelines 18 | module.exports = { 19 | parserPreset: 'conventional-changelog-conventionalcommits', 20 | rules: { 21 | 'subject-max-length': [2, 'always', 50], 22 | 'subject-case': [ 23 | 2, 24 | 'never', 25 | ['sentence-case', 'start-case'], 26 | ], 27 | 'subject-empty': [2, 'never'], 28 | 'subject-full-stop': [2, 'never', '.'], 29 | 'type-case': [2, 'always', 'lower-case'], 30 | 'type-empty': [2, 'never'], 31 | 'type-enum': [ 32 | 2, 33 | 'always', 34 | [ 35 | 'feat', 36 | 'fix', 37 | 'perf', 38 | 'refactor', 39 | 'cs', 40 | 'test', 41 | 'build', 42 | 'ci', 43 | 'docs', 44 | 'changelog', 45 | 'bump' 46 | ], 47 | ], 48 | 'scope-empty': [2, 'always'], 49 | 'header-max-length': [2, 'always', 75], 50 | 'body-leading-blank': [1, 'always'], 51 | 'body-max-line-length': [2, 'always', 75], 52 | 'footer-leading-blank': [1, 'always'], 53 | 'footer-max-line-length': [2, 'always', 75] 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/embeds/DiscordEmbedFooter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord.embeds; 18 | 19 | /** 20 | * A footer element for a {@link DiscordEmbed} 21 | * 22 | * @author Pascal Zarrad 23 | */ 24 | public class DiscordEmbedFooter { 25 | 26 | /** 27 | * The text that the footer should contain 28 | */ 29 | private String text; 30 | 31 | /** 32 | * The url to the icon of the footer 33 | */ 34 | private String icon_url; 35 | 36 | public DiscordEmbedFooter(String text, String icon_url) { 37 | this.text = text; 38 | this.icon_url = icon_url; 39 | } 40 | 41 | public DiscordEmbedFooter() { 42 | } 43 | 44 | public DiscordEmbedFooter(String text) { 45 | this.text = text; 46 | } 47 | 48 | public String getText() { 49 | return text; 50 | } 51 | 52 | public void setText(String text) { 53 | this.text = text; 54 | } 55 | 56 | public String getIcon_url() { 57 | return icon_url; 58 | } 59 | 60 | public void setIcon_url(String icon_url) { 61 | this.icon_url = icon_url; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/embeds/DiscordEmbedField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord.embeds; 18 | 19 | /** 20 | * Fields that can be added to an {@link DiscordEmbed} 21 | * to display embedded text 22 | * 23 | * @author Pascal Zarrad 24 | */ 25 | public class DiscordEmbedField { 26 | 27 | /** 28 | * The name of the embed field 29 | */ 30 | private String name; 31 | 32 | /** 33 | * The value of the embed field 34 | */ 35 | private String value; 36 | 37 | /** 38 | * Decides whether the field should be displayed inline or not 39 | */ 40 | private boolean inline; 41 | 42 | public DiscordEmbedField(String name, String value) { 43 | this.name = name; 44 | this.value = value; 45 | } 46 | 47 | public DiscordEmbedField(String name, String value, boolean inline) { 48 | this.name = name; 49 | this.value = value; 50 | this.inline = inline; 51 | } 52 | 53 | public DiscordEmbedField() { 54 | } 55 | 56 | public String getName() { 57 | return name; 58 | } 59 | 60 | public void setName(String name) { 61 | this.name = name; 62 | } 63 | 64 | public String getValue() { 65 | return value; 66 | } 67 | 68 | public void setValue(String value) { 69 | this.value = value; 70 | } 71 | 72 | public boolean isInline() { 73 | return inline; 74 | } 75 | 76 | public void setInline(boolean inline) { 77 | this.inline = inline; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/embeds/DiscordAuthorEmbed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord.embeds; 18 | 19 | /** 20 | * An element to set the author of an {@link DiscordEmbed}. 21 | * 22 | * @author Pascal Zarrad 23 | */ 24 | public class DiscordAuthorEmbed { 25 | 26 | /** 27 | * The name of the author 28 | */ 29 | private String name; 30 | 31 | /** 32 | * The url of the author 33 | */ 34 | private String url; 35 | 36 | /** 37 | * The url to the icon of the author 38 | */ 39 | private String icon_url; 40 | 41 | public DiscordAuthorEmbed(String name, String url, String icon_url) { 42 | this.name = name; 43 | this.url = url; 44 | this.icon_url = icon_url; 45 | } 46 | 47 | public DiscordAuthorEmbed(String name, String url) { 48 | this.name = name; 49 | this.url = url; 50 | } 51 | 52 | public DiscordAuthorEmbed(String name) { 53 | this.name = name; 54 | } 55 | 56 | public DiscordAuthorEmbed() { 57 | } 58 | 59 | public String getName() { 60 | return name; 61 | } 62 | 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | public String getUrl() { 68 | return url; 69 | } 70 | 71 | public void setUrl(String url) { 72 | this.url = url; 73 | } 74 | 75 | public String getIcon_url() { 76 | return icon_url; 77 | } 78 | 79 | public void setIcon_url(String icon_url) { 80 | this.icon_url = icon_url; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/embeds/DiscordEmbedColor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord.embeds; 18 | 19 | /** 20 | * Provides the basic colors that can be used for embeds. 21 | * The values of the colors are their decimal value. 22 | *

23 | * Also this class provides a method to get the decimal value of a 24 | * 25 | * @author Pascal Zarrad 26 | */ 27 | public class DiscordEmbedColor { 28 | 29 | /** 30 | * Red color 31 | */ 32 | public static final int RED = 16711680; 33 | 34 | /** 35 | * Blue color 36 | */ 37 | public static final int BLUE = 26367; 38 | 39 | /** 40 | * Green color 41 | */ 42 | public static final int GREEN = 510208; 43 | 44 | /** 45 | * Yellow color 46 | */ 47 | public static final int YELLOW = 15924992; 48 | 49 | /** 50 | * Orange color 51 | */ 52 | public static final int ORANGE = 16746496; 53 | 54 | /** 55 | * Converts a hexadecimal color code to a decimal color code. 56 | * This method supports hexadecimal strings as parameter with and without a leading #. 57 | * 58 | * @param hexCode The hexadecimal color code to convert to a decimal value 59 | * @return The decimal value 60 | */ 61 | public static int convertHexToDecColor(String hexCode) throws NumberFormatException { 62 | if (hexCode.startsWith("#")) { 63 | String pureHex = hexCode.substring(1); 64 | return Integer.parseInt(pureHex, 16); 65 | } else { 66 | return Integer.parseInt(hexCode, 16); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 4.0.0 21 | 22 | tc-discord-webhooks 23 | com.github.playerforcehd 24 | 1.1.1 25 | 26 | tc-discord-webhooks-server 27 | jar 28 | 29 | 30 | 31 | org.jetbrains.teamcity 32 | server-api 33 | ${teamcity-version} 34 | provided 35 | 36 | 37 | 38 | org.jetbrains.teamcity 39 | server-web-api 40 | ${teamcity-version} 41 | war 42 | provided 43 | 44 | 45 | 46 | org.jetbrains.teamcity 47 | tests-support 48 | ${teamcity-version} 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 59 | 1.8 60 | 1.8 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/test/java/com/gtihub/playerforcehd/tcdiscordwebhooks/discord/DiscordEmbedColorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.gtihub.playerforcehd.tcdiscordwebhooks.discord; 18 | 19 | import com.github.playerforcehd.tcdiscordwebhooks.discord.embeds.DiscordEmbedColor; 20 | import org.testng.annotations.Test; 21 | 22 | import static org.testng.Assert.assertEquals; 23 | import static org.testng.Assert.fail; 24 | 25 | /** 26 | * Tests the {@link DiscordEmbedColor#convertHexToDecColor(String)} method which 27 | * is used to convert hexadecimal color codes into their decimal equivalent. 28 | * 29 | * @author Pascal Zarrad 30 | */ 31 | public class DiscordEmbedColorTest { 32 | 33 | /** 34 | * The color code used for the check 35 | */ 36 | private final String hexColor = "db2525"; 37 | 38 | /** 39 | * Test if the conversion of a hexadecimal number to a decimal number does work properly. 40 | */ 41 | @Test 42 | public void testConversionOfHexToDecColor() { 43 | try { 44 | int decimalColorCode = DiscordEmbedColor.convertHexToDecColor(this.hexColor); 45 | assertEquals(decimalColorCode, 14361893, "Expected decimal color code to be 14361893 but got " + decimalColorCode + "!"); 46 | } catch (NumberFormatException e) { 47 | fail("Failed to convert hexadecimal to decimal color code.", e); 48 | } 49 | } 50 | 51 | /** 52 | * Test if the conversion of a hexadecimal number with a leading diamond 53 | * to a decimal number does work properly. 54 | */ 55 | @Test 56 | public void testConversionOfHexToDecColorWithLeadingDiamond() { 57 | try { 58 | String hexColorWithDiamond = "#" + this.hexColor; 59 | int decimalColorCode = DiscordEmbedColor.convertHexToDecColor(hexColorWithDiamond); 60 | assertEquals(decimalColorCode, 14361893, "Expected decimal color code to be 14361893 but got " + decimalColorCode + "!"); 61 | } catch (NumberFormatException e) { 62 | fail("Failed to convert hexadecimal to decimal color code.", e); 63 | } 64 | } 65 | 66 | /** 67 | * Tests if entering an invalid hexadecimal string ends up in a {@link NumberFormatException} 68 | */ 69 | @Test 70 | public void testConversionFailsWhenEnteringInvalidHexString() { 71 | String invalidHexadecimalString = "a24fZ"; // Z is not a hexadecimal digit 72 | try { 73 | DiscordEmbedColor.convertHexToDecColor(invalidHexadecimalString); 74 | fail("Entering an invalid hexadecimal string should result in a NumberFormatException!"); 75 | } catch (Exception e) { 76 | if (!(e instanceof NumberFormatException)) { 77 | fail("The thrown exception was expected to be a NumberFormatException but got any other exception!", e); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at p.zarrad@outlook.de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # TC Discord WebHooks [![Build Status](https://travis-ci.org/pascal-zarrad/tc-discord-webhooks.svg?branch=master)](https://travis-ci.org/pascal-zarrad/tc-discord-webhooks) 2 | 3 | > **This plugin is in a community maintainance state** 4 | > 5 | > I use GitHub Actions instead of TeamCity right now and don't have time to maintain or update this plugin by myself. 6 | > I decided to discontinue the development of this plugin. 7 | > As of 2021/12/06, this plugin still works and can be installed manually. 8 | > 9 | > I won't provide any maintainance of the plugin by myself. If anyone wants to keep it up to date or contribute code (features, fixes, etc...), I'd be happy about that. Feel always free to do that if you want to. 10 | > I will continue to manage contributions and publish new versions of the plugin. 11 | 12 | ## About 13 | A TeamCity plugin which allows the easy creation of Discord WebHooks to notify users on a Discord server about the current build status of projects. 14 | 15 | **Features:** 16 | - Discord Integration to get notified on a Discord server 17 | - Simple setup 18 | - Beautiful messages out of the box 19 | 20 | ## System Requirements 21 | To use this plugin you need at least TeamCity 2018.2 and Java 8. 22 | In addition, a Discord server is necessary as you can't have webhooks without a server. 23 | 24 | ## Installation 25 | Simply put the plugins ZIP file into the plugin's directory of your TeamCity server and restart it. 26 | You should also be able to upload the plugin using the settings page of your TeamCity installation. 27 | For further information refer to the official JetBrains documentation: [Installing additional plugins](https://www.jetbrains.com/help/teamcity/installing-additional-plugins.html) 28 | 29 | ## Usage 30 | The usage of this plugin is simple. 31 | Just create a webhook for one of your Discord channels as shown here: [Discord WebHook HowTo](https://support.discordapp.com/hc/en-us/articles/228383668-Intro-to-Webhooks). 32 | 33 | Then enter the WebHook URL and a Username (recommended to also enter the name!) under "My Settings & Tools" -> "Notification Rules" -> "Discord WebHook". 34 | Configure your rules as normal. You should now receive notifications on Discord! 35 | 36 | If you use a proxy, ensure that `http.proxyHost` and `http.proxyPort` are set properly. 37 | 38 | ## Development on Linux 39 | 40 | ### Prerequisites 41 | To develop and build TC Discord Webhooks on Linux you need the following tools: 42 | - Docker 43 | - docker-compose 44 | - make 45 | 46 | Note that you might also just use the `mvn` command of your local Maven 2 installation to build the project. 47 | But using the Makefile ensures that you have your environment configured properly. 48 | 49 | Also ensure your local system is configured properly. 50 | If you encounter issues while downloading Maven dependencies, you might check [this](https://stackoverflow.com/a/45644890) 51 | StackOverflow comment that might help you to fix the issue. 52 | 53 | **Use the following setup ONLY for development purposes! It is not production ready!** 54 | 55 | ### Building 56 | 57 | Use the build target of the Makefile to build the project: 58 | ``` 59 | make build 60 | ``` 61 | To build the project without `make`, simply run the `package` goal of Maven. 62 | 63 | ### Running the TeamCity development server 64 | 65 | Start the local TeamCity server: 66 | ``` 67 | make up 68 | ``` 69 | 70 | Stop the local TeamCity server: 71 | ``` 72 | make stop 73 | ``` 74 | 75 | Delete the containers of the local TeamCity server: 76 | ``` 77 | make down 78 | ``` 79 | 80 | Rebuild the plugin and restart the TeamCity server: 81 | ``` 82 | make redeploy 83 | ``` 84 | 85 | Rebuild the local Docker stack: 86 | ``` 87 | make rebuild_docker 88 | ``` 89 | 90 | ## Development on Windows 91 | 92 | ### Prerequisites 93 | To develop and build TC Discord Webhooks on Linux you need the following tools: 94 | - Docker Desktop 95 | - Maven 2 96 | 97 | **Use the following setup ONLY for development purposes! It is not production ready!** 98 | 99 | ### Building 100 | 101 | To build the project without Docker, simply run the `package` goal of Maven: 102 | ``` 103 | mvn clean package 104 | ``` 105 | 106 | ### Running the TeamCity development server 107 | 108 | To set up the containers and start the Docker stack, take a look at the commands below. 109 | After the containers have been started you can open TeamCity in your browser. 110 | TeamCity should be reachable at `http://localhost:8080/`. 111 | 112 | Start the local TeamCity server: 113 | ``` 114 | docker-compose up -d 115 | ``` 116 | 117 | Stop the local TeamCity server: 118 | ``` 119 | docker-compose stop 120 | ``` 121 | 122 | Remove the local TeamCity server containers: 123 | ``` 124 | docker-compose down 125 | ``` 126 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/DiscordWebHookPayload.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord; 18 | 19 | import com.github.playerforcehd.tcdiscordwebhooks.discord.embeds.DiscordEmbed; 20 | 21 | /** 22 | * A basic Discord payload that can be serialized into 23 | * JSON to be sent to Discord's WebHook API. 24 | *

25 | * The fields of this class represent the attributes of a 26 | * WebHook request as described in: 27 | * https://discordapp.com/developers/docs/resources/webhook 28 | * 29 | * @author Pascal Zarrad 30 | */ 31 | public class DiscordWebHookPayload { 32 | 33 | /** 34 | * The username that will be used as the sender username 35 | */ 36 | private String username; 37 | 38 | /** 39 | * The url to the avatar to use in the message 40 | */ 41 | private String avatar_url; 42 | 43 | /** 44 | * Enable or disable the text-to-speech feature for this message 45 | */ 46 | private boolean tts; 47 | 48 | /** 49 | * The content as a simple text message that should be send with the payload 50 | */ 51 | private String content; 52 | 53 | /** 54 | * The embeds that should be send with the payload 55 | */ 56 | private DiscordEmbed[] embeds; 57 | 58 | /** 59 | * Creates a new {@link DiscordWebHookPayload} that is prepared to send a simple text message bundled with an embed. 60 | * 61 | * @param username The username that will be used as the sender username 62 | * @param avatar_url The url to the avatar to use in the message 63 | * @param tts Enable or disable the text-to-speech feature for this message 64 | * @param content The content as a simple text message that should be send with the payload 65 | * @param embeds The embeds that should be send with the payload 66 | */ 67 | public DiscordWebHookPayload(String username, String avatar_url, boolean tts, String content, DiscordEmbed[] embeds) { 68 | this.username = username; 69 | this.avatar_url = avatar_url; 70 | this.tts = tts; 71 | this.content = content; 72 | this.embeds = embeds; 73 | } 74 | 75 | /** 76 | * Creates a new {@link DiscordWebHookPayload} that is prepared to be send as a simple text message 77 | * 78 | * @param username The username that will be used as the sender username 79 | * @param avatar_url The url to the avatar to use in the message 80 | * @param tts Enable or disable the text-to-speech feature for this message 81 | * @param content The content as a simple text message that should be send with the payload 82 | */ 83 | public DiscordWebHookPayload(String username, String avatar_url, boolean tts, String content) { 84 | this.username = username; 85 | this.avatar_url = avatar_url; 86 | this.tts = tts; 87 | this.content = content; 88 | } 89 | 90 | /** 91 | * Creates a new {@link DiscordWebHookPayload} that is prepared to be send bundled with {@link DiscordEmbed}'s 92 | * 93 | * @param username The username that will be used as the sender username 94 | * @param avatar_url The url to the avatar to use in the message 95 | * @param embeds The embeds that should be send with the payload 96 | */ 97 | public DiscordWebHookPayload(String username, String avatar_url, DiscordEmbed[] embeds) { 98 | this.username = username; 99 | this.avatar_url = avatar_url; 100 | this.embeds = embeds; 101 | } 102 | 103 | public DiscordWebHookPayload() { 104 | } 105 | 106 | public String getUsername() { 107 | return username; 108 | } 109 | 110 | public void setUsername(String username) { 111 | this.username = username; 112 | } 113 | 114 | public String getAvatar_url() { 115 | return avatar_url; 116 | } 117 | 118 | public void setAvatar_url(String avatar_url) { 119 | this.avatar_url = avatar_url; 120 | } 121 | 122 | public boolean isTts() { 123 | return tts; 124 | } 125 | 126 | public void setTts(boolean tts) { 127 | this.tts = tts; 128 | } 129 | 130 | public String getContent() { 131 | return content; 132 | } 133 | 134 | public void setContent(String content) { 135 | this.content = content; 136 | } 137 | 138 | public DiscordEmbed[] getEmbeds() { 139 | return embeds; 140 | } 141 | 142 | public void setEmbeds(DiscordEmbed[] embeds) { 143 | this.embeds = embeds; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/embeds/DiscordEmbed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord.embeds; 18 | 19 | /** 20 | * A {@link DiscordEmbed} represents a Java object that contains every attribute 21 | * that can be applied to an embedded Discord message. 22 | *

23 | * NOTE: As embeds produced by this application are meant to be used for WebHooks, 24 | * the attribute type is always set to "rich". 25 | * Some attributes like video are not available because they are not 26 | * supported. So implementing them does not make any sense. 27 | *

28 | * The documentation about Discord's embeds can be found here: 29 | * https://discordapp.com/developers/docs/resources/webhook 30 | * 31 | * @author Pascal Zarrad 32 | */ 33 | public class DiscordEmbed { 34 | 35 | /** 36 | * The title that of the embed 37 | */ 38 | private String title; 39 | /** 40 | * The description of the embed 41 | */ 42 | private String description; 43 | 44 | /** 45 | * The URL of the embed 46 | */ 47 | private String url; 48 | 49 | /** 50 | * The color that the embed should have 51 | */ 52 | private int color; 53 | 54 | /** 55 | * The footer to use for the embed 56 | */ 57 | private DiscordEmbedFooter footer; 58 | 59 | /** 60 | * The image to use for the embed 61 | */ 62 | private DiscordEmbedImage image; 63 | 64 | /** 65 | * The thumbnail to set for the embed. 66 | * NOTE: The thumbnail embed object has the same properties as 67 | * an image embed, so they share a class. 68 | */ 69 | private DiscordEmbedImage thumbnail; 70 | 71 | /** 72 | * Contains additional fields of text for the embed 73 | */ 74 | private DiscordEmbedField[] fields; 75 | 76 | public DiscordEmbed(String title, String description, String url, int color, DiscordEmbedFooter footer, DiscordEmbedImage image, DiscordEmbedImage thumbnail, DiscordEmbedField[] fields) { 77 | this.title = title; 78 | this.description = description; 79 | this.url = url; 80 | this.color = color; 81 | this.footer = footer; 82 | this.image = image; 83 | this.thumbnail = thumbnail; 84 | this.fields = fields; 85 | } 86 | 87 | public DiscordEmbed(String title, String description, String url, int color, DiscordEmbedFooter footer, DiscordEmbedImage thumbnail, DiscordEmbedField[] fields) { 88 | this.title = title; 89 | this.description = description; 90 | this.url = url; 91 | this.color = color; 92 | this.footer = footer; 93 | this.thumbnail = thumbnail; 94 | this.fields = fields; 95 | } 96 | 97 | public DiscordEmbed(String title, String description, String url, int color, DiscordEmbedFooter footer, DiscordEmbedField[] fields) { 98 | this.title = title; 99 | this.description = description; 100 | this.url = url; 101 | this.color = color; 102 | this.footer = footer; 103 | this.fields = fields; 104 | } 105 | 106 | public DiscordEmbed() { 107 | } 108 | 109 | public String getTitle() { 110 | return title; 111 | } 112 | 113 | public void setTitle(String title) { 114 | this.title = title; 115 | } 116 | 117 | public String getDescription() { 118 | return description; 119 | } 120 | 121 | public void setDescription(String description) { 122 | this.description = description; 123 | } 124 | 125 | public String getUrl() { 126 | return url; 127 | } 128 | 129 | public void setUrl(String url) { 130 | this.url = url; 131 | } 132 | 133 | public int getColor() { 134 | return color; 135 | } 136 | 137 | public void setColor(int color) { 138 | this.color = color; 139 | } 140 | 141 | public DiscordEmbedFooter getFooter() { 142 | return footer; 143 | } 144 | 145 | public void setFooter(DiscordEmbedFooter footer) { 146 | this.footer = footer; 147 | } 148 | 149 | public DiscordEmbedImage getImage() { 150 | return image; 151 | } 152 | 153 | public void setImage(DiscordEmbedImage image) { 154 | this.image = image; 155 | } 156 | 157 | public DiscordEmbedImage getThumbnail() { 158 | return thumbnail; 159 | } 160 | 161 | public void setThumbnail(DiscordEmbedImage thumbnail) { 162 | this.thumbnail = thumbnail; 163 | } 164 | 165 | public DiscordEmbedField[] getFields() { 166 | return fields; 167 | } 168 | 169 | public void setFields(DiscordEmbedField[] fields) { 170 | this.fields = fields; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/discord/DiscordWebHookProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.discord; 18 | 19 | import com.google.gson.Gson; 20 | import org.apache.http.HttpHost; 21 | import org.apache.http.client.config.RequestConfig; 22 | import org.apache.http.client.methods.CloseableHttpResponse; 23 | import org.apache.http.client.methods.HttpPost; 24 | import org.apache.http.entity.StringEntity; 25 | import org.apache.http.impl.client.CloseableHttpClient; 26 | import org.apache.http.impl.client.HttpClients; 27 | 28 | import java.io.IOException; 29 | import java.net.URISyntaxException; 30 | import java.net.URL; 31 | import java.nio.charset.StandardCharsets; 32 | 33 | /** 34 | * Handles the communication between the Discord WebHook API and the TeamCity server. 35 | * Also handles the serialization of the Discord WebHook Payloads 36 | * 37 | * @author Pascal Zarrad 38 | */ 39 | public class DiscordWebHookProcessor { 40 | 41 | /** 42 | * The charset used for the requests 43 | */ 44 | private static final String HTTP_CHARSET = StandardCharsets.UTF_8.toString(); 45 | 46 | /** 47 | * The GSON instance used to serialize the {@link DiscordWebHookPayload}'s 48 | */ 49 | private final Gson GSON; 50 | 51 | public DiscordWebHookProcessor() { 52 | this.GSON = new Gson(); 53 | } 54 | 55 | /** 56 | * Send a WebHook request to the Discord API. 57 | * This method accepts a {@link DiscordWebHookPayload} as argument and serialises it before sending it. 58 | * 59 | * @param webHookURL The URL of the WebHook that is targeted 60 | * @param discordWebHookPayload The payload which contains the content to send 61 | * @return true if the request succeeded 62 | * @throws IOException Thrown when any I/O operation fails 63 | * @throws URISyntaxException Thrown when the given #webHookURL is invalid 64 | * @see DiscordWebHookProcessor#sendDiscordWebHook(String, String) 65 | */ 66 | public boolean sendDiscordWebHook(String webHookURL, DiscordWebHookPayload discordWebHookPayload) throws IOException, URISyntaxException { 67 | return this.sendDiscordWebHook(webHookURL, this.serializeDiscordWebHookPayload(discordWebHookPayload)); 68 | } 69 | 70 | /** 71 | * Send a WebHook request to the Discord API 72 | * 73 | * @param webHookURL The URL of the WebHook that is targeted 74 | * @param discordWebHookPayload The payload which contains the content to send 75 | * @return true if the request succeeded 76 | * @throws IOException Thrown when any I/O operation fails 77 | * @throws URISyntaxException Thrown when the given #webHookURL is invalid 78 | */ 79 | public boolean sendDiscordWebHook(String webHookURL, String discordWebHookPayload) throws IOException, URISyntaxException { 80 | // Send Discord WebHook 81 | URL url = new URL(webHookURL); 82 | int responseCode; // We default to 400, when request succeeded, this should be 204 83 | try (CloseableHttpClient httpClient = HttpClients.createDefault()) { 84 | HttpPost httpPost = new HttpPost(url.toURI()); 85 | httpPost.addHeader("User-Agent", "TeamCity Discord WebHook v1"); 86 | httpPost.addHeader("Accept-Language", "en-US,en;q=0.5"); 87 | httpPost.addHeader("Content-Type", "application/json"); 88 | httpPost.setEntity(new StringEntity(discordWebHookPayload, HTTP_CHARSET)); 89 | String httpProxyHost = System.getProperty("http.proxyHost"); 90 | String httpProxyPort = System.getProperty("http.proxyPort"); 91 | if (httpProxyHost != null && httpProxyPort.trim().length() > 0 && httpProxyPort != null) { 92 | int port = Integer.parseInt(httpProxyPort); 93 | HttpHost proxy = new HttpHost(httpProxyHost, port, "http"); 94 | RequestConfig.Builder reqConfigBuilder = RequestConfig.custom(); 95 | reqConfigBuilder = reqConfigBuilder.setProxy(proxy); 96 | RequestConfig config = reqConfigBuilder.build(); 97 | httpPost.setConfig(config); 98 | } 99 | try (CloseableHttpResponse response = httpClient.execute(httpPost)) { 100 | responseCode = response.getStatusLine().getStatusCode(); 101 | } 102 | } 103 | return responseCode == 204; // When request returned status 204, the request was a success 104 | } 105 | 106 | /** 107 | * Serializes a {@link DiscordWebHookPayload} into a JSON string. 108 | * 109 | * @param discordWebHookPayload The payload the serialize 110 | * @return The JSOn string of the {@link DiscordWebHookPayload} 111 | */ 112 | public String serializeDiscordWebHookPayload(DiscordWebHookPayload discordWebHookPayload) { 113 | return this.GSON.toJson(discordWebHookPayload); 114 | } 115 | 116 | public Gson getGSON() { 117 | return GSON; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to TeamCity Discord Webhooks - but to keep everything organized, we need some guidelines and standards that apply to the project. We tried to keep those guidelines as short as possible and make it easy for new contributors to get started. Please read these guidelines carefully, to keep up with our issue, branching and pull request standards. 4 | 5 | ## Code of conduct 6 | 7 | Please refer to our code of conduct before you contribute anything as it sets some behavior rules to make a pleasant together. At the moment, when you contribute anything, you must follow this code of conduct. 8 | Dealing with questions, bugs and feature requests. 9 | 10 | If you have any questions, bugs or feature requests, feel free to open an issue. Note that you must use one of our issue templates that your issue is valid - valid issues will always retrieve the attention that is required to solve them. Before you create an issue, search for your subject and be sure to not create a duplicate. Always provide a clear and concise explanation of your matter of concern. 11 | Security issues. 12 | 13 | If you find some really serious security issue, please don't create a GitHub issue for it in the first line - it could put all users of TeamCity Discord Webhooks into great risk. Send a mail to p.zarrad@outlook.de instead, to keep it structured you can simply copy our bug template into your mail. 14 | 15 | ## Contributing code 16 | 17 | Code contributions are great because they allow anyone to contribute to their favorite projects - in addition they help us to make progress. Although there are some things to keep in mind when contributing code. You grant us a perpetual license to use your code under the MIT license at the moment you contribute something to this project. This contributing code section covers all defined standards that have nothing to do with or coding standards, as they are separated from our contribution guide - but we are recommending to read them, as code that not follows them will be rejected. 18 | First time contributors. 19 | 20 | As a first time contributor, you might find it hard to find an issue that you can work on. To make it easier for you, some issues will be labeled as "good first issue". This type of issue can be often done with a few changes to the source code, which does not require a too deep knowledge of the projects source code. 21 | Process of a code contribution. 22 | 23 | The step by step process for code contributions to TeamCity Discord Webhooks looks like the following: 24 | 25 | - Choose an issue that you want to resolve (Create one if you want to implement something, but there is no issue for it) 26 | - Create a comment in that issue that you want to resolve it. 27 | - You can now start to develop. Though the issue will not be yours till it has been assigned to you by a collaborator or maintainer. 28 | - If you're done with development, create a pull request. 29 | - Your PR will be reviewed by a collaborator or maintainer. 30 | - If your PR has been approved you're done, else start again at number 3 and make the required changes. 31 | 32 | ## Guidelines for issues 33 | 34 | The following things should be noted when working with issues: 35 | 36 | - Always use the matching template when you create new issues. 37 | - Keep the issue up to date with your current progress. 38 | - Do not create duplicates. 39 | - If you report a security issue, first visit our [security guidelines](SECURITY.md). 40 | 41 | If you don't follow these guidelines, your issue will be closed and labeled as invalid or duplicate. 42 | 43 | ## Pull requests, branching and commits 44 | ### Branching 45 | 46 | Our project uses a very standard branching model. 47 | We have the `master` branch that is always the most stable branch. 48 | A workflow deploys the latest push to the `master` branch to the Maven central repository. 49 | Pull requests to the `master` branch will only be accepted if they contain a version bump. 50 | The `develop` branch is our default branch. 51 | All contributions will be merged into `develop` using pull requests. 52 | A merge to the `master` branch triggers a snapshot release. 53 | If you want to contribute, fork the project and do your changes. 54 | You should create a feature or bug branch when contributing. 55 | We do not use documentation branches to minify the overhead. 56 | 57 | ### Commits 58 | 59 | Commit messages are great to express the intention of specific changes. In this project they are not only used 60 | to document changes but also to auto generate our changelogs. 61 | This means that some strict rules are necessary to ensure meaningful commit messages when contributing. 62 | 63 | This project uses some abbreviation of the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard. 64 | 65 | Your messages must have the following format: 66 | ``` 67 | type: some imperative subject line 68 | ``` 69 | 70 | If your commit contains breaking changes: 71 | ``` 72 | type: some imperative subject line 73 | 74 | BREAKING CHANGE: Some breaking change 75 | ``` 76 | 77 | Possible types: 78 | - feat: A new feature 79 | - fix: A bug fix 80 | - perf: A code change that improves performance 81 | - refactor: A code change that neither fixes a bug nor adds a feature 82 | - cs: Changes that do not affect the meaning of the code 83 | - test: Adding missing tests or correcting existing tests 84 | - build: Changes that affect the build system or external dependencies 85 | - ci: Changes to the CI configuration files and scripts 86 | - docs: Documentation only changes 87 | - changelog: Internally used by actions for commits that generate changelogs 88 | - bump: Bump version of the project 89 | 90 | Breaking changes should be noted using a "BREAKING CHANGE:" footer. 91 | The "!" notation must not be used to indicate a breaking change. 92 | You can also use concrete descriptions. You must put an empty line between the subject and footer. 93 | The subject after the double colon must start with a lower case letter. 94 | The subject line must be written in the imperative form (e.g. do this, make that, add something, ...). 95 | 96 | Scopes must not be used in commit messages, therefore no scopes have been defined. 97 | 98 | ### Pull requests 99 | 100 | When you're ready to create a pull request, use the predefined template for it. Choose a meaningful title (maybe the issues title will fit) and add all required labels. It is very important to link the right issue in the "Solves issue" section of the template, as every PR must be the result of an open issue. 101 | 102 | Requirements for an approved PR: 103 | 104 | - Merge into develop: You need one approving review from a collaborator or maintainer. 105 | - The TravisCI checks have to pass. 106 | - The commit messages must follow the guidelines mentioned in this document. 107 | - All requested changes and conversations have to be resolved before the merge. 108 | - You need a linked issue that the PR solves. 109 | - Merge into master: Only develop is allowed to be merged into master. 110 | - Merge into master: You need two approving reviews. One must be from a maintainer. 111 | 112 | ## You're ready 113 | 114 | As you read this contribution guidelines, the code of conduct, and most important our coding guidelines you are good to go! We really appreciate your work spent on contributions to our project, as people like you make the open source community great ❤️! 115 | 116 | If you have some suggestions to this contributing guidelines, feel free to suggest them! 117 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 4.0.0 21 | 22 | com.github.playerforcehd 23 | tc-discord-webhooks 24 | 1.1.1 25 | pom 26 | 27 | TC Discord WebHooks 28 | A TeamCity plugin that allows the easy creation of Discord WebHooks that are used to notify users on 29 | a Discord server about the current state of builds. 30 | 31 | https://github.com/pascal-zarrad/tc-discord-webhooks 32 | 33 | 34 | 2018.2 35 | UTF-8 36 | 37 | 38 | 39 | 40 | Apache-2.0 License 41 | https://www.apache.org/licenses/LICENSE-2.0 42 | 43 | 44 | 45 | 46 | 47 | Pascal Zarrad 48 | p.zarrad@outlook.de 49 | - None - 50 | https://pascal-zarrad.de/ 51 | 52 | 53 | 54 | 55 | scm:git:git://github.com/pascal-zarrad/tc-discord-webhooks.git 56 | scm:git:ssh://github.com/pascal-zarrad/tc-discord-webhooks.git 57 | https://github.com/pascal-zarrad/tc-discord-webhooks/tree/master 58 | 59 | 60 | 61 | 62 | JetBrains 63 | https://download.jetbrains.com/teamcity-repository 64 | 65 | 66 | 67 | 68 | tc-discord-webhooks-server 69 | build 70 | 71 | 72 | 73 | 74 | 75 | 82 | 83 | 84 | org.apache.httpcomponents 85 | httpclient 86 | 4.5.6 87 | provided 88 | 89 | 90 | 91 | com.google.code.gson 92 | gson 93 | 2.4 94 | provided 95 | 96 | 97 | 98 | 99 | org.testng 100 | testng 101 | 6.8.21 102 | test 103 | 104 | 105 | 106 | com.github.tomakehurst 107 | wiremock-jre8 108 | 2.23.2 109 | test 110 | 111 | 112 | 113 | 114 | 115 | JetBrains 116 | https://download.jetbrains.com/teamcity-repository 117 | 118 | 119 | 120 | 121 | 122 | 123 | org.jacoco 124 | jacoco-maven-plugin 125 | 0.8.5 126 | 127 | 128 | 129 | prepare-agent 130 | 131 | 132 | 133 | report 134 | prepare-package 135 | 136 | report 137 | 138 | 139 | 140 | 141 | 142 | org.jetbrains.teamcity 143 | teamcity-sdk-maven-plugin 144 | 0.4 145 | 146 | 147 | 148 | 149 | 150 | org.apache.maven.plugins 151 | maven-compiler-plugin 152 | 3.8.1 153 | 154 | 1.8 155 | 1.8 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-javadoc-plugin 161 | 3.0.1 162 | 163 | 164 | attach-javadocs 165 | 166 | jar 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | org.jetbrains.teamcity 176 | teamcity-sdk-maven-plugin 177 | 0.4 178 | 179 | 180 | 181 | 182 | 183 | org.apache.maven.plugins 184 | maven-javadoc-plugin 185 | 3.0.1 186 | 187 | 8 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/test/java/com/gtihub/playerforcehd/tcdiscordwebhooks/DiscordWebHookTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.gtihub.playerforcehd.tcdiscordwebhooks; 18 | 19 | import com.github.playerforcehd.tcdiscordwebhooks.discord.DiscordWebHookPayload; 20 | import com.github.playerforcehd.tcdiscordwebhooks.discord.DiscordWebHookProcessor; 21 | import com.github.playerforcehd.tcdiscordwebhooks.discord.embeds.*; 22 | import com.github.tomakehurst.wiremock.WireMockServer; 23 | import com.google.gson.JsonObject; 24 | import com.google.gson.JsonParser; 25 | import org.testng.annotations.AfterMethod; 26 | import org.testng.annotations.BeforeMethod; 27 | import org.testng.annotations.Test; 28 | 29 | import java.io.IOException; 30 | import java.net.URISyntaxException; 31 | 32 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 33 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; 34 | import static org.testng.Assert.*; 35 | 36 | /** 37 | * Tests to check if the DiscordWebHookPayload's are working properly. 38 | * 39 | * @author Pascal Zarrad 40 | */ 41 | public class DiscordWebHookTest { 42 | 43 | /** 44 | * An example payload that has been validated to work properly. 45 | * It uses every supported feature, so the serialization and sending of WebHooks can 46 | * be tested full scope. 47 | */ 48 | private final String exampleWebHookPayload = "{\n" + 49 | " \"username\": \"Test User\",\n" + 50 | " \"avatar_url\": \"http://localhost/myAvatar.png\",\n" + 51 | " \"tts\": true,\n" + 52 | " \"content\": \"This is great content!\",\n" + 53 | " \"embeds\": [\n" + 54 | " {\n" + 55 | " \"title\": \"Embed Title!\",\n" + 56 | " \"description\": \"My Description\",\n" + 57 | " \"url\": \"https://discordapp.com/\",\n" + 58 | " \"color\": 16711680,\n" + 59 | " \"footer\": {\n" + 60 | " \"text\": \"Test Text\",\n" + 61 | " \"icon_url\": \"http://localhost/footerIcon.png\"\n" + 62 | " },\n" + 63 | " \"image\": {\n" + 64 | " \"url\": \"http://localhost/EmbeddedImage.png\"\n" + 65 | " },\n" + 66 | " \"thumbnail\": {\n" + 67 | " \"url\": \"http://localhost/thumbnail.png\"\n" + 68 | " },\n" + 69 | " \"fields\": [\n" + 70 | " {\n" + 71 | " \"name\": \"An embedded field\",\n" + 72 | " \"value\": \"This is an embedded field!\",\n" + 73 | " \"inline\": false\n" + 74 | " }\n" + 75 | " ]\n" + 76 | " }\n" + 77 | " ]\n" + 78 | "}"; 79 | 80 | /** 81 | * The {@link WireMockServer}used to mock a WebServer to test the execution of WebHooks 82 | */ 83 | private WireMockServer wireMockServer; 84 | 85 | /** 86 | * Prepares a {@link WireMockServer} to provide support for test relying on a WebServer. 87 | */ 88 | @BeforeMethod 89 | public void prepare() { 90 | this.wireMockServer = new WireMockServer(options().dynamicPort().bindAddress("127.0.0.1")); 91 | this.wireMockServer.start(); 92 | } 93 | 94 | /** 95 | * Stops the running {@link WireMockServer} 96 | */ 97 | @AfterMethod 98 | public void reset() { 99 | this.wireMockServer.stop(); 100 | } 101 | 102 | /** 103 | * Tests if the values that are passed to a DiscordWebHookPayload are assigned to the right fields. 104 | */ 105 | @Test 106 | public void testSerializationOfDiscordWebHookPayloadEqualsReferencePayloadJSON() { 107 | // Create a DiscordWebHookPayload 108 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload("Test User", "http://localhost/myAvatar.png", true, "This is great content!", new DiscordEmbed[]{ 109 | new DiscordEmbed("Embed Title!", "My Description", "https://discordapp.com/", DiscordEmbedColor.RED, 110 | new DiscordEmbedFooter("Test Text", "http://localhost/footerIcon.png"), 111 | new DiscordEmbedImage("http://localhost/EmbeddedImage.png"), 112 | new DiscordEmbedImage("http://localhost/thumbnail.png"), 113 | new DiscordEmbedField[]{ 114 | new DiscordEmbedField("An embedded field", "This is an embedded field!") 115 | }) 116 | }); 117 | // Create DiscordWebHookProcessor 118 | DiscordWebHookProcessor discordWebHookProcessor = new DiscordWebHookProcessor(); 119 | // Serialize the DiscordWebHookPayload into a JSON string 120 | String discordWebHookPayloadJSON = discordWebHookProcessor.serializeDiscordWebHookPayload(discordWebHookPayload); 121 | // Parse our created payload 122 | JsonParser parser = new JsonParser(); 123 | JsonObject payloadElement = (JsonObject) parser.parse(discordWebHookPayloadJSON); 124 | // Parse the reference sting 125 | JsonObject referenceElement = (JsonObject) parser.parse(this.exampleWebHookPayload); 126 | // Now the two JSonElements should have the same content 127 | assertEquals(payloadElement, referenceElement, "The serialized payload element does not equal to the reference element!"); 128 | } 129 | 130 | /** 131 | * Test if the {@link DiscordWebHookProcessor#sendDiscordWebHook(String, String)} sends a valid WebHook request 132 | */ 133 | @Test 134 | public void testValidDiscordWebHook() { 135 | // Create a DiscordWebHookPayload 136 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload("Test User", "http://localhost/myAvatar.png", false, "This is great content!", new DiscordEmbed[]{ 137 | new DiscordEmbed("Embed Title!", "My Description", "https://discordapp.com/", DiscordEmbedColor.RED, 138 | new DiscordEmbedFooter("Test Text", "http://localhost/footerIcon.png"), 139 | new DiscordEmbedImage("http://localhost/EmbeddedImage.png"), 140 | new DiscordEmbedImage("http://localhost/thumbnail.png"), 141 | new DiscordEmbedField[]{ 142 | new DiscordEmbedField("An embedded field", "This is an embedded field!") 143 | }) 144 | }); 145 | // Create DiscordWebHookProcessor 146 | DiscordWebHookProcessor discordWebHookProcessor = new DiscordWebHookProcessor(); 147 | String discordWebHookContent = discordWebHookProcessor.serializeDiscordWebHookPayload(discordWebHookPayload); 148 | // Configure WireMock 149 | String localRequestUrl = "http://" + this.wireMockServer.getOptions().bindAddress() + ":" + this.wireMockServer.port() + "/anyWebHookID/anyWebHookToken"; 150 | this.wireMockServer.stubFor(any(urlPathEqualTo("/anyWebHookID/anyWebHookToken")) 151 | .withHeader("User-Agent", equalTo("TeamCity Discord WebHook v1")) 152 | .withHeader("Accept-Language", equalTo("en-US,en;q=0.5")) 153 | .withHeader("Content-Type", equalTo("application/json")) 154 | .withRequestBody(equalToJson(discordWebHookContent)) 155 | .willReturn(noContent()) 156 | ); 157 | // Send WebHook 158 | try { 159 | assertTrue(discordWebHookProcessor.sendDiscordWebHook(localRequestUrl, discordWebHookPayload), "The send request should end up in Error 204 (No content) and trigger a return true, which represents a valid request!"); 160 | } catch (IOException | URISyntaxException e) { 161 | fail("Failed to send Discord WebHook due to an IOException!", e); 162 | } 163 | } 164 | 165 | /** 166 | * Test if the {@link DiscordWebHookProcessor#sendDiscordWebHook(String, String)} fails when sending an invalid request 167 | */ 168 | @Test 169 | public void testValidDiscordWebHookFailsForInvalidContent() { 170 | // Create a DiscordWebHookPayload 171 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(null, null, false, null); 172 | // Create DiscordWebHookProcessor 173 | DiscordWebHookProcessor discordWebHookProcessor = new DiscordWebHookProcessor(); 174 | String discordWebHookContent = discordWebHookProcessor.serializeDiscordWebHookPayload(discordWebHookPayload); 175 | // Configure WireMock 176 | String localRequestUrl = "http://" + this.wireMockServer.getOptions().bindAddress() + ":" + this.wireMockServer.port() + "/anyWebHookID/anyWebHookToken"; 177 | this.wireMockServer.stubFor(any(urlPathEqualTo("/anyWebHookID/anyWebHookToken")) 178 | .withHeader("User-Agent", equalTo("TeamCity Discord WebHook v1")) 179 | .withHeader("Accept-Language", equalTo("en-US,en;q=0.5")) 180 | .withHeader("Content-Type", equalTo("application/json")) 181 | .withRequestBody(equalToJson(discordWebHookContent)) // Using the example payload content to check here. 182 | .willReturn(badRequest()) 183 | ); 184 | // Send WebHook 185 | try { 186 | assertFalse(discordWebHookProcessor.sendDiscordWebHook(localRequestUrl, discordWebHookPayload), "The WebServer accepted the request, but the content did not equal a valid format or had valid content!"); 187 | } catch (IOException | URISyntaxException e) { 188 | fail("Failed to send Discord WebHook due to an IOException!", e); 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tc-discord-webhooks-server/src/main/java/com/github/playerforcehd/tcdiscordwebhooks/notificator/DiscordNotificator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Pascal Zarrad 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.playerforcehd.tcdiscordwebhooks.notificator; 18 | 19 | import com.github.playerforcehd.tcdiscordwebhooks.discord.DiscordWebHookPayload; 20 | import com.github.playerforcehd.tcdiscordwebhooks.discord.DiscordWebHookProcessor; 21 | import com.github.playerforcehd.tcdiscordwebhooks.discord.embeds.DiscordEmbed; 22 | import com.github.playerforcehd.tcdiscordwebhooks.discord.embeds.DiscordEmbedColor; 23 | import com.github.playerforcehd.tcdiscordwebhooks.discord.embeds.DiscordEmbedField; 24 | import jetbrains.buildServer.Build; 25 | import jetbrains.buildServer.notification.Notificator; 26 | import jetbrains.buildServer.notification.NotificatorRegistry; 27 | import jetbrains.buildServer.responsibility.ResponsibilityEntry; 28 | import jetbrains.buildServer.responsibility.TestNameResponsibilityEntry; 29 | import jetbrains.buildServer.serverSide.*; 30 | import jetbrains.buildServer.serverSide.comments.Comment; 31 | import jetbrains.buildServer.serverSide.mute.MuteInfo; 32 | import jetbrains.buildServer.serverSide.problems.BuildProblemInfo; 33 | import jetbrains.buildServer.tests.TestName; 34 | import jetbrains.buildServer.users.NotificatorPropertyKey; 35 | import jetbrains.buildServer.users.PropertyKey; 36 | import jetbrains.buildServer.users.SUser; 37 | import jetbrains.buildServer.vcs.VcsRoot; 38 | import org.apache.log4j.Logger; 39 | import org.jetbrains.annotations.NotNull; 40 | import org.jetbrains.annotations.Nullable; 41 | 42 | import java.io.IOException; 43 | import java.net.URISyntaxException; 44 | import java.util.ArrayList; 45 | import java.util.Collection; 46 | import java.util.List; 47 | import java.util.Set; 48 | 49 | /** 50 | * The {@link Notificator} service that handles triggered notifications 51 | * 52 | * @author Pascal Zarrad 53 | */ 54 | public class DiscordNotificator implements Notificator { 55 | 56 | /** 57 | * The logger used for debug messages. 58 | * Mostly used for error logging. 59 | */ 60 | private static final Logger LOGGER = Logger.getLogger(DiscordNotificator.class); 61 | /** 62 | * The type of this {@link Notificator} 63 | */ 64 | private static final String TYPE = "DiscordNotificator"; 65 | /** 66 | * The display name of this notificator 67 | */ 68 | private static final String DISPLAY_NAME = "Discord WebHook"; 69 | /** 70 | * The name of the {@link PropertyKey} {@link #WEBHOOK_URL} 71 | */ 72 | private static final String WEBHOOK_URL_KEY = "DiscordWebHookURL"; 73 | /** 74 | * The name of the {@link PropertyKey} {@link #USERNAME} 75 | */ 76 | private static final String WEBHOOK_USERNAME_KEY = "DiscordUsername"; 77 | /** 78 | * {@link PropertyKey} of the property that defines the URL of the WebHook 79 | */ 80 | private static final PropertyKey WEBHOOK_URL = new NotificatorPropertyKey(TYPE, WEBHOOK_URL_KEY); 81 | /** 82 | * {@link PropertyKey} of the property that defines the Username of the WebHook 83 | */ 84 | private static final PropertyKey USERNAME = new NotificatorPropertyKey(TYPE, WEBHOOK_USERNAME_KEY); 85 | /** 86 | * The string used for situation where no data is available to display 87 | */ 88 | private static final String NO_DATA = ""; 89 | /** 90 | * The {@link DiscordWebHookProcessor} that is used to trigger the WebHooks 91 | */ 92 | private final DiscordWebHookProcessor discordWebHookProcessor; 93 | 94 | /** 95 | * The {@link SBuildServer} this {@link Notificator} belongs to 96 | */ 97 | private SBuildServer sBuildServer; 98 | 99 | public DiscordNotificator(NotificatorRegistry notificatorRegistry, SBuildServer sBuildServer) { 100 | this.discordWebHookProcessor = new DiscordWebHookProcessor(); 101 | this.sBuildServer = sBuildServer; 102 | this.initializeNotificator(notificatorRegistry); 103 | } 104 | 105 | /** 106 | * Creates all the {@link UserPropertyInfo}'s and registers this {@link Notificator} 107 | * 108 | * @param notificatorRegistry The {@link NotificatorRegistry} where this {@link Notificator} will be registered to 109 | */ 110 | private void initializeNotificator(NotificatorRegistry notificatorRegistry) { 111 | ArrayList userProperties = new ArrayList<>(); 112 | userProperties.add(new UserPropertyInfo(WEBHOOK_URL_KEY, "WebHook URL")); 113 | userProperties.add(new UserPropertyInfo(WEBHOOK_USERNAME_KEY, "Username")); 114 | notificatorRegistry.register(this, userProperties); 115 | } 116 | 117 | /** 118 | * Send the notification by triggering the 119 | * {@link DiscordWebHookProcessor#sendDiscordWebHook(String, DiscordWebHookPayload)} 120 | * method using the data given in the discordWebHookPayload parameter. 121 | * 122 | * @param discordWebHookPayload The payload to send 123 | * @param users The users that should be notified 124 | */ 125 | private void processNotify(@NotNull DiscordWebHookPayload discordWebHookPayload, @NotNull Set users) { 126 | for (SUser user : users) { 127 | String webHookUrl = user.getPropertyValue(WEBHOOK_URL); 128 | String username = user.getPropertyValue(USERNAME); 129 | if (webHookUrl == null || webHookUrl.equals("")) { 130 | LOGGER.error("The Discord WebHook URL for user '" + user.getName() + "' has not been set. Can't execute the WebHook!"); 131 | return; 132 | } 133 | if (username != null && !username.equals("")) { 134 | discordWebHookPayload.setUsername(username); 135 | } 136 | try { 137 | this.discordWebHookProcessor.sendDiscordWebHook(webHookUrl, discordWebHookPayload); 138 | } catch (IOException | URISyntaxException e) { 139 | LOGGER.error("Failed to send the WebHook!", e); 140 | } 141 | } 142 | } 143 | 144 | /** 145 | * Gets a Project from a {@link SRunningBuild} by searching for a project 146 | * that has the same project id as the running build. 147 | * 148 | * @param sRunningBuild The {@link SRunningBuild} from which the Project should be grabbed 149 | * @return The project that owns the build or null 150 | */ 151 | private SProject getProjectFromRunningBuild(SRunningBuild sRunningBuild) { 152 | for (SProject project : this.sBuildServer.getProjectManager().getProjects()) { 153 | if (project.getProjectId().equals(sRunningBuild.getProjectId())) { 154 | return project; 155 | } 156 | } 157 | return null; 158 | } 159 | 160 | /** 161 | * Builds the {@link DiscordEmbedField}'s that are used in all notifications 162 | * that are based on an {@link SRunningBuild}. 163 | * 164 | * @param sRunningBuild The build from which the fields will be build 165 | * @return The {@link DiscordEmbedField}'s created from the {@link SRunningBuild} 166 | */ 167 | private DiscordEmbedField[] buildFieldsForRunningBuild(SRunningBuild sRunningBuild) { 168 | List discordEmbedFields = new ArrayList<>(); 169 | // Grab data 170 | // Project 171 | SProject project = getProjectFromRunningBuild(sRunningBuild); 172 | String projectName = NO_DATA; 173 | if (project != null) { 174 | projectName = project.getName(); 175 | } 176 | discordEmbedFields.add(new DiscordEmbedField("Project: ", projectName, true)); 177 | // Build name 178 | discordEmbedFields.add(new DiscordEmbedField("Build:", sRunningBuild.getBuildTypeName(), true)); 179 | // Branch 180 | Branch branch = sRunningBuild.getBranch(); 181 | String branchName = "Default"; 182 | if (branch != null && !branch.getName().equals(Branch.DEFAULT_BRANCH_NAME)) { 183 | branchName = branch.getDisplayName(); 184 | } 185 | discordEmbedFields.add(new DiscordEmbedField("Branch", branchName, true)); 186 | Comment comment = sRunningBuild.getBuildComment(); 187 | if(comment != null) { 188 | discordEmbedFields.add(new DiscordEmbedField("Comment", comment.getComment(), false)); 189 | } 190 | return discordEmbedFields.toArray(new DiscordEmbedField[0]); 191 | } 192 | 193 | @Override 194 | public void notifyBuildStarted(@NotNull SRunningBuild sRunningBuild, @NotNull Set users) { 195 | String title = "Build started"; 196 | String description = "A build with the ID " + sRunningBuild.getBuildId() + " has been started!"; 197 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + sRunningBuild.getBuildId(); 198 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 199 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 200 | new DiscordEmbed( 201 | title, 202 | description, 203 | url, 204 | DiscordEmbedColor.BLUE, 205 | null, 206 | null, 207 | null, 208 | buildFieldsForRunningBuild(sRunningBuild) 209 | ) 210 | }); 211 | this.processNotify(discordWebHookPayload, users); 212 | } 213 | 214 | @Override 215 | public void notifyBuildSuccessful(@NotNull SRunningBuild sRunningBuild, @NotNull Set users) { 216 | String title = "Build succeeded!"; 217 | String description = "The build with the ID " + sRunningBuild.getBuildId() + " has succeeded!"; 218 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + sRunningBuild.getBuildId(); 219 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 220 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 221 | new DiscordEmbed( 222 | title, 223 | description, 224 | url, 225 | DiscordEmbedColor.GREEN, 226 | null, 227 | null, 228 | null, 229 | buildFieldsForRunningBuild(sRunningBuild) 230 | ) 231 | }); 232 | this.processNotify(discordWebHookPayload, users); 233 | } 234 | 235 | @Override 236 | public void notifyBuildFailed(@NotNull SRunningBuild sRunningBuild, @NotNull Set users) { 237 | String title = "Build failed"; 238 | String description = "The build with the ID " + sRunningBuild.getBuildId() + " has failed!"; 239 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + sRunningBuild.getBuildId(); 240 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 241 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 242 | new DiscordEmbed( 243 | title, 244 | description, 245 | url, 246 | DiscordEmbedColor.RED, 247 | null, 248 | null, 249 | null, 250 | buildFieldsForRunningBuild(sRunningBuild) 251 | ) 252 | }); 253 | this.processNotify(discordWebHookPayload, users); 254 | } 255 | 256 | @Override 257 | public void notifyBuildFailedToStart(@NotNull SRunningBuild sRunningBuild, @NotNull Set users) { 258 | String title = "Build failed to start"; 259 | String description = "The build with the ID " + sRunningBuild.getBuildId() + " has failed to start!"; 260 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + sRunningBuild.getBuildId(); 261 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 262 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 263 | new DiscordEmbed( 264 | title, 265 | description, 266 | url, 267 | DiscordEmbedColor.RED, 268 | null, 269 | null, 270 | null, 271 | buildFieldsForRunningBuild(sRunningBuild) 272 | ) 273 | }); 274 | this.processNotify(discordWebHookPayload, users); 275 | } 276 | 277 | @Override 278 | public void notifyLabelingFailed(@NotNull Build build, @NotNull VcsRoot vcsRoot, @NotNull Throwable throwable, @NotNull Set users) { 279 | String title = "Labeling failed"; 280 | String description = "Labeling of build with the ID " + build.getBuildId() + " has failed!"; 281 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + build.getBuildId(); 282 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 283 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 284 | new DiscordEmbed( 285 | title, 286 | description, 287 | url, 288 | DiscordEmbedColor.RED, 289 | null, 290 | null, 291 | null, 292 | new DiscordEmbedField[]{} 293 | ) 294 | }); 295 | this.processNotify(discordWebHookPayload, users); 296 | } 297 | 298 | @Override 299 | public void notifyBuildFailing(@NotNull SRunningBuild sRunningBuild, @NotNull Set users) { 300 | String title = "Build is failing"; 301 | String description = "The build with the ID " + sRunningBuild.getBuildId() + " is failing!"; 302 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + sRunningBuild.getBuildId(); 303 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 304 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 305 | new DiscordEmbed( 306 | title, 307 | description, 308 | url, 309 | DiscordEmbedColor.RED, 310 | null, 311 | null, 312 | null, 313 | buildFieldsForRunningBuild(sRunningBuild) 314 | ) 315 | }); 316 | this.processNotify(discordWebHookPayload, users); 317 | } 318 | 319 | @Override 320 | public void notifyBuildProbablyHanging(@NotNull SRunningBuild sRunningBuild, @NotNull Set users) { 321 | String title = "Build is probably hanging"; 322 | String description = "The build with the ID " + sRunningBuild.getBuildId() + " is probably hanging!"; 323 | String url = this.sBuildServer.getRootUrl() + "/viewLog.html?buildId=" + sRunningBuild.getBuildId(); 324 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 325 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 326 | new DiscordEmbed( 327 | title, 328 | description, 329 | url, 330 | DiscordEmbedColor.ORANGE, 331 | null, 332 | null, 333 | null, 334 | buildFieldsForRunningBuild(sRunningBuild) 335 | ) 336 | }); 337 | this.processNotify(discordWebHookPayload, users); 338 | } 339 | 340 | @Override 341 | public void notifyResponsibleChanged(@NotNull SBuildType sBuildType, @NotNull Set users) { 342 | String title = "Responsibility for build type has changed"; 343 | String description = "The responsibility for the build type " + sBuildType.getExtendedFullName() + " has changed!"; 344 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 345 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 346 | new DiscordEmbed( 347 | title, 348 | description, 349 | "", 350 | DiscordEmbedColor.ORANGE, 351 | null, 352 | null, 353 | null, 354 | new DiscordEmbedField[]{} 355 | ) 356 | }); 357 | this.processNotify(discordWebHookPayload, users); 358 | } 359 | 360 | @Override 361 | public void notifyResponsibleAssigned(@NotNull SBuildType sBuildType, @NotNull Set users) { 362 | String title = "Responsibility assigned"; 363 | String description = "Responsibility for build type " + sBuildType.getExtendedFullName() + " has been assigned!"; 364 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 365 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 366 | new DiscordEmbed( 367 | title, 368 | description, 369 | "", 370 | DiscordEmbedColor.ORANGE, 371 | null, 372 | null, 373 | null, 374 | new DiscordEmbedField[]{} 375 | ) 376 | }); 377 | this.processNotify(discordWebHookPayload, users); 378 | } 379 | 380 | @Override 381 | public void notifyResponsibleChanged(@Nullable TestNameResponsibilityEntry testNameResponsibilityEntry, @NotNull TestNameResponsibilityEntry testNameResponsibilityEntry1, @NotNull SProject sProject, @NotNull Set users) { 382 | String title = "Responsibility changed"; 383 | String description = "Responsibility for the project " + sProject.getFullName() + " has changed!"; 384 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 385 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 386 | new DiscordEmbed( 387 | title, 388 | description, 389 | "", 390 | DiscordEmbedColor.ORANGE, 391 | null, 392 | null, 393 | null, 394 | new DiscordEmbedField[]{} 395 | ) 396 | }); 397 | this.processNotify(discordWebHookPayload, users); 398 | } 399 | 400 | @Override 401 | public void notifyResponsibleAssigned(@Nullable TestNameResponsibilityEntry testNameResponsibilityEntry, @NotNull TestNameResponsibilityEntry testNameResponsibilityEntry1, @NotNull SProject sProject, @NotNull Set users) { 402 | String title = "Responsibility assigned"; 403 | String description = "Responsibility for project " + sProject.getFullName() + " has been assigned!"; 404 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 405 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 406 | new DiscordEmbed( 407 | title, 408 | description, 409 | "", 410 | DiscordEmbedColor.ORANGE, 411 | null, 412 | null, 413 | null, 414 | new DiscordEmbedField[]{} 415 | ) 416 | }); 417 | this.processNotify(discordWebHookPayload, users); 418 | } 419 | 420 | @Override 421 | public void notifyResponsibleChanged(@NotNull Collection collection, @NotNull ResponsibilityEntry responsibilityEntry, @NotNull SProject sProject, @NotNull Set users) { 422 | String title = "Responsibility changed"; 423 | String description = "Responsibility for project " + sProject.getFullName() + " has been changed!"; 424 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 425 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 426 | new DiscordEmbed( 427 | title, 428 | description, 429 | "", 430 | DiscordEmbedColor.ORANGE, 431 | null, 432 | null, 433 | null, 434 | new DiscordEmbedField[]{} 435 | ) 436 | }); 437 | this.processNotify(discordWebHookPayload, users); 438 | } 439 | 440 | @Override 441 | public void notifyResponsibleAssigned(@NotNull Collection collection, @NotNull ResponsibilityEntry responsibilityEntry, @NotNull SProject sProject, @NotNull Set users) { 442 | String title = "Responsibility assigned"; 443 | String description = "Responsibility for one or more tests of project " + sProject.getFullName() + " have been assigned!"; 444 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 445 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 446 | new DiscordEmbed( 447 | title, 448 | description, 449 | "", 450 | DiscordEmbedColor.ORANGE, 451 | null, 452 | null, 453 | null, 454 | new DiscordEmbedField[]{} 455 | ) 456 | }); 457 | this.processNotify(discordWebHookPayload, users); 458 | } 459 | 460 | @Override 461 | public void notifyBuildProblemResponsibleAssigned(@NotNull Collection collection, @NotNull ResponsibilityEntry responsibilityEntry, @NotNull SProject sProject, @NotNull Set users) { 462 | String title = "Responsibility assigned"; 463 | String description = "Responsibility for one or more build problems of project " + sProject.getFullName() + " have been assigned!"; 464 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 465 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 466 | new DiscordEmbed( 467 | title, 468 | description, 469 | "", 470 | DiscordEmbedColor.ORANGE, 471 | null, 472 | null, 473 | null, 474 | new DiscordEmbedField[]{} 475 | ) 476 | }); 477 | this.processNotify(discordWebHookPayload, users); 478 | } 479 | 480 | @Override 481 | public void notifyBuildProblemResponsibleChanged(@NotNull Collection collection, @NotNull ResponsibilityEntry responsibilityEntry, @NotNull SProject sProject, @NotNull Set users) { 482 | String title = "Responsibility assigned"; 483 | String description = "Responsibility for one or more tests of project " + sProject.getFullName() + " has been changed!"; 484 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 485 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 486 | new DiscordEmbed( 487 | title, 488 | description, 489 | "", 490 | DiscordEmbedColor.ORANGE, 491 | null, 492 | null, 493 | null, 494 | new DiscordEmbedField[]{} 495 | ) 496 | }); 497 | this.processNotify(discordWebHookPayload, users); 498 | } 499 | 500 | @Override 501 | public void notifyTestsMuted(@NotNull Collection collection, @NotNull MuteInfo muteInfo, @NotNull Set users) { 502 | String title = "Tests muted"; 503 | if (muteInfo.getProject() != null) { 504 | muteInfo.getProject().getFullName(); 505 | String description = "One or more tests of the project " + muteInfo.getProject().getFullName() + " have been muted!"; 506 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 507 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 508 | new DiscordEmbed( 509 | title, 510 | description, 511 | "", 512 | DiscordEmbedColor.ORANGE, 513 | null, 514 | null, 515 | null, 516 | new DiscordEmbedField[]{} 517 | ) 518 | }); 519 | this.processNotify(discordWebHookPayload, users); 520 | } 521 | } 522 | 523 | @Override 524 | public void notifyTestsUnmuted(@NotNull Collection collection, @NotNull MuteInfo muteInfo, @Nullable SUser sUser, @NotNull Set users) { 525 | String title = "Tests unmuted"; 526 | if (muteInfo.getProject() != null) { 527 | muteInfo.getProject().getFullName(); 528 | String description = "One or more tests of the project " + muteInfo.getProject().getFullName() + " have been unmuted!"; 529 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 530 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 531 | new DiscordEmbed( 532 | title, 533 | description, 534 | "", 535 | DiscordEmbedColor.ORANGE, 536 | null, 537 | null, 538 | null, 539 | new DiscordEmbedField[]{} 540 | ) 541 | }); 542 | this.processNotify(discordWebHookPayload, users); 543 | } 544 | } 545 | 546 | @Override 547 | public void notifyBuildProblemsMuted(@NotNull Collection collection, @NotNull MuteInfo muteInfo, @NotNull Set users) { 548 | String title = "Build problems muted"; 549 | if (muteInfo.getProject() != null) { 550 | muteInfo.getProject().getFullName(); 551 | String description = "One or more build problems of the project " + muteInfo.getProject().getFullName() + " have been muted!"; 552 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 553 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 554 | new DiscordEmbed( 555 | title, 556 | description, 557 | "", 558 | DiscordEmbedColor.ORANGE, 559 | null, 560 | null, 561 | null, 562 | new DiscordEmbedField[]{} 563 | ) 564 | }); 565 | this.processNotify(discordWebHookPayload, users); 566 | } 567 | } 568 | 569 | @Override 570 | public void notifyBuildProblemsUnmuted(@NotNull Collection collection, @NotNull MuteInfo muteInfo, @Nullable SUser sUser, @NotNull Set users) { 571 | String title = "Build problems unmuted"; 572 | if (muteInfo.getProject() != null) { 573 | muteInfo.getProject().getFullName(); 574 | String description = "One or more build problems of the project " + muteInfo.getProject().getFullName() + " have been unmuted!"; 575 | DiscordWebHookPayload discordWebHookPayload = new DiscordWebHookPayload(); 576 | discordWebHookPayload.setEmbeds(new DiscordEmbed[]{ 577 | new DiscordEmbed( 578 | title, 579 | description, 580 | "", 581 | DiscordEmbedColor.ORANGE, 582 | null, 583 | null, 584 | null, 585 | new DiscordEmbedField[]{} 586 | ) 587 | }); 588 | this.processNotify(discordWebHookPayload, users); 589 | } 590 | } 591 | 592 | @NotNull 593 | @Override 594 | public String getNotificatorType() { 595 | return TYPE; 596 | } 597 | 598 | @NotNull 599 | @Override 600 | public String getDisplayName() { 601 | return DISPLAY_NAME; 602 | } 603 | } 604 | --------------------------------------------------------------------------------