├── .github
├── CODEOWNERS
├── dependabot.yml
└── workflows
│ ├── cd.yaml
│ └── jenkins-security-scan.yml
├── .mvn
├── maven.config
└── extensions.xml
├── docs
└── images
│ ├── hudson-irc.PNG
│ └── hudson-irc-project.PNG
├── src
├── main
│ ├── webapp
│ │ ├── help.html
│ │ ├── help-globalConfigEnable.html
│ │ ├── help-globalConfigPort.html
│ │ ├── help-globalConfigChannels.html
│ │ ├── help-instanceConfigChannels.html
│ │ ├── help-globalConfigCommandPrefix.html
│ │ ├── help-globalTrustAllCertificates.html
│ │ ├── help-globalConfigNickServ.html
│ │ └── help-globalConfigUseColors.html
│ ├── resources
│ │ ├── hudson
│ │ │ └── plugins
│ │ │ │ └── ircbot
│ │ │ │ ├── Messages.properties
│ │ │ │ ├── IrcUserProperty
│ │ │ │ └── config.jelly
│ │ │ │ ├── IrcPublisher
│ │ │ │ ├── config.jelly
│ │ │ │ └── global.jelly
│ │ │ │ └── IrcNotifyStep
│ │ │ │ ├── config.jelly
│ │ │ │ └── global.jelly
│ │ └── index.jelly
│ └── java
│ │ └── hudson
│ │ └── plugins
│ │ └── ircbot
│ │ ├── PluginImpl.java
│ │ ├── v2
│ │ ├── IRCMessageTargetConverter.java
│ │ ├── IRCConnectionProvider.java
│ │ ├── IRCPrivateChat.java
│ │ ├── IRCChannel.java
│ │ ├── IRCColorizer.java
│ │ ├── PircListener.java
│ │ └── IRCConnection.java
│ │ ├── IrcUserProperty.java
│ │ ├── steps
│ │ └── IrcNotifyStep.java
│ │ └── IrcPublisher.java
└── test
│ └── java
│ └── hudson
│ └── plugins
│ └── ircbot
│ └── v2
│ └── IRCColorizerTest.java
├── .editorconfig
├── .gitignore
├── Jenkinsfile
├── checkstyle.xml
├── README.adoc
├── pom.xml
└── CHANGELOG.adoc
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @jenkinsci/ircbot-plugin-developers
2 |
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 | -Dchangelist.format=%d.v%s
4 |
--------------------------------------------------------------------------------
/docs/images/hudson-irc.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/ircbot-plugin/HEAD/docs/images/hudson-irc.PNG
--------------------------------------------------------------------------------
/src/main/webapp/help.html:
--------------------------------------------------------------------------------
1 |
2 | Notify build results to and accept commands from an IRC chat server.
3 |
--------------------------------------------------------------------------------
/src/main/resources/hudson/plugins/ircbot/Messages.properties:
--------------------------------------------------------------------------------
1 | IrcCause.ShortDescription=Started by IRC message from {0}
2 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfigEnable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Check this to enable the IRC bot
4 |
5 |
--------------------------------------------------------------------------------
/docs/images/hudson-irc-project.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/ircbot-plugin/HEAD/docs/images/hudson-irc-project.PNG
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfigPort.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | The IRC server port. Most IRC servers listen on port 194.
4 |
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | trim_trailing_whitespace = true
7 | charset = utf-8
8 |
9 | [*.{html,java,jelly,xml}]
10 | indent_style = space
11 | indent_size = 4
12 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfigChannels.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | List of channels the bot should join initially.
4 | Channels must be specified as name/password pairs, where password is not needed for non-protected channels.
5 | Name must start with #, e.g. #mychannel.
6 |
7 |
--------------------------------------------------------------------------------
/src/main/webapp/help-instanceConfigChannels.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | List of channels the bot should notify. Channels must be specified as name/password pairs,
4 | where password is not needed for non-protected channels.
5 | Name must start with #, e.g. #mychannel.
6 | Leave empty if the bots' main channels should be used instead.
7 |
8 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfigCommandPrefix.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | This is the string that must prefix all the commands on the bot, e.g. if you set it to:
4 |
5 | !jenkins
6 |
7 | Then you need to type
!jenkins status to get the build status report.
8 |
9 |
10 | Use the help command to get a list of all available commands.
11 |
12 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.13
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: "maven"
6 | directory: "/"
7 | schedule:
8 | interval: "daily"
9 | reviewers:
10 | - "jimklimov"
11 | - package-ecosystem: "github-actions"
12 | directory: "/"
13 | schedule:
14 | interval: "daily"
15 | reviewers:
16 | - "jimklimov"
17 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | jobs:
11 | maven-cd:
12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1
13 | secrets:
14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.project
2 | /target
3 | /.settings
4 | /.classpath
5 | /work*
6 | /.idea
7 |
8 | ### https://github.com/github/gitignore/blob/master/Maven.gitignore
9 | /pom.xml.tag
10 | /pom.xml.releaseBackup
11 | /pom.xml.versionsBackup
12 | /pom.xml.next
13 | /release.properties
14 | /dependency-reduced-pom.xml
15 | /buildNumber.properties
16 | /.mvn/timing.properties
17 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar
18 | /.mvn/wrapper/maven-wrapper.jar
19 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
This plugin is an IRC bot that can publish build results to IRC channels.
9 |
Note that the instant-messaging plugin is required for this plugin.
10 | Please make sure that it is installed, too!
11 |
12 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalTrustAllCertificates.html:
--------------------------------------------------------------------------------
1 |
2 | Trust all certificates when SSL is used.
3 |
4 | This is very insecure as it
5 | defeats one of the points of SSL: Making sure your connecting to the right server.
6 |
7 | You should seriously consider to import the certificate into your truststore instead!
8 | See e.g. Telling Java to accept self-signed certificates .
9 |
10 |
--------------------------------------------------------------------------------
/src/main/resources/hudson/plugins/ircbot/IrcUserProperty/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [ opened, synchronize, reopened ]
9 | workflow_dispatch:
10 |
11 | permissions:
12 | security-events: write
13 | contents: read
14 | actions: read
15 |
16 | jobs:
17 | security-scan:
18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
19 | with:
20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
22 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | /*
2 | * See the documentation for more options:
3 | * https://github.com/jenkins-infra/pipeline-library/
4 | */
5 | buildPlugin(
6 | useContainerAgent: true,
7 | configurations: [
8 | // Test the common case (i.e., a recent LTS release) on both Linux and Windows
9 | // with same core version as the lowest baseline requested by pom.xml
10 | [ platform: 'linux', jdk: '17', jenkins: '2.479.3' ],
11 | [ platform: 'windows', jdk: '17', jenkins: '2.479.3' ],
12 |
13 | // Test the bleeding edge of the compatibility spectrum (i.e., the latest supported Java runtime).
14 | // see also https://www.jenkins.io/doc/developer/plugin-development/choosing-jenkins-baseline/
15 | [ platform: 'linux', jdk: '21', jenkins: '2.484' ],
16 | ])
17 |
--------------------------------------------------------------------------------
/src/test/java/hudson/plugins/ircbot/v2/IRCColorizerTest.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import org.jvnet.hudson.test.Issue;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | import org.pircbotx.Colors;
8 |
9 | import static org.junit.jupiter.api.Assertions.assertEquals;
10 |
11 | class IRCColorizerTest {
12 |
13 | @Test
14 | @Issue("JENKINS-22360")
15 | void shouldColorizeKeywords() {
16 | String message = "Build job123 is STILL FAILING: https://server.com/build/42";
17 | String colorizedMessage = IRCColorizer.colorize(message);
18 |
19 | assertEquals("Build job123 is " + Colors.BOLD + Colors.RED + "STILL FAILING" + Colors.NORMAL + ": https://server.com/build/42", colorizedMessage);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/PluginImpl.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot;
2 |
3 | import hudson.Plugin;
4 | import hudson.plugins.im.IMPlugin;
5 | import hudson.plugins.ircbot.v2.IRCConnectionProvider;
6 |
7 | /**
8 | * Entry point of the plugin.
9 | *
10 | * @author Renaud Bruyeron
11 | * @version $Id: PluginImpl.java 23738 2009-11-15 18:36:59Z kutzi $
12 | */
13 | public class PluginImpl extends Plugin {
14 |
15 | private transient final IMPlugin imPlugin;
16 |
17 | public PluginImpl() {
18 | this.imPlugin = new IMPlugin(IRCConnectionProvider.getInstance());
19 | }
20 |
21 | /**
22 | * {@inheritDoc}
23 | */
24 | @Override
25 | public void start() throws Exception {
26 | super.start();
27 | this.imPlugin.start();
28 | }
29 |
30 | /**
31 | * {@inheritDoc}
32 | */
33 | @Override
34 | public void stop() throws Exception {
35 | this.imPlugin.stop();
36 | super.stop();
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfigNickServ.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Setting this value allows the bot to try and identify with NickServ. Once identified,
4 | other services can choose to give this bot higher powers...
5 |
6 | Further information on what is NickServ can be found here .
7 |
8 | It goes without saying that your IRC Server must be running the NickServ service.
9 |
10 | The bot name must be registered with NickServ first.
11 |
12 | There are a few ways you can do this, but one that works well is to:
13 |
14 |
15 | log into IRC
16 | /nick botname (for example, /nick jenkins-bot)
17 | /msg NickServ register secretpassword
18 | NickServ should respond with a successful message
19 |
20 |
21 | Then you can log out, set the password here to "secretpassword" and allow this
22 | bot to attempt to log in.
23 |
24 |
--------------------------------------------------------------------------------
/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/webapp/help-globalConfigUseColors.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Checking this box allows the bot to send colorized messages depending on the
4 | current and previous states of a job.
5 |
6 | The bot tries to send green messages for successful jobs
7 | and red messages for failures but this may not be right
8 | if your IRC client has defined a custom theme of colors.
9 |
10 |
11 | Here are the rules for coloring messages:
12 |
13 | Starting previously successful or fixed job: green
14 | Starting previously first time failed job: bold yellow
15 | Starting previously failed job: red
16 | First successful build (fixed job): underlined bold green
17 | First failure: underlined bold red
18 | Still successful job: bold green
19 | Still failing job: bold red
20 | Message containing 'test': bold magenta
21 | Default message: bold gray
22 |
23 |
24 | There is no support for colorblind theme for now.
25 |
26 |
--------------------------------------------------------------------------------
/src/main/resources/hudson/plugins/ircbot/IrcPublisher/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/IRCMessageTargetConverter.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import hudson.plugins.im.DefaultIMMessageTarget;
4 | import hudson.plugins.im.GroupChatIMMessageTarget;
5 | import hudson.plugins.im.IMMessageTarget;
6 | import hudson.plugins.im.IMMessageTargetConversionException;
7 | import hudson.plugins.im.IMMessageTargetConverter;
8 |
9 | import java.util.Collection;
10 | import java.util.LinkedList;
11 | import java.util.List;
12 |
13 | public class IRCMessageTargetConverter implements IMMessageTargetConverter {
14 |
15 | //@Override
16 | public IMMessageTarget fromString(String targetAsString)
17 | throws IMMessageTargetConversionException {
18 | if (targetAsString == null || targetAsString.trim().length() == 0) {
19 | return null;
20 | }
21 |
22 | targetAsString = targetAsString.trim();
23 |
24 | if (targetAsString.startsWith("#")) {
25 | return new GroupChatIMMessageTarget(targetAsString);
26 | } else {
27 | return new DefaultIMMessageTarget(targetAsString);
28 | }
29 | }
30 |
31 | public List allFromString(final Collection targetsAsString)
32 | throws IMMessageTargetConversionException {
33 | List finalTargets = new LinkedList();
34 | for(String target : targetsAsString) {
35 | finalTargets.add(fromString(target));
36 | }
37 | return finalTargets;
38 | }
39 |
40 | //@Override
41 | public String toString(IMMessageTarget target) {
42 | return target.toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/resources/hudson/plugins/ircbot/IrcNotifyStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/IRCConnectionProvider.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 |
5 | import hudson.plugins.im.IMConnection;
6 | import hudson.plugins.im.IMConnectionProvider;
7 | import hudson.plugins.im.IMException;
8 | import hudson.plugins.im.IMPublisherDescriptor;
9 | import hudson.plugins.ircbot.IrcPublisher;
10 |
11 | public class IRCConnectionProvider extends IMConnectionProvider {
12 |
13 | private static final IMConnectionProvider INSTANCE = new IRCConnectionProvider();
14 |
15 | public static final synchronized IMConnectionProvider getInstance() {
16 | return INSTANCE;
17 | }
18 |
19 | public static final synchronized void setDesc(IMPublisherDescriptor desc) throws IMException {
20 | INSTANCE.setDescriptor(desc);
21 | INSTANCE.releaseConnection();
22 | }
23 |
24 | @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR",
25 | justification = "Yes we do generally init() ourselves")
26 | private IRCConnectionProvider() {
27 | super();
28 | init();
29 | }
30 |
31 | @Override
32 | public synchronized IMConnection createConnection() throws IMException {
33 | releaseConnection();
34 |
35 | if (getDescriptor() == null) {
36 | throw new IMException("Descriptor not set");
37 | }
38 |
39 | IMConnection imConnection = new IRCConnection((IrcPublisher.DescriptorImpl)getDescriptor(),
40 | getAuthenticationHolder());
41 | if (imConnection.connect()) {
42 | return imConnection;
43 | } else {
44 | imConnection.close();
45 | }
46 | throw new IMException("Connection failed");
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/IRCPrivateChat.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import hudson.plugins.im.IMChat;
4 | import hudson.plugins.im.IMException;
5 | import hudson.plugins.im.IMMessageListener;
6 |
7 | /**
8 | * Representation of an IRC private (user-to-user) chat.
9 | *
10 | * @author kutzi
11 | */
12 | public class IRCPrivateChat implements IMChat {
13 |
14 | private final PircListener listener;
15 | private final String nick;
16 | private final String chatPartner;
17 | private final IRCConnection connection;
18 |
19 | public IRCPrivateChat(IRCConnection connection, PircListener listener, String nick, String chatPartner) {
20 | this.connection = connection;
21 | this.listener = listener;
22 | this.nick = nick;
23 | this.chatPartner = chatPartner;
24 | }
25 |
26 | //@Override
27 | public String getNickName(String senderId) {
28 | return senderId;
29 | }
30 |
31 | //@Override
32 | public String getIMId(String senderId) {
33 | return senderId;
34 | }
35 |
36 | //@Override
37 | public boolean isMultiUserChat() {
38 | return false;
39 | }
40 |
41 | //@Override
42 | public boolean isCommandsAccepted() {
43 | return true;
44 | }
45 |
46 | //@Override
47 | public void addMessageListener(IMMessageListener listener) {
48 | this.listener.addMessageListener(this.nick, this.chatPartner, listener);
49 | }
50 |
51 | //@Override
52 | public void removeMessageListener(IMMessageListener listener) {
53 | this.listener.removeMessageListener(this.nick, listener);
54 | }
55 |
56 | //@Override
57 | public void sendMessage(String message) throws IMException {
58 | this.connection.send(chatPartner, message);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/IRCChannel.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import hudson.plugins.im.IMChat;
4 | import hudson.plugins.im.IMException;
5 | import hudson.plugins.im.IMMessageListener;
6 |
7 | /**
8 | * Representation of an IRC channel.
9 | *
10 | * @author kutzi
11 | */
12 | public class IRCChannel implements IMChat {
13 |
14 | private final String channelName;
15 | private final PircListener listener;
16 | private final IRCConnection connection;
17 | private final boolean commandsAccepted;
18 |
19 | public IRCChannel(String channelName, IRCConnection connection, PircListener listener, boolean commandsAccepted) {
20 | this.channelName = channelName;
21 | this.connection = connection;
22 | this.listener = listener;
23 | this.commandsAccepted = commandsAccepted;
24 | }
25 |
26 | //@Override
27 | public String getNickName(String senderId) {
28 | return senderId;
29 | }
30 |
31 | //@Override
32 | public String getIMId(String senderId) {
33 | return senderId;
34 | }
35 |
36 | //@Override
37 | public boolean isMultiUserChat() {
38 | return true;
39 | }
40 |
41 | //@Override
42 | public boolean isCommandsAccepted() {
43 | return this.commandsAccepted;
44 | }
45 |
46 | //@Override
47 | public void addMessageListener(IMMessageListener listener) {
48 | this.listener.addMessageListener(this.channelName, listener);
49 | }
50 |
51 | //@Override
52 | public void removeMessageListener(IMMessageListener listener) {
53 | this.listener.removeMessageListener(this.channelName, listener);
54 | }
55 |
56 | //@Override
57 | public void sendMessage(String message) throws IMException {
58 | this.connection.send(channelName, message);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/IrcUserProperty.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Created on Dec 21, 2006 2:40:45 PM
3 | *
4 | * Copyright FullSIX
5 | */
6 | package hudson.plugins.ircbot;
7 |
8 | import hudson.Extension;
9 | import hudson.model.User;
10 | import hudson.model.UserPropertyDescriptor;
11 |
12 | import net.sf.json.JSONObject;
13 |
14 | import org.kohsuke.stapler.StaplerRequest;
15 |
16 | /**
17 | * User property to assign an IRC nickname to a Jenkins user.
18 | *
19 | * @author bruyeron (original author)
20 | * @author $Author$ (last change)
21 | * @version $Id: IrcUserProperty.java 23738 2009-11-15 18:36:59Z kutzi $
22 | */
23 | public class IrcUserProperty extends hudson.model.UserProperty {
24 | @Extension
25 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
26 |
27 | private final String nick;
28 |
29 | public IrcUserProperty(String nick) {
30 | this.nick = nick;
31 | }
32 |
33 | public String getNick() {
34 | if (nick != null)
35 | return nick;
36 |
37 | return user.getId();
38 |
39 | }
40 |
41 | /**
42 | * @see hudson.model.Describable#getDescriptor()
43 | */
44 | @Override
45 | public UserPropertyDescriptor getDescriptor() {
46 | return DESCRIPTOR;
47 | }
48 |
49 | public static final class DescriptorImpl extends UserPropertyDescriptor {
50 | public DescriptorImpl() {
51 | super(IrcUserProperty.class);
52 | }
53 |
54 | @Override
55 | public String getDisplayName() {
56 | return "IRC";
57 | }
58 |
59 | @Override
60 | public IrcUserProperty newInstance(User user) {
61 | return new IrcUserProperty(null);
62 | }
63 |
64 | @Override
65 | public IrcUserProperty newInstance(StaplerRequest req, JSONObject formData)
66 | throws FormException {
67 | if (req == null) {
68 | throw new IllegalArgumentException("req must be non null");
69 | }
70 | return new IrcUserProperty(req.getParameter("irc.nick"));
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/IRCColorizer.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import hudson.model.ResultTrend;
4 |
5 | import java.util.regex.Matcher;
6 | import java.util.regex.Pattern;
7 |
8 | import org.pircbotx.Colors;
9 |
10 | /**
11 | * Simple support for IRC colors.
12 | *
13 | * @author syl20bnr
14 | * @author kutzi
15 | */
16 | // See http://flylib.com/books/en/4.74.1.47/1/ for some tips on IRC colors
17 | public class IRCColorizer {
18 |
19 | /**
20 | * Very simple pattern to recognize test results.
21 | */
22 | private static final Pattern TEST_CLASS_PATTERN = Pattern.compile(".*test.*", Pattern.CASE_INSENSITIVE);
23 |
24 | /**
25 | * Colorize the message line if certain keywords are found in it.
26 | *
27 | * @param message
28 | * String content that we want to send
29 | *
30 | * @return
31 | * String markup (maybe with colors) that we would send
32 | */
33 | public static String colorize(String message){
34 |
35 | if(message.contains("Starting ")) {
36 | return message;
37 | } else {
38 | String line = colorForBuildResult(message);
39 | if (line.equals(message)) { // line didn't contain a build result
40 | Matcher m = TEST_CLASS_PATTERN.matcher(message);
41 | if (m.matches()){
42 | return Colors.BOLD + Colors.MAGENTA + line;
43 | }
44 | }
45 | return line;
46 | }
47 | }
48 |
49 | private static String colorForBuildResult(String line) {
50 | for (ResultTrend result : ResultTrend.values()) {
51 |
52 | String keyword = result.getID();
53 |
54 | int index = line.indexOf(keyword);
55 | if (index != -1) {
56 | final String color;
57 | switch (result) {
58 | case FIXED: color = Colors.BOLD + Colors.UNDERLINE + Colors.GREEN; break;
59 | case SUCCESS: color = Colors.BOLD + Colors.GREEN; break;
60 | case FAILURE: color = Colors.BOLD + Colors.UNDERLINE + Colors.RED; break;
61 | case STILL_FAILING: color = Colors.BOLD + Colors.RED; break;
62 | case UNSTABLE: color = Colors.BOLD + Colors.UNDERLINE + Colors.BROWN; break;
63 | case STILL_UNSTABLE: color = Colors.BOLD + Colors.BROWN; break;
64 | case NOW_UNSTABLE: color = Colors.BOLD + Colors.MAGENTA; break;
65 | case ABORTED: color = Colors.BOLD + Colors.LIGHT_GRAY; break;
66 | default: return line;
67 | }
68 |
69 | return line.substring(0, index) + color + keyword + Colors.NORMAL
70 | + line.substring(index + keyword.length(), line.length());
71 | }
72 | }
73 | return line;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/resources/hudson/plugins/ircbot/IrcPublisher/global.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
9 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
49 |
50 |
51 |
53 |
54 |
55 |
57 |
58 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
69 |
70 |
71 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | ${cs}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/main/resources/hudson/plugins/ircbot/IrcNotifyStep/global.jelly:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
9 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
48 |
49 |
50 |
52 |
53 |
54 |
56 |
57 |
58 |
60 |
61 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | ${cs}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/steps/IrcNotifyStep.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.steps;
2 |
3 | /* Snatched one-for-one from Jabber plugin implem by akomakom :
4 | * https://github.com/jenkinsci/jabber-plugin/pull/18/files#diff-c197962302397baf3a4cc36463dce5ea
5 | * and renamed some tokens for IRC plugin symbols.
6 | * Most of the implementation depends on the instmsg plugin changes in PR:
7 | * https://github.com/jenkinsci/instant-messaging-plugin/pull/16
8 | */
9 |
10 | import edu.umd.cs.findbugs.annotations.NonNull;
11 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
12 |
13 | import hudson.Extension;
14 | import hudson.FilePath;
15 | import hudson.Launcher;
16 | import hudson.model.Run;
17 | import hudson.model.TaskListener;
18 | import hudson.plugins.im.MatrixJobMultiplier;
19 | import hudson.plugins.im.NotificationStrategy;
20 | import hudson.plugins.im.build_notify.BuildToChatNotifier;
21 | import hudson.plugins.im.build_notify.DefaultBuildToChatNotifier;
22 | import hudson.plugins.ircbot.IrcPublisher;
23 | import hudson.plugins.ircbot.v2.IRCMessageTargetConverter;
24 | import hudson.util.ListBoxModel;
25 |
26 | import java.util.Arrays;
27 | import java.util.Collections;
28 | import java.util.HashSet;
29 | import java.util.List;
30 | import java.util.Set;
31 |
32 | import org.apache.commons.lang.StringUtils;
33 |
34 | import org.jenkinsci.plugins.workflow.steps.Step;
35 | import org.jenkinsci.plugins.workflow.steps.StepContext;
36 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
37 | import org.jenkinsci.plugins.workflow.steps.StepExecution;
38 | import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution;
39 |
40 | import org.kohsuke.stapler.DataBoundConstructor;
41 | import org.kohsuke.stapler.DataBoundSetter;
42 |
43 | /**
44 | * Pipeline step
45 | */
46 | public class IrcNotifyStep extends Step {
47 |
48 | // NOTE: Bump this number if the class evolves as a breaking change
49 | // (e.g. serializable fields change)
50 | private static final long serialVersionUID = 2;
51 |
52 | private final static char TARGET_SEPARATOR_CHAR = ' ';
53 | private final static IRCMessageTargetConverter CONVERTER = new IRCMessageTargetConverter();
54 |
55 | private String targets;
56 | private boolean notifyOnStart; // Set to true explicitly in an ircNotify step reporting start of build
57 | private boolean notifySuspects;
58 | private boolean notifyCulprits;
59 | private boolean notifyFixers;
60 | private boolean notifyUpstreamCommitters;
61 | private String notificationStrategy = NotificationStrategy.ALL.getDisplayName();
62 | private BuildToChatNotifier buildToChatNotifier = new DefaultBuildToChatNotifier();
63 | private MatrixJobMultiplier matrixNotifier = MatrixJobMultiplier.ONLY_PARENT;
64 |
65 | // Append an additional message to usual notifications about build start/completion
66 | private String extraMessage;
67 |
68 | // Instead of build status messages, send an arbitrary message to specified
69 | // or default (global config) targets with the pipeline step (and ignoring
70 | // the strategy and filtering rules options above)
71 | private String customMessage;
72 |
73 | @Override
74 | public StepExecution start(StepContext context) throws Exception {
75 | return new IrcNotifyStepExecution(this, context);
76 | }
77 |
78 | @DataBoundConstructor
79 | public IrcNotifyStep() {
80 | this.targets = ""; // Notify all channels subscribed via global config
81 | this.notifyOnStart = false;
82 | }
83 |
84 | @DataBoundSetter
85 | public void setTargets(String targets) {
86 | this.targets = targets;
87 | }
88 |
89 | public String getTargets() {
90 | return targets;
91 | }
92 |
93 | public boolean isNotifyOnStart() {
94 | return notifyOnStart;
95 | }
96 |
97 | @DataBoundSetter
98 | public void setNotifyOnStart(boolean notifyOnStart) {
99 | this.notifyOnStart = notifyOnStart;
100 | }
101 |
102 | public boolean isNotifySuspects() {
103 | return notifySuspects;
104 | }
105 |
106 | @DataBoundSetter
107 | public void setNotifySuspects(boolean notifySuspects) {
108 | this.notifySuspects = notifySuspects;
109 | }
110 |
111 | public boolean isNotifyCulprits() {
112 | return notifyCulprits;
113 | }
114 |
115 | @DataBoundSetter
116 | public void setNotifyCulprits(boolean notifyCulprits) {
117 | this.notifyCulprits = notifyCulprits;
118 | }
119 |
120 | public boolean isNotifyFixers() {
121 | return notifyFixers;
122 | }
123 |
124 | @DataBoundSetter
125 | public void setNotifyFixers(boolean notifyFixers) {
126 | this.notifyFixers = notifyFixers;
127 | }
128 |
129 | public boolean isNotifyUpstreamCommitters() {
130 | return notifyUpstreamCommitters;
131 | }
132 |
133 | @DataBoundSetter
134 | public void setNotifyUpstreamCommitters(boolean notifyUpstreamCommitters) {
135 | this.notifyUpstreamCommitters = notifyUpstreamCommitters;
136 | }
137 |
138 | public BuildToChatNotifier getBuildToChatNotifier() {
139 | return buildToChatNotifier;
140 | }
141 |
142 | @DataBoundSetter
143 | public void setBuildToChatNotifier(BuildToChatNotifier buildToChatNotifier) {
144 | this.buildToChatNotifier = buildToChatNotifier;
145 | }
146 |
147 | public MatrixJobMultiplier getMatrixNotifier() {
148 | return matrixNotifier;
149 | }
150 |
151 | @DataBoundSetter
152 | public void setMatrixNotifier(MatrixJobMultiplier matrixNotifier) {
153 | this.matrixNotifier = matrixNotifier;
154 | }
155 |
156 | public String getNotificationStrategy() {
157 | return notificationStrategy;
158 | }
159 |
160 | @DataBoundSetter
161 | public void setNotificationStrategy(String notificationStrategy) {
162 | this.notificationStrategy = notificationStrategy;
163 | }
164 |
165 | public String getExtraMessage() {
166 | return extraMessage;
167 | }
168 |
169 | @DataBoundSetter
170 | public void setExtraMessage(String extraMessage) {
171 | this.extraMessage = extraMessage;
172 | }
173 |
174 | public String getCustomMessage() {
175 | return customMessage;
176 | }
177 |
178 | @DataBoundSetter
179 | public void setCustomMessage(String customMessage) {
180 | this.customMessage = customMessage;
181 | }
182 |
183 | private static class IrcNotifyStepExecution extends SynchronousNonBlockingStepExecution {
184 | // NOTE: Bump this number if the class evolves as a breaking change
185 | // (e.g. serializable fields change)
186 | private static final long serialVersionUID = 2;
187 |
188 | @SuppressFBWarnings({"SE_TRANSIENT_FIELD_NOT_RESTORED"})
189 | private transient final IrcNotifyStep step;
190 |
191 | public IrcNotifyStepExecution(@NonNull IrcNotifyStep step, @NonNull StepContext context) {
192 | super(context);
193 | this.step = step;
194 | }
195 |
196 | @Override
197 | protected Void run() throws Exception {
198 | // getContext().get(TaskListener.class).getLogger().println("IrcNotifyStep: sending message with strategy " + step.notificationStrategy);
199 |
200 | IrcPublisher publisher = new IrcPublisher(
201 | step.targets.equals("")
202 | ? IrcPublisher.DESCRIPTOR.getDefaultTargets()
203 | : CONVERTER.allFromString((List)Arrays.asList(StringUtils.split(step.targets, TARGET_SEPARATOR_CHAR))),
204 | step.notificationStrategy,
205 | step.notifyOnStart,
206 | step.notifySuspects,
207 | step.notifyCulprits,
208 | step.notifyFixers,
209 | step.notifyUpstreamCommitters,
210 | step.buildToChatNotifier,
211 | step.matrixNotifier
212 | );
213 | publisher.setExtraMessage(step.extraMessage);
214 | publisher.setCustomMessage(step.customMessage);
215 |
216 | publisher.perform(
217 | getContext().get(Run.class),
218 | getContext().get(FilePath.class),
219 | getContext().get(Launcher.class),
220 | getContext().get(TaskListener.class));
221 |
222 | return null;
223 | }
224 | }
225 |
226 |
227 | @Extension(optional=true)
228 | public static final class DescriptorImpl extends StepDescriptor {
229 |
230 | @Override
231 | public Set extends Class>> getRequiredContext() {
232 | Set> context = new HashSet<>();
233 | Collections.addAll(context, FilePath.class, Run.class, Launcher.class, TaskListener.class);
234 | return Collections.unmodifiableSet(context);
235 | }
236 |
237 | @Override
238 | public String getFunctionName() {
239 | return "ircNotify";
240 | }
241 |
242 | @Override
243 | public String getDisplayName() {
244 | return "IRC Notification";
245 | }
246 |
247 | public ListBoxModel doFillNotificationStrategyItems() {
248 | ListBoxModel items = new ListBoxModel();
249 | for (NotificationStrategy strategy : NotificationStrategy.values()) {
250 | items.add(strategy.getDisplayName(), strategy.getDisplayName());
251 | }
252 | return items;
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | = IRCBot Plugin
2 |
3 | image:https://img.shields.io/jenkins/plugin/v/ircbot.svg[link="https://plugins.jenkins.io/ircbot" alt="Jenkins Plugin"]
4 | image:https://img.shields.io/github/release/jenkinsci/ircbot-plugin.svg?label=release[link="https://github.com/jenkinsci/ircbot-plugin/releases/latest" alt="GitHub release"]
5 | image:https://img.shields.io/jenkins/plugin/i/ircbot.svg?color=blue[link="https://plugins.jenkins.io/ircbot" alt="Jenkins Plugin Installs"]
6 | image:https://ci.jenkins.io/job/Plugins/job/ircbot-plugin/job/master/badge/icon[link="https://ci.jenkins.io/job/Plugins/job/ircbot-plugin/job/master/" alt="Build Status"]
7 | image:https://img.shields.io/github/license/jenkinsci/ircbot-plugin.svg[link="https://github.com/jenkinsci/ircbot-plugin/blob/master/LICENSE.txt" alt="GitHub license"]
8 | image:https://img.shields.io/maintenance/yes/2025.svg[link="https://github.com/jenkinsci/ircbot-plugin" alt="Maintenance"]
9 |
10 | == Introduction
11 |
12 | This plugin enables Jenkins to send build notífications via IRC and
13 | lets you interact with Jenkins via an IRC bot. Like some other IM protocol
14 | plugins, for most of the common IM capabilities it relies on the
15 | https://github.com/jenkinsci/instant-messaging-plugin[`instant-messaging-plugin`]
16 |
17 | Among many other abilities, it can be used for hands-off notifications
18 | about build status changes with a globally configured IRC channel
19 | subscription, in both legacy freestyle jobs and newly with pipelines
20 | as an `ircNotify` step.
21 |
22 | Interesting usage examples for the pipeline step include:
23 |
24 | * `ircNotify()` to alert default subscribed channels per global config,
25 | using common notification strategies to filter messages about newly
26 | introduced failures and fixes (the empty parenthesis mean to use the
27 | default settings, very simply to code)
28 |
29 | * `ircNotify targets: "username #channelname"` to alert certain recipients
30 | (in particular, certain users via private chats) - note that the list gets
31 | built into the job configuration rather than default subscribed channels
32 |
33 | * `ircNotify customMessage: "Some text", targets: "adminname"` to always
34 | post the specified string
35 |
36 | * Note: due to constraints of the current implementation, please use a
37 | special form of notification for reporting a start of pipeline build:
38 | `ircNotify notifyOnStart:true` otherwise the step is treated as a build
39 | completion notification with a `NOT BUILT` status (before some verdict
40 | becomes available).
41 |
42 | == Installation Requirements
43 |
44 | [[IRCPlugin-Features]]
45 | == Features
46 |
47 | See the required
48 | https://plugins.jenkins.io/instant-messaging/[instant-messaging-plugin]
49 | for a description of protocol-independent features.
50 |
51 | [[IRCPlugin-IRC-specificfeatures]]
52 | === IRC-specific features
53 |
54 | * support password-protected chatrooms
55 | * support NickServ authentication
56 | * the rate at which messages are send is throttled to one every 500ms to
57 | avoid being subject of flood control on the IRC server. +
58 | This rate can be configured with the system
59 | property hudson.plugins.ircbot.messageRate
60 |
61 | [[IRCPlugin-Pipelinesyntaxfeatures]]
62 | === Pipeline syntax features
63 |
64 | Starting with release 2.31 this plugin can be called as a pipeline step.
65 | The same toggles as configurable in a legacy job post-build task can be
66 | specified as named arguments to the pipeline step, with a difference
67 | that they are executed at once.
68 |
69 | [[IRCPlugin-Examplepipelinesteps]]
70 | ==== Example pipeline steps
71 |
72 | Example `ircNotify` pipeline step syntax variants (not exhaustive):
73 |
74 | [source,syntaxhighlighter-pre]
75 | ----
76 | pipeline {
77 | agent any
78 | options {
79 | disableConcurrentBuilds()
80 | skipDefaultCheckout()
81 | }
82 | stages {
83 | stage ('Notify') {
84 | steps {
85 | // Notify a start of build; appends the extra message at the end (note: prefix with separators if needed)
86 | ircNotify notifyOnStart:true, extraMessage: "Running IRCBot test..."
87 | }
88 | }
89 |
90 | stage ('PM') {
91 | steps {
92 | // Post a verbatim custom message; can specify non-default targets (defaults are in global config)
93 | ircNotify targets: "jim #myroom", customMessage: "Hello from IRCBot"
94 | }
95 | }
96 | }
97 | post {
98 | always {
99 | // Notify the verdict only known at end of build; send to default targets by default notification strategy
100 | // ircNotify()
101 | // ...or with a particular strategy:
102 | ircNotify notificationStrategy:"FAILURE_AND_FIXED"
103 | }
104 | }
105 | }
106 | ----
107 |
108 | with an output in the IRC room like the following:
109 |
110 | [source,syntaxhighlighter-pre]
111 | ----
112 | (06:37:03) jenkins2: (notice) Starting build #214 for job GitOrg » reponame » branchname Running IRCBot test... (previous build: SUCCESS)
113 | (06:37:04) jenkins2: (notice) Hello from IRCBot
114 | (06:37:05) jenkins2: (notice) Project GitOrg » reponame » branchname build #214: SUCCESS in 7.2 sec: https://jenkins.localdomain/job/GitOrg/job/reponame/job/branchname/214/
115 | ----
116 |
117 | [[IRCPlugin-Pipelinesteparguments]]
118 | ==== Pipeline step arguments
119 |
120 | The `ircNotify` step optionally accepts any of the following parameters,
121 | and effectively passes them to the
122 | https://plugins.jenkins.io/instant-messaging/[instant-messaging-plugin]
123 | for practical application.
124 |
125 | [width="100%",cols="12%,13%,75%",options="header",]
126 | |===
127 | |argument name |syntax |description
128 | |`targets` |space-separated string |Send the notification (or a
129 | "customMessage") to specified user name(s) and channel name(s), the
130 | latter start with a hash sign.
131 | |`notifyOnStart` |boolean |Set to *true* explicitly in an `ircNotify`
132 | step reporting a start of build, instead of a build completion
133 | |`notifySuspects` |boolean |Select if the (build completion) notification
134 | should alarm the committers to (newly) failed builds
135 | |`notifyCulprits` |boolean |Specifies if culprits - i.e. committers to
136 | previous already failing builds - should be informed about subsequent
137 | build failures.
138 | |`notifyFixers` |boolean |Specifies if 'fixers' should be informed
139 | about builds they fixed.
140 | |`notifyUpstreamCommitters` |boolean |Specifies if upstream
141 | committers should be informed about build failures.
142 | |`extraMessage` |string |Append an additional message to usual
143 | notifications about build start/completion (note: you may want
144 | to start this string with a separator such as a semicolon)
145 | |`customMessage` |string |*Instead* of build status messages, send
146 | an arbitrary message to specified or default (global config) targets
147 | with the pipeline step (and ignoring the strategy and filtering rules
148 | options above)
149 | |`notificationStrategy` |string a|
150 | https://github.com/jenkinsci/instant-messaging-plugin/blob/master/src/main/java/hudson/plugins/im/NotificationStrategy.java
151 |
152 | * `"ALL"` - No matter what, notifications should always be sent.
153 | * `"ANY_FAILURE"` - Whenever there is a failure, a notification should be sent.
154 | * `"FAILURE_AND_FIXED"` - Whenever there is a failure or a failure was fixed,
155 | a notification should be sent.
156 | * `"NEW_FAILURE_AND_FIXED"` - Whenever there is a new failure or a failure was
157 | fixed, a notification should be sent. Similar to `FAILURE_AND_FIXED`, but
158 | repeated failures do not trigger a notification.
159 | * `"STATECHANGE_ONLY"` - Notifications should be sent only if there was a
160 | change in the build state, or this was the first build.
161 | |===
162 |
163 | The following options can be specified, but not sure to what effect and
164 | how (TODO: try in practice and document here):
165 |
166 | [cols=",,",options="header",]
167 | |===
168 | |`argument name` |syntax |description
169 | |`buildToChatNotifier` |class name?
170 | |https://github.com/jenkinsci/instant-messaging-plugin/blob/master/src/main/java/hudson/plugins/im/IMPublisher.java#L88
171 |
172 | |`matrixMultiplier` |string or java/groovy token? a|
173 | * https://github.com/jenkinsci/instant-messaging-plugin/blob/master/src/main/java/hudson/plugins/im/IMPublisher.java#L89
174 | * https://github.com/jenkinsci/instant-messaging-plugin/blob/master/src/main/java/hudson/plugins/im/MatrixJobMultiplier.java
175 |
176 | e.g. per `MatrixJobMultiplier`:
177 |
178 | * `ONLY_CONFIGURATIONS`
179 | * `ONLY_PARENT`
180 | * `ALL`
181 | |===
182 |
183 | [[IRCPlugin-KnownIssues]]
184 | == Known Issues
185 |
186 | Please look into the
187 | http://issues.jenkins-ci.org/secure/IssueNavigator.jspa?mode=hide&reset=true&jqlQuery=project+%3D+JENKINS+AND+status+in+%28Open%2C+%22In+Progress%22%2C+Reopened%29+AND+component+%3D+%27ircbot-plugin%27[issue
188 | tracker] for any open issues for this plugin.
189 |
190 | [[IRCPlugin-DebuggingProblems]]
191 | === Debugging Problems
192 |
193 | If you experience any problems using the plugin please increase the log
194 | level of the logger `hudson.plugins.ircbot` to FINEST (see
195 | https://www.jenkins.io/doc/book/system-administration/viewing-logs/[Logging]), try to
196 | reproduce the problem and attach the collected logs to the JIRA issue.
197 |
198 | If the bot does not respond to your queries in IRC chat, make sure you are using
199 | the correct "Command prefix" if one is set up (see your `$JENKINS_URL/configure` =>
200 | IRC Notification => Advanced), e.g.
201 | ```
202 | !heyJenkins cb
203 | ```
204 | ...and also that you are asking on the channel which is not marked "Notification only",
205 | and not e.g. in a private chat when only a channel connection is configured and you
206 | "Disallow Private Chat" in the Advanced configuration section. Note that per currently
207 | open issues, you may have to use the command prefix in a private chat as well.
208 |
209 | [[IRCPlugin-Changelog]]
210 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.jenkins-ci.plugins
7 | plugin
8 | 5.30
9 |
10 |
11 |
12 | org.jvnet.hudson.plugins
13 | ircbot
14 | hpi
15 | ${revision}.${changelist}
16 |
17 | Jenkins IRC Plugin
18 | A build status publisher that notifies channels on an IRC server, and can accept some commands, using the instant-messaging plugin
19 |
20 | https://github.com/jenkinsci/${project.artifactId}-plugin
21 | 2006
22 |
23 |
24 |
25 | MIT license
26 | https://opensource.org/licenses/MIT
27 | All source code is under the MIT license.
28 |
29 |
30 |
31 |
32 | 3
33 | 999999-SNAPSHOT
34 | jenkinsci/${project.artifactId}-plugin
35 |
36 | 2.479
37 |
38 |
39 | ${jenkins.baseline}.3
40 |
41 | Max
42 | Low
43 | false
44 |
45 |
46 |
47 |
48 |
49 | io.jenkins.tools.bom
50 | bom-${jenkins.baseline}.x
51 | 5054.v620b_5d2b_d5e6
52 | import
53 | pom
54 |
55 |
56 | javax.annotation
57 | javax.annotation-api
58 | 1.3.2
59 |
60 |
61 | org.apache.commons
62 | commons-text
63 | 1.15.0
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.jvnet.hudson.plugins
71 | instant-messaging
72 | 2.875.v954b_d3f33534
73 |
74 |
75 |
76 | org.jenkins-ci
77 | symbol-annotation
78 |
79 |
80 |
81 |
82 |
83 | com.github.pircbotx
84 | pircbotx
85 | 2.3.1
86 |
87 |
88 |
92 |
93 |
94 |
98 |
99 |
100 |
101 |
102 |
104 |
105 |
106 |
107 | compile
108 |
109 |
110 |
111 | org.apache.commons
112 | commons-lang3
113 |
114 |
115 |
116 | org.slf4j
117 | slf4j-api
118 |
119 |
120 |
121 | com.google.guava
122 | guava
123 |
124 |
125 |
126 | commons-codec
127 | commons-codec
128 |
129 |
130 |
131 |
132 |
133 |
134 | org.jenkins-ci.plugins.workflow
135 | workflow-step-api
136 |
137 |
138 |
139 |
140 |
141 |
142 | repo.jenkins-ci.org
143 | https://repo.jenkins-ci.org/public/
144 |
145 |
146 | jenkins-snapshots
147 | https://repo.jenkins-ci.org/content/repositories/snapshots
148 |
149 | true
150 |
151 |
152 |
153 | jenkins-artifactory-snapshots
154 | https://repo.jenkins-ci.org/artifactory/snapshots
155 |
156 | true
157 |
158 |
159 |
160 | jitpack.io
161 | https://jitpack.io
162 |
163 |
164 |
165 |
166 |
167 | jimklimov
168 | Jim Klimov
169 | jimklimov+jenkinsci@gmail.com
170 | 1
171 |
172 |
173 | kutzi
174 | Christoph Kutzinski
175 | kutzi@gmx.de
176 | 1
177 |
178 |
179 |
180 |
181 | scm:git:https://github.com/${gitHubRepo}.git
182 | scm:git:git@github.com:${gitHubRepo}.git
183 | https://github.com/${gitHubRepo}
184 | ${scmTag}
185 |
186 |
187 |
188 |
189 | repo.jenkins-ci.org
190 | https://repo.jenkins-ci.org/public/
191 |
192 |
193 |
194 |
195 |
196 |
197 | maven-checkstyle-plugin
198 | 3.6.0
199 |
200 | ${basedir}/checkstyle.xml
201 | true
202 |
203 | ${project.build.sourceDirectory}
204 |
205 |
206 | ${project.build.testSourceDirectory}
207 |
208 |
209 |
210 |
211 |
212 | check
213 |
214 |
215 |
216 |
217 |
218 | maven-javadoc-plugin
219 |
220 | true
221 |
222 |
223 |
224 | org.jenkins-ci.tools
225 | maven-hpi-plugin
226 |
227 |
228 | FINE
229 |
230 | 2.0
231 |
232 |
233 |
234 |
239 |
265 |
266 |
267 |
268 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/PircListener.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package hudson.plugins.ircbot.v2;
5 |
6 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
7 |
8 | import hudson.plugins.im.IMConnectionListener;
9 | import hudson.plugins.im.IMMessage;
10 | import hudson.plugins.im.IMMessageListener;
11 |
12 | import java.util.List;
13 | import java.util.concurrent.CopyOnWriteArrayList;
14 | import java.util.logging.Logger;
15 |
16 | import org.pircbotx.PircBotX;
17 | import org.pircbotx.exception.DaoException;
18 | import org.pircbotx.hooks.ListenerAdapter;
19 | import org.pircbotx.hooks.events.DisconnectEvent;
20 | import org.pircbotx.hooks.events.InviteEvent;
21 | import org.pircbotx.hooks.events.JoinEvent;
22 | import org.pircbotx.hooks.events.KickEvent;
23 | import org.pircbotx.hooks.events.MessageEvent;
24 | import org.pircbotx.hooks.events.NickAlreadyInUseEvent;
25 | import org.pircbotx.hooks.events.NickChangeEvent;
26 | import org.pircbotx.hooks.events.NoticeEvent;
27 | import org.pircbotx.hooks.events.PartEvent;
28 | import org.pircbotx.hooks.events.PrivateMessageEvent;
29 | import org.pircbotx.hooks.events.ServerResponseEvent;
30 |
31 | /**
32 | * PircBot listener to react to certain IRC events.
33 | *
34 | * @author kutzi (original)
35 | * @author $Author: kutzi $ (last change)
36 | */
37 | @SuppressFBWarnings(value = "DM_STRING_CTOR", justification = "we want a new instance here to enable reference comparison")
38 | public class PircListener extends ListenerAdapter {
39 |
40 | private static final Logger LOGGER = Logger.getLogger(PircListener.class.getName());
41 |
42 | public static final String CHAT_ESTABLISHER = new String("<<>>");
43 |
44 | private final List listeners = new CopyOnWriteArrayList();
45 |
46 | private final List msgListeners = new CopyOnWriteArrayList();
47 |
48 | private final List joinListeners = new CopyOnWriteArrayList();
49 |
50 | private final List inviteListeners = new CopyOnWriteArrayList();
51 |
52 | private final List partListeners = new CopyOnWriteArrayList();
53 |
54 |
55 | volatile boolean explicitDisconnect = false;
56 |
57 |
58 | @java.lang.SuppressWarnings("unused")
59 | private final PircBotX pircBot;
60 | private final String nick;
61 |
62 | public PircListener(PircBotX pircBot, String nick) {
63 | this.pircBot = pircBot;
64 | this.nick = nick;
65 | }
66 |
67 | // /**
68 | // * {@inheritDoc}
69 | // */
70 | // @Override
71 | // protected void handleLine(String line) {
72 | // LOGGER.fine(line);
73 | // super.handleLine(line);
74 | // }
75 |
76 | /**
77 | * {@inheritDoc}
78 | */
79 | @Override
80 | public void onMessage(MessageEvent event) {
81 | for (MessageListener l : this.msgListeners) {
82 | if(l.target.equals(event.getChannel().getName())) {
83 | l.listener.onMessage(new IMMessage(event.getUser().getNick(),
84 | event.getChannel().getName(), event.getMessage()));
85 | }
86 | }
87 | }
88 |
89 | /**
90 | * {@inheritDoc}
91 | */
92 | @Override
93 | public void onPrivateMessage(PrivateMessageEvent event) {
94 | String sender = event.getUser().getNick();
95 | String message = event.getMessage();
96 | if (!sender.equals(CHAT_ESTABLISHER)) {
97 | // Ensure that later the ...getChannel(sender) in IRCConnection.send()
98 | // would not throw exceptions about unknown channel, but would actually
99 | // send a response to this sender. The "bot" here is a PircBotX layer,
100 | // not our instant-messaging-plugin Bot class instance. We get() the
101 | // UserChannelDao a few times here to avoid the hassle of spelling out
102 | // a parameterized class temporary instance.
103 | synchronized (PircListener.class) {
104 | try {
105 | if (null == event.getBot().getUserChannelDao().getChannel(sender)) {
106 | event.getBot().getUserChannelDao().createChannel(sender);
107 | }
108 | } catch (DaoException ignored) {
109 | event.getBot().getUserChannelDao().createChannel(sender);
110 | }
111 | }
112 | }
113 | for (MessageListener l : this.msgListeners) {
114 | if (this.nick.equals(l.target)) {
115 | if (l.sender.equals(CHAT_ESTABLISHER) || sender.equals(l.sender)) {
116 | l.listener.onMessage(new IMMessage(sender, this.nick, message));
117 | }
118 | }
119 | }
120 | }
121 |
122 | /**
123 | * Someone send me a notice. Possibly NickServ after identifying.
124 | */
125 | @Override
126 | public void onNotice(NoticeEvent event) {
127 | String sourceNick = event.getUser().getNick();
128 | String notice = event.getMessage();
129 | LOGGER.info("Notice from " + sourceNick + ": '" + normalize(notice) + "'");
130 | }
131 |
132 | /**
133 | * {@inheritDoc}
134 | */
135 | @Override
136 | public void onJoin(JoinEvent event) {
137 | String sender = event.getUser().getNick();
138 | for (JoinListener l : this.joinListeners) {
139 | if (this.nick.equals(sender)) {
140 | l.channelJoined(event.getChannel().getName());
141 | }
142 | }
143 | }
144 |
145 | @Override
146 | public void onPart(PartEvent event) {
147 | String sender = event.getUser().getNick();
148 | for (PartListener l : this.partListeners) {
149 | if (this.nick.equals(sender)) {
150 | l.channelParted(event.getChannel().getName());
151 | }
152 | }
153 | }
154 |
155 | @Override
156 | public void onKick(KickEvent event) {
157 | String recipientNick = event.getRecipient().getNick();
158 | for(PartListener l : this.partListeners) {
159 | if (this.nick.equals(recipientNick)) {
160 | l.channelParted(event.getChannel().getName());
161 | }
162 | }
163 | }
164 |
165 | @Override
166 | public void onServerResponse(ServerResponseEvent event) {
167 | int code = event.getCode();
168 |
169 | if (code == 433) {
170 | return; // should be handled by onNickAlreadyInUse
171 | }
172 |
173 | if (code >= 400 && code <= 599) {
174 | LOGGER.warning("IRC server responded error " + code + " Message:\n" +
175 | event.getParsedResponse());
176 | }
177 | }
178 |
179 | public void onNickChange(NickChangeEvent event){
180 | LOGGER.info("Nick '" + event.getOldNick() + "' was changed. Now it is used nick '" + event.getNewNick() + "'.");
181 | }
182 |
183 | public void onNickAlreadyInUse(NickAlreadyInUseEvent event){
184 | LOGGER.warning("Nick '" + nick + "' is already in use ");
185 | // String nickservPass = IrcPublisher.DESCRIPTOR.getNickServPassword();
186 | // if(nickservPass!=null){
187 | // LOGGER.info("Nick '" + nick + "' is already in use, trying to regain it.");
188 | // String userservPass = IrcPublisher.DESCRIPTOR.getUserservPassword();
189 | // String userName = IrcPublisher.DESCRIPTOR.getUserName();
190 | // String nick = IrcPublisher.DESCRIPTOR.getNick();
191 | // if(userservPass!=null && userName!=null)
192 | // pircBot.sendIRC().message("USERSERV", "login " + userName + " " + userservPass);
193 | // pircBot.sendIRC().message("NICKSERV", "regain " + nick + " " + nickservPass);
194 | // pircBot.sendIRC().changeNick(nick);
195 | // }
196 | }
197 |
198 | @Override
199 | public void onDisconnect(DisconnectEvent event) {
200 |
201 | if (!explicitDisconnect) {
202 | for (IMConnectionListener l : this.listeners) {
203 | l.connectionBroken(null);
204 | }
205 | }
206 | explicitDisconnect = false;
207 | }
208 |
209 | @Override
210 | public void onInvite(InviteEvent event) {
211 | for (InviteListener listener : inviteListeners) {
212 | listener.inviteReceived(event.getChannel());
213 | }
214 | }
215 |
216 |
217 | // Note that the add/removeXyzListener methods needn't be synchronized because of the CopyOnWriteLists
218 |
219 | public void addConnectionListener(IMConnectionListener listener) {
220 | this.listeners.add(listener);
221 | }
222 |
223 | public void removeConnectionListener(IMConnectionListener listener) {
224 | this.listeners.remove(listener);
225 | }
226 |
227 | public void addMessageListener(String target, IMMessageListener listener) {
228 | this.msgListeners.add(new MessageListener(target, listener));
229 | }
230 |
231 | public void addMessageListener(String target, String sender, IMMessageListener listener) {
232 | this.msgListeners.add(new MessageListener(target, sender, listener));
233 | }
234 |
235 | public void removeMessageListener(String target, IMMessageListener listener) {
236 | this.msgListeners.remove(new MessageListener(target, listener));
237 | }
238 |
239 | public void addJoinListener(JoinListener listener) {
240 | this.joinListeners.add(listener);
241 | }
242 |
243 | public void removeJoinListener(JoinListener listener) {
244 | this.joinListeners.remove(listener);
245 | }
246 |
247 | public void addInviteListener(InviteListener listener) {
248 | this.inviteListeners.add(listener);
249 | }
250 |
251 | public void removeInviteListener(InviteListener listener) {
252 | this.inviteListeners.remove(listener);
253 | }
254 |
255 | public void addPartListener(PartListener listener) {
256 | this.partListeners.add(listener);
257 | }
258 |
259 | public void removePartListener(PartListener listener) {
260 | this.partListeners.remove(listener);
261 | }
262 |
263 | private static final class MessageListener {
264 | private final String target;
265 | private final String sender;
266 | private final IMMessageListener listener;
267 |
268 | public MessageListener(String expectedMessageTarget, IMMessageListener listener) {
269 | this.target = expectedMessageTarget;
270 | this.sender = null;
271 | this.listener = listener;
272 | }
273 |
274 | public MessageListener(String expectedMessageTarget, String expectedMessageSender,
275 | IMMessageListener listener) {
276 | this.target = expectedMessageTarget;
277 | this.sender = expectedMessageSender;
278 | this.listener = listener;
279 | }
280 |
281 | @Override
282 | public int hashCode() {
283 | final int prime = 31;
284 | int result = 1;
285 | result = prime * result
286 | + ((listener == null) ? 0 : listener.hashCode());
287 | result = prime * result
288 | + ((target == null) ? 0 : target.hashCode());
289 | return result;
290 | }
291 |
292 | @Override
293 | public boolean equals(Object obj) {
294 | if (this == obj)
295 | return true;
296 | if (obj == null)
297 | return false;
298 | if (getClass() != obj.getClass())
299 | return false;
300 | MessageListener other = (MessageListener) obj;
301 | if (listener == null) {
302 | if (other.listener != null)
303 | return false;
304 | } else if (!listener.equals(other.listener))
305 | return false;
306 | if (target == null) {
307 | if (other.target != null)
308 | return false;
309 | } else if (!target.equals(other.target))
310 | return false;
311 | return true;
312 | }
313 | }
314 |
315 | /**
316 | * Removes any IRC special characters (I know of. Where is a authorative guide for them??)
317 | * for the message.
318 | *
319 | * http://oreilly.com/pub/h/1953
320 | */
321 | private static String normalize(String ircMessage) {
322 | String msg = ircMessage.replace("\u0001", "");
323 | msg = msg.replace("\u0002", "");
324 | msg = msg.replace("\u0016", "");
325 | msg = msg.replace("\u000F", "");
326 |
327 | return msg;
328 | }
329 |
330 | public interface JoinListener {
331 | /**
332 | * Is called when the ircbot joins a channel.
333 | *
334 | * @param channelName
335 | * Name of the IRC channel to act in
336 | */
337 | void channelJoined(String channelName);
338 | }
339 |
340 | public interface InviteListener {
341 | /**
342 | * Is called when the ircbot is invited to a channel.
343 | *
344 | * @param channelName
345 | * Name of the IRC channel to act in
346 | */
347 | void inviteReceived(String channelName);
348 | }
349 |
350 | public interface PartListener {
351 | /**
352 | * Is called when the ircbot is disconnected (leaves or is kicked) from a channel.
353 | *
354 | * @param channelName
355 | * Name of the IRC channel to act in
356 | */
357 | void channelParted(String channelName);
358 | }
359 |
360 | }
361 |
--------------------------------------------------------------------------------
/CHANGELOG.adoc:
--------------------------------------------------------------------------------
1 | = Changelog
2 |
3 | See [GitHub releases](https://github.com/jenkinsci/ircbot-plugin/releases) for all newer versions.
4 |
5 | [[IRCPlugin-Version-2.43]]
6 | == Version 2.44 (2022-07-21)
7 |
8 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.43...jenkinsci:ircbot-2.44
9 |
10 | * Some more recipe updates for dependency versions
11 |
12 | [[IRCPlugin-Version-2.43]]
13 | == Version 2.43 (2022-04-16)
14 |
15 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.42...jenkinsci:ircbot-2.43
16 |
17 | * Update to new major version of PircBotX 2.1
18 | * Align Guava usage with current Jenkins ecosystem
19 | * Some more recipe updates for dependency versions
20 |
21 | [[IRCPlugin-Version-2.42]]
22 | == Version 2.42 (2021-11-22)
23 |
24 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.41...jenkinsci:ircbot-2.42
25 |
26 | * Some more recipe updates for dependency versions
27 |
28 | [[IRCPlugin-Version-2.41]]
29 | == Version 2.41 (2021-10-03)
30 |
31 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.40...jenkinsci:ircbot-2.41
32 |
33 | * Some more recipe updates for dependency versions
34 | * Decoupling from Jenkins-core Guava version (PR #52, thanks @basil)
35 |
36 | [[IRCPlugin-Version-2.40]]
37 | == Version 2.40 (2021-09-28)
38 |
39 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.39...jenkinsci:ircbot-2.40
40 |
41 | * Some more recipe updates, dependency versions and cosmetic fixes
42 |
43 | [[IRCPlugin-Version-2.39]]
44 | == Version 2.39 (2021-09-08)
45 |
46 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.38...jenkinsci:ircbot-2.39
47 |
48 | * Some more recipe updates, dependency versions and cosmetic fixes
49 |
50 | [[IRCPlugin-Version-2.38]]
51 | == Version 2.38 (2021-09-08)
52 |
53 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.37...jenkinsci:ircbot-2.38
54 |
55 | * Updated build recipes to include dependabot and to keep the `pom.xml`
56 | style common with some other plugins to simplify cross-pollination
57 | * Bumped several dependency versions
58 | * Rectified parts of codebase to match the new quality constraints
59 |
60 | [[IRCPlugin-Version-2.37]]
61 | == Version 2.37 (2021-08-27)
62 |
63 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.36...jenkinsci:ircbot-2.37
64 |
65 | * Remove usages of Guava
66 |
67 | [[IRCPlugin-Version-2.36]]
68 | == Version 2.36 (2020-12-12)
69 |
70 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.35...jenkinsci:ircbot-2.36
71 |
72 | * Revised solution for job configuration UI broken after Jenkins core
73 | 2.264 (dropped HTML tables)
74 | * Modernized saving and processing of job and global configurations
75 | ** https://jenkins.io/security/advisory/2019-04-03/#SECURITY-829 should
76 | now be fixed
77 | ** Plain-text passwords were optionally "scrambled" for some years now,
78 | but in this release the changes done by @timja make use of standard
79 | `secretPassword` fields. To update the on-disk representation of
80 | your IRC user and channel passwords, it should suffice to open and
81 | save your Jenkins global configuration after upgrading the plugin.
82 | ** Note that some field names would be updated in the configuration file
83 | `$JENKINS_HOME/hudson.plugins.ircbot.IrcPublisher.xml` after this
84 | upgrade.
85 | * Changed default IRC nickname for new deployments of the bot to `jenkins`
86 | (from `jenkins-bot`) be in limits set by RFC 2812 (9 chars), and added
87 | notes to the nickname editing fields in the UI (global config of the
88 | server as a client to IRC; individual users' configs to link their IRC
89 | accounts to Jenkins account)
90 |
91 | [[IRCPlugin-Version-2.35]]
92 | [[IRCPlugin-Version-2.34]]
93 | == Version 2.34 and 2.35 (2020-12-05)
94 |
95 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.33...jenkinsci:ircbot-2.35
96 |
97 | * First-shot solution for job configuration UI broken after Jenkins core 2.264 (dropped HTML tables)
98 | * Refresh plugin recipe
99 | * Further docs migration into Git
100 |
101 | [[IRCPlugin-Version-2.33]]
102 | [[IRCPlugin-Version-2.32]]
103 | == Version 2.32 and 2.33 (2020-04-23)
104 |
105 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.31...jenkinsci:ircbot-2.33
106 |
107 | * Fixed use of default targets from global config
108 | * Ported documentation from old Wiki to in-codebase README
109 |
110 | [[IRCPlugin-Version-2.31]]
111 | == Version 2.31 (2019-08-09)
112 |
113 | https://github.com/jenkinsci/ircbot-plugin/compare/ircbot-2.30...jenkinsci:ircbot-2.31
114 |
115 | * Added a Jenkinsfile to CI-test the plugin itself:
116 | https://github.com/jenkinsci/ircbot-plugin/pull/19
117 | * Non-functional whitespace, javadoc and style fixes in the codebase for
118 | better maintainability:
119 | https://github.com/jenkinsci/ircbot-plugin/pull/20
120 | * Added pipeline support to match capabilities of
121 | instant-messaging-plugin release 1.36 (not fully published) and 1.37:
122 | https://github.com/jenkinsci/ircbot-plugin/pull/21 and then 1.38:
123 | https://github.com/jenkinsci/ircbot-plugin/pull/23
124 | ** So now there is an `ircNotify()` step with numerous supported
125 | options (or default activity if none are passed) for `Jenkinsfile`
126 | scripts!
127 | * Added a `customMessage` option to instantly post the specified
128 | message to specified IM target(s) as the payload of a step (not tied
129 | into notification strategies for build start/completion): part of
130 | https://github.com/jenkinsci/ircbot-plugin/pull/21
131 | * Note that currently there is special syntax for build-start
132 | notifications from a pipeline step (e.g. `ircNotify notifyOnStart:true`)
133 | which is intended as a *temporary* solution to the problem of not having
134 | support for an IM-specific `options{...}` setting in pipelines yet, and
135 | so reporting "NOT BUILT" (via completion-notification mode) if there was
136 | no verdict yet
137 |
138 | [[IRCPlugin-Version-2.30]]
139 | == Version 2.30 (2017-08-25)
140 |
141 | * A rapid series of releases (2.28, 2.29, 2.30) led up to addition of
142 | SASL in PircBotX, and some warnings fixes
143 |
144 | [[IRCPlugin-Version-2.27]]
145 | == Version 2.27 (2016-03-03)
146 |
147 | * Bump of PircBotX and other dependencies versions
148 |
149 | [[IRCPlugin-Version-2.26]]
150 | == Version 2.26 (2015-02-19)
151 |
152 | * don't make concurrent builds wait for the previous build (with
153 | instant-messaging-plugin 1.33)
154 | https://issues.jenkins-ci.org/browse/JENKINS-26892[JENKINS-26892]
155 | * make delay between messages configurable via system property
156 | "hudson.plugins.ircbot.messageRate"
157 | * try to connect to NickServ protected up to 2 minutes in case NickServ
158 | is reacting very slowly
159 |
160 | [[IRCPlugin-Version-2.25]]
161 | == Version 2.25 (Apr 2, 2014)
162 |
163 | * Fixed a NullPointerException introduced in 2.24
164 | https://issues.jenkins-ci.org/browse/JENKINS-22478[JENKINS-22478]
165 |
166 | [[IRCPlugin-Version-2.24]]
167 | == Version 2.24 (Mar 29, 2014)
168 |
169 | * Added basic support for SOCKS proxies (thanks Andrew Bonney)
170 | * Fixed: dropped whitespace if IRC colors were used
171 | https://issues.jenkins-ci.org/browse/JENKINS-22360[JENKINS-22360]
172 | (thanks Marius Gedminas)
173 | * Updated to PircBotX 1.9
174 |
175 | [[IRCPlugin-Version-2.23]]
176 | == Version 2.23 (May 22, 2013)
177 |
178 | * new option to disallow bot commands from private chats
179 | * new option to trust self-signed SSL certificates
180 |
181 | [[IRCPlugin-Version-2.22]]
182 | == Version 2.22 (Mar 1, 2013)
183 |
184 | * fixed a problem with reconnects
185 | (https://issues.jenkins-ci.org/browse/JENKINS-17017[JENKINS-17017])
186 | * Update to PircBotX 1.8
187 | (https://code.google.com/p/pircbotx/wiki/ChangeLog#1.8_-_January_11th,_2013)
188 |
189 | [[IRCPlugin-Version-2.21]]
190 | == Version 2.21 (Dec 15, 2012)
191 |
192 | * new option to colorize build notifications based on the build
193 | outcome. +
194 | Note that this feature may change in the future - especially regarding
195 | 'what' is colorized and the colors!
196 | * fixed https://issues.jenkins-ci.org/browse/JENKINS-13697[issue
197 | #13967] (ArrayIndexOutOfBounds Exception when I try to setup a second
198 | IRC channel in Jenkins configuration)
199 |
200 | [[IRCPlugin-Version-2.20]]
201 | == Version 2.20 (Oct 13, 2012)
202 |
203 | * fixed a bug when updating from previous versions on Windows
204 |
205 | [[IRCPlugin-Version-2.19]]
206 | == Version 2.19
207 |
208 | * See https://plugins.jenkins.io/instant-messaging/[instant-messaging
209 | plugin] 1.22 for new features. Also:
210 | * Make IRC login name configurable
211 | https://issues.jenkins-ci.org/browse/JENKINS-14467[JENKINS-14467]
212 | * Update to PircBotX 1.7
213 |
214 | [[IRCPlugin-Version-2.18]]
215 | == Version 2.18
216 |
217 | * fixed: unable to connect to ircu servers
218 | https://issues.jenkins-ci.org/browse/JENKINS-11623[JENKINS-11623]
219 | * See https://plugins.jenkins.io/instant-messaging/[instant-messaging
220 | plugin] 1.21 for more new features
221 |
222 | [[IRCPlugin-Version-2.17]]
223 | == Version 2.17
224 |
225 | skipped
226 |
227 | [[IRCPlugin-Version-2.16]]
228 | == Version 2.16
229 |
230 | * fixed: password authentication not working since 2.14
231 | (https://issues.jenkins-ci.org/browse/JENKINS-10862[JENKINS-10862])
232 |
233 | [[IRCPlugin-Version-2.15]]
234 | == Version 2.15
235 |
236 | * fixed: NickServ password wasn't saved
237 | (https://issues.jenkins-ci.org/browse/JENKINS-10145[JENKINS-10145])
238 |
239 | [[IRCPlugin-Version-2.14]]
240 | == Version 2.14
241 |
242 | * Support SSL connections
243 | (https://issues.jenkins-ci.org/browse/JENKINS-3543[JENKINS-3543])
244 | ** *Attention:* as the underlying IRC library had to be replaced to
245 | achieve this, it's not completely unlikely that you could experience
246 | some regressions. Please open a new issue in that case.
247 | * New option to specify IRC server encoding
248 | (https://issues.jenkins-ci.org/browse/JENKINS-10090[JENKINS-10090])
249 |
250 | [[IRCPlugin-Version-2.13]]
251 | == Version 2.13
252 |
253 | * See
254 | https://wiki.jenkins.io/display/JENKINS/Instant+Messaging+Plugin#InstantMessagingPlugin-Version1.16[Instant-Messaging
255 | plugin 1.16] for new features
256 |
257 | [[IRCPlugin-Version-2.12]]
258 | == Version 2.12
259 |
260 | * See
261 | https://wiki.jenkins.io/display/JENKINS/Instant+Messaging+Plugin#InstantMessagingPlugin-Version1.15[Instant-Messaging
262 | plugin 1.15] for new features
263 |
264 | [[IRCPlugin-Version-2.11]]
265 | == Version 2.11
266 |
267 | * see instant-messaging plugin 1.14 for changes!
268 |
269 | [[IRCPlugin-Version-2.9]]
270 | == Version 2.9
271 |
272 | * wait 5 seconds after identifying with NickServ before trying to join
273 | channels. Should minimize problems if channels are restricted and the
274 | NickServ identification isn't fast enough before the bot tries to join
275 | the channels. Refs.
276 | http://issues.jenkins-ci.org/browse/JENKINS-6600[JENKINS-6600] ,
277 | http://issues.jenkins-ci.org/browse/JENKINS-8451[JENKINS-8451]
278 |
279 | [[IRCPlugin-Version-2.8]]
280 | == Version 2.8
281 |
282 | * fixed: NullPointerException because of incorrect migration of old
283 | configurations.
284 | http://issues.jenkins-ci.org/browse/JENKINS-8001[JENKINS-8001]
285 | * new feature: new chat notifier which prints the failing tests, too
286 | http://issues.jenkins-ci.org/browse/JENKINS-7035[JENKINS-7035]
287 |
288 | [[IRCPlugin-Version-2.7]]
289 | == Version 2.7
290 |
291 | * improvement: bot commands are now extensible and open for other
292 | plugins (see class BotCommand).
293 | * improvement: added an extension point to customize the message the bot
294 | sends to chats for notification (see class BuildToChatNotifier).
295 | * improvement: bot may be invited to channels
296 | (http://issues.jenkins-ci.org/browse/JENKINS-6600[issue 6600] )
297 |
298 | [[IRCPlugin-Version-2.6]]
299 | == Version 2.6
300 |
301 | * fixed: disconnects (and no reconnects) when changing the global config
302 | (http://issues.jenkins-ci.org/browse/JENKINS-6933[issue #6933])
303 | * improved behaviour when plugin is disabled. I.e. doesn't log
304 | unnecessary stuff.
305 | * fixed: plugins configure option not visible
306 | http://issues.jenkins-ci.org/browse/JENKINS-5978[JENKINS-5978]
307 | http://issues.jenkins-ci.org/browse/JENKINS-5233[JENKINS-5233]
308 | * use UTF-8 as encoding for sending/receiving messages (previously used
309 | default encoding of the Hudson server)
310 |
311 | [[IRCPlugin-Version-2.5]]
312 | == Version 2.5
313 |
314 | * fixed: _notify upstream commiter_ would have notified committers of
315 | 'old' builds
316 | (http://issues.jenkins-ci.org/browse/JENKINS-6712[JENKINS-6712])
317 | * improvement: print useful project names for matrix jobs
318 | (http://issues.jenkins-ci.org/browse/JENKINS-6560[JENKINS-6560] )
319 | * fixed: don't delay Hudson startup
320 | (http://issues.jenkins-ci.org/browse/JENKINS-4346[JENKINS-4346] )
321 | * feature: _userstat_ command for bot
322 | (http://issues.jenkins-ci.org/browse/JENKINS-6147[JENKINS-6147] )
323 | * fixed: don't count offline computer for the executors count
324 | (http://issues.jenkins-ci.org/browse/JENKINS-6387[JENKINS-6387])
325 |
326 | [[IRCPlugin-Version-2.4]]
327 | == Version 2.4
328 |
329 | * fixed: bot output sometimes send to wrong user
330 | (http://issues.jenkins-ci.org/browse/JENKINS-6484[JENKINS-6484])
331 |
332 | [[IRCPlugin-Version-2.3]]
333 | == Version 2.3
334 |
335 | * allow to pass build parameters with the _build_ command
336 | (http://issues.jenkins-ci.org/browse/JENKINS-5058[JENKINS-5058] ) *Make
337 | sure that instant-messaging 1.7 or later is installed.*
338 | * allow to set NickServ passwords
339 |
340 | [[IRCPlugin-Version-2.2]]
341 | == Version 2.2
342 |
343 | * support password-protected chatrooms
344 |
345 | [[IRCPlugin-Version-2.1]]
346 | == Version 2.1
347 |
348 | * new option to inform upstream committers
349 | (http://issues.jenkins-ci.org/browse/JENKINS-4629[JENKINS-4629] )
350 | * Bot uses /msg command to inform channels/users instead of /notice as
351 | before. You can restore the old behaviour in the global configuration.
352 | (http://issues.jenkins-ci.org/browse/JENKINS-5087[JENKINS-5087] )
353 |
354 | [[IRCPlugin-Version-2.0]]
355 | == Version 2.0
356 |
357 | * This is the first version which is build upon the _instant-messaging_
358 | plugin. *Make sure that instant-messaging 1.3 is installed.*
359 | * *This version needs Hudson 1.319 or newer*
360 | * Though much care has been taken to migrate settings from previous
361 | versions, because of the amount of the changes it cannot be guaranteed
362 | that all old settings are migrated correctly!
363 | * This version supports all options that the Jabber plugin supports. See
364 | https://wiki.jenkins.io/pages/viewpage.action?pageId=753770#[there] for
365 | more info.
366 | * Command responses are no longer send as private messages to the user.
367 | Instead they are send to the channel. If you want private messages then
368 | send the command as a private message to the bot.
369 |
370 | [[IRCPlugin-Usage]]
371 | == Usage
372 |
373 | When you install this plugin, your Hudson configuration page gets
374 | additional "IRC Notification" option as illustrated below: +
375 | [.confluence-embedded-file-wrapper]#image:docs/images/hudson-irc.PNG[image]# +
376 | +
377 | In addition, each project should add a "Post-build Actions"> "IRC
378 | Notification" configuration as illustrated below: +
379 | +
380 | [.confluence-embedded-file-wrapper]#image:docs/images/hudson-irc-project.PNG[image]# +
381 | +
382 | For the project configuration, leave the Channels blank to default to
383 | the channels defined in the controller IRC configration.
384 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/v2/IRCConnection.java:
--------------------------------------------------------------------------------
1 | package hudson.plugins.ircbot.v2;
2 |
3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4 |
5 | import hudson.Util;
6 | import hudson.plugins.im.AuthenticationHolder;
7 | import hudson.plugins.im.GroupChatIMMessageTarget;
8 | import hudson.plugins.im.IMConnection;
9 | import hudson.plugins.im.IMConnectionListener;
10 | import hudson.plugins.im.IMException;
11 | import hudson.plugins.im.IMMessage;
12 | import hudson.plugins.im.IMMessageListener;
13 | import hudson.plugins.im.IMMessageTarget;
14 | import hudson.plugins.im.IMPresence;
15 | import hudson.plugins.im.bot.Bot;
16 | import hudson.plugins.im.tools.ExceptionHelper;
17 | import hudson.plugins.ircbot.IrcPublisher.DescriptorImpl;
18 | import hudson.plugins.ircbot.v2.PircListener.InviteListener;
19 | import hudson.plugins.ircbot.v2.PircListener.JoinListener;
20 | import hudson.plugins.ircbot.v2.PircListener.PartListener;
21 |
22 | import java.net.Proxy;
23 | import java.nio.charset.Charset;
24 | import java.util.Collections;
25 | import java.util.HashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.Set;
29 | import java.util.concurrent.CountDownLatch;
30 | import java.util.concurrent.TimeUnit;
31 | import java.util.logging.Level;
32 | import java.util.logging.Logger;
33 | import java.util.stream.Collectors;
34 |
35 | import javax.net.SocketFactory;
36 | import javax.net.ssl.SSLSocketFactory;
37 |
38 | import org.pircbotx.Channel;
39 | import org.pircbotx.Configuration;
40 | import org.pircbotx.Configuration.Builder;
41 | import org.pircbotx.PircBotX;
42 | import org.pircbotx.ProxySocketFactory;
43 | import org.pircbotx.UtilSSLSocketFactory;
44 | import org.pircbotx.cap.SASLCapHandler;
45 | import org.pircbotx.delay.StaticReadonlyDelay;
46 | import org.pircbotx.exception.NotReadyException;
47 | import org.pircbotx.hooks.ListenerAdapter;
48 | import org.pircbotx.hooks.events.ConnectEvent;
49 |
50 | import static java.util.logging.Level.WARNING;
51 |
52 | /**
53 | * IRC specific implementation of an {@link IMConnection}.
54 | *
55 | * @author kutzi
56 | */
57 | public class IRCConnection implements IMConnection, JoinListener, InviteListener, PartListener {
58 |
59 | private static final Logger LOGGER = Logger.getLogger(IRCConnection.class.getName());
60 |
61 | private final DescriptorImpl descriptor;
62 | private final AuthenticationHolder authentication;
63 |
64 |
65 | private Thread botThread;
66 |
67 | private final Builder cfg;
68 | private volatile PircBotX pircConnection;
69 | private final PircListener listener;
70 |
71 | private List groupChats;
72 |
73 | private final Map bots = new HashMap();
74 |
75 | private final Map privateChats = new HashMap();
76 |
77 |
78 | @SuppressFBWarnings(value={"UR_UNINIT_READ"},
79 | justification="UR_UNINIT_READ: TODO: this is probably a genuine problem but I don't know why; MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR: Need ecosystem change to separate IRCConnection construction from PircListener connection")
80 | public IRCConnection(DescriptorImpl descriptor, AuthenticationHolder authentication) {
81 | Builder config = new Configuration.Builder();
82 |
83 | // TODO: setVerbose is gone in 2.x - or is it default now?
84 | // if (LOGGER.isLoggable(Level.FINEST)) {
85 | // this.pircConnection.setVerbose(true);
86 | // }
87 | this.descriptor = descriptor;
88 | this.authentication = authentication;
89 |
90 | if (descriptor.getDefaultTargets() != null) {
91 | this.groupChats = descriptor.getDefaultTargets();
92 | } else {
93 | this.groupChats = Collections.emptyList();
94 | }
95 |
96 | config.setServerHostname(descriptor.getHost());
97 | config.setServerPort(descriptor.getPort());
98 | if (this.descriptor.isSasl()) {
99 | LOGGER.info("Enabling SASL");
100 | config.setCapEnabled(true);
101 | config.addCapHandler(new SASLCapHandler(this.descriptor.getLogin(), this.descriptor.getSecretPassword().getPlainText()));
102 | } else {
103 | if (this.descriptor.getSecretPassword() != null) {
104 | String password = Util.fixEmpty(this.descriptor.getSecretPassword().getPlainText());
105 | if (password != null) {
106 | config.setServerPassword(password);
107 | }
108 | }
109 | }
110 |
111 | if (this.descriptor.getSecretNickServPassword() != null) {
112 | final String nickServPassword = Util.fixEmpty(this.descriptor.getSecretNickServPassword().getPlainText());
113 | if (nickServPassword != null) {
114 | config.setNickservPassword(nickServPassword);
115 | }
116 | }
117 |
118 |
119 | String socksHost = Util.fixEmpty(this.descriptor.getSocksHost());
120 |
121 | final SocketFactory sf;
122 | if (this.descriptor.isSsl()) {
123 | if (this.descriptor.isTrustAllCertificates()) {
124 | sf = new UtilSSLSocketFactory().trustAllCertificates();
125 | } else {
126 | sf = SSLSocketFactory.getDefault();
127 | }
128 | } else if (socksHost != null && this.descriptor.getSocksPort() > 0) {
129 | sf = new ProxySocketFactory(Proxy.Type.SOCKS, this.descriptor.getSocksHost(), this.descriptor.getSocksPort());
130 | } else {
131 | sf = SocketFactory.getDefault();
132 | }
133 | config.setSocketFactory(sf);
134 |
135 |
136 | config.setLogin(this.descriptor.getLogin());
137 | config.setName(this.descriptor.getNick());
138 | config.setMessageDelay(new StaticReadonlyDelay(this.descriptor.getMessageRate()));
139 | config.setEncoding(Charset.forName(this.descriptor.getCharset()));
140 |
141 | this.listener = new PircListener(this.pircConnection, this.descriptor.getNick());
142 | // MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR
143 | // https://spotbugs.readthedocs.io/en/stable/bugDescriptions.html
144 | // Overridable method is called from constructor
145 | // It may also leak the "this" reference of the partially constructed object.
146 | this.listener.addJoinListener(this);
147 | this.listener.addInviteListener(this);
148 | this.listener.addPartListener(this);
149 |
150 |
151 | listener.addMessageListener(this.descriptor.getNick(),
152 | PircListener.CHAT_ESTABLISHER, new ChatEstablishedListener());
153 |
154 | config.addListener(listener);
155 |
156 | config.setAutoNickChange(false);
157 |
158 | // we're still handling reconnection logic by ourself. Maybe not a good idea in the long run...
159 | config.setAutoReconnect(false);
160 |
161 | cfg = config;
162 | }
163 |
164 | //@Override
165 | public void close() {
166 | this.listener.explicitDisconnect = true;
167 |
168 | // if (this.pircConnection != null) {
169 | // if (this.pircConnection.isConnected()) {
170 | // this.listener.removeJoinListener(this);
171 | // this.listener.removePartListener(this);
172 | // this.listener.removeInviteListener(this);
173 | //
174 | // this.pircConnection.disconnect();
175 | // }
176 | //
177 | // // Perform a proper shutdown, also freeing all the resources (input-/output-thread)
178 | // // Note that with PircBotx 2.x the threads are gone and we can maybe simplify this
179 | // this.pircConnection.shutdown(true);
180 | // }
181 |
182 | if (botThread != null) {
183 | this.botThread.interrupt();
184 | }
185 | }
186 |
187 | //@Override
188 | public boolean isConnected() {
189 | return this.pircConnection != null && this.pircConnection.isConnected();
190 | }
191 |
192 | //@Override
193 | @SuppressFBWarnings(value = "SIC_INNER_SHOULD_BE_STATIC_ANON",
194 | justification = "Commented below, no idea how to solve")
195 | public boolean connect() {
196 | try {
197 |
198 | LOGGER.info(String.format("Connecting to %s:%s as %s using charset %s",
199 | this.descriptor.getHost(), this.descriptor.getPort(), this.descriptor.getNick(), this.descriptor.getCharset()));
200 |
201 |
202 | if (botThread != null) {
203 | botThread.interrupt();
204 | }
205 |
206 | final CountDownLatch connectLatch = new CountDownLatch(1);
207 |
208 | // SIC_INNER_SHOULD_BE_STATIC_ANON.... whatever that means:
209 | // The class hudson.plugins.im.build_notify.PrintFailingTestsBuildToChatNotifier$1
210 | // could be refactored into a named _static_ inner class.
211 | ListenerAdapter connectListener = new ListenerAdapter() {
212 |
213 | @Override
214 | public void onConnect(ConnectEvent event)
215 | throws Exception {
216 | connectLatch.countDown();
217 |
218 | LOGGER.info("connected to IRC");
219 | }
220 | };
221 | cfg.addListener(connectListener);
222 |
223 | botThread = new Thread("IRC Bot") {
224 | public void run() {
225 | pircConnection = new PircBotX(cfg.buildConfiguration());
226 | try {
227 | pircConnection.startBot();
228 | } catch (Exception e) {
229 | LOGGER.warning("Error connecting to irc: " + e);
230 | }
231 | }
232 | };
233 | botThread.start();
234 |
235 | try {
236 | boolean connected = connectLatch.await(2, TimeUnit.MINUTES);
237 |
238 | if (!connected) {
239 | LOGGER.warning("Time out waiting for connecting to irc");
240 | close();
241 | return false;
242 | }
243 | } catch (InterruptedException e) {
244 | LOGGER.warning("Interrupted waiting for connecting to irc: " + e);
245 | Thread.currentThread().interrupt();
246 | }
247 |
248 | pircConnection.getConfiguration().getListenerManager().removeListener(connectListener);
249 |
250 |
251 |
252 | // final String nickServPassword = this.descriptor.getNickServPassword();
253 | // if(Util.fixEmpty(nickServPassword) != null) {
254 | // this.pircConnection.identify(nickServPassword);
255 | //
256 | // if (!this.groupChats.isEmpty()) {
257 | // // Sleep some time so chances are good we're already identified
258 | // // when we try to join the channels.
259 | // // Unfortunately there seems to be no standard way in IRC to recognize
260 | // // if one has been identified already.
261 | // LOGGER.fine("Sleeping some time to wait for being authenticated");
262 | // try {
263 | // Thread.sleep(TimeUnit.SECONDS.toMillis(5));
264 | // } catch (InterruptedException e) {
265 | // // ignore
266 | // }
267 | // }
268 | // }
269 |
270 | joinGroupChats();
271 |
272 | return pircConnection.isConnected();
273 | } catch (RuntimeException e) {
274 | LOGGER.log(WARNING, "Error connecting to irc", e);
275 | return false;
276 | }
277 | }
278 |
279 | private void joinGroupChats() {
280 |
281 | long startTime = System.currentTimeMillis();
282 | long timeout = TimeUnit.MINUTES.toMillis(2);
283 |
284 | // (Re-)try connecting to channels until timeout of 2 minutes is reached.
285 | // This is because we might not be connected to nickserv, yet, even with the sleep of 5 seconds, we've done earlier
286 | Exception ex = null;
287 | while ((System.currentTimeMillis() - startTime) < timeout) {
288 |
289 | for (IMMessageTarget groupChat : this.groupChats) {
290 | try {
291 | joinGroupChat(groupChat);
292 | } catch (Exception e) {
293 | LOGGER.warning("Unable to connect to channel '" + groupChat + "'.\n"
294 | + "Message: " + ExceptionHelper.dump(e));
295 | // I we got here something big is broken and we shouldn't continue trying to connect
296 | ex = e;
297 | break;
298 | }
299 | }
300 |
301 | try {
302 | Thread.sleep(TimeUnit.SECONDS.toMillis(5));
303 | } catch (InterruptedException e) {
304 | // ignore
305 | }
306 |
307 | if (areWeConnectedToAllChannels()) {
308 | break;
309 | }
310 |
311 | LOGGER.info("Still not connected to all channels. Retrying.");
312 | }
313 |
314 | if (ex == null && !areWeConnectedToAllChannels()) {
315 | LOGGER.warning("Still not connected to all channels after " + timeout + " minutes. Giving up.");
316 | }
317 | }
318 |
319 | private boolean areWeConnectedToAllChannels() {
320 | Set groupChatNames =
321 | this.groupChats.stream()
322 | .map(GroupChatIMMessageTarget.class::cast)
323 | .map(GroupChatIMMessageTarget::getName)
324 | .collect(Collectors.toSet());
325 |
326 | Set connectedToChannels =
327 | this.pircConnection.getUserChannelDao().getAllChannels().stream()
328 | .map(Channel::getName)
329 | .collect(Collectors.toSet());
330 |
331 | return groupChatNames.equals(connectedToChannels);
332 | }
333 |
334 | private GroupChatIMMessageTarget getGroupChatForChannelName(String channelName) {
335 | for (IMMessageTarget messageTarget : groupChats) {
336 | if (!(messageTarget instanceof GroupChatIMMessageTarget)) {
337 | continue;
338 | }
339 | GroupChatIMMessageTarget groupChat = (GroupChatIMMessageTarget) messageTarget;
340 | if (groupChat.getName().equals(channelName)) {
341 | return groupChat;
342 | }
343 | }
344 | return null;
345 | }
346 |
347 | private void joinGroupChat(IMMessageTarget groupChat) {
348 | if (! (groupChat instanceof GroupChatIMMessageTarget)) {
349 | LOGGER.warning(groupChat + " is no channel. Cannot join.");
350 | return;
351 | }
352 |
353 | GroupChatIMMessageTarget channel = (GroupChatIMMessageTarget)groupChat;
354 | LOGGER.info("Trying to join channel " + channel.getName());
355 |
356 | if (channel.hasPassword()) {
357 | this.pircConnection.sendIRC().joinChannel(channel.getName(), channel.getSecretPassword().getPlainText());
358 | } else {
359 | this.pircConnection.sendIRC().joinChannel(channel.getName());
360 | }
361 | }
362 |
363 | //@Override
364 | public void channelJoined(String channelName) {
365 | GroupChatIMMessageTarget groupChat = getGroupChatForChannelName(channelName);
366 | if (groupChat == null) {
367 | LOGGER.log(Level.INFO, "Joined to channel {0} but I don't seem to belong here", channelName);
368 | return;
369 | }
370 | Bot bot = new Bot(new IRCChannel(channelName, this, this.listener, !groupChat.isNotificationOnly()),
371 | this.descriptor.getNick(), this.descriptor.getHost(),
372 | this.descriptor.getCommandPrefix(), this.authentication);
373 | bots.put(channelName, bot);
374 | LOGGER.log(Level.INFO, "Joined channel {0} and bot registered", channelName);
375 | }
376 |
377 | //@Override
378 | public void inviteReceived(String channelName) {
379 | GroupChatIMMessageTarget groupChat = getGroupChatForChannelName(channelName);
380 | if (groupChat == null) {
381 | LOGGER.log(Level.INFO, "Invited to channel {0} but I don't seem to belong here", channelName);
382 | return;
383 | }
384 | LOGGER.log(Level.INFO, "Invited to join {0}", channelName);
385 | joinGroupChat(groupChat);
386 | }
387 |
388 | //@Override
389 | public void channelParted(String channelName) {
390 | GroupChatIMMessageTarget groupChat = getGroupChatForChannelName(channelName);
391 | if (groupChat == null) {
392 | LOGGER.log(Level.INFO, "I'm leaving {0} but I never seemed to belong there in the first place", channelName);
393 | return;
394 | }
395 | if (bots.containsKey(channelName)) {
396 | Bot bot = bots.remove(channelName);
397 | bot.shutdown();
398 | LOGGER.log(Level.INFO, "I have left {0}", channelName);
399 | } else {
400 | LOGGER.log(Level.INFO, "No bot ever registered for {0}", channelName);
401 | }
402 | }
403 |
404 | //@Override
405 | public void addConnectionListener(IMConnectionListener listener) {
406 | this.listener.addConnectionListener(listener);
407 | }
408 |
409 | //@Override
410 | public void removeConnectionListener(IMConnectionListener listener) {
411 | this.listener.removeConnectionListener(listener);
412 | }
413 |
414 | //@Override
415 | public void send(IMMessageTarget target, String text) throws IMException {
416 | send(target.toString(), text);
417 | }
418 |
419 | public void send(String target, String text) throws IMException {
420 | Channel channel = this.pircConnection.getUserChannelDao().getChannel(target);
421 |
422 | boolean useColors = this.descriptor.isUseColors();
423 | if (useColors) {
424 | String mode;
425 | try {
426 | mode = channel.getMode();
427 | } catch (NotReadyException e) {
428 | throw new IMException(e);
429 | }
430 | if (mode.contains("c")) {
431 | LOGGER.warning("Bot is configured to use colors, but channel " + target + " disallows colors!");
432 | useColors = false;
433 | }
434 | }
435 |
436 | // IRC doesn't support multiline messages (see http://stackoverflow.com/questions/7039478/linebreak-irc-protocol)
437 | // therefore we split the message on line breaks and send each line as its own message:
438 | String[] lines = text.split("\\r?\\n|\\r");
439 | for (String line : lines) {
440 | if (useColors){
441 | line = IRCColorizer.colorize(line);
442 | }
443 | if (this.descriptor.isUseNotice()) {
444 | this.pircConnection.sendIRC().notice(target, line);
445 | } else {
446 | this.pircConnection.sendIRC().message(target, line);
447 | }
448 | }
449 | }
450 |
451 | //@Override
452 | public void setPresence(IMPresence presence, String statusMessage)
453 | throws IMException {
454 | if (presence.ordinal() >= IMPresence.OCCUPIED.ordinal()) {
455 | if (statusMessage == null || statusMessage.trim().length() == 0) {
456 | statusMessage = "away";
457 | }
458 | this.pircConnection.sendRaw().rawLineNow("AWAY " + statusMessage);
459 | } else {
460 | this.pircConnection.sendRaw().rawLineNow("AWAY");
461 | }
462 | }
463 |
464 | /**
465 | * Listens for chat requests from singular users (i.e. private chat requests).
466 | * Creates a new bot for each request, if we're not already in a chat with
467 | * that user.
468 | */
469 | private class ChatEstablishedListener implements IMMessageListener {
470 |
471 | //@Override
472 | public void onMessage(IMMessage message) {
473 | if(descriptor.isDisallowPrivateChat()) {
474 | // ignore private chat, if disallow private chat commands.
475 | return;
476 | }
477 |
478 | if(!message.getTo().equals(descriptor.getNick())) {
479 | throw new IllegalStateException("Intercepted message to '" + message.getTo()
480 | + "'. That shouldn't happen!");
481 | }
482 |
483 | synchronized (privateChats) {
484 | if (privateChats.containsKey(message.getFrom())) {
485 | // ignore. We're already in a chat with partner
486 | return;
487 | }
488 |
489 | IRCPrivateChat chat = new IRCPrivateChat(
490 | IRCConnection.this, listener,
491 | descriptor.getUserName(), message.getFrom());
492 | Bot bot = new Bot(
493 | chat,
494 | descriptor.getNick(), descriptor.getHost(),
495 | descriptor.getCommandPrefix(), authentication,
496 | false // Tell the bot to accept all messages, not requiring a prefix (still parsing it away if present)
497 | );
498 |
499 | privateChats.put(message.getFrom(), bot);
500 |
501 | // we must replay this message as it could contain a command
502 | bot.onMessage(message);
503 | }
504 | }
505 | }
506 | }
507 |
--------------------------------------------------------------------------------
/src/main/java/hudson/plugins/ircbot/IrcPublisher.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Created on Dec 6, 2006 9:25:19 AM
3 | */
4 | package hudson.plugins.ircbot;
5 |
6 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
7 |
8 | import hudson.Extension;
9 | import hudson.Util;
10 | import hudson.model.AbstractProject;
11 | import hudson.model.User;
12 | import hudson.plugins.im.GroupChatIMMessageTarget;
13 | import hudson.plugins.im.IMConnection;
14 | import hudson.plugins.im.IMException;
15 | import hudson.plugins.im.IMMessageTarget;
16 | import hudson.plugins.im.IMMessageTargetConverter;
17 | import hudson.plugins.im.IMPublisher;
18 | import hudson.plugins.im.IMPublisherDescriptor;
19 | import hudson.plugins.im.MatrixJobMultiplier;
20 | import hudson.plugins.im.NotificationStrategy;
21 | import hudson.plugins.im.build_notify.BuildToChatNotifier;
22 | import hudson.plugins.im.config.ParameterNames;
23 | import hudson.plugins.im.tools.ExceptionHelper;
24 | import hudson.plugins.ircbot.v2.IRCConnectionProvider;
25 | import hudson.plugins.ircbot.v2.IRCMessageTargetConverter;
26 | import hudson.tasks.BuildStepDescriptor;
27 | import hudson.tasks.Publisher;
28 | import hudson.util.Scrambler;
29 | import hudson.util.Secret;
30 |
31 | import java.nio.charset.Charset;
32 | import java.util.ArrayList;
33 | import java.util.Collections;
34 | import java.util.List;
35 | import java.util.SortedMap;
36 | import java.util.logging.Logger;
37 |
38 | import net.sf.json.JSONArray;
39 | import net.sf.json.JSONObject;
40 |
41 | import org.apache.commons.lang.StringUtils;
42 |
43 | import org.kohsuke.stapler.StaplerRequest;
44 |
45 | /**
46 | * Publishes build results to IRC channels.
47 | *
48 | * @author bruyeron (original author)
49 | * @author $Author: kutzi $ (last change)
50 | * @version $Id: IrcPublisher.java 39408 2011-05-01 10:52:54Z kutzi $
51 | */
52 | public class IrcPublisher extends IMPublisher {
53 |
54 | private static final Logger LOGGER = Logger.getLogger(IrcPublisher.class.getName());
55 |
56 | /**
57 | * Descriptor should be singleton.
58 | */
59 | @Extension
60 | public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
61 |
62 | private static final IMMessageTargetConverter CONVERTER = new IRCMessageTargetConverter();
63 |
64 | /**
65 | * channels to notify with build status If not empty, this replaces the main
66 | * channels defined at the descriptor level.
67 | * @deprecated only used to deserialize old instances. please use {@link #getNotificationTargets()}
68 | */
69 | @SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
70 | @Deprecated
71 | public List channels = new ArrayList();
72 |
73 | public IrcPublisher(List defaultTargets, String notificationStrategy,
74 | boolean notifyGroupChatsOnBuildStart,
75 | boolean notifySuspects,
76 | boolean notifyCulprits,
77 | boolean notifyFixers,
78 | boolean notifyUpstreamCommitters,
79 | BuildToChatNotifier buildToChatNotifier,
80 | MatrixJobMultiplier matrixMultiplier)
81 | {
82 | super(defaultTargets, notificationStrategy, notifyGroupChatsOnBuildStart,
83 | notifySuspects, notifyCulprits, notifyFixers, notifyUpstreamCommitters,
84 | buildToChatNotifier, matrixMultiplier);
85 | }
86 |
87 | /**
88 | * @see hudson.model.Describable#getDescriptor()
89 | */
90 | @Override
91 | public BuildStepDescriptor getDescriptor() {
92 | return DESCRIPTOR;
93 | }
94 |
95 | // from IMPublisher:
96 | @Override
97 | protected String getConfiguredIMId(User user) {
98 | IrcUserProperty ircUserProperty = (IrcUserProperty) user.getProperties().get(IrcUserProperty.DESCRIPTOR);
99 | if (ircUserProperty != null) {
100 | return ircUserProperty.getNick();
101 | }
102 | return null;
103 | }
104 |
105 | @Override
106 | protected IMConnection getIMConnection() throws IMException {
107 | return IRCConnectionProvider.getInstance().currentConnection();
108 | }
109 |
110 | @Override
111 | protected String getPluginName() {
112 | return "IRC notifier plugin";
113 | }
114 |
115 | // deserialize/migrate old instances
116 | @SuppressWarnings("deprecation")
117 | protected Object readResolve() {
118 | super.readResolve();
119 | if (this.getNotificationTargets() == null) {
120 | if (this.channels != null) {
121 | List targets = new ArrayList(this.channels.size());
122 | for (String channel : channels) {
123 | targets.add(new GroupChatIMMessageTarget(channel));
124 | }
125 | setNotificationTargets(targets);
126 | } else {
127 | setNotificationTargets(Collections.emptyList());
128 | }
129 | }
130 | this.channels = null;
131 |
132 | if (getNotificationStrategy() == null) {
133 | // set to the only available strategy in ircbot <= 1.7
134 | setNotificationStrategy(NotificationStrategy.STATECHANGE_ONLY);
135 | }
136 | return this;
137 | }
138 |
139 | /**
140 | * Descriptor for {@link IrcPublisher}
141 | */
142 | public static final class DescriptorImpl extends BuildStepDescriptor implements IMPublisherDescriptor {
143 |
144 | private static final String PREFIX = "irc_publisher.";
145 |
146 | private static final String[] CHARSETS;
147 |
148 | static {
149 | SortedMap availableCharsets = Charset.availableCharsets();
150 | String[] cs = new String[availableCharsets.size()];
151 | cs[0] = "UTF-8";
152 | int i = 1;
153 | for (String csName : availableCharsets.keySet()) {
154 | if (!"UTF-8".equals(csName)) {
155 | cs[i++] = csName;
156 | }
157 | }
158 | CHARSETS = cs;
159 | }
160 |
161 | public String[] getCharsets() {
162 | return CHARSETS;
163 | }
164 |
165 | boolean enabled = false;
166 |
167 | String hostname = null;
168 |
169 | Integer port = 194;
170 |
171 | private boolean ssl;
172 |
173 | private boolean disallowPrivateChat;
174 |
175 | private String login = "PircBotx";
176 |
177 | private boolean sslTrustAllCertificates;
178 |
179 | @Deprecated
180 | transient String password = null;
181 | Secret secretPassword;
182 |
183 | private boolean sasl;
184 |
185 | /* Note that RFC 2812 limits by default to 9 chars; some servers enable more */
186 | String nick = "jenkins";
187 |
188 | @Deprecated
189 | transient String nickServPassword = null;
190 | Secret secretNickServPassword;
191 |
192 | private String socksHost = null;
193 |
194 | private Integer socksPort = 1080;
195 |
196 | private Integer messageRate = getMessageRateFromSystemProperty();
197 |
198 | /**
199 | * channels to join
200 | *
201 | * @deprecated Only to deserialize old descriptors
202 | */
203 | @Deprecated
204 | List channels;
205 |
206 | private List defaultTargets;
207 |
208 | String commandPrefix = "!jenkins";
209 |
210 | @Deprecated
211 | private transient String hudsonLogin;
212 |
213 | private String jenkinsLogin;
214 |
215 | private boolean useNotice;
216 |
217 | private String charset;
218 |
219 | private boolean useColors;
220 |
221 | DescriptorImpl() {
222 | super(IrcPublisher.class);
223 | load();
224 |
225 | if (isEnabled()) {
226 | try {
227 | IRCConnectionProvider.setDesc(this);
228 | } catch (final Exception e) {
229 | // Server temporarily unavailable or misconfigured?
230 | LOGGER.warning(ExceptionHelper.dump(e));
231 | }
232 | } else {
233 | try {
234 | IRCConnectionProvider.setDesc(null);
235 | } catch (IMException e) {
236 | // ignore
237 | }
238 | }
239 | }
240 |
241 | /**
242 | * Check boxes values are not passed in the posted form when they are unchecked.
243 | * The workaround consists in acceding these values via the JSON representation.
244 | */
245 | @Deprecated
246 | @SuppressFBWarnings(value="UPM_UNCALLED_PRIVATE_METHOD",
247 | justification="Here just in case for UI and config loads")
248 | private static List fillChannelsFromJSON(JSONObject root){
249 | throw new UnsupportedOperationException();
250 | }
251 |
252 | /**
253 | * @see hudson.model.Descriptor#configure(org.kohsuke.stapler.StaplerRequest)
254 | */
255 | @Override
256 | @SuppressFBWarnings(value="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT",
257 | justification="There are, in fact, side effects")
258 | public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
259 | this.enabled = "on".equals(req.getParameter("irc_publisher.enabled"))
260 | || "true".equals(req.getParameter("irc_publisher.enabled"));
261 | if (this.enabled) {
262 | JSONObject enabled = formData.getJSONObject("enabled");
263 | req.bindJSON(this, enabled);
264 |
265 | // try to establish the connection
266 | try {
267 | IRCConnectionProvider.setDesc(this);
268 | IRCConnectionProvider.getInstance().currentConnection();
269 | } catch (final Exception e) {
270 | LOGGER.warning(ExceptionHelper.dump(e));
271 | }
272 | } else {
273 | IRCConnectionProvider.getInstance().releaseConnection();
274 | try {
275 | IRCConnectionProvider.setDesc(null);
276 | } catch (IMException e) {
277 | // ignore
278 | }
279 | }
280 |
281 | save();
282 | return super.configure(req, formData);
283 | }
284 |
285 | /**
286 | * @see hudson.model.Descriptor#getDisplayName()
287 | */
288 | @Override
289 | public String getDisplayName() {
290 | return "IRC Notification";
291 | }
292 |
293 | /**
294 | * @see hudson.model.Descriptor#getHelpFile()
295 | */
296 | @Override
297 | public String getHelpFile() {
298 | return "/plugin/ircbot/help.html";
299 | }
300 |
301 | @Override
302 | // TODO refactor to use stapler databinding, rather than hand binding
303 | public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException {
304 | if (req == null) {
305 | throw new IllegalArgumentException("req must be non null");
306 | }
307 |
308 | List targets = new ArrayList<>();
309 | if (formData.has("notificationTargets")) {
310 | JSONArray jchans = formData.optJSONArray("notificationTargets");
311 | if (jchans != null) {
312 | for (int i = 0; i < jchans.size(); i++) {
313 | JSONObject channel = jchans.getJSONObject(i);
314 | String name = channel.getString("name");
315 | if (Util.fixEmptyAndTrim(name) == null) {
316 | throw new FormException("Channel name must not be empty", "channel.name");
317 | }
318 | Secret channelPasssword = Secret.fromString(channel.getString("secretPassword"));
319 | boolean notificationOnly = channel.getBoolean("notificationOnly");
320 |
321 | targets.add(new GroupChatIMMessageTarget(name, channelPasssword, notificationOnly));
322 | }
323 | } else {
324 | // if only one channel then it comes as an object
325 | JSONObject notificationTarget = formData.getJSONObject("notificationTargets");
326 |
327 | String name = notificationTarget.getString("name");
328 | if (Util.fixEmptyAndTrim(name) == null) {
329 | throw new FormException("Channel name must not be empty", "channel.name");
330 | }
331 | Secret channelPasssword = Secret.fromString(notificationTarget.getString("secretPassword"));
332 | boolean notificationOnly = notificationTarget.getBoolean("notificationOnly");
333 |
334 | targets.add(new GroupChatIMMessageTarget(name, channelPasssword, notificationOnly));
335 | }
336 | }
337 |
338 | String n = req.getParameter(getParamNames().getStrategy());
339 | if (n == null) {
340 | n = PARAMETERVALUE_STRATEGY_DEFAULT;
341 | } else {
342 | boolean foundStrategyValueMatch = false;
343 | for (final String strategyValue : PARAMETERVALUE_STRATEGY_VALUES) {
344 | if (strategyValue.equals(n)) {
345 | foundStrategyValueMatch = true;
346 | break;
347 | }
348 | }
349 | if (! foundStrategyValueMatch) {
350 | n = PARAMETERVALUE_STRATEGY_DEFAULT;
351 | }
352 | }
353 | boolean notifyStart = "on".equals(req.getParameter(getParamNames().getNotifyStart()));
354 | boolean notifySuspects = "on".equals(req.getParameter(getParamNames().getNotifySuspects()));
355 | boolean notifyCulprits = "on".equals(req.getParameter(getParamNames().getNotifyCulprits()));
356 | boolean notifyFixers = "on".equals(req.getParameter(getParamNames().getNotifyFixers()));
357 | boolean notifyUpstream = "on".equals(req.getParameter(getParamNames().getNotifyUpstreamCommitters()));
358 |
359 | MatrixJobMultiplier matrixJobMultiplier = MatrixJobMultiplier.ONLY_CONFIGURATIONS;
360 | if (formData.has("matrixNotifier")) {
361 | String o = formData.getString("matrixNotifier");
362 | matrixJobMultiplier = MatrixJobMultiplier.valueOf(o);
363 | }
364 |
365 | return new IrcPublisher(targets, n, notifyStart, notifySuspects, notifyCulprits,
366 | notifyFixers, notifyUpstream,
367 | req.bindJSON(BuildToChatNotifier.class,formData.getJSONObject("buildToChatNotifier")),
368 | matrixJobMultiplier);
369 | }
370 |
371 | @Override
372 | public boolean isApplicable(@SuppressWarnings("rawtypes") Class extends AbstractProject> jobType) {
373 | return true;
374 | }
375 |
376 | /**
377 | * @return the commandPrefix
378 | */
379 | //@Override
380 | public String getCommandPrefix() {
381 | return commandPrefix;
382 | }
383 |
384 | /**
385 | * @return the hostname
386 | */
387 | //@Override
388 | public String getHostname() {
389 | return hostname;
390 | }
391 |
392 | public String getJenkinsLogin() {
393 | return jenkinsLogin;
394 | }
395 |
396 | public void setJenkinsLogin(String jenkinsLogin) {
397 | this.jenkinsLogin = jenkinsLogin;
398 | }
399 |
400 | /**
401 | * Returns the nickname that should be used to identify against the IRC server.
402 | *
403 | * @return the nick
404 | */
405 | public String getNick() {
406 | return nick;
407 | }
408 |
409 | /**
410 | * @return The password that should be used to try and identify
411 | * with NickServ.
412 | *
413 | * @deprecated use {@link #getSecretNickServPassword()}
414 | */
415 | @Deprecated
416 | public String getNickServPassword() {
417 | return getSecretNickServPassword().getPlainText();
418 | }
419 |
420 | public Secret getSecretNickServPassword() {
421 | return secretNickServPassword;
422 | }
423 |
424 | public String getLogin() {
425 | return this.login;
426 | }
427 |
428 | //@Override
429 | public Secret getSecretPassword() {
430 | return secretPassword;
431 | }
432 |
433 | public boolean isSasl() {
434 | return this.sasl;
435 | }
436 |
437 | //@Override
438 | public int getPort() {
439 | return port;
440 | }
441 |
442 | public String getSocksHost() {
443 | return socksHost;
444 | }
445 |
446 | public int getSocksPort() {
447 | return socksPort;
448 | }
449 |
450 | public boolean isSsl() {
451 | return this.ssl;
452 | }
453 |
454 | public boolean isTrustAllCertificates() {
455 | return this.sslTrustAllCertificates;
456 | }
457 |
458 | public boolean isDisallowPrivateChat() {
459 | return this.disallowPrivateChat;
460 | }
461 |
462 | public Integer getMessageRate() { return this.messageRate; }
463 |
464 | //@Override
465 | public boolean isEnabled() {
466 | return enabled;
467 | }
468 |
469 | //@Override
470 | public String getDefaultIdSuffix() {
471 | // not implemented for IRC, yet
472 | return null;
473 | }
474 |
475 | //@Override
476 | public String getHost() {
477 | return this.hostname;
478 | }
479 |
480 | @Override
481 | public String getHudsonUserName() {
482 | return this.jenkinsLogin;
483 | }
484 |
485 | //@Override
486 | public String getPluginDescription() {
487 | return "IRC notifier plugin";
488 | }
489 |
490 | //@Override
491 | public String getUserName() {
492 | return this.nick;
493 | }
494 |
495 | //@Override
496 | public boolean isExposePresence() {
497 | return true;
498 | }
499 |
500 | //@Override
501 | public List getDefaultTargets() {
502 | if (this.defaultTargets == null) {
503 | return Collections.emptyList();
504 | }
505 |
506 | return this.defaultTargets;
507 | }
508 |
509 |
510 | public void setEnabled(boolean enabled) {
511 | this.enabled = enabled;
512 | }
513 |
514 | public void setHostname(String hostname) {
515 | this.hostname = hostname;
516 | }
517 |
518 | public void setPort(int port) {
519 | this.port = port;
520 | }
521 |
522 | public void setSsl(boolean ssl) {
523 | this.ssl = ssl;
524 | }
525 |
526 | public void setDisallowPrivateChat(boolean disallowPrivateChat) {
527 | this.disallowPrivateChat = disallowPrivateChat;
528 | }
529 |
530 | public void setLogin(String login) {
531 | this.login = login;
532 | }
533 |
534 | public boolean isSslTrustAllCertificates() {
535 | return sslTrustAllCertificates;
536 | }
537 |
538 | public void setSslTrustAllCertificates(boolean sslTrustAllCertificates) {
539 | this.sslTrustAllCertificates = sslTrustAllCertificates;
540 | }
541 |
542 | public void setSecretPassword(Secret secretPassword) {
543 | this.secretPassword = secretPassword;
544 | }
545 |
546 | public void setSasl(boolean sasl) {
547 | this.sasl = sasl;
548 | }
549 |
550 | public void setNick(String nick) {
551 | this.nick = nick;
552 | }
553 |
554 | public void setSecretNickServPassword(Secret secretNickServPassword) {
555 | this.secretNickServPassword = secretNickServPassword;
556 | }
557 |
558 | public void setSocksHost(String socksHost) {
559 | this.socksHost = socksHost;
560 | }
561 |
562 | public void setSocksPort(Integer socksPort) {
563 | this.socksPort = socksPort;
564 | }
565 |
566 | public void setMessageRate(Integer messageRate) {
567 | this.messageRate = messageRate;
568 | }
569 |
570 | public void setDefaultTargets(List defaultTargets) {
571 | this.defaultTargets = defaultTargets;
572 | }
573 |
574 | public void setCommandPrefix(String commandPrefix) {
575 | this.commandPrefix = commandPrefix;
576 | }
577 |
578 | public void setUseNotice(boolean useNotice) {
579 | this.useNotice = useNotice;
580 | }
581 |
582 | public void setCharset(String charset) {
583 | this.charset = charset;
584 | }
585 |
586 | public void setUseColors(boolean useColors) {
587 | this.useColors = useColors;
588 | }
589 |
590 | //@Override
591 | public IMMessageTargetConverter getIMMessageTargetConverter() {
592 | return CONVERTER;
593 | }
594 |
595 | /**
596 | * @return Boolean flag which specifies if the bot should use
597 | * the /notice command instead of the /msg command to notify.
598 | */
599 | public boolean isUseNotice() {
600 | return this.useNotice;
601 | }
602 |
603 | /**
604 | * @return Boolean flag which specifies if the bot should
605 | * send message with colors.
606 | */
607 | public boolean isUseColors() {
608 | return this.useColors;
609 | }
610 |
611 | public String getCharset() {
612 | return this.charset;
613 | }
614 |
615 | @SuppressFBWarnings(value = "SIC_INNER_SHOULD_BE_STATIC_ANON",
616 | justification = "No idea how to solve")
617 | public ParameterNames getParamNames() {
618 | return new ParameterNames() {
619 | @Override
620 | protected String getPrefix() {
621 | return PREFIX;
622 | }
623 | };
624 | }
625 |
626 | /**
627 | * Fetches message rate, defaults to 0.5 second if none are set or invalid value.
628 | * @return message rate in milliseconds
629 | */
630 | protected Integer getMessageRateFromSystemProperty() {
631 | try {
632 | return Integer.parseInt(System.getProperty("hudson.plugins.ircbot.messageRate", "500"));
633 | } catch (NumberFormatException nfe) {
634 | return Integer.valueOf(500);
635 | }
636 | }
637 |
638 | /**
639 | * Deserialize old descriptors.
640 | */
641 | @SuppressWarnings("deprecation")
642 | protected Object readResolve() {
643 | if (this.defaultTargets == null) {
644 | if (this.channels != null) {
645 | this.defaultTargets = new ArrayList<>(this.channels.size());
646 | for (String channel : this.channels) {
647 | this.defaultTargets.add(new GroupChatIMMessageTarget(channel));
648 | }
649 |
650 | this.channels = null;
651 | }
652 | }
653 |
654 | if (this.charset == null) {
655 | this.charset = "UTF-8";
656 | }
657 |
658 | if (this.messageRate == null) {
659 | this.messageRate = getMessageRateFromSystemProperty();
660 | }
661 |
662 | if (StringUtils.isNotBlank(this.password)) {
663 | this.secretPassword = Secret.fromString(Scrambler.descramble(this.password));
664 | this.password = null;
665 | }
666 | if (StringUtils.isNotBlank(this.nickServPassword)) {
667 | this.secretNickServPassword = Secret.fromString(Scrambler.descramble(this.nickServPassword));
668 | this.nickServPassword = null;
669 | }
670 |
671 | if (StringUtils.isNotBlank(this.hudsonLogin)) {
672 | this.jenkinsLogin = this.hudsonLogin;
673 | this.hudsonLogin = null;
674 | }
675 |
676 | return this;
677 | }
678 |
679 | }
680 | }
681 |
--------------------------------------------------------------------------------