telegram and instagram wrappers, DtsodFile

This commit is contained in:
timerix 2023-02-23 18:33:47 +06:00
parent adbebc37ec
commit 327d06b3d0
12 changed files with 436 additions and 238 deletions

View File

@ -1,57 +1,39 @@
namespace InstaFollowersOverseer;
public class Config
public class Config : DtsodFile
{
private const string config_file="config.dtsod";
private const string config_example_file="config-example.dtsod";
#nullable disable
public string botToken;
public string instagramLogin;
public string instagramPassword;
#nullable enable
public Config(DtsodV23 configDtsod)
{
botToken = configDtsod[nameof(botToken)];
instagramLogin = configDtsod[nameof(instagramLogin)];
instagramPassword = configDtsod[nameof(instagramPassword)];
}
public Config(string fileNameWithoutExt) : base(fileNameWithoutExt) { }
public static Config ReadFromFile()
public override void LoadFromFile()
{
if (!File.Exists(config_file))
var dtsod = ReadDtsodFromFile(true);
try
{
EmbeddedResources.CopyToFile(
$"{EmbeddedResourcesPrefix}.{config_example_file}",
config_example_file);
throw new Exception($"File {config_file} doesnt exist. You have create config. See {config_example_file}");
botToken = dtsod[nameof(botToken)];
instagramLogin = dtsod[nameof(instagramLogin)];
instagramPassword = dtsod[nameof(instagramPassword)];
}
catch (Exception ex)
{
throw new Exception($"your {FileName} format is invalid\n"
+ $"See {FileExampleName}", innerException: ex);
}
return new Config(new DtsodV23(File.ReadAllText(config_file)));
}
public DtsodV23 ToDtsod()
public override DtsodV23 ToDtsod()
{
var d = new DtsodV23
{
{ nameof(botToken), botToken },
{ nameof(instagramLogin), instagramLogin },
{ nameof(instagramLogin), instagramLogin }
{ nameof(instagramPassword), instagramPassword }
};
return d;
}
public override string ToString() => ToDtsod().ToString();
public void SaveToFile()
{
File.Copy(config_file,
$"backups/{config_file}.old-"+
"{DateTime.Now.ToString(MyTimeFormat.ForFileNames)}",
true);
File.OpenWrite(config_file)
.FluentWriteString("#DtsodV23\n")
.WriteString(ToDtsod().ToString());
}
}

64
DtsodFile.cs Normal file
View File

@ -0,0 +1,64 @@
using System.IO;
namespace InstaFollowersOverseer;
public abstract class DtsodFile
{
public readonly string FileNameWithoutExt;
public readonly string FileName;
public readonly string FileExampleName;
public DtsodFile(string fileNameWithoutExt)
{
FileNameWithoutExt = fileNameWithoutExt;
FileName = fileNameWithoutExt + ".dtsod";
FileExampleName = fileNameWithoutExt + "-example.dtsod";
}
public void CreateBackup()
{
string backupPath=$"backups/{FileNameWithoutExt}.d/{FileNameWithoutExt}"
+DateTime.Now.ToString(MyTimeFormat.ForFileNames)+".dtsod";
Program.MainLogger.LogInfo($"creating backup if file {FileName} at path {backupPath}");
File.Copy(FileName,backupPath,false);
}
public DtsodV23 ReadDtsodFromFile(bool trhowIfFileNotFound)
{
Program.MainLogger.LogInfo($"reading file {FileName}");
EmbeddedResources.CopyToFile(
$"{EmbeddedResourcesPrefix}.{FileExampleName}",
FileExampleName);
if (!File.Exists(FileName))
{
File.WriteAllText(FileName, "#DtsodV23\n");
string message = $"file {FileName} doesnt exist, created new blank";
if (trhowIfFileNotFound)
throw new FileNotFoundException(message);
Program.MainLogger.LogWarn(message);
return new DtsodV23();
}
string fileText = File.ReadAllText(FileName);
Program.MainLogger.LogDebug(fileText);
return new DtsodV23(fileText);
}
public abstract void LoadFromFile();
public abstract DtsodV23 ToDtsod();
public void SaveToFile()
{
Program.MainLogger.LogInfo($"saving file {FileName}");
string dtsodStr = ToDtsod().ToString();
Program.MainLogger.LogDebug(dtsodStr);
if(File.Exists(FileName))
CreateBackup();
File.OpenWrite(FileName)
.FluentWriteString("#DtsodV23\n")
.FluentWriteString(dtsodStr)
.Close();
}
}

View File

@ -1,17 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!--project info-->
<Authors>Timerix</Authors>
<Description>Telegram bot that notifies users when somebody follows/unfollows theit instagram accounts</Description>
<RepositoryType>GIT</RepositoryType>
<RepositoryUrl>https://github.com/Timerix22/InstaFollowersOverseer</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<!--compilation properties-->
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<DebugType>embedded</DebugType>
<!--language features-->
<LangVersion>preview</LangVersion>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<!--third-party dependencies-->
<ItemGroup>
<PackageReference Include="InstaSharper" Version="1.4.82" />
<PackageReference Include="Telegram.Bot" Version="18.0.0" />
<PackageReference Include="Telegram.Bots.Extensions.Polling" Version="5.9.0" />
<!--PackageReference Include="Telegram.Bots.Extensions.Polling" Version="5.9.0" /-->
</ItemGroup>
<!--dtlib dependencies-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<ProjectReference Include="..\DTLib\DTLib.Logging\DTLib.Logging.csproj" />
<ProjectReference Include="..\DTLib\DTLib.Dtsod\DTLib.Dtsod.csproj" />
@ -25,6 +37,6 @@
<ItemGroup>
<None Remove="resources\**\*" />
<EmbeddedResource Include="resources\**\*" />
<None Remove=".gitignore" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">ShowAndRun</s:String>
</wpf:ResourceDictionary>

View File

@ -0,0 +1,37 @@
using System.Net.Http;
using InstaSharper.Logger;
namespace InstaFollowersOverseer.Instagram;
public class InstagramApiLogger : IInstaLogger
{
public ContextLogger _logger = new("api", InstagramWrapper.InstagramLogger);
public void LogRequest(HttpRequestMessage r)
{
_logger.LogDebug("http",$"request {r.Method.Method.ToUpper()} from {r.RequestUri}:\n"
+ r.Content?.ReadAsStringAsync().GetAwaiter().GetResult());
}
public void LogRequest(Uri uri)
{
}
public void LogResponse(HttpResponseMessage r)
{
_logger.LogDebug("http",$"responce from " +
(r.RequestMessage!=null && r.RequestMessage.RequestUri!=null ? r.RequestMessage.RequestUri.ToString() : "unknown")
+ $" :\n "+ r.Content.ReadAsStringAsync().GetAwaiter().GetResult());
}
public void LogException(Exception ex)
{
_logger.LogError(ex);
}
public void LogInfo(string info)
{
_logger.LogInfo(info);
}
}

View File

@ -0,0 +1,58 @@
using InstaSharper.API;
using InstaSharper.API.Builder;
using InstaSharper.Classes;
using InstaSharper.Classes.Models;
namespace InstaFollowersOverseer.Instagram;
public static class InstagramWrapper
{
public static ContextLogger InstagramLogger = new("instagram",ParentLogger);
private static IInstaApi Api=null!;
public static async void Init()
{
try
{
InstagramLogger.LogInfo("initializing instagram wrapper");
if (CurrentConfig is null)
throw new NullReferenceException("config is null");
var apiLogger = new InstagramApiLogger();
// disabling http request/responce logging
apiLogger._logger.DebugLogEnabled = false;
Api = InstaApiBuilder.CreateBuilder()
.UseLogger(apiLogger)
.SetUser(new UserSessionData
{
UserName = CurrentConfig.instagramLogin,
Password = CurrentConfig.instagramPassword
})
.SetRequestDelay(RequestDelay.FromSeconds(0, 1))
.Build();
InstagramLogger.LogInfo("instagram login starting");
var rezult= await Api.LoginAsync();
if (!rezult.Succeeded)
throw new Exception("login exception:\n" + rezult.Info + '\n' + rezult.Value);
InstagramLogger.LogInfo("instagram wrapper have initialized and connected successfully");
}
catch (OperationCanceledException) {}
catch (Exception ex)
{
InstagramLogger.LogError("init", ex);
Program.Stop();
}
}
public static async Task<InstaUser?> GetUserAsync(string usernameOrUrl)
{
// url
if (usernameOrUrl.Contains('/'))
{
throw new NotImplementedException("get user by url");
}
// username
var u=await Api.GetUserAsync(usernameOrUrl);
return u.Succeeded ? u.Value : null;
}
}

View File

@ -2,6 +2,7 @@
global using System.Threading.Tasks;
global using System.Linq;
global using System.Collections.Generic;
global using DTLib;
global using DTLib.Filesystem;
global using DTLib.Extensions;
global using DTLib.Dtsod;
@ -10,18 +11,20 @@ global using File = DTLib.Filesystem.File;
global using Directory = DTLib.Filesystem.Directory;
global using Path = DTLib.Filesystem.Path;
global using static InstaFollowersOverseer.SharedData;
using System.Net.Http;
using System.Text;
using System.Threading;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
namespace InstaFollowersOverseer;
static class Program
{
public static readonly ContextLogger MainLogger = new("main", ParentLogger);
private static CancellationTokenSource MainCancel=new();
public static CancellationToken MainCancelToken = MainCancel.Token;
public static void Stop() => MainCancel.Cancel();
static void Main()
{
Console.InputEncoding=Encoding.UTF8;
@ -29,90 +32,31 @@ static class Program
DTLibInternalLogging.SetLogger(MainLogger.ParentLogger);
try
{
config = Config.ReadFromFile();
userSettings = UserSettings.ReadFromFile();
MainLogger.LogInfo("reading config");
CurrentConfig.LoadFromFile();
CurrentUsersData.LoadFromFile();
CancellationTokenSource mainCancel = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
mainCancel.Cancel();
Stop();
Thread.Sleep(1000);
MainLogger.LogInfo("all have cancelled");
e.Cancel = false;
};
var bot = new TelegramBotClient(config.botToken, new HttpClient());
var receiverOptions = new ReceiverOptions
{
AllowedUpdates = { }, // receive all update types
Instagram.InstagramWrapper.Init();
Telegram.TelegramWrapper.Init();
};
bot.StartReceiving(BotApiUpdateHandler, BotApiExceptionHandler, receiverOptions, mainCancel.Token);
Task.Delay(-1, mainCancel.Token).GetAwaiter().GetResult();
Task.Delay(-1, MainCancel.Token).GetAwaiter().GetResult();
Thread.Sleep(1000);
}
catch (OperationCanceledException) {}
catch (Exception ex)
{
MainLogger.LogError(ex);
}
CurrentConfig.SaveToFile();
CurrentUsersData.SaveToFile();
Console.ResetColor();
}
private static ContextLogger botLogger = new ContextLogger("bot", MainLogger.ParentLogger);
static async Task BotApiUpdateHandler(ITelegramBotClient bot, Update update, CancellationToken cls)
{
try
{
switch (update.Type)
{
case UpdateType.Message:
{
var message = update.Message!;
if (message.Text!.StartsWith('/'))
{
botLogger.LogInfo($"user {message.Chat.Id} sent command {message.Text}");
var spl = message.Text.SplitToList(' ');
string command = spl[0].Substring(1);
spl.RemoveAt(0);
string[] args = spl.ToArray();
switch (command)
{
case "start":
await bot.SendTextMessageAsync(message.Chat, "hi");
break;
case "oversee":
break;
// default:
// throw new BotCommandException(command, args);
}
}
else botLogger.LogDebug($"message recieved: {message.Text}");
break;
} /*
case UpdateType.EditedMessage:
break;
case UpdateType.InlineQuery:
break;
case UpdateType.ChosenInlineResult:
break;
case UpdateType.CallbackQuery:
break;*/
default:
botLogger.LogWarn($"unknown update type: {update.Type}");
break;
}
}
catch (Exception ex)
{
botLogger.LogWarn("UpdateHandler", ex);
}
}
static Task BotApiExceptionHandler(ITelegramBotClient bot, Exception ex, CancellationToken cls)
{
botLogger.LogError(ex);
return Task.CompletedTask;
}
}

View File

@ -4,13 +4,10 @@ public static class SharedData
{
internal const string EmbeddedResourcesPrefix = "InstaFollowersOverseer.resources";
#nullable disable
internal static Config config;
internal static UserSettings userSettings;
#nullable enable
internal static Config CurrentConfig = new("config");
internal static UsersData CurrentUsersData = new("users-data");
public static readonly ContextLogger MainLogger = new ContextLogger("main",new CompositeLogger(
new ConsoleLogger(),
new FileLogger("logs","InstaFollowersOverseer"))
);
public static readonly CompositeLogger ParentLogger = new(
new ConsoleLogger(),
new FileLogger("logs", "InstaFollowersOverseer"));
}

134
Telegram/TelegramWrapper.cs Normal file
View File

@ -0,0 +1,134 @@
using System.Net.Http;
using System.Threading;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using InstaFollowersOverseer.Instagram;
namespace InstaFollowersOverseer.Telegram;
public static class TelegramWrapper
{
private static ContextLogger TelegramLogger = new("telegram", ParentLogger);
private static TelegramBotClient Bot=null!;
public static async void Init()
{
try
{
TelegramLogger.LogInfo("initializing telegram wrapper");
if (CurrentConfig is null)
throw new NullReferenceException("config is null");
Bot = new TelegramBotClient(CurrentConfig.botToken, new HttpClient());
await Bot.SetMyCommandsAsync(new BotCommand[]
{
new() { Command = "start", Description = "starts the bot"},
// new() { Command = "help", Description = "shows commands list" },
new() { Command = "oversee", Description = "[instagram username] - " +
"enables notifications about instagram user's followers" },
new() { Command = "list", Description = "shows list of overseeing instagram users" }
});
var receiverOptions = new ReceiverOptions
{
// AllowedUpdates = { }, // receive all update types
};
TelegramLogger.LogInfo("bot starting recieving long polls");
Bot.StartReceiving(BotApiUpdateHandler, BotApiExceptionHandler, receiverOptions, Program.MainCancelToken);
TelegramLogger.LogInfo("telegram wrapper have initialized successfully");
}
catch (OperationCanceledException) {}
catch (Exception ex)
{
TelegramLogger.LogError("init", ex);
Program.Stop();
}
}
private static Task BotApiExceptionHandler(ITelegramBotClient bot, Exception ex, CancellationToken cls)
{
TelegramLogger.LogError(ex);
return Task.CompletedTask;
}
static async Task SendInfoReply(string text, Message replyToMessage)
{
TelegramLogger.LogInfo(text);
await Bot.SendTextMessageAsync(replyToMessage.Chat, text,
replyToMessageId: replyToMessage.MessageId,
parseMode:ParseMode.MarkdownV2);
}
static async Task SendErrorReply(string text, Message replyToMessage)
{
TelegramLogger.LogWarn(text);
await Bot.SendTextMessageAsync(replyToMessage.Chat, "error: "+text,
replyToMessageId: replyToMessage.MessageId,
parseMode:ParseMode.MarkdownV2);
}
private static async Task BotApiUpdateHandler(ITelegramBotClient bot, Update update, CancellationToken cls)
{
try
{
switch (update.Type)
{
case UpdateType.Message:
{
var message = update.Message!;
if (message.Text!.StartsWith('/'))
{
TelegramLogger.LogInfo($"user {message.Chat.Id} sent command {message.Text}");
var spl = message.Text.SplitToList(' ');
string command = spl[0].Substring(1);
spl.RemoveAt(0);
string[] args = spl.ToArray();
await ExecCommandAsync(command, args, message);
}
else TelegramLogger.LogDebug($"message recieved: {message.Text}");
break;
}
/*case UpdateType.InlineQuery:
break;
case UpdateType.ChosenInlineResult:
break;
case UpdateType.CallbackQuery:
break;*/
default:
TelegramLogger.LogWarn($"unknown update type: {update.Type}");
break;
}
}
catch (OperationCanceledException) {}
catch (Exception ex)
{
TelegramLogger.LogWarn("UpdateHandler", ex);
}
}
private static async Task ExecCommandAsync(string command, string[] args, Message message)
{
switch (command)
{
case "start":
await Bot.SendTextMessageAsync(message.Chat, "hi");
break;
case "oversee":
{
string usernameOrUrl = args[0];
await SendInfoReply($"searching for instagram user <{usernameOrUrl}>", message);
var user = await InstagramWrapper.GetUserAsync(usernameOrUrl);
if (user is null)
{
await SendErrorReply($"user **{usernameOrUrl}** doesnt exist", message);
return;
}
CurrentUsersData.AddOrSet(message.Chat.Id.ToString(), new InstagramObservableParams(usernameOrUrl));
CurrentUsersData.SaveToFile();
break;
}
default:
await SendErrorReply("ivalid command", message);
break;
}
}
}

View File

@ -1,114 +0,0 @@
namespace InstaFollowersOverseer;
public class UserSettings
{
private const string user_settings_file="user-settings.dtsod";
private const string user_settings_example_file="user-settings-example.dtsod";
private Dictionary<string, List<InstagramObservableParams>> userSettings=new();
private UserSettings()
{
}
public UserSettings(DtsodV23 _userSettings)
{
try
{
foreach (var uset in _userSettings)
{
string telegramUserId = uset.Key;
List<InstagramObservableParams> oparams = new List<InstagramObservableParams>();
foreach (DtsodV23 _overseeParams in uset.Value)
oparams.Add(new InstagramObservableParams(_overseeParams));
userSettings.Add(telegramUserId, oparams);
}
}
catch (Exception ex)
{
throw new Exception($"your {user_settings_file} format is invalid\n"
+ $"See {user_settings_example_file}", innerException:ex);
}
}
public static UserSettings ReadFromFile()
{
EmbeddedResources.CopyToFile(
$"{EmbeddedResourcesPrefix}.{user_settings_example_file}",
user_settings_example_file);
if (File.Exists(user_settings_file))
return new UserSettings(new DtsodV23(File.ReadAllText(user_settings_file)));
MainLogger.LogWarn($"file {user_settings_file} doesnt exist, creating new");
File.WriteAllText(user_settings_file,"#DtsodV23\n");
return new UserSettings();
}
public DtsodV23 ToDtsod()
{
var b = new DtsodV23();
foreach (var userS in userSettings)
b.Add(userS.Key,
userS.Value.Select(iop =>
iop.ToDtsod()
).ToList());
return b;
}
public override string ToString() => ToDtsod().ToString();
public void SaveToFile()
{
File.Copy(user_settings_file,
$"backups/{user_settings_file}.old-"+
"{DateTime.Now.ToString(MyTimeFormat.ForFileNames)}",
true);
File.OpenWrite(user_settings_file)
.FluentWriteString("#DtsodV23\n")
.WriteString(ToDtsod().ToString());
}
public List<InstagramObservableParams> Get(string telegramUserId)
{
if (!userSettings.TryGetValue(telegramUserId, out var overseeParams))
throw new Exception($"there is no settings for user {telegramUserId}");
return overseeParams;
}
public void AddOrSet(string telegramUserId, InstagramObservableParams instagramObservableParams)
{
// Add
// doesnt contain settings for telegramUserId
if (!userSettings.TryGetValue(telegramUserId, out var thisUserSettings))
{
userSettings.Add(telegramUserId, new (){ instagramObservableParams });
return;
}
// Set
// settings for telegramUserId contain InstagramObservableParams with instagramObservableParams.instagramUserId
for (var i = 0; i < thisUserSettings.Count; i++)
{
if (thisUserSettings[i].instagramUserId == instagramObservableParams.instagramUserId)
{
thisUserSettings[i] = instagramObservableParams;
return;
}
}
// Add
// doesnt contain InstagramObservableParams with instagramObservableParams.instagramUserId
thisUserSettings.Add(instagramObservableParams);
}
public void AddOrSet(string telegramUserId, IEnumerable<InstagramObservableParams> instagramObservableParams)
{
foreach (var p in instagramObservableParams)
AddOrSet(telegramUserId, p);
}
}

81
UsersData.cs Normal file
View File

@ -0,0 +1,81 @@
namespace InstaFollowersOverseer;
public class UsersData : DtsodFile
{
private Dictionary<string, List<InstagramObservableParams>> usersData=new();
public UsersData(string fileName) : base(fileName) {}
public override void LoadFromFile()
{
var dtsod=ReadDtsodFromFile(false);
try
{
foreach (var uset in dtsod)
{
string telegramUserId = uset.Key;
List<InstagramObservableParams> oparams = new();
foreach (DtsodV23 _overseeParams in uset.Value)
oparams.Add(new InstagramObservableParams(_overseeParams));
usersData.Add(telegramUserId, oparams);
}
}
catch (Exception ex)
{
throw new Exception($"your {FileName} format is invalid\n"
+ $"See {FileExampleName}", innerException: ex);
}
}
public override DtsodV23 ToDtsod()
{
var b = new DtsodV23();
foreach (var userS in usersData)
b.Add(userS.Key,
userS.Value.Select<InstagramObservableParams, DtsodV23>(iop =>
iop.ToDtsod()
).ToList());
return b;
}
public List<InstagramObservableParams> Get(string telegramUserId)
{
if (!usersData.TryGetValue(telegramUserId, out var overseeParams))
throw new Exception($"there is no settings for user {telegramUserId}");
return overseeParams;
}
public void AddOrSet(string telegramUserId, InstagramObservableParams instagramObservableParams)
{
// Add
// doesnt contain settings for telegramUserId
if (!usersData.TryGetValue(telegramUserId, out var thisUsersData))
{
usersData.Add(telegramUserId, new (){ instagramObservableParams });
return;
}
// Set
// settings for telegramUserId contain InstagramObservableParams with instagramObservableParams.instagramUserId
for (var i = 0; i < thisUsersData.Count; i++)
{
if (thisUsersData[i].instagramUserId == instagramObservableParams.instagramUserId)
{
thisUsersData[i] = instagramObservableParams;
return;
}
}
// Add
// doesnt contain InstagramObservableParams with instagramObservableParams.instagramUserId
thisUsersData.Add(instagramObservableParams);
}
public void AddOrSet(string telegramUserId, IEnumerable<InstagramObservableParams> instagramObservableParams)
{
foreach (var p in instagramObservableParams)
AddOrSet(telegramUserId, p);
}
}