Add slack and github connectors
This commit is contained in:
54
README.md
Normal file
54
README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
## Connectors
|
||||||
|
|
||||||
|
### Github (issues)
|
||||||
|
|
||||||
|
Create an user and a PAT token:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "PAT_TOKEN",
|
||||||
|
"repository": "testrepo",
|
||||||
|
"owner": "ci-forks",
|
||||||
|
"botUserName": "localai-bot"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Discord
|
||||||
|
|
||||||
|
Follow the steps in: https://discordpy.readthedocs.io/en/stable/discord.html to create a discord bot.
|
||||||
|
|
||||||
|
The token of the bot is in the "Bot" tab. Also enable " Message Content Intent " in the Bot tab!
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"token": "Bot DISCORDTOKENHERE",
|
||||||
|
"defaultChannel": "OPTIONALCHANNELINT"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Slack
|
||||||
|
|
||||||
|
See slack.yaml
|
||||||
|
|
||||||
|
- Create a new App from a manifest (copy-paste from `slack.yaml`)
|
||||||
|
- Create Oauth token bot token from "OAuth & Permissions" -> "OAuth Tokens for Your Workspace"
|
||||||
|
- Create App level token (from "Basic Information" -> "App-Level Tokens" ( `scope connections:writeRoute authorizations:read` ))
|
||||||
|
|
||||||
|
In the UI, when configuring the connector:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"botToken": "xoxb-...",
|
||||||
|
"appToken": "xapp-1-..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Telegram
|
||||||
|
|
||||||
|
Ask a token to @botfather
|
||||||
|
|
||||||
|
In the UI, when configuring the connector:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "token": "botfathertoken" }
|
||||||
|
```
|
||||||
@@ -557,21 +557,26 @@ func (a *Agent) Run() error {
|
|||||||
|
|
||||||
// Expose a REST API to interact with the agent to ask it things
|
// Expose a REST API to interact with the agent to ask it things
|
||||||
|
|
||||||
todoTimer := time.NewTicker(a.options.periodicRuns)
|
//todoTimer := time.NewTicker(a.options.periodicRuns)
|
||||||
|
timer := time.NewTimer(a.options.periodicRuns)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case job := <-a.jobQueue:
|
case job := <-a.jobQueue:
|
||||||
// Consume the job and generate a response
|
// Consume the job and generate a response
|
||||||
// TODO: Give a short-term memory to the agent
|
// TODO: Give a short-term memory to the agent
|
||||||
a.consumeJob(job, UserRole)
|
a.consumeJob(job, UserRole)
|
||||||
|
timer.Reset(a.options.periodicRuns)
|
||||||
case <-a.context.Done():
|
case <-a.context.Done():
|
||||||
// Agent has been canceled, return error
|
// Agent has been canceled, return error
|
||||||
return ErrContextCanceled
|
return ErrContextCanceled
|
||||||
case <-todoTimer.C:
|
case <-timer.C:
|
||||||
if !a.options.standaloneJob {
|
if !a.options.standaloneJob {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// TODO: We should also do not immediately fire this timer but
|
||||||
|
// instead have a cool-off timer starting after we received the last job
|
||||||
a.periodicallyRun()
|
a.periodicallyRun()
|
||||||
|
timer.Reset(a.options.periodicRuns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,9 +119,11 @@ func (a *AgentPool) List() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ConnectorTelegram = "telegram"
|
ConnectorTelegram = "telegram"
|
||||||
ConnectorSlack = "slack"
|
ConnectorSlack = "slack"
|
||||||
ActionSearch = "search"
|
ConnectorDiscord = "discord"
|
||||||
|
ConnectorGithubIssues = "github-issues"
|
||||||
|
ActionSearch = "search"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AvailableActions = []string{ActionSearch}
|
var AvailableActions = []string{ActionSearch}
|
||||||
@@ -154,7 +156,12 @@ type Connector interface {
|
|||||||
Start(a *Agent)
|
Start(a *Agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
var AvailableConnectors = []string{ConnectorTelegram, ConnectorSlack}
|
var AvailableConnectors = []string{
|
||||||
|
ConnectorTelegram,
|
||||||
|
ConnectorSlack,
|
||||||
|
ConnectorDiscord,
|
||||||
|
ConnectorGithubIssues,
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AgentConfig) availableConnectors() []Connector {
|
func (a *AgentConfig) availableConnectors() []Connector {
|
||||||
connectors := []Connector{}
|
connectors := []Connector{}
|
||||||
@@ -180,6 +187,10 @@ func (a *AgentConfig) availableConnectors() []Connector {
|
|||||||
connectors = append(connectors, cc)
|
connectors = append(connectors, cc)
|
||||||
case ConnectorSlack:
|
case ConnectorSlack:
|
||||||
connectors = append(connectors, connector.NewSlack(config))
|
connectors = append(connectors, connector.NewSlack(config))
|
||||||
|
case ConnectorDiscord:
|
||||||
|
connectors = append(connectors, connector.NewDiscord(config))
|
||||||
|
case ConnectorGithubIssues:
|
||||||
|
connectors = append(connectors, connector.NewGithub(config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return connectors
|
return connectors
|
||||||
|
|||||||
105
example/webui/connector/discord.go
Normal file
105
example/webui/connector/discord.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/mudler/local-agent-framework/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Discord struct {
|
||||||
|
token string
|
||||||
|
defaultChannel string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDiscord(config map[string]string) *Discord {
|
||||||
|
return &Discord{
|
||||||
|
token: config["token"],
|
||||||
|
defaultChannel: config["defaultChannel"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) AgentResultCallback() func(state agent.ActionState) {
|
||||||
|
return func(state agent.ActionState) {
|
||||||
|
// Send the result to the bot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
|
||||||
|
return func(state agent.ActionCurrentState) bool {
|
||||||
|
// Send the reasoning to the bot
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) Start(a *agent.Agent) {
|
||||||
|
|
||||||
|
Token := d.token
|
||||||
|
// Create a new Discord session using the provided bot token.
|
||||||
|
dg, err := discordgo.New(Token)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error creating Discord session,", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the messageCreate func as a callback for MessageCreate events.
|
||||||
|
dg.AddHandler(d.messageCreate(a))
|
||||||
|
|
||||||
|
// In this example, we only care about receiving message events.
|
||||||
|
dg.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsDirectMessages | discordgo.IntentMessageContent
|
||||||
|
|
||||||
|
// Open a websocket connection to Discord and begin listening.
|
||||||
|
err = dg.Open()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error opening connection,", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-a.Context().Done()
|
||||||
|
dg.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (d *Discord) messageCreate(a *agent.Agent) func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
|
return func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
interact := func() {
|
||||||
|
//m := m.ContentWithMentionsReplaced()
|
||||||
|
content := m.Content
|
||||||
|
|
||||||
|
content = strings.ReplaceAll(content, "<@"+s.State.User.ID+"> ", "")
|
||||||
|
|
||||||
|
job := a.Ask(
|
||||||
|
agent.WithText(
|
||||||
|
content,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
_, err := s.ChannelMessageSend(m.ChannelID, job.Response)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error sending message,", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interact if we are mentioned
|
||||||
|
for _, mention := range m.Mentions {
|
||||||
|
if mention.ID == s.State.User.ID {
|
||||||
|
interact()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or we are in the default channel (if one is set!)
|
||||||
|
if d.defaultChannel != "" && m.ChannelID == d.defaultChannel {
|
||||||
|
interact()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
example/webui/connector/githubissue.go
Normal file
153
example/webui/connector/githubissue.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-github/v61/github"
|
||||||
|
"github.com/mudler/local-agent-framework/agent"
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GithubIssues struct {
|
||||||
|
token string
|
||||||
|
repository string
|
||||||
|
owner string
|
||||||
|
agent *agent.Agent
|
||||||
|
pollInterval time.Duration
|
||||||
|
client *github.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGithub(config map[string]string) *GithubIssues {
|
||||||
|
client := github.NewClient(nil).WithAuthToken(config["token"])
|
||||||
|
interval, err := time.ParseDuration(config["pollInterval"])
|
||||||
|
if err != nil {
|
||||||
|
interval = 1 * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GithubIssues{
|
||||||
|
client: client,
|
||||||
|
token: config["token"],
|
||||||
|
repository: config["repository"],
|
||||||
|
owner: config["owner"],
|
||||||
|
pollInterval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GithubIssues) AgentResultCallback() func(state agent.ActionState) {
|
||||||
|
return func(state agent.ActionState) {
|
||||||
|
// Send the result to the bot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GithubIssues) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
|
||||||
|
return func(state agent.ActionCurrentState) bool {
|
||||||
|
// Send the reasoning to the bot
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GithubIssues) Start(a *agent.Agent) {
|
||||||
|
// Start the connector
|
||||||
|
g.agent = a
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(g.pollInterval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
fmt.Println("Fire in da hole!")
|
||||||
|
g.issuesService()
|
||||||
|
case <-a.Context().Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GithubIssues) issuesService() {
|
||||||
|
user, _, err := g.client.Users.Get(g.agent.Context(), "")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("\nerror: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
issues, _, err := g.client.Issues.ListByRepo(
|
||||||
|
g.agent.Context(),
|
||||||
|
g.owner,
|
||||||
|
g.repository,
|
||||||
|
&github.IssueListByRepoOptions{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error listing issues", err)
|
||||||
|
}
|
||||||
|
for _, issue := range issues {
|
||||||
|
// Do something with the issue
|
||||||
|
if issue.IsPullRequest() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []openai.ChatCompletionMessage{
|
||||||
|
{
|
||||||
|
Role: "system",
|
||||||
|
Content: fmt.Sprintf("This is a conversation with an user that opened a Github issue with title '%s'.", issue.GetTitle()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: issue.GetBody(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
comments, _, _ := g.client.Issues.ListComments(g.agent.Context(), g.owner, g.repository, issue.GetNumber(),
|
||||||
|
&github.IssueListCommentsOptions{})
|
||||||
|
|
||||||
|
mustAnswer := false
|
||||||
|
botAnsweredAlready := false
|
||||||
|
for i, comment := range comments {
|
||||||
|
role := "user"
|
||||||
|
if comment.GetUser().GetLogin() == user.GetLogin() {
|
||||||
|
botAnsweredAlready = true
|
||||||
|
role = "assistant"
|
||||||
|
}
|
||||||
|
messages = append(messages, openai.ChatCompletionMessage{
|
||||||
|
Role: role,
|
||||||
|
Content: comment.GetBody(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// if last comment is from the user and mentions the bot username, we must answer
|
||||||
|
if comment.User.GetName() != user.GetLogin() && len(comments)-1 == i {
|
||||||
|
if strings.Contains(comment.GetBody(), fmt.Sprintf("@%s", user.GetLogin())) {
|
||||||
|
fmt.Println("Bot was mentioned in the last comment")
|
||||||
|
mustAnswer = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(comments) == 0 || !botAnsweredAlready {
|
||||||
|
// if no comments, or bot didn't answer yet, we must answer
|
||||||
|
fmt.Println("No comments, or bot didn't answer yet")
|
||||||
|
fmt.Println("Comments:", len(comments))
|
||||||
|
fmt.Println("Bot answered already", botAnsweredAlready)
|
||||||
|
mustAnswer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mustAnswer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res := g.agent.Ask(
|
||||||
|
agent.WithConversationHistory(messages),
|
||||||
|
)
|
||||||
|
|
||||||
|
_, _, err := g.client.Issues.CreateComment(
|
||||||
|
g.agent.Context(),
|
||||||
|
g.owner, g.repository,
|
||||||
|
issue.GetNumber(), &github.IssueComment{
|
||||||
|
Body: github.String(res.Response),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating comment", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,15 +41,15 @@ func (t *Slack) AgentReasoningCallback() func(state agent.ActionCurrentState) bo
|
|||||||
func (t *Slack) Start(a *agent.Agent) {
|
func (t *Slack) Start(a *agent.Agent) {
|
||||||
api := slack.New(
|
api := slack.New(
|
||||||
t.botToken,
|
t.botToken,
|
||||||
slack.OptionDebug(true),
|
// slack.OptionDebug(true),
|
||||||
slack.OptionLog(log.New(os.Stdout, "api: ", log.Lshortfile|log.LstdFlags)),
|
slack.OptionLog(log.New(os.Stdout, "api: ", log.Lshortfile|log.LstdFlags)),
|
||||||
slack.OptionAppLevelToken(t.appToken),
|
slack.OptionAppLevelToken(t.appToken),
|
||||||
)
|
)
|
||||||
|
|
||||||
client := socketmode.New(
|
client := socketmode.New(
|
||||||
api,
|
api,
|
||||||
socketmode.OptionDebug(true),
|
// socketmode.OptionDebug(true),
|
||||||
socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)),
|
//socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)),
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
for evt := range client.Events {
|
for evt := range client.Events {
|
||||||
|
|||||||
@@ -14,22 +14,29 @@ import (
|
|||||||
type Telegram struct {
|
type Telegram struct {
|
||||||
Token string
|
Token string
|
||||||
lastChatID int64
|
lastChatID int64
|
||||||
|
bot *bot.Bot
|
||||||
|
agent *agent.Agent
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send any text message to the bot after the bot has been started
|
// Send any text message to the bot after the bot has been started
|
||||||
|
|
||||||
func (t *Telegram) AgentResultCallback() func(state agent.ActionState) {
|
func (t *Telegram) AgentResultCallback() func(state agent.ActionState) {
|
||||||
return func(state agent.ActionState) {
|
return func(state agent.ActionState) {
|
||||||
// Send the result to the bot
|
t.bot.SetMyDescription(t.agent.Context(), &bot.SetMyDescriptionParams{
|
||||||
|
Description: state.Reasoning,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Telegram) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
|
func (t *Telegram) AgentReasoningCallback() func(state agent.ActionCurrentState) bool {
|
||||||
return func(state agent.ActionCurrentState) bool {
|
return func(state agent.ActionCurrentState) bool {
|
||||||
// Send the reasoning to the bot
|
t.bot.SetMyDescription(t.agent.Context(), &bot.SetMyDescriptionParams{
|
||||||
|
Description: state.Reasoning,
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Telegram) Start(a *agent.Agent) {
|
func (t *Telegram) Start(a *agent.Agent) {
|
||||||
ctx, cancel := signal.NotifyContext(a.Context(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(a.Context(), os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -54,6 +61,9 @@ func (t *Telegram) Start(a *agent.Agent) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.bot = b
|
||||||
|
t.agent = a
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for m := range a.ConversationChannel() {
|
for m := range a.ConversationChannel() {
|
||||||
if t.lastChatID == 0 {
|
if t.lastChatID == 0 {
|
||||||
|
|||||||
@@ -19,28 +19,25 @@
|
|||||||
<input type="text" name="name" id="name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Name">
|
<input type="text" name="name" id="name" class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-lg border-gray-300 rounded-md bg-gray-700 text-white" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="connectorsSection">
|
<div id="connectorsSection">
|
||||||
<div class="connector mb-4">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="addConnectorButton" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
<button type="button" id="addConnectorButton" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
Add Connector
|
Add Connector
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const actions = `{{ range .Actions }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||||
|
const connectors = `{{ range .Connectors }}<option value="{{.}}">{{.}}</option>{{ end }}`;
|
||||||
|
|
||||||
document.getElementById('addConnectorButton').addEventListener('click', function() {
|
document.getElementById('addConnectorButton').addEventListener('click', function() {
|
||||||
const connectorsSection = document.getElementById('connectorsSection');
|
const connectorsSection = document.getElementById('connectorsSection');
|
||||||
const newConnectorIndex = connectorsSection.getElementsByClassName('connector').length;
|
const newConnectorIndex = connectorsSection.getElementsByClassName('connector').length;
|
||||||
|
|
||||||
const newConnectorHTML = `
|
const newConnectorHTML = `
|
||||||
<div class="connector mb-4">
|
<div class="connector mb-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="connectorType${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Type</label>
|
<label for="connectorType${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Type</label>
|
||||||
<select name="connectors[${newConnectorIndex}].type" id="connectorType${newConnectorIndex}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
|
<select name="connectors[${newConnectorIndex}].type" id="connectorType${newConnectorIndex}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
|
||||||
{{ range .Connectors }}
|
`+connectors+` </select>
|
||||||
<option value="{{.}}">{{.}}</option>
|
|
||||||
{{ end }}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="connectorConfig${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label>
|
<label for="connectorConfig${newConnectorIndex}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label>
|
||||||
@@ -54,31 +51,28 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-4" id="action_box">
|
<div class="mb-4" id="action_box">
|
||||||
<div class="action mb-4">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button id="action_button" type="button" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Add action</button>
|
<button id="action_button" type="button" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-500 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Add action</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.getElementById('action_button').addEventListener('click', function() {
|
document.getElementById('action_button').addEventListener('click', function() {
|
||||||
const actionsSection = document.getElementById('action_box');
|
const actionsSection = document.getElementById('action_box');
|
||||||
const ii = actionsSection.getElementsByClassName('connector').length;
|
const ii = actionsSection.getElementsByClassName('action').length;
|
||||||
|
|
||||||
const newConnectorHTML = `
|
const newActionHTML = `
|
||||||
<div class="action mb-4">
|
<div class="action mb-4">
|
||||||
<label for="actionsName${ii}" class="block text-lg font-medium text-gray-400">Connector Type</label>
|
<label for="actionsName${ii}" class="block text-lg font-medium text-gray-400">Action</label>
|
||||||
<select name="actions[${ii}].name" id="actionsName${ii}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
|
<select name="actions[${ii}].name" id="actionsName${ii}" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white">
|
||||||
{{ range .Actions }}
|
`+actions+`</select>
|
||||||
<option value="{{.}}">{{.}}</option>
|
|
||||||
{{ end }}
|
|
||||||
</select>
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="actionsConfig${ii}" class="block text-lg font-medium text-gray-400">Connector Config (JSON)</label>
|
<label for="actionsConfig${ii}" class="block text-lg font-medium text-gray-400">Action Config (JSON)</label>
|
||||||
<textarea id="actionsConfig${ii}" name="actions[${ii}].config" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder='{"results":"5"}'></textarea>
|
<textarea id="actionsConfig${ii}" name="actions[${ii}].config" class="mt-1 focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-gray-700 text-white" placeholder='{"results":"5"}'></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
actionsSection.insertAdjacentHTML('beforeend', newConnectorHTML);
|
actionsSection.insertAdjacentHTML('beforeend', newActionHTML);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -5,9 +5,11 @@ go 1.22
|
|||||||
toolchain go1.22.2
|
toolchain go1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bwmarrin/discordgo v0.28.1
|
||||||
github.com/donseba/go-htmx v1.8.0
|
github.com/donseba/go-htmx v1.8.0
|
||||||
github.com/go-telegram/bot v1.2.1
|
github.com/go-telegram/bot v1.2.1
|
||||||
github.com/gofiber/fiber/v2 v2.52.4
|
github.com/gofiber/fiber/v2 v2.52.4
|
||||||
|
github.com/google/go-github/v61 v61.0.0
|
||||||
github.com/onsi/ginkgo/v2 v2.15.0
|
github.com/onsi/ginkgo/v2 v2.15.0
|
||||||
github.com/onsi/gomega v1.31.1
|
github.com/onsi/gomega v1.31.1
|
||||||
github.com/sap-nocops/duckduckgogo v0.0.0-20201102135645-176990152850
|
github.com/sap-nocops/duckduckgogo v0.0.0-20201102135645-176990152850
|
||||||
@@ -23,6 +25,7 @@ require (
|
|||||||
github.com/go-logr/logr v1.3.0 // indirect
|
github.com/go-logr/logr v1.3.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
@@ -34,6 +37,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.9.0 // indirect
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
|||||||
15
go.sum
15
go.sum
@@ -4,6 +4,8 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
|
|||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
|
||||||
|
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -24,9 +26,14 @@ github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84Egg
|
|||||||
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
|
||||||
|
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
|
||||||
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -69,19 +76,27 @@ github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHY
|
|||||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
38
slack.yaml
Normal file
38
slack.yaml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
## To install:
|
||||||
|
## Create an app from this manifest.
|
||||||
|
## Generate tokens:
|
||||||
|
## appLevel token from "Basic Information" -> "App-Level Tokens" ( scope connections:writeRoute authorizations:read )
|
||||||
|
## bot token from "OAuth & Permissions" -> "OAuth Tokens for Your Workspace"
|
||||||
|
## When configuring the connector, supply the tokens with:
|
||||||
|
## { "botToken": "OAuth Tokens for Your Workspace", "appToken": "App-Level Tokens" }
|
||||||
|
display_information:
|
||||||
|
name: LocalAgent
|
||||||
|
description: LocalAgent bot
|
||||||
|
background_color: "#0040ff"
|
||||||
|
features:
|
||||||
|
bot_user:
|
||||||
|
display_name: LocalAgent
|
||||||
|
always_online: true
|
||||||
|
oauth_config:
|
||||||
|
scopes:
|
||||||
|
bot:
|
||||||
|
- app_mentions:read
|
||||||
|
- channels:history
|
||||||
|
- channels:read
|
||||||
|
- chat:write
|
||||||
|
- commands
|
||||||
|
- im:history
|
||||||
|
- im:read
|
||||||
|
- im:write
|
||||||
|
- users:read
|
||||||
|
- users:read.email
|
||||||
|
settings:
|
||||||
|
event_subscriptions:
|
||||||
|
bot_events:
|
||||||
|
- app_mention
|
||||||
|
- message.im
|
||||||
|
interactivity:
|
||||||
|
is_enabled: true
|
||||||
|
org_deploy_enabled: false
|
||||||
|
socket_mode_enabled: true
|
||||||
|
token_rotation_enabled: false
|
||||||
Reference in New Issue
Block a user