support attachments

This commit is contained in:
Nicolas Constant 2020-07-22 19:27:25 -04:00
parent 1bcb00cdd5
commit 10104187d5
No known key found for this signature in database
GPG key ID: 1E9F677FB01A5688
10 changed files with 226 additions and 66 deletions

View file

@ -0,0 +1,9 @@
namespace BirdsiteLive.ActivityPub
{
public class Attachment
{
public string type { get; set; }
public string mediaType { get; set; }
public string url { get; set; }
}
}

View file

@ -24,7 +24,7 @@ namespace BirdsiteLive.ActivityPub
//public string conversation { get; set; }
public string content { get; set; }
//public Dictionary<string,string> contentMap { get; set; }
public string[] attachment { get; set; }
public Attachment[] attachment { get; set; }
public string[] tag { get; set; }
//public Dictionary<string, string> replies;
}

View file

@ -0,0 +1,119 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BirdsiteLive.ActivityPub;
using BirdsiteLive.Common.Settings;
using Tweetinvi.Models;
using Tweetinvi.Models.Entities;
namespace BirdsiteLive.Domain
{
public interface IStatusService
{
Note GetStatus(string username, ITweet tweet);
}
public class StatusService : IStatusService
{
private readonly InstanceSettings _instanceSettings;
#region Ctor
public StatusService(InstanceSettings instanceSettings)
{
_instanceSettings = instanceSettings;
}
#endregion
public Note GetStatus(string username, ITweet tweet)
{
var actorUrl = $"https://{_instanceSettings.Domain}/users/{username}";
var noteId = $"https://{_instanceSettings.Domain}/users/{username}/statuses/{tweet.Id}";
var noteUrl = $"https://{_instanceSettings.Domain}/@{username}/{tweet.Id}";
var to = $"{actorUrl}/followers";
var apPublic = "https://www.w3.org/ns/activitystreams#Public";
var note = new Note
{
id = $"{noteId}/activity",
published = tweet.CreatedAt.ToString("s") + "Z",
url = noteUrl,
attributedTo = actorUrl,
//to = new [] {to},
//cc = new [] { apPublic },
to = new[] { to },
cc = new[] { apPublic },
//cc = new string[0],
sensitive = false,
content = $"<p>{tweet.Text}</p>",
attachment = GetAttachments(tweet.Media),
tag = new string[0]
};
return note;
}
private Attachment[] GetAttachments(List<IMediaEntity> media)
{
var result = new List<Attachment>();
foreach (var m in media)
{
var mediaUrl = GetMediaUrl(m);
var mediaType = GetMediaType(m.MediaType, mediaUrl);
if (mediaType == null) continue;
var att = new Attachment
{
type = "Document",
mediaType = mediaType,
url = mediaUrl
};
result.Add(att);
}
return result.ToArray();
}
private string GetMediaUrl(IMediaEntity media)
{
switch (media.MediaType)
{
case "photo": return media.MediaURLHttps;
case "animated_gif": return media.VideoDetails.Variants[0].URL;
case "video": return media.VideoDetails.Variants.OrderByDescending(x => x.Bitrate).First().URL;
default: return null;
}
}
private string GetMediaType(string mediaType, string mediaUrl)
{
switch (mediaType)
{
case "photo":
var ext = Path.GetExtension(mediaUrl);
switch (ext)
{
case ".jpg":
case ".jpeg":
return "image/jpeg";
case ".png":
return "image/png";
}
return null;
case "animated_gif":
return "image/gif";
case "video":
return "video/mp4";
}
return null;
}
}
}

View file

@ -18,8 +18,6 @@ namespace BirdsiteLive.Domain
public interface IUserService
{
Actor GetUser(TwitterUser twitterUser);
//Note GetStatus(TwitterUser user, ITweet tweet);
Note GetStatus(string username, ITweet tweet);
Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity);
Task<bool> UndoFollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityUndoFollow activity);
}
@ -75,41 +73,6 @@ namespace BirdsiteLive.Domain
return user;
}
public Note GetStatus(string username, ITweet tweet)
{
//var actor = GetUser(user);
var actorUrl = $"{_host}/users/{username}";
var noteId = $"{_host}/users/{username}/statuses/{tweet.Id}";
var noteUrl = $"{_host}/@{username}/{tweet.Id}";
var to = $"{actorUrl}/followers";
var apPublic = "https://www.w3.org/ns/activitystreams#Public";
var note = new Note
{
id = $"{noteId}/activity",
published = tweet.CreatedAt.ToString("s") + "Z",
url = noteUrl,
attributedTo = actorUrl,
//to = new [] {to},
//cc = new [] { apPublic },
to = new[] { apPublic },
cc = new[] { to },
sensitive = false,
content = $"<p>{tweet.Text}</p>",
attachment = new string[0],
tag = new string[0]
};
return note;
}
public async Task<bool> FollowRequestedAsync(string signature, string method, string path, string queryString, Dictionary<string, string> requestHeaders, ActivityFollow activity)
{
// Validate

View file

@ -0,0 +1,11 @@
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.Pipeline.Models;
namespace BirdsiteLive.Pipeline.Contracts
{
public interface ISaveProgressionProcessor
{
Task ProcessAsync(UserWithTweetsToSync userWithTweetsToSync, CancellationToken ct);
}
}

View file

@ -6,6 +6,6 @@ namespace BirdsiteLive.Pipeline.Contracts
{
public interface ISendTweetsToFollowersProcessor
{
Task ProcessAsync(UserWithTweetsToSync userWithTweetsToSync, CancellationToken ct);
Task<UserWithTweetsToSync> ProcessAsync(UserWithTweetsToSync userWithTweetsToSync, CancellationToken ct);
}
}

View file

@ -0,0 +1,29 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.Pipeline.Contracts;
using BirdsiteLive.Pipeline.Models;
namespace BirdsiteLive.Pipeline.Processors
{
public class SaveProgressionProcessor : ISaveProgressionProcessor
{
private readonly ITwitterUserDal _twitterUserDal;
#region Ctor
public SaveProgressionProcessor(ITwitterUserDal twitterUserDal)
{
_twitterUserDal = twitterUserDal;
}
#endregion
public async Task ProcessAsync(UserWithTweetsToSync userWithTweetsToSync, CancellationToken ct)
{
var userId = userWithTweetsToSync.User.Id;
var lastPostedTweet = userWithTweetsToSync.Tweets.Select(x => x.Id).Max();
var minimumSync = userWithTweetsToSync.Followers.Select(x => x.FollowingsSyncStatus[userId]).Min();
await _twitterUserDal.UpdateTwitterUserAsync(userId, lastPostedTweet, minimumSync);
}
}
}

View file

@ -1,60 +1,84 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.DAL.Contracts;
using BirdsiteLive.DAL.Models;
using BirdsiteLive.Domain;
using BirdsiteLive.Pipeline.Contracts;
using BirdsiteLive.Pipeline.Models;
using BirdsiteLive.Twitter;
using Tweetinvi.Models;
namespace BirdsiteLive.Pipeline.Processors
{
public class SendTweetsToFollowersProcessor : ISendTweetsToFollowersProcessor
{
private readonly IActivityPubService _activityPubService;
private readonly IUserService _userService;
private readonly IStatusService _statusService;
private readonly IFollowersDal _followersDal;
private readonly ITwitterUserDal _twitterUserDal;
#region Ctor
public SendTweetsToFollowersProcessor(IActivityPubService activityPubService, IUserService userService, IFollowersDal followersDal, ITwitterUserDal twitterUserDal)
public SendTweetsToFollowersProcessor(IActivityPubService activityPubService, IFollowersDal followersDal, IStatusService statusService)
{
_activityPubService = activityPubService;
_userService = userService;
_followersDal = followersDal;
_twitterUserDal = twitterUserDal;
_statusService = statusService;
}
#endregion
public async Task ProcessAsync(UserWithTweetsToSync userWithTweetsToSync, CancellationToken ct)
public async Task<UserWithTweetsToSync> ProcessAsync(UserWithTweetsToSync userWithTweetsToSync, CancellationToken ct)
{
var user = userWithTweetsToSync.User;
var userId = user.Id;
foreach (var follower in userWithTweetsToSync.Followers)
{
var fromStatusId = follower.FollowingsSyncStatus[userId];
var tweetsToSend = userWithTweetsToSync.Tweets.Where(x => x.Id > fromStatusId).OrderBy(x => x.Id).ToList();
var syncStatus = fromStatusId;
foreach (var tweet in tweetsToSend)
try
{
var note = _userService.GetStatus(user.Acct, tweet);
var result = await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), follower.Host, follower.InboxUrl);
if (result == HttpStatusCode.Accepted)
syncStatus = tweet.Id;
else
break;
await ProcessFollowerAsync(userWithTweetsToSync.Tweets, follower, userId, user);
}
catch (Exception e)
{
Console.WriteLine(e);
//TODO handle error
}
follower.FollowingsSyncStatus[userId] = syncStatus;
await _followersDal.UpdateFollowerAsync(follower);
}
var lastPostedTweet = userWithTweetsToSync.Tweets.Select(x => x.Id).Max();
var minimumSync = userWithTweetsToSync.Followers.Select(x => x.FollowingsSyncStatus[userId]).Min();
await _twitterUserDal.UpdateTwitterUserAsync(userId, lastPostedTweet, minimumSync);
return userWithTweetsToSync;
}
private async Task ProcessFollowerAsync(IEnumerable<ITweet> tweets, Follower follower, int userId,
SyncTwitterUser user)
{
var fromStatusId = follower.FollowingsSyncStatus[userId];
var tweetsToSend = tweets.Where(x => x.Id > fromStatusId).OrderBy(x => x.Id).ToList();
var syncStatus = fromStatusId;
try
{
foreach (var tweet in tweetsToSend)
{
var note = _statusService.GetStatus(user.Acct, tweet);
var result = await _activityPubService.PostNewNoteActivity(note, user.Acct, tweet.Id.ToString(), follower.Host,
follower.InboxUrl);
if (result == HttpStatusCode.Accepted)
syncStatus = tweet.Id;
else
throw new Exception("Posting new note activity failed");
}
}
finally
{
if (syncStatus != fromStatusId)
{
follower.FollowingsSyncStatus[userId] = syncStatus;
await _followersDal.UpdateFollowerAsync(follower);
}
}
}
}
}

View file

@ -92,7 +92,7 @@ namespace BirdsiteLive.Controllers
//cc = new [] { apPublic },
sensitive = false,
content = "<p>Woooot</p>",
attachment = new string[0],
attachment = new Attachment[0],
tag = new string[0]
}
};

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using BirdsiteLive.ActivityPub;
@ -18,12 +19,14 @@ namespace BirdsiteLive.Controllers
{
private readonly ITwitterService _twitterService;
private readonly IUserService _userService;
private readonly IStatusService _statusService;
#region Ctor
public UsersController(ITwitterService twitterService, IUserService userService)
public UsersController(ITwitterService twitterService, IUserService userService, IStatusService statusService)
{
_twitterService = twitterService;
_userService = userService;
_statusService = statusService;
}
#endregion
@ -62,7 +65,7 @@ namespace BirdsiteLive.Controllers
//var user = _twitterService.GetUser(id);
//if (user == null) return NotFound();
var status = _userService.GetStatus(id, tweet);
var status = _statusService.GetStatus(id, tweet);
var jsonApUser = JsonConvert.SerializeObject(status);
return Content(jsonApUser, "application/activity+json; charset=utf-8");
}
@ -78,6 +81,8 @@ namespace BirdsiteLive.Controllers
using (var reader = new StreamReader(Request.Body))
{
var body = await reader.ReadToEndAsync();
//System.IO.File.WriteAllText($@"C:\apdebug\{Guid.NewGuid()}.json", body);
var activity = ApDeserializer.ProcessActivity(body);
// Do something
var signature = r.Headers["Signature"].First();