Skip to content

Commit

Permalink
live media
Browse files Browse the repository at this point in the history
  • Loading branch information
pavel-zhur committed Sep 20, 2024
1 parent 26c997a commit bb73a32
Show file tree
Hide file tree
Showing 22 changed files with 4,458 additions and 2 deletions.
7 changes: 5 additions & 2 deletions OneShelf.Videos/OneShelf.Videos.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
builder.Configuration.AddJsonFile("appsettings.Secrets.json");
builder.Services
.AddVideosBusinessLogic(builder.Configuration);
var host = builder.Build();
using var host = builder.Build();

var service1 = host.Services.GetRequiredService<Service1>();
var service2 = host.Services.GetRequiredService<Service2>();
var service3 = host.Services.GetRequiredService<Service3>();
var service4 = host.Services.GetRequiredService<Service4>();

await using var videosDatabase = host.Services.GetRequiredService<VideosDatabase>();
await videosDatabase.Database.MigrateAsync();

await service4.Try();

//await videosDatabase.CreateMissingTopics();
//await videosDatabase.UpdateMessagesTopics();

Expand All @@ -40,4 +43,4 @@

//await service2.UploadPhotos((await service1.GetExport1()).OrderBy(_ => Random.Shared.NextDouble()).ToList());
//await service2.UploadVideos((await service1.GetExport2()).OrderBy(_ => Random.Shared.NextDouble()).ToList());
await service2.CreateAlbums(await service1.GetAlbums());
//await service2.CreateAlbums(await service1.GetAlbums());
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@
public record VideosOptions
{
public required string BasePath { get; init; }

public required string TelegramAuthPath { get; init; }
public required string TelegramApiId { get; init; }
public required string TelegramApiHash { get; init; }
public required string TelegramPhoneNumber { get; init; }
public required IReadOnlyList<long> TelegramChatIds { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="CasCap.Apis.GooglePhotos" Version="3.0.1" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="ExifLibNet" Version="2.1.4" />
<PackageReference Include="WTelegramClient" Version="4.1.9" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public static IServiceCollection AddVideosBusinessLogic(this IServiceCollection
.AddScoped<Service1>()
.AddScoped<Service2>()
.AddScoped<Service3>()
.AddScoped<Service4>()
.AddSingleton<TelegramLoggerInitializer>()
.AddScoped<VideosDatabaseOperations>()
.AddMyGooglePhotos();
}
Expand Down
266 changes: 266 additions & 0 deletions OneShelf.Videos/OneShelf.Videos.BusinessLogic/Services/Service4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using OneShelf.Videos.BusinessLogic.Models;
using OneShelf.Videos.Database;
using OneShelf.Videos.Database.Models;
using OneShelf.Videos.Database.Models.Enums;
using TL;
using WTelegram;
using Document = TL.Document;

namespace OneShelf.Videos.BusinessLogic.Services;

public class Service4(IOptions<VideosOptions> options, ILogger<Service4> logger, VideosDatabase videosDatabase, TelegramLoggerInitializer _) : IDisposable
{
private byte[]? _session;

public async Task Try()
{
await using var client = await Login();

var allChats = await client.Messages_GetAllChats();

foreach (var chatId in options.Value.TelegramChatIds)
{
var chat = allChats.chats[chatId];

await ProcessChat(client, chat);
}
}

private async Task ProcessChat(Client client, ChatBase chat)
{
var inputPeer = chat.ToInputPeer();

var messages = await ReadAllMessages(client, inputPeer);
// await File.WriteAllTextAsync("all.json", JsonConvert.SerializeObject(messages, Formatting.Indented));

await LoadStories(client, inputPeer, messages);

var topics = await GetTopicsAndMedia(messages);

//await client.DownloadFileAsync(topics[0].media[0].document.ToFileLocation(topics[0].media[0].document.LargestThumbSize), outputStream)

logger.LogInformation(string.Join(Environment.NewLine, topics.Select(x => $"{x.Key}={x.Value.name} (d: {x.Value.media.Count(x => x.document != null)}, p: {x.Value.media.Count(x => x.photo != null)})")));

await Save(chat, topics);
}

private async Task Save(ChatBase chat, Dictionary<int, (string name, List<(Document? document, Photo? photo, Message message, string mediaFlags)> media)> topics)
{
var liveChat = await videosDatabase.LiveChats.Include(x => x.Topics).ThenInclude(x => x.Mediae).SingleOrDefaultAsync(x => x.Id == chat.ID);
if (liveChat == null)
{
liveChat = new()
{
Id = chat.ID,
Topics = new List<LiveTopic>(),
};

videosDatabase.LiveChats.Add(liveChat);
}

liveChat.Title = chat.Title;

foreach (var topic in topics)
{
var liveTopic = liveChat.Topics.SingleOrDefault(x => x.Id == topic.Key);
if (liveTopic == null)
{
liveTopic = new()
{
Id = topic.Key,
Mediae = new List<LiveMedia>(),
};

liveChat.Topics.Add(liveTopic);
}

liveTopic.Title = topic.Value.name;

var liveMediae = liveTopic.Mediae.ToDictionary(x => x.Id);
foreach (var media in topic.Value.media)
{
var liveMedia = liveMediae.GetValueOrDefault(media.message.ID);
if (liveMedia == null)
{
liveMedia = new()
{
Id = media.message.ID,
};

liveTopic.Mediae.Add(liveMedia);
}

liveMedia.MessageDate = media.message.fwd_from?.date ?? media.message.Date;
liveMedia.IsForwarded = media.message.fwd_from != null;
liveMedia.MediaType = media.message.media.GetType().ToString();
liveMedia.MediaFlags = media.mediaFlags;

if (media.document is {} document)
{
liveMedia.Type = LiveMediaType.Document;
liveMedia.MediaDate = document.date;
liveMedia.MediaId = document.ID;
liveMedia.FileName = document.Filename;
liveMedia.Flags = document.flags.ToString();
liveMedia.MimeType = document.mime_type;
liveMedia.Size = document.size;
liveMedia.DocumentAttributes = JsonConvert.SerializeObject(document.attributes);
liveMedia.DocumentAttributeTypes = string.Join(", ", document.attributes.Select(a => a.GetType()));

var attributeVideo = document.attributes.OfType<DocumentAttributeVideo>().SingleOrDefault();
if (attributeVideo != null)
{
liveMedia.Width = attributeVideo.w;
liveMedia.Height = attributeVideo.h;
liveMedia.Duration = attributeVideo.duration;
liveMedia.VideoFlags = attributeVideo.flags.ToString();
}
}
else if (media.photo is {} photo)
{
liveMedia.Type = LiveMediaType.Photo;
liveMedia.MediaDate = photo.date;
liveMedia.MediaId = photo.ID;
liveMedia.Flags = photo.flags.ToString();
liveMedia.Size = photo.LargestPhotoSize.FileSize;
liveMedia.Width = photo.LargestPhotoSize.Width;
liveMedia.Height = photo.LargestPhotoSize.Height;
}
else
{
throw new("A media should either be a document or a photo.");
}
}
}

await videosDatabase.SaveChangesAsync();
}

private async Task LoadStories(Client client, InputPeer inputPeer, List<MessageBase> messages)
{
foreach (var message in messages)
{
if (message is Message { media: MessageMediaStory { story: null } story })
{
story.story = (await client.Stories_GetStoriesByID(new InputPeerUserFromMessage { peer = inputPeer, msg_id = message.ID, user_id = story.peer.ID }, story.id)).stories.Single();
}
}
}

private static async Task<Dictionary<int, (string name, List<(Document? document, Photo? photo, Message message, string mediaFlags)> media)>> GetTopicsAndMedia(List<MessageBase> messages)
{
var topics = new Dictionary<int, (string name, List<(Document? document, Photo? photo, Message message, string mediaFlags)> media)>
{
{ 0, ("General", new()) },
};

foreach (var messageBase in messages.OrderBy(x => x.ID))
{
// getting topic id
int? topicId = null;
if (messageBase.ReplyTo is MessageReplyHeader { flags: MessageReplyHeader.Flags flags } header)
{
if (flags.HasFlag(MessageReplyHeader.Flags.has_reply_to_top_id))
{
topicId = header.reply_to_top_id;
}
else if (flags.HasFlag(MessageReplyHeader.Flags.has_reply_to_msg_id))
{
topicId = header.reply_to_msg_id;
}
}

if (topicId.HasValue && !topics.ContainsKey(topicId.Value)) topicId = null;

// handling the topics collection
if (messageBase is MessageService { action: MessageActionTopicCreate messageActionTopicCreate })
{
topics.Add(messageBase.ID, (messageActionTopicCreate.title, new()));
}

if (messageBase is MessageService { action: MessageActionTopicEdit messageActionTopicEdit })
{
topics[topicId!.Value] = (messageActionTopicEdit.title, topics[topicId.Value].media);
}

(Document? document, Photo? photo, Message message, string mediaFlags)? media = messageBase switch
{
Message { media: MessageMediaDocument { document: Document document, flags: { } mediaFlags } } message => (document, null, message, mediaFlags.ToString()),
Message { media: MessageMediaPhoto { photo: Photo photo, flags: { } mediaFlags } } message => (null, photo, message, mediaFlags.ToString()),
Message { media: MessageMediaStory { story: StoryItem { media: MessageMediaDocument { document: Document document, flags: { } mediaFlags } } } } message => (document, null, message, mediaFlags.ToString()),
Message { media: MessageMediaStory { story: StoryItem { media: MessageMediaPhoto { photo: Photo photo, flags: { } mediaFlags } } } } message => (null, photo, message, mediaFlags.ToString()),
_ => default,
};

if (media.HasValue)
topics[topicId ?? 0].media.Add(media.Value);
}

return topics;
}

private static async Task<List<MessageBase>> ReadAllMessages(Client client, InputPeer inputPeer)
{
var offsetId = 0;
List<MessageBase> messages = new();
int? count = null;
while (true)
{
var z = await client.Messages_GetHistory(inputPeer, offset_id: offsetId);
if (!z.Messages.Any()) break;
count ??= z.Count;
messages.AddRange(z.Messages);
offsetId = z.Messages.Min(x => x.ID);
}

if (messages.Count != count) throw new("Could not read all messages.");
return messages;
}

private async Task<Client> Login()
{
Client? client = null;
try
{
client = new(
what => what switch
{
"api_id" => options.Value.TelegramApiId,
"api_hash" => options.Value.TelegramApiHash,
"phone_number" => options.Value.TelegramPhoneNumber,
"verification_code" => null,
"password" => "secret!", // if user has enabled 2FA
_ => null, // let WTelegramClient decide the default config
},
File.Exists(options.Value.TelegramAuthPath)
? await File.ReadAllBytesAsync(options.Value.TelegramAuthPath)
: null,
x => _session = x);

var myself = await client.LoginUserIfNeeded();

logger.LogInformation("We are logged-in as {myself} (id {id})", myself, myself.id);
return client;
}
catch
{
if (client != null) await client.DisposeAsync();
throw;
}
}

public void Dispose()
{
if (_session != null)
{
File.WriteAllBytes(options.Value.TelegramAuthPath, _session);
logger.LogInformation("Successfully disposed the telegram client session.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.Extensions.Logging;

namespace OneShelf.Videos.BusinessLogic.Services;

public class TelegramLoggerInitializer
{
public TelegramLoggerInitializer(ILogger<Service4> logger)
{
WTelegram.Helpers.Log = (logLevel, message) => logger.Log((LogLevel)logLevel, message);
}
}
Loading

0 comments on commit bb73a32

Please sign in to comment.