Skip to content

Commit

Permalink
Check Threads container API for readiness
Browse files Browse the repository at this point in the history
30 second delay is dumb
Also add Threads link to feeds
  • Loading branch information
golf1052 committed Jun 21, 2024
1 parent 7bca2c2 commit a9fd654
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 26 deletions.
6 changes: 6 additions & 0 deletions SeattleCarsInBikeLanes.Tests/AdminPageControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ public AdminPageControllerTests()
Id = "002",
Permalink = "https://threads.net/002"
});
mockThreadsClient.Setup(c => c.GetThreadsMediaContainerStatus(It.IsAny<string>()).Result)
.Returns(new ThreadsMediaContainerStatus()
{
Id = "001",
Status = "FINISHED"
});

mockSecretClient.Setup(m => m.GetSecret(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
.Returns(Azure.Response.FromValue(SecretModelFactory.KeyVaultSecret(new SecretProperties("test"), "test"), Mock.Of<Azure.Response>()));
Expand Down
73 changes: 48 additions & 25 deletions SeattleCarsInBikeLanes/Controllers/AdminPageController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -619,14 +619,18 @@ public async Task<IActionResult> PostTweet([FromBody] PostTweetRequest request)
tweetText,
tweetImageLinks[0]);
// Threads API recommends waiting 30 seconds between creating the media container and publishing it
await Task.Delay(TimeSpan.FromSeconds(30));
string threadsPostId = await threadsClient.PublishThreadsMediaContainer(threadsMediaContainerId);
ThreadsMediaObject uploadedThreadPost = await threadsClient.GetThreadsMediaObject(threadsPostId,
"id,permalink");

foreach (var reportedItem in reportedItems)
// but we'll check the container status API instead.
var containerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, threadsMediaContainerId);
if (containerStatus.Status == "FINISHED")
{
reportedItem.ThreadsLink = uploadedThreadPost.Permalink;
string threadsPostId = await threadsClient.PublishThreadsMediaContainer(threadsMediaContainerId);
ThreadsMediaObject uploadedThreadPost = await threadsClient.GetThreadsMediaObject(threadsPostId,
"id,permalink");

foreach (var reportedItem in reportedItems)
{
reportedItem.ThreadsLink = uploadedThreadPost.Permalink;
}
}
}
else if (tweetImageLinks.Count > 1)
Expand All @@ -649,14 +653,17 @@ public async Task<IActionResult> PostTweet([FromBody] PostTweetRequest request)
null,
null,
containerIds);
await Task.Delay(TimeSpan.FromSeconds(30));
string threadsPostId = await threadsClient.PublishThreadsMediaContainer(carouselContainerId);
ThreadsMediaObject uploadedThreadsPost = await threadsClient.GetThreadsMediaObject(threadsPostId,
"id,permalink");

foreach (var reportedItem in reportedItems)
var containerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, carouselContainerId);
if (containerStatus.Status == "FINISHED")
{
reportedItem.ThreadsLink = uploadedThreadsPost.Permalink;
string threadsPostId = await threadsClient.PublishThreadsMediaContainer(carouselContainerId);
ThreadsMediaObject uploadedThreadsPost = await threadsClient.GetThreadsMediaObject(threadsPostId,
"id,permalink");

foreach (var reportedItem in reportedItems)
{
reportedItem.ThreadsLink = uploadedThreadsPost.Permalink;
}
}
}

Expand Down Expand Up @@ -1247,20 +1254,26 @@ public async Task<IActionResult> PostMonthlyStats([FromBody] PostMonthlyStatsReq
// Finally post to Threads
try
{
string firstThreadsPostId;
string firstThreadsPostId = string.Empty;
if (!skipMostCars)
{
string firstCreationId = await threadsClient.CreateThreadsMediaContainer("TEXT",
$"{introText}{mostCarsText} {GetSocialLinkForThreads(mostCars[0])}");
await Task.Delay(TimeSpan.FromSeconds(30));
firstThreadsPostId = await threadsClient.PublishThreadsMediaContainer(firstCreationId);
var firstContainerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, firstCreationId);
if (firstContainerStatus.Status == "FINISHED")
{
firstThreadsPostId = await threadsClient.PublishThreadsMediaContainer(firstCreationId);
}
}
else
{
string firstCreationId = await threadsClient.CreateThreadsMediaContainer("TEXT",
$"{introText}");
await Task.Delay(TimeSpan.FromSeconds(30));
firstThreadsPostId = await threadsClient.PublishThreadsMediaContainer(firstCreationId);
var firstContainerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, firstCreationId);
if (firstContainerStatus.Status == "FINISHED")
{
firstThreadsPostId = await threadsClient.PublishThreadsMediaContainer(firstCreationId);
}
}

string latestThreadsPostId = firstThreadsPostId;
Expand All @@ -1274,23 +1287,33 @@ public async Task<IActionResult> PostMonthlyStats([FromBody] PostMonthlyStatsReq
string creationId = await threadsClient.CreateThreadsMediaContainer("TEXT",
$"{mostCarsText} {GetSocialLinkForThreads(item)}",
replyToId: latestThreadsPostId);
await Task.Delay(TimeSpan.FromSeconds(30));
latestThreadsPostId = await threadsClient.PublishThreadsMediaContainer(creationId);
var containerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, creationId);
if (containerStatus.Status == "FINISHED")
{
latestThreadsPostId = await threadsClient.PublishThreadsMediaContainer(creationId);
}
}
}
}

string secondCreationId = await threadsClient.CreateThreadsMediaContainer("TEXT",
$"{mostRidiculousText} {GetSocialLinkForThreads(mostRidiculousReportedItem)}",
replyToId: latestThreadsPostId);
await Task.Delay(TimeSpan.FromSeconds(30));
string secondThreadsPostId = await threadsClient.PublishThreadsMediaContainer(secondCreationId);
var secondContainerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, secondCreationId);
string secondThreadsPostId = string.Empty;
if (secondContainerStatus.Status == "FINISHED")
{
secondThreadsPostId = await threadsClient.PublishThreadsMediaContainer(secondCreationId);
}

string thirdCreationId = await threadsClient.CreateThreadsMediaContainer("TEXT",
$"{worstIntersectionText}",
replyToId: secondThreadsPostId);
await Task.Delay(TimeSpan.FromSeconds(30));
string thirdThreadsPostId = await threadsClient.PublishThreadsMediaContainer(thirdCreationId);
var thirdContainerStatus = await helperMethods.WaitForThreadsMediaContainer(threadsClient, thirdCreationId);
if (thirdContainerStatus.Status == "FINISHED")
{
string thirdThreadsPostId = await threadsClient.PublishThreadsMediaContainer(thirdCreationId);
}
}
catch (Exception ex)
{
Expand Down
41 changes: 41 additions & 0 deletions SeattleCarsInBikeLanes/HelperMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Azure.Maps.Search;
using Azure.Maps.Search.Models;
using Azure.Security.KeyVault.Secrets;
using golf1052.ThreadsAPI;
using golf1052.ThreadsAPI.Models;
using HtmlAgilityPack;
using LinqToTwitter;
using LinqToTwitter.Common;
Expand Down Expand Up @@ -470,5 +472,44 @@ public virtual string GetBlueskyPostUrl(string atUri)
string[] splitUri = atUri.Split('/');
return $"https://bsky.app/profile/{splitUri[2]}/post/{splitUri[4]}";
}

public async Task<ThreadsMediaContainerStatus> WaitForThreadsMediaContainer(ThreadsClient threadsClient,
string containerId,
long delayInMilliseconds = 250,
long timeoutInMilliseconds = 5 * 1000 * 60)
{
DateTime startTime = DateTime.UtcNow;
DateTime timeoutTime = startTime + TimeSpan.FromMilliseconds(timeoutInMilliseconds);
string containerStatusString = string.Empty;
ThreadsMediaContainerStatus containerStatus = await threadsClient.GetThreadsMediaContainerStatus(containerId);
if (string.IsNullOrWhiteSpace(containerStatus.Status))
{
throw new Exception("Container status is empty");
}
containerStatusString = containerStatus.Status;

while (containerStatusString == "IN_PROGRESS")
{
if (DateTime.UtcNow > timeoutTime)
{
throw new Exception("Container status is still in progress after timeout");
}

await Task.Delay(TimeSpan.FromMilliseconds(delayInMilliseconds));
containerStatus = await threadsClient.GetThreadsMediaContainerStatus(containerId);
if (string.IsNullOrWhiteSpace(containerStatus.Status))
{
throw new Exception("Container status is empty");
}
containerStatusString = containerStatus.Status;
}

if (containerStatusString == "EXPIRED" || containerStatusString == "ERROR")
{
throw new Exception("Container status is expired or has errored");
}

return containerStatus;
}
}
}
5 changes: 5 additions & 0 deletions SeattleCarsInBikeLanes/Providers/FeedProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ private void AddReportedItemToFeed(ReportedItem reportedItem, List<SyndicationIt
contentBuilder.Append($"<p><a href=\"{reportedItem.BlueskyLink}\">Bluesky post</a></p>");
}

if (!string.IsNullOrWhiteSpace(reportedItem.ThreadsLink))
{
contentBuilder.Append($"<p><a href=\"{reportedItem.ThreadsLink}\">Threads post</a></p>");
}

TextSyndicationContent content = SyndicationContent.CreateHtmlContent(contentBuilder.ToString());
DateTimeOffset pubDate = new DateTimeOffset(reportedItem.CreatedAt);
SyndicationItem item = new SyndicationItem(titleBuilder.ToString(), content, null, reportedItem.TweetId, pubDate);
Expand Down
2 changes: 1 addition & 1 deletion SeattleCarsInBikeLanes/SeattleCarsInBikeLanes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageReference Include="golf1052.atproto.net" Version="0.3.0" />
<PackageReference Include="golf1052.Mastodon" Version="0.7.1" />
<PackageReference Include="golf1052.ThreadsAPI" Version="0.1.1" />
<PackageReference Include="golf1052.ThreadsAPI" Version="0.2.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.57" />
<PackageReference Include="idunno.Authentication.Basic" Version="2.3.1" />
<PackageReference Include="Imgur.API" Version="5.0.0" />
Expand Down

0 comments on commit a9fd654

Please sign in to comment.