Skip to content

Commit

Permalink
Merge pull request #18 from CLIP-HPC/discord
Browse files Browse the repository at this point in the history
Discord
  • Loading branch information
pja237 authored Aug 10, 2022
2 parents 9adaf23 + 35e7f03 commit 22b7560
Show file tree
Hide file tree
Showing 30 changed files with 669 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
push:
tags:
- v**
pull_request:
branches:
- master

jobs:

Expand Down
65 changes: 58 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
# goslmailer

> **Info**
> **News & Info**
>
> v2.4.0
>
> * discord connector
> * new connectors initialization code
>
> Now also works with SLURM < 21.08
>
> For templating differences between slurm>21.08 and slurm<21.08 see [templating guide](./templates/README.md)
>
## Drop-in notification delivery solution for slurm that can do:

* message delivery to:
* [**discord**](https://discord.com/)
* [**matrix**](https://matrix.org/)
* [**telegram**](https://telegram.org/)
* [**msteams**](https://teams.com)
* **e-mail**
* [**e-mail**](https://en.wikipedia.org/wiki/Email)
* gathering of job **statistics**
* generating **hints** for users on how to tune their job scripts (see examples below)
* **templateable** messages ([readme](./templates/README.md))
Expand Down Expand Up @@ -43,6 +51,7 @@ To support future additional receiver schemes, a [connector package](connectors/

## Currently available connectors:

* [**discord**](#discord-connector) bot --mail-user=`discord`:channelId
* [**matrix**](#matrix-connector) bot --mail-user=`matrix:`roomId
* [**telegram**](#telegram-connector) bot --mail-user=`telegram:`chatId
* [**mailto**](#mailto-connector) --mail-user=`mailto:`email-addr
Expand All @@ -56,7 +65,11 @@ See each connector details below...

## Building and installing

### Build
### Option 1. Download latest precompiled binaries [here](https://github.com/CLIP-HPC/goslmailer/releases/latest)

Unpack, follow [instructions](#install)

### Option 2. Build

#### Quick version, without end to end testing

Expand Down Expand Up @@ -114,6 +127,13 @@ make
* config file has the same format as [goslmailer](cmd/goslmailer/goslmailer.conf.annotated_example), so you can use the same one (other connectors configs are not needed)
* start the service (with -c switch pointing to config file)

#### discoslurmbot

* place binary in a path to your liking
* place [discoslurmbot.conf](./cmd/discoslurmbot/discoslurmbot.conf) in a path to your liking
* config file has the same format as [goslmailer](cmd/goslmailer/goslmailer.conf.annotated_example), so you can use the same one (other connectors configs are not needed)
* start the service (with -c switch pointing to config file)


---

Expand Down Expand Up @@ -150,6 +170,14 @@ On startup, gobler reads its config file and spins-up a `connector monitor` for

## Connectors

| connector | spooling/throttling capable (gobler) |
|-----------|---------------------------|
| discord | yes |
| matrix | no |
| telegram | yes |
| msteams | yes |
| mailto | no |

### default connector

Specifies which receiver scheme is the default one, in case when user didn't specify `--mail-user` and slurm sent a bare username.
Expand Down Expand Up @@ -185,6 +213,33 @@ See [annotated configuration example](cmd/goslmailer/goslmailer.conf.annotated_e

---

### discord connector

Prerequisites for the discord connector:

1. a discord bot must be created and
2. the bot daemon service **discoslurmbot** must be running ([example config file](./cmd/discoslurmbot/discoslurmbot.conf)).
3. once the bot is running, it will wake up on the configured `triggerString` and send the user a private message with slurm job submission instructions


#### Discord Bot setup

1. User settings -> Advanced -> Developer mode ON
2. [Discord developer portal](https://discord.com/developers/applications) -> New Application -> Fill out BotName
3. Once the application is saved, select *Bot* from left menu -> Add Bot -> message: "A wild bot has appeared!"
4. Left menu: OAuth2 -> Copy Client ID
5. Modify this url with the Client ID from 4. and open in browser: `https://discord.com/api/oauth2/authorize?client_id=<CLIENT-ID>&permissions=8&scope=bot`
6. "An external application BotName wants to access your Discord Account" message -> Select server -> Continue
7. Grant Administrator permissions -> yes/no/maybe ? -> Authorize
8. [Discord developer portal](https://discord.com/developers/applications) -> Select BotName -> Bot menu -> Reset Token -> Copy and Save, to be used in discoslurmbot.conf

Or follow this [tutorial](https://discordpy.readthedocs.io/en/stable/discord.html)

![Discord card](./images/discord.png)

* discord bot and packaged developed using [discordgo](https://github.com/bwmarrin/discordgo) and with the help of _Discord Gophers_
---

### telegram connector

Sends **1on1** or **group chat** messages about jobs via [telegram messenger app](https://telegram.org/)
Expand Down Expand Up @@ -287,10 +342,6 @@ See [annotated configuration example](cmd/goslmailer/goslmailer.conf.annotated_e

---

## ToDo

---

## Gotchas

### msteams
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.3.0
v2.4.0
28 changes: 28 additions & 0 deletions cmd/discoslurmbot/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Copyright (c) 2015, Bruce Marriner
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of discordgo nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

11 changes: 11 additions & 0 deletions cmd/discoslurmbot/discoslurmbot.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{ # remember to remove comments from this json example ;)
"logfile": "", # if empty -> stderr, else log to specified file
"connectors": {
"discord": {
"name": "DiscoSlurmBot", # name that is used in the bot welcome message
"triggerString": "showmeslurm", # string (in channel or DM) that triggers the bot to respond with an instructional DM to the user
"token": "PasteBotTokenHere", # place to put the bot token
"messageTemplate": "/path/to/template.md" # template file to use
}
}
}
154 changes: 154 additions & 0 deletions cmd/discoslurmbot/discoslurmbot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package main

import (
"fmt"
"log"
"os"
"os/signal"
"syscall"

"github.com/CLIP-HPC/goslmailer/internal/cmdline"
"github.com/CLIP-HPC/goslmailer/internal/config"
"github.com/CLIP-HPC/goslmailer/internal/logger"
"github.com/CLIP-HPC/goslmailer/internal/version"
"github.com/bwmarrin/discordgo"
)

const app = "discoslurmbot"

type botConfig struct {
config.ConfigContainer
l *log.Logger
}

// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the authenticated bot has access to.
//
// It is called whenever a message is created but only when it's sent through a
// server as we did not request IntentsDirectMessages.
func messageCreate(bc botConfig) func(*discordgo.Session, *discordgo.MessageCreate) {
return func(s *discordgo.Session, m *discordgo.MessageCreate) {

fmt.Printf("session: %#v\n", s)
fmt.Printf("message: %#v\n", m)
fmt.Printf("message content: %#v\n", m.Content)
fmt.Printf("author: %#v\n", m.Author.ID)
fmt.Printf("user.id: %#v\n", s.State.User.ID)

// Ignore all messages created by the bot itself
// This isn't required in this specific example but it's a good practice.
if m.Author.ID == s.State.User.ID {
return
}
// In this example, we only care about messages that are "ping".
if m.Content != bc.Connectors["discord"]["triggerString"] {
return
}

// We create the private channel with the user who sent the message.
channel, err := s.UserChannelCreate(m.Author.ID)
if err != nil {
// If an error occurred, we failed to create the channel.
//
// Some common causes are:
// 1. We don't share a server with the user (not possible here).
// 2. We opened enough DM channels quickly enough for Discord to
// label us as abusing the endpoint, blocking us from opening
// new ones.
bc.l.Println("error creating channel:", err)
s.ChannelMessageSend(
m.ChannelID,
"Something went wrong while sending the DM!",
)
return
}
// Then we send the message through the channel we created.
msg := fmt.Sprintf("Welcome,\nI am %s,\nplease use this switch in your job submission script in addition to '--mail-type' and i'll get back to you:\n '--mail-user=discord:%s'", bc.Connectors["discord"]["botname"], channel.ID)
_, err = s.ChannelMessageSend(channel.ID, msg)
if err != nil {
// If an error occurred, we failed to send the message.
//
// It may occur either when we do not share a server with the
// user (highly unlikely as we just received a message) or
// the user disabled DM in their settings (more likely).
bc.l.Println("error sending DM message:", err)
s.ChannelMessageSend(
m.ChannelID,
"Failed to send you a DM. "+
"Did you disable DM in your privacy settings?",
)
}
}
}

func main() {

// parse command line params
cmd, err := cmdline.NewCmdArgs(app)
if err != nil {
log.Fatalf("ERROR: parse command line failed with: %q\n", err)
}

if *(cmd.Version) {
l := log.New(os.Stderr, app+":", log.Lshortfile|log.Ldate|log.Lmicroseconds)
version.DumpVersion(l)
os.Exit(0)
}

// read config file
cfg := config.NewConfigContainer()
err = cfg.GetConfig(*(cmd.CfgFile))
if err != nil {
log.Fatalf("ERROR: getConfig() failed: %s\n", err)
}

// setup logger
l, err := logger.SetupLogger(cfg.Logfile, "gobler")
if err != nil {
log.Fatalf("setuplogger(%s) failed with: %q\n", cfg.Logfile, err)
}

l.Println("===================== discoslurmbot start ======================================")

version.DumpVersion(l)

if _, ok := cfg.Connectors["discord"]["token"]; !ok {
l.Fatalf("MAIN: fetching config[connectors][discord][token] failed: %s\n", err)
}

// Create a new Discord session using the provided bot token.
dg, err := discordgo.New("Bot " + cfg.Connectors["discord"]["token"])
if err != nil {
l.Println("error creating Discord session,", err)
return
}

// Register the messageCreate func as a callback for MessageCreate events.
bc := botConfig{
*cfg,
l,
}
dg.AddHandler(messageCreate(bc))

// In this example, we only care about receiving message events.
// pja: and DMs
dg.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentDirectMessages

// Open a websocket connection to Discord and begin listening.
err = dg.Open()
if err != nil {
l.Println("error opening connection,", err)
return
}

// Wait here until CTRL-C or other term signal is received.
l.Println("Bot is now running. Press CTRL-C to exit.")
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-sc

// Cleanly close down the Discord session.
dg.Close()

l.Println("===================== discoslurmbot end ========================================")
}
14 changes: 9 additions & 5 deletions cmd/gobler/gobler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import (
"os"
"sync"

_ "github.com/CLIP-HPC/goslmailer/connectors/discord"
_ "github.com/CLIP-HPC/goslmailer/connectors/mailto"
_ "github.com/CLIP-HPC/goslmailer/connectors/matrix"
_ "github.com/CLIP-HPC/goslmailer/connectors/msteams"
_ "github.com/CLIP-HPC/goslmailer/connectors/telegram"
"github.com/CLIP-HPC/goslmailer/internal/cmdline"
"github.com/CLIP-HPC/goslmailer/internal/config"
"github.com/CLIP-HPC/goslmailer/internal/connectors"
Expand All @@ -20,8 +25,7 @@ type MsgList []message.MessagePack
func main() {

var (
conns = make(connectors.Connectors)
wg sync.WaitGroup
wg sync.WaitGroup
)

// parse command line params
Expand All @@ -30,7 +34,7 @@ func main() {
log.Fatalf("ERROR: parse command line failed with: %q\n", err)
}

if *(cmd.Version) == true {
if *(cmd.Version) {
l := log.New(os.Stderr, "gobler:", log.Lshortfile|log.Ldate|log.Lmicroseconds)
version.DumpVersion(l)
os.Exit(0)
Expand All @@ -56,7 +60,7 @@ func main() {
cfg.DumpConfig(l)

// populate map with configured referenced connectors
err = conns.PopulateConnectors(cfg, l)
err = connectors.ConMap.PopulateConnectors(cfg, l)
if err != nil {
l.Printf("MAIN: PopulateConnectors() failed with: %s\n", err)
}
Expand All @@ -74,7 +78,7 @@ func main() {
continue
}
// func (cm *conMon) SpinUp(conns connectors.Connectors, wg sync.WaitGroup, l *log.Logger) error {
err = cm.SpinUp(conns, &wg, l)
err = cm.SpinUp(connectors.ConMap, &wg, l)
if err != nil {
l.Printf("MAIN: SpinUp(%s) failed with: %s\n", con, err)
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/goslmailer/goslmailer.conf.annotated_example
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@
"useLookup": "no",
"format": "MarkdownV2"
},
"discord": {
"name": "DiscoSlurmBot", # name that is used in the bot welcome message
"triggerString": "showmeslurm", # string (in channel or DM) that triggers the bot to respond with an instructional DM to the user
"token": "PasteBotTokenHere", # place to put the bot token
"messageTemplate": "/path/to/template.md" # template file to use
}
"matrix": {
"username": "@myuser:matrix.org",
"token": "syt_dGRpZG9ib3QXXXXXXXEyQMBEmvOVp_10Jm93",
Expand Down
Loading

0 comments on commit 22b7560

Please sign in to comment.