├── .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 |

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 | 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 | 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> 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 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 | --------------------------------------------------------------------------------