mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-21 05:43:19 -07:00
Merge pull request #2095 from tidusjar/feature/mattermost-fix
Fixed #2055 and #1903
This commit is contained in:
commit
3d06815399
5 changed files with 381 additions and 20 deletions
|
@ -5,6 +5,6 @@ namespace Ombi.Api.Mattermost
|
||||||
{
|
{
|
||||||
public interface IMattermostApi
|
public interface IMattermostApi
|
||||||
{
|
{
|
||||||
Task<string> PushAsync(string webhook, MattermostBody message);
|
Task PushAsync(string webhook, MattermostMessage message);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,14 +14,10 @@ namespace Ombi.Api.Mattermost
|
||||||
|
|
||||||
private readonly IApi _api;
|
private readonly IApi _api;
|
||||||
|
|
||||||
public async Task<string> PushAsync(string webhook, MattermostBody message)
|
public async Task PushAsync(string webhook, MattermostMessage message)
|
||||||
{
|
{
|
||||||
var request = new Request(string.Empty, webhook, HttpMethod.Post);
|
var client = new MatterhookClient(webhook);
|
||||||
|
await client.PostAsync(_api, message);
|
||||||
request.AddJsonBody(message);
|
|
||||||
|
|
||||||
var result = await _api.RequestContent(request);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
168
src/Ombi.Api.Mattermost/Models/MattermostClient.cs
Normal file
168
src/Ombi.Api.Mattermost/Models/MattermostClient.cs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Code taken from https://github.com/PromoFaux/Matterhook.NET.MatterhookClient
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Mattermost.Models
|
||||||
|
{
|
||||||
|
public class MatterhookClient
|
||||||
|
{
|
||||||
|
private readonly Uri _webhookUrl;
|
||||||
|
private readonly HttpClient _httpClient = new HttpClient();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new Mattermost Client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="webhookUrl">The URL of your Mattermost Webhook</param>
|
||||||
|
/// <param name="timeoutSeconds">Timeout Value (Default 100)</param>
|
||||||
|
public MatterhookClient(string webhookUrl, int timeoutSeconds = 100)
|
||||||
|
{
|
||||||
|
if (!Uri.TryCreate(webhookUrl, UriKind.Absolute, out _webhookUrl))
|
||||||
|
throw new ArgumentException("Mattermost URL invalid");
|
||||||
|
|
||||||
|
_httpClient.Timeout = new TimeSpan(0, 0, 0, timeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MattermostMessage CloneMessage(MattermostMessage inMsg)
|
||||||
|
{
|
||||||
|
var outMsg = new MattermostMessage
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Channel = inMsg.Channel,
|
||||||
|
Username = inMsg.Username,
|
||||||
|
IconUrl = inMsg.IconUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
return outMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MattermostAttachment CloneAttachment(MattermostAttachment inAtt)
|
||||||
|
{
|
||||||
|
var outAtt = new MattermostAttachment
|
||||||
|
{
|
||||||
|
AuthorIcon = inAtt.AuthorIcon,
|
||||||
|
AuthorLink = inAtt.AuthorLink,
|
||||||
|
AuthorName = inAtt.AuthorName,
|
||||||
|
Color = inAtt.Color,
|
||||||
|
Fallback = inAtt.Fallback,
|
||||||
|
Fields = inAtt.Fields,
|
||||||
|
ImageUrl = inAtt.ImageUrl,
|
||||||
|
Pretext = inAtt.Pretext,
|
||||||
|
ThumbUrl = inAtt.ThumbUrl,
|
||||||
|
Title = inAtt.Title,
|
||||||
|
TitleLink = inAtt.TitleLink,
|
||||||
|
Text = ""
|
||||||
|
};
|
||||||
|
return outAtt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Post Message to Mattermost server. Messages will be automatically split if total text length > 4000
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="api"></param>
|
||||||
|
/// <param name="inMessage">The messsage you wish to send</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task PostAsync(IApi api, MattermostMessage inMessage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var outMessages = new List<MattermostMessage>();
|
||||||
|
|
||||||
|
var msgCount = 0;
|
||||||
|
|
||||||
|
var lines = new string[] { };
|
||||||
|
if (inMessage.Text != null)
|
||||||
|
{
|
||||||
|
lines = inMessage.Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
//start with one cloned inMessage in the list
|
||||||
|
outMessages.Add(CloneMessage(inMessage));
|
||||||
|
|
||||||
|
//add text from original. If we go over 3800, we'll split it to a new inMessage.
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (line.Length + outMessages[msgCount].Text.Length > 3800)
|
||||||
|
{
|
||||||
|
|
||||||
|
msgCount += 1;
|
||||||
|
outMessages.Add(CloneMessage(inMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
outMessages[msgCount].Text += $"{line}\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Length of text on the last (or first if only one) inMessage.
|
||||||
|
var lenMessageText = outMessages[msgCount].Text.Length;
|
||||||
|
|
||||||
|
//does our original have attachments?
|
||||||
|
if (inMessage.Attachments?.Any() ?? false)
|
||||||
|
{
|
||||||
|
outMessages[msgCount].Attachments = new List<MattermostAttachment>();
|
||||||
|
|
||||||
|
//loop through them in a similar fashion to the inMessage text above.
|
||||||
|
foreach (var att in inMessage.Attachments)
|
||||||
|
{
|
||||||
|
//add this attachment to the outgoing message
|
||||||
|
outMessages[msgCount].Attachments.Add(CloneAttachment(att));
|
||||||
|
//get a count of attachments on this message, and subtract one so we know the index of the current new attachment
|
||||||
|
var attIndex = outMessages[msgCount].Attachments.Count - 1;
|
||||||
|
|
||||||
|
//Get the text lines
|
||||||
|
lines = att.Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
//Get the total length of all attachments on the current outgoing message
|
||||||
|
var lenAllAttsText = outMessages[msgCount].Attachments.Sum(a => a.Text.Length);
|
||||||
|
|
||||||
|
if (lenMessageText + lenAllAttsText + line.Length > 3800)
|
||||||
|
{
|
||||||
|
msgCount += 1;
|
||||||
|
attIndex = 0;
|
||||||
|
outMessages.Add(CloneMessage(inMessage));
|
||||||
|
outMessages[msgCount].Attachments = new List<MattermostAttachment> { CloneAttachment(att) };
|
||||||
|
}
|
||||||
|
|
||||||
|
outMessages[msgCount].Attachments[attIndex].Text += $"{line}\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (outMessages.Count > 1)
|
||||||
|
{
|
||||||
|
var num = 1;
|
||||||
|
foreach (var msg in outMessages)
|
||||||
|
{
|
||||||
|
msg.Text = $"`({num}/{msgCount + 1}): ` " + msg.Text;
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var msg in outMessages)
|
||||||
|
{
|
||||||
|
var request = new Request("", _webhookUrl.ToString(), HttpMethod.Post);
|
||||||
|
request.AddJsonBody(msg);
|
||||||
|
await api.Request(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
src/Ombi.Api.Mattermost/Models/MattermostMessage.cs
Normal file
181
src/Ombi.Api.Mattermost/Models/MattermostMessage.cs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Code taken from https://github.com/PromoFaux/Matterhook.NET.MatterhookClient
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Mattermost.Models
|
||||||
|
|
||||||
|
{
|
||||||
|
public class MattermostMessage
|
||||||
|
{
|
||||||
|
|
||||||
|
//https://docs.mattermost.com/developer/webhooks-incoming.html
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Channel to post to
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "channel")]
|
||||||
|
public string Channel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Username for bot
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "username")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bot/User Icon
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "icon_url")]
|
||||||
|
public Uri IconUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message body. Supports Markdown
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "text")]
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Richtext attachments
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "attachments")]
|
||||||
|
public List<MattermostAttachment> Attachments { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://docs.mattermost.com/developer/message-attachments.html#message-attachments
|
||||||
|
/// </summary>
|
||||||
|
public class MattermostAttachment
|
||||||
|
{
|
||||||
|
//https://docs.mattermost.com/developer/message-attachments.html#attachment-options
|
||||||
|
#region AttachmentOptions
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A required plain-text summary of the post. This is used in notifications, and in clients that don’t support formatted text (eg IRC).
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "fallback")]
|
||||||
|
public string Fallback { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A hex color code that will be used as the left border color for the attachment. If not specified, it will default to match the left hand sidebar header background color.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "color")]
|
||||||
|
public string Color { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional text that should appear above the formatted data
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "pretext")]
|
||||||
|
public string Pretext { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text to be included in the attachment. It can be formatted using Markdown. If it includes more than 300 characters or more than 5 line breaks, the message will be collapsed and a “Show More” link will be added to expand the message.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "text")]
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
//https://docs.mattermost.com/developer/message-attachments.html#author-details
|
||||||
|
#region AuthorDetails
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional name used to identify the author. It will be included in a small section at the top of the attachment.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "author_name")]
|
||||||
|
public string AuthorName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional URL used to hyperlink the author_name. If no author_name is specified, this field does nothing.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "author_link")]
|
||||||
|
public Uri AuthorLink { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional URL used to display a 16x16 pixel icon beside the author_name.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "author_icon")]
|
||||||
|
public Uri AuthorIcon { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
//https://docs.mattermost.com/developer/message-attachments.html#titles
|
||||||
|
#region Titles
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional title displayed below the author information in the attachment.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional URL used to hyperlink the title. If no title is specified, this field does nothing.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "title_link")]
|
||||||
|
public Uri TitleLink { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fields can be included as an optional array within attachments, and are used to display information in a table format inside the attachment.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "fields")]
|
||||||
|
public List<MattermostField> Fields { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
//https://docs.mattermost.com/developer/message-attachments.html#images
|
||||||
|
#region Images
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional URL to an image file (GIF, JPEG, PNG, or BMP) that is displayed inside a message attachment.
|
||||||
|
/// Large images are resized to a maximum width of 400px or a maximum height of 300px, while still maintaining the original aspect ratio.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "image_url")]
|
||||||
|
public Uri ImageUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An optional URL to an image file(GIF, JPEG, PNG, or BMP) that is displayed as a 75x75 pixel thumbnail on the right side of an attachment.
|
||||||
|
/// We recommend using an image that is already 75x75 pixels, but larger images will be scaled down with the aspect ratio maintained.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "thumb_url")]
|
||||||
|
public Uri ThumbUrl { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://docs.mattermost.com/developer/message-attachments.html#fieldshttps://docs.mattermost.com/developer/message-attachments.html#fields
|
||||||
|
/// </summary>
|
||||||
|
public class MattermostField
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A title shown in the table above the value.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text value of the field. It can be formatted using Markdown.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "value")]
|
||||||
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optionally set to “True” or “False” to indicate whether the value is short enough to be displayed beside other values.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(PropertyName = "short")]
|
||||||
|
public bool Short { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,10 +59,18 @@ namespace Ombi.Notifications.Agents
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
|
//notification.Other.Add("overview", model.RequestType == RequestType.Movie ? base.MovieRequest.Overview : TvRequest.);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddOtherInformation(NotificationOptions model, NotificationMessage notification,
|
||||||
|
NotificationMessageContent parsed)
|
||||||
|
{
|
||||||
|
notification.Other.Add("image", parsed.Image);
|
||||||
|
notification.Other.Add("title", model.RequestType == RequestType.Movie ? MovieRequest.Title : TvRequest.Title);
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task NewIssue(NotificationOptions model, MattermostNotificationSettings settings)
|
protected override async Task NewIssue(NotificationOptions model, MattermostNotificationSettings settings)
|
||||||
{
|
{
|
||||||
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.Issue, model);
|
var parsed = await LoadTemplate(NotificationAgent.Mattermost, NotificationType.Issue, model);
|
||||||
|
@ -75,7 +83,7 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +99,7 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +115,7 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +157,7 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +174,7 @@ namespace Ombi.Notifications.Agents
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +190,7 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
Message = parsed.Message,
|
Message = parsed.Message,
|
||||||
};
|
};
|
||||||
notification.Other.Add("image", parsed.Image);
|
AddOtherInformation(model, notification, parsed);
|
||||||
await Send(notification, settings);
|
await Send(notification, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,12 +198,20 @@ namespace Ombi.Notifications.Agents
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var body = new MattermostBody
|
var body = new MattermostMessage
|
||||||
{
|
{
|
||||||
username = string.IsNullOrEmpty(settings.Username) ? "Ombi" : settings.Username,
|
Username = string.IsNullOrEmpty(settings.Username) ? "Ombi" : settings.Username,
|
||||||
channel = settings.Channel,
|
Channel = settings.Channel,
|
||||||
text = model.Message,
|
Text = model.Message,
|
||||||
icon_url = settings.IconUrl
|
IconUrl = new Uri(settings.IconUrl),
|
||||||
|
Attachments = new List<MattermostAttachment>
|
||||||
|
{
|
||||||
|
new MattermostAttachment
|
||||||
|
{
|
||||||
|
Title = model.Other.ContainsKey("title") ? model.Other["title"] : string.Empty,
|
||||||
|
ImageUrl = model.Other.ContainsKey("image") ? new Uri(model.Other["image"]) : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
await Api.PushAsync(settings.WebhookUrl, body);
|
await Api.PushAsync(settings.WebhookUrl, body);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue