Skip to content

Commit

Permalink
telegram and slack, mostly working
Browse files Browse the repository at this point in the history
  • Loading branch information
nothingmn committed Sep 17, 2024
1 parent c35bb83 commit 7a1bb21
Show file tree
Hide file tree
Showing 20 changed files with 382 additions and 234 deletions.
24 changes: 22 additions & 2 deletions Ollabotica/BotConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class BotConfiguration

public string AdminChatIdsRaw { get; set; }

public List<long> AllowedChatIds
public List<long> AllowedChatIdsAsLong
{
get
{
Expand All @@ -41,7 +41,7 @@ public List<long> AllowedChatIds
}
}

public List<long> AdminChatIds
public List<long> AdminChatIdsAsLong
{
get
{
Expand All @@ -50,4 +50,24 @@ public List<long> AdminChatIds
.ToList() ?? new List<long>();
}
}

public List<string> AllowedChatIds
{
get
{
return AllowedChatIdsRaw?.Split(',')
.Select(id => id.Trim())
.ToList() ?? new List<string>();
}
}

public List<string> AdminChatIds
{
get
{
return AdminChatIdsRaw?.Split(',')
.Select(id => id.Trim())
.ToList() ?? new List<string>();
}
}
}
2 changes: 1 addition & 1 deletion Ollabotica/BotManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task StartBotsAsync()
foreach (var botConfig in _botConfigurations)
{
// Resolve TelegramBotService from IServiceProvider
var botService = _serviceProvider.GetRequiredKeyedService<IBotService>(ServiceTypes.Telegram.ToString());
var botService = _serviceProvider.GetRequiredKeyedService<IBotService>(botConfig.ServiceType);
await botService.StartAsync(botConfig);
_botServices.Add(botService);
}
Expand Down
150 changes: 150 additions & 0 deletions Ollabotica/BotServices/SlackBotService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using Microsoft.AspNetCore.Mvc.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.VisualBasic;
using Newtonsoft.Json.Linq;
using Ollabotica.ChatServices;
using OllamaSharp;
using Slack.NetStandard.AsyncEnumerable;
using Slack.NetStandard.Interaction;
using Slack.NetStandard.Messages.Elements.RichText;
using Slack.NetStandard.Socket;
using SlackAPI;
using SlackAPI.WebSocketMessages;
using Telegram.Bot.Types.Enums;

namespace Ollabotica.BotServices;

/// <summary>
/// This class will handle a single bot's Slack and Ollama connections.
/// </summary>
public class SlackBotService : IBotService
{
private BotConfiguration _config;
private SocketModeClient _slackClient;
private OllamaApiClient _ollamaClient;
private readonly ILogger<SlackBotService> _logger;
private readonly MessageInputRouter _messageInputRouter;
private readonly MessageOutputRouter _messageOutputRouter;
private readonly SlackChatService _slackChatService;
private ClientWebSocket _clientWebSocket;
private Chat _ollamaChat;
private CancellationTokenSource _cts;

// Inject all required dependencies via constructor
public SlackBotService(ILogger<SlackBotService> logger, MessageInputRouter messageInputRouter, MessageOutputRouter messageOutputRouter, SlackChatService chatService)
{
_logger = logger;
_messageInputRouter = messageInputRouter;
_messageOutputRouter = messageOutputRouter;
_slackChatService = chatService;
_cts = new CancellationTokenSource();
}

public async Task StartAsync(BotConfiguration botConfig)
{
_config = botConfig;
_ollamaClient = new OllamaApiClient(botConfig.OllamaUrl, botConfig.DefaultModel);
_ollamaClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {botConfig.OllamaToken}");
_ollamaChat = new Chat(_ollamaClient, "");

_clientWebSocket = new ClientWebSocket();
var _slackClient = new SocketModeClient();
await _slackClient.ConnectAsync(botConfig.ChatAuthToken);
_slackChatService.Init(_slackClient);

await foreach (var envelope in _slackClient.EnvelopeAsyncEnumerable(_cts.Token))
{
await HandleMessageAsync(envelope);
}

_logger.LogInformation($"Bot {_config.Name} started for Slack.");
}

public async Task StopAsync()
{
_cts.Cancel();
await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "App shutting down", CancellationToken.None);
_slackClient.Dispose();
_logger.LogInformation("Bot stopped.");
}

private async Task HandleMessageAsync(Envelope slackMessage)
{
if (!slackMessage.Type.Equals("events_api"))
return; // Ignore bot messages

//(Slack.NetStandard.EventsApi.EventCallback)slackMessage.Payload).Event
var payload = (slackMessage.Payload as Slack.NetStandard.EventsApi.EventCallback);
if (payload is null)
return;

var message = (Slack.NetStandard.Messages.Message)payload.Event;

if (message is null)
return;

_logger.LogInformation($"Received Slack slackMessage: {message.Text} from user {message.User} in {message.Channel.NameNormalized}");

bool isAdmin = _config.AdminChatIds.Contains(message.User);

var m = new ChatMessage()
{
MessageId = slackMessage.EnvelopeId,
IncomingText = message.Text,
ChatId = slackMessage.EnvelopeId,
UserIdentity = $"{message.User}"
};

if (_config.AllowedChatIds.Contains(message.User))
{
if (m.IncomingText != null)
{
_logger.LogInformation(
$"Received chat slackMessage from: {m.UserIdentity} for {_slackChatService.BotId}: {m.IncomingText}");

try
{
// Route the slackMessage through the input processors
var shouldContinue = await _messageInputRouter.Route(m, _ollamaChat, _slackChatService, isAdmin, _config);

if (shouldContinue)
{
var p = "";
if (string.IsNullOrWhiteSpace(p)) p = m.IncomingText;
// Send the prompt to Ollama and gather response
await foreach (var answerToken in _ollamaChat.Send(p))
{
await _slackChatService.SendChatActionAsync(m, "Typing");
m.OutgoingText += p;
await _messageOutputRouter.Route(m, _ollamaChat, _slackChatService, isAdmin, answerToken, _config);
}
await _messageOutputRouter.Route(m, _ollamaChat, _slackChatService, isAdmin, "\n", _config);
}
}
catch (Exception e)
{
_logger.LogError(e, $"Error processing slackMessage {m.ChatId}");
if (isAdmin)
{
m.OutgoingText = e.ToString();
await _slackChatService.SendTextMessageAsync(m);
}
}
}
else
{
m.OutgoingText = "I can only process text messages.";
await _slackChatService.SendTextMessageAsync(m);
}
}
else
{
_logger.LogWarning($"Received slackMessage from unauthorized chat: {message.User}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;

namespace Ollabotica;
namespace Ollabotica.BotServices;

/// <summary>
/// This class will handle a single bot's Telegram and Ollama connections.
Expand Down Expand Up @@ -65,11 +65,11 @@ private async Task HandleUpdateAsync(ITelegramBotClient client, Update update, C
if (update.Type == UpdateType.Message && update.Message != null)
{
var message = update.Message;
await _telegramClient.SendChatActionAsync(message.Chat.Id, ChatAction.Typing);
await _telegramClient.SendChatActionAsync(message.Chat.Id.ToString(), ChatAction.Typing);

bool isAdmin = _config.AdminChatIds.Contains(message.Chat.Id);
bool isAdmin = _config.AdminChatIdsAsLong.Contains(message.Chat.Id);

if (_config.AllowedChatIds.Contains(message.Chat.Id))
if (_config.AllowedChatIdsAsLong.Contains(message.Chat.Id))
{
if (message.Text != null)
{
Expand All @@ -78,8 +78,15 @@ private async Task HandleUpdateAsync(ITelegramBotClient client, Update update, C

try
{
var m = new ChatMessage()
{
MessageId = message.Chat.Id.ToString(),
IncomingText = message.Text,
ChatId = message.Chat.Id.ToString(),
UserIdentity = $"{message.Chat.FirstName} {message.Chat.LastName}"
};
// Route the message through the input processors
var shouldContinue = await _messageInputRouter.Route(message, _ollamaChat, _telegramChatService, isAdmin, _config);
var shouldContinue = await _messageInputRouter.Route(m, _ollamaChat, _telegramChatService, isAdmin, _config);

if (shouldContinue)
{
Expand All @@ -88,24 +95,24 @@ private async Task HandleUpdateAsync(ITelegramBotClient client, Update update, C
// Send the prompt to Ollama and gather response
await foreach (var answerToken in _ollamaChat.Send(p))
{
await _telegramClient.SendChatActionAsync(message.Chat.Id, ChatAction.Typing);
await _messageOutputRouter.Route(message, _ollamaChat, _telegramChatService, isAdmin, answerToken, _config);
await _telegramClient.SendChatActionAsync(message.Chat.Id.ToString(), ChatAction.Typing);
await _messageOutputRouter.Route(m, _ollamaChat, _telegramChatService, isAdmin, answerToken, _config);
}
await _messageOutputRouter.Route(message, _ollamaChat, _telegramChatService, isAdmin, "\n", _config);
await _messageOutputRouter.Route(m, _ollamaChat, _telegramChatService, isAdmin, "\n", _config);
}
}
catch (Exception e)
{
_logger.LogError(e, $"Error processing message {message.MessageId}");
if (isAdmin)
{
await _telegramClient.SendTextMessageAsync(message.Chat.Id, e.ToString(), cancellationToken: cancellationToken);
await _telegramClient.SendTextMessageAsync(message.Chat.Id.ToString(), e.ToString(), cancellationToken: cancellationToken);
}
}
}
else
{
await _telegramClient.SendTextMessageAsync(message.Chat.Id, "I can only process text messages.", cancellationToken: cancellationToken);
await _telegramClient.SendTextMessageAsync(message.Chat.Id.ToString(), "I can only process text messages.", cancellationToken: cancellationToken);
}
}
else
Expand Down
16 changes: 16 additions & 0 deletions Ollabotica/ChatMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ollabotica;

public class ChatMessage
{
public string IncomingText { get; set; }
public string OutgoingText { get; set; }
public string UserIdentity { get; set; }
public string MessageId { get; set; }
public string ChatId { get; set; }
}
76 changes: 76 additions & 0 deletions Ollabotica/ChatServices/SlackChatService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using SlackAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.Logging;
using Slack.NetStandard.AsyncEnumerable;
using Slack.NetStandard.Messages.Blocks;
using Slack.NetStandard.Socket;
using SlackAPI.WebSocketMessages;
using Telegram.Bot;

namespace Ollabotica.ChatServices;

public class SlackChatService : IChatService
{
private readonly ILogger<SlackChatService> _log;
private SocketModeClient client = null;

public SlackChatService(ILogger<SlackChatService> log)
{
_log = log;
}

public void Init<T>(T chatClient) where T : class
{
client = chatClient as SocketModeClient;
}

public string BotId
{
get
{
return this.GetHashCode().ToString();
}
}

public async Task SendChatActionAsync(ChatMessage message, string action)
{
await client.Send(System.Text.Json.JsonSerializer.Serialize(new Typing() { }));
}

public async Task SendTextMessageAsync(ChatMessage message, string text)
{
message.OutgoingText = text;
await this.SendTextMessageAsync(message);
}

public async Task SendTextMessageAsync(ChatMessage message)
{
_log.LogInformation("Sending message to chatId: {chatId}, message: {text}", message.ChatId, message.OutgoingText);
var msg = System.Text.Json.JsonSerializer.Serialize(new Acknowledge()
{
EnvelopeId = message.ChatId,
Payload = new Slack.NetStandard.Messages.Message()
{
Blocks = new List<IMessageBlock>
{
new Section(message.OutgoingText)
}
}
});
_log.LogInformation("Message Sent: {msg}", msg);
try
{
await client.Send(msg);
}
catch (Exception e)
{
Console.WriteLine(e);
}

}
}
Loading

0 comments on commit 7a1bb21

Please sign in to comment.