Merge pull request #4449 from warwickmm/remove_telegram
Remove Telegram backend
This commit is contained in:
@@ -105,8 +105,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.T
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.TencentCOS", "Duplicati\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj", "{545DD6D4-9476-42D6-B51C-A28E000C489E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Telegram", "Duplicati\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj", "{B6BB37DE-1DCA-401B-B92F-202F341916EA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -317,10 +315,6 @@ Global
|
||||
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6BB37DE-1DCA-401B-B92F-202F341916EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6BB37DE-1DCA-401B-B92F-202F341916EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6BB37DE-1DCA-401B-B92F-202F341916EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6BB37DE-1DCA-401B-B92F-202F341916EA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -90,10 +90,6 @@
|
||||
<Project>{ae035e01-c917-4f13-a35e-78f21c1a2f17}</Project>
|
||||
<Name>Duplicati.Library.Backend.Tardigrade</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj">
|
||||
<Project>{f61679a9-e5de-468a-b5a4-05f92d0143d2}</Project>
|
||||
<Name>Duplicati.Library.Backend.Telegram</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj">
|
||||
<Project>{545dd6d4-9476-42d6-b51c-a28e000c489e}</Project>
|
||||
<Name>Duplicati.Library.Backend.TencentCOS</Name>
|
||||
|
||||
@@ -96,10 +96,6 @@
|
||||
<Project>{ae035e01-c917-4f13-a35e-78f21c1a2f17}</Project>
|
||||
<Name>Duplicati.Library.Backend.Tardigrade</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj">
|
||||
<Project>{f61679a9-e5de-468a-b5a4-05f92d0143d2}</Project>
|
||||
<Name>Duplicati.Library.Backend.Telegram</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj">
|
||||
<Project>{545dd6d4-9476-42d6-b51c-a28e000c489e}</Project>
|
||||
<Name>Duplicati.Library.Backend.TencentCOS</Name>
|
||||
|
||||
@@ -111,10 +111,6 @@
|
||||
<Project>{32a74526-3e5f-413a-8cb4-1efdad4c8b91}</Project>
|
||||
<Name>Duplicati.Library.Backend.Sia</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj">
|
||||
<Project>{f61679a9-e5de-468a-b5a4-05f92d0143d2}</Project>
|
||||
<Name>Duplicati.Library.Backend.Telegram</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Library\Compression\Duplicati.Library.Compression.csproj">
|
||||
<Project>{19ECCE09-B5EB-406C-8C57-BAC66997D469}</Project>
|
||||
<Name>Duplicati.Library.Compression</Name>
|
||||
|
||||
@@ -134,10 +134,6 @@
|
||||
<Project>{C0270709-2A40-43B5-8CF1-69581B9FA2A1}</Project>
|
||||
<Name>Duplicati.Library.Backend.TahoeLAFS</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj">
|
||||
<Project>{f61679a9-e5de-468a-b5a4-05f92d0143d2}</Project>
|
||||
<Name>Duplicati.Library.Backend.Telegram</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj">
|
||||
<Project>{BAE27510-8B5D-44B2-B33E-372A98908041}</Project>
|
||||
<Name>Duplicati.Library.Backend.WEBDAV</Name>
|
||||
|
||||
@@ -221,10 +221,6 @@
|
||||
<Project>{ae035e01-c917-4f13-a35e-78f21c1a2f17}</Project>
|
||||
<Name>Duplicati.Library.Backend.Tardigrade</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj">
|
||||
<Project>{f61679a9-e5de-468a-b5a4-05f92d0143d2}</Project>
|
||||
<Name>Duplicati.Library.Backend.Telegram</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj">
|
||||
<Project>{545dd6d4-9476-42d6-b51c-a28e000c489e}</Project>
|
||||
<Name>Duplicati.Library.Backend.TencentCOS</Name>
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using Duplicati.Library.Common.IO;
|
||||
using TeleSharp.TL;
|
||||
|
||||
namespace Duplicati.Library.Backend
|
||||
{
|
||||
public class ChannelFileInfo : IEquatable<ChannelFileInfo>
|
||||
{
|
||||
public int MessageId { get; }
|
||||
public long MediaDocAccessHash { get; }
|
||||
public long DocumentId { get; }
|
||||
public int Version { get; }
|
||||
public long Size { get; }
|
||||
public string Name { get; }
|
||||
public DateTime Date { get; }
|
||||
|
||||
public ChannelFileInfo()
|
||||
{ }
|
||||
|
||||
public ChannelFileInfo(int messageId, long mediaDocAccessHash, long documentId, int version, long size, string name, DateTime date)
|
||||
{
|
||||
MessageId = messageId;
|
||||
MediaDocAccessHash = mediaDocAccessHash;
|
||||
DocumentId = documentId;
|
||||
Version = version;
|
||||
Size = size;
|
||||
Name = name;
|
||||
Date = date;
|
||||
}
|
||||
|
||||
public FileEntry ToFileEntry()
|
||||
{
|
||||
return new FileEntry(Name, Size)
|
||||
{
|
||||
LastModification = Date,
|
||||
LastAccess = Date,
|
||||
IsFolder = false
|
||||
};
|
||||
}
|
||||
|
||||
public TLInputDocumentFileLocation ToFileLocation()
|
||||
{
|
||||
return new TLInputDocumentFileLocation
|
||||
{
|
||||
Id = DocumentId,
|
||||
Version = Version,
|
||||
AccessHash = MediaDocAccessHash
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(ChannelFileInfo other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return MessageId == other.MessageId;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is ChannelFileInfo other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return MessageId;
|
||||
}
|
||||
|
||||
public static bool operator ==(ChannelFileInfo left, ChannelFileInfo right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ChannelFileInfo left, ChannelFileInfo right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B6BB37DE-1DCA-401B-B92F-202F341916EA}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Duplicati.Library.Backend</RootNamespace>
|
||||
<AssemblyName>Duplicati.Library.Backend.Telegram</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>Duplicati.snk</AssemblyOriginatorKeyFile>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<UseMSBuildEngine>false</UseMSBuildEngine>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="SharpAESCrypt, Version=1.3.3.0, Culture=neutral, PublicKeyToken=null">
|
||||
<HintPath>..\..\..\..\packages\SharpAESCrypt.exe.1.3.3\lib\netstandard2.0\SharpAESCrypt.exe</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="TeleSharp.TL, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\..\packages\TLSharp.0.1.0.574\lib\net46\TeleSharp.TL.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="TLSharp.Core, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\..\packages\TLSharp.0.1.0.574\lib\net46\TLSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ChannelFileInfo.cs" />
|
||||
<Compile Include="EncryptedFileSessionStore.cs" />
|
||||
<Compile Include="Extensions\TelegramClientExtensions.cs" />
|
||||
<Compile Include="StreamReadHelper.cs" />
|
||||
<Compile Include="TelegramBackend.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Strings.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AutoUpdater\Duplicati.Library.AutoUpdater.csproj">
|
||||
<Project>{7e119745-1f62-43f0-936c-f312a1912c0b}</Project>
|
||||
<Name>Duplicati.Library.AutoUpdater</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Logging\Duplicati.Library.Logging.csproj">
|
||||
<Project>{d10a5fc0-11b4-4e70-86aa-8aea52bd9798}</Project>
|
||||
<Name>Duplicati.Library.Logging</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj">
|
||||
<Project>{52826615-7964-47FE-B4B3-1B2DBDF605B9}</Project>
|
||||
<Name>Duplicati.Library.Modules.Builtin</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Utility\Duplicati.Library.Utility.csproj">
|
||||
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
|
||||
<Name>Duplicati.Library.Utility</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Interface\Duplicati.Library.Interface.csproj">
|
||||
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
|
||||
<Name>Duplicati.Library.Interface</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Localization\Duplicati.Library.Localization.csproj">
|
||||
<Project>{B68F2214-951F-4F78-8488-66E1ED3F50BF}</Project>
|
||||
<Name>Duplicati.Library.Localization</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Common\Duplicati.Library.Common.csproj">
|
||||
<Project>{D63E53E4-A458-4C2F-914D-92F715F58ACF}</Project>
|
||||
<Name>Duplicati.Library.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="Duplicati.snk" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
Binary file not shown.
@@ -1,159 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Duplicati.Library.AutoUpdater;
|
||||
using TLSharp.Core;
|
||||
|
||||
namespace Duplicati.Library.Backend
|
||||
{
|
||||
public class EncryptedFileSessionStore : ISessionStore
|
||||
{
|
||||
private static readonly object m_lockObj = new object();
|
||||
private static readonly uint[] m_lookup32 = CreateLookup32();
|
||||
|
||||
private readonly string m_teleDataPath;
|
||||
private readonly string m_password;
|
||||
private readonly SHA256 m_sha = SHA256.Create();
|
||||
private static readonly ConcurrentDictionary<string, byte[]> m_userIdLastSessionMap = new ConcurrentDictionary<string, byte[]>();
|
||||
|
||||
public EncryptedFileSessionStore(string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
}
|
||||
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
var appName = AutoUpdateSettings.AppName;
|
||||
m_teleDataPath = Path.Combine(appData, appName, nameof(Telegram));
|
||||
m_password = password;
|
||||
|
||||
Directory.CreateDirectory(m_teleDataPath);
|
||||
}
|
||||
|
||||
|
||||
public void Save(Session session)
|
||||
{
|
||||
if (session.AuthKey == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sessionId = session.SessionUserId;
|
||||
var filePath = GetSessionFilePath(sessionId);
|
||||
var sessionBytes = session.ToBytes();
|
||||
|
||||
if (m_userIdLastSessionMap.TryGetValue(sessionId, out var sessionCache))
|
||||
{
|
||||
if (sessionCache.SequenceEqual(sessionBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
WriteToEncryptedStorage(sessionBytes, m_password, filePath, sessionId);
|
||||
}
|
||||
|
||||
public Session Load(string userId)
|
||||
{
|
||||
if (m_userIdLastSessionMap.TryGetValue(userId, out var cachedBytes))
|
||||
{
|
||||
var cachedSession = Session.FromBytes(cachedBytes, this, userId);
|
||||
return cachedSession;
|
||||
}
|
||||
|
||||
var filePath = GetSessionFilePath(userId);
|
||||
var sessionBytes = ReadFromEncryptedStorage(m_password, filePath);
|
||||
|
||||
if (sessionBytes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var session = Session.FromBytes(sessionBytes, this, userId);
|
||||
return session;
|
||||
}
|
||||
|
||||
private string GetSessionFilePath(string userId)
|
||||
{
|
||||
userId = userId.TrimStart('+');
|
||||
var sha = GetShortSha(userId);
|
||||
var sessionFilePath = Path.Combine(m_teleDataPath, $"t_{sha}.dat");
|
||||
|
||||
return sessionFilePath;
|
||||
}
|
||||
|
||||
private static void WriteToEncryptedStorage(byte[] bytesToWrite, string pass, string path, string sessionId)
|
||||
{
|
||||
lock (m_lockObj)
|
||||
{
|
||||
using (var sessionMs = new MemoryStream(bytesToWrite))
|
||||
using (var file = File.Open(path, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
SharpAESCrypt.SharpAESCrypt.Encrypt(pass, sessionMs, file);
|
||||
}
|
||||
|
||||
m_userIdLastSessionMap[sessionId] = bytesToWrite;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] ReadFromEncryptedStorage(string pass, string path)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
if (fileInfo.Exists == false || fileInfo.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
lock (m_lockObj)
|
||||
{
|
||||
using (var sessionMs = new MemoryStream())
|
||||
using (var file = File.Open(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
SharpAESCrypt.SharpAESCrypt.Decrypt(pass, file, sessionMs);
|
||||
return sessionMs.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetShortSha(string input)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(input);
|
||||
|
||||
var longShaBytes = m_sha.ComputeHash(inputBytes);
|
||||
var longSha = ByteArrayToHexViaLookup32(longShaBytes);
|
||||
var result = longSha.Substring(0, 16);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint[] CreateLookup32()
|
||||
{
|
||||
var result = new uint[256];
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
var s = i.ToString("X2");
|
||||
result[i] = s[0] + ((uint)s[1] << 16);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ByteArrayToHexViaLookup32(byte[] bytes)
|
||||
{
|
||||
var lookup32 = m_lookup32;
|
||||
var result = new char[bytes.Length * 2];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
var val = lookup32[bytes[i]];
|
||||
result[2 * i] = (char)val;
|
||||
result[2 * i + 1] = (char)(val >> 16);
|
||||
}
|
||||
|
||||
return new string(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TLSharp.Core;
|
||||
using TLSharp.Core.Network;
|
||||
|
||||
namespace Duplicati.Library.Backend.Extensions
|
||||
{
|
||||
public static class TelegramClientExtensions
|
||||
{
|
||||
private static readonly BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Default;
|
||||
|
||||
public static async Task WrapperConnectAsync(this TelegramClient client, CancellationToken cancelToken = default(CancellationToken))
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
var oldTransportField = client.GetTcpTransport();
|
||||
var tcpClient = oldTransportField.GetTcpClient();
|
||||
var handler = client.GetTcpClientConnectionHandler();
|
||||
|
||||
var remoteEndpoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
|
||||
var newTransport = new TcpTransport(remoteEndpoint.Address.ToString(), remoteEndpoint.Port, handler);
|
||||
|
||||
client.GetTcpTransportFieldInfo().SetValue(client, newTransport);
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
await client.ConnectAsync(false, cancelToken);
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
oldTransportField.Dispose();
|
||||
}
|
||||
|
||||
public static bool IsReallyConnected(this TelegramClient client)
|
||||
{
|
||||
if (client.IsConnected == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sender = GetMtProtoSender(client);
|
||||
|
||||
if (sender == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var tcpTransport = client.GetTcpTransport();
|
||||
|
||||
if (tcpTransport == null || tcpTransport.IsConnected == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var tcpClient = tcpTransport.GetTcpClient();
|
||||
|
||||
if (tcpClient == null || tcpClient.Connected == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static TcpClientConnectionHandler GetTcpClientConnectionHandler(this TelegramClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
var handlerFieldInfo = typeof(TelegramClient).GetField("handler", _bindingFlags);
|
||||
var handler = (TcpClientConnectionHandler)handlerFieldInfo.GetValue(client);
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static TcpClient GetTcpClient(this TcpTransport tcpTransport)
|
||||
{
|
||||
if (tcpTransport == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tcpTransport));
|
||||
}
|
||||
|
||||
var tcpClientFieldInfo = typeof(TcpTransport).GetField("tcpClient", _bindingFlags);
|
||||
var tcpClient = (TcpClient)tcpClientFieldInfo.GetValue(tcpTransport);
|
||||
|
||||
return tcpClient;
|
||||
}
|
||||
|
||||
private static FieldInfo GetTcpTransportFieldInfo(this TelegramClient client)
|
||||
{
|
||||
var transportFieldInfo = typeof(TelegramClient).GetField("transport", _bindingFlags);
|
||||
|
||||
return transportFieldInfo;
|
||||
}
|
||||
|
||||
private static TcpTransport GetTcpTransport(this TelegramClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
var transportFieldInfo = client.GetTcpTransportFieldInfo();
|
||||
var oldTransportField = (TcpTransport)transportFieldInfo.GetValue(client);
|
||||
|
||||
return oldTransportField;
|
||||
}
|
||||
|
||||
private static MtProtoSender GetMtProtoSender(this TelegramClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
var senderFieldInfo = typeof(TelegramClient).GetField("sender", _bindingFlags);
|
||||
var sender = (MtProtoSender)senderFieldInfo.GetValue(client);
|
||||
|
||||
return sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#region Disclaimer / License
|
||||
// Copyright (C) 2015, The Duplicati Team
|
||||
// http://www.duplicati.com, info@duplicati.com
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
#endregion
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Telegram")]
|
||||
[assembly: AssemblyDescription("A Telegram backend for Duplicati")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Duplicati Team")]
|
||||
[assembly: AssemblyProduct("Duplicati.Backend.Telegram")]
|
||||
[assembly: AssemblyCopyright("LGPL, Copyright © Duplicati Team 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("afa68988-4d82-490e-8044-acbc7d52ef4f")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("2.0.0.7")]
|
||||
[assembly: AssemblyFileVersion("2.0.0.7")]
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.IO;
|
||||
using Duplicati.Library.Utility;
|
||||
|
||||
namespace Duplicati.Library.Backend
|
||||
{
|
||||
/// <summary>
|
||||
/// Private helper class to fix a bug with the StreamReader
|
||||
/// </summary>
|
||||
internal class StreamReadHelper : OverrideableStream
|
||||
{
|
||||
/// <summary>
|
||||
/// Once the stream has returned 0 as the read count it is disposed,
|
||||
/// and subsequent read requests will throw an ObjectDisposedException
|
||||
/// </summary>
|
||||
private bool m_empty;
|
||||
|
||||
/// <summary>
|
||||
/// Basic initialization, just pass the stream to the super class
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public StreamReadHelper(Stream stream) : base(stream)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Override the read function to make sure that we only return less than the requested amount of data if the stream is exhausted
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to place data in</param>
|
||||
/// <param name="offset">The offset into the buffer to start at</param>
|
||||
/// <param name="count">The number of bytes to read</param>
|
||||
/// <returns>The number of bytes read</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var readCount = 0;
|
||||
int a;
|
||||
|
||||
while (!m_empty && count > 0)
|
||||
{
|
||||
a = base.Read(buffer, offset, count);
|
||||
readCount += a;
|
||||
count -= a;
|
||||
offset += a;
|
||||
m_empty = a == 0;
|
||||
}
|
||||
|
||||
return readCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using Duplicati.Library.Localization.Short;
|
||||
|
||||
namespace Duplicati.Library.Backend
|
||||
{
|
||||
internal static class Strings
|
||||
{
|
||||
#region Constants
|
||||
|
||||
public const string API_ID_KEY = "api-id";
|
||||
public const string API_HASH_KEY = "api-hash";
|
||||
public const string PHONE_NUMBER_KEY = "phone-number";
|
||||
public const string AUTH_CODE_KEY = "auth-code";
|
||||
public const string AUTH_PASSWORD = "auth-password";
|
||||
public const string CHANNEL_NAME = "channel-name";
|
||||
|
||||
#endregion
|
||||
|
||||
#region General
|
||||
|
||||
public static string DisplayName { get; } = "Telegram";
|
||||
public static string Description => LC.L("This backend can read and write data to a Telegram backend");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Errors
|
||||
|
||||
public static string WrongAuthCodeError => LC.L("The auth code is incorrect");
|
||||
public static string NoAuthCodeError => LC.L("The auth code is missing");
|
||||
public static string NoPasswordError => LC.L("The password is missing");
|
||||
public static string NoChannelNameError => LC.L("The channel name is missing");
|
||||
public static string NoApiIdError => LC.L("The API ID is missing");
|
||||
public static string NoApiHashError => LC.L("The API hash is missing");
|
||||
public static string NoPhoneNumberError => LC.L("The phone number is missing");
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Descriptions
|
||||
|
||||
public static string ApiIdShort => LC.L("The API ID");
|
||||
public static string ApiHashShort => LC.L("The API hash");
|
||||
public static string ApiIdLong => LC.L("The API ID retrieved from https://my.telegram.org/");
|
||||
public static string ApiHashLong => LC.L("The API hash retrieved from https://my.telegram.org/");
|
||||
public static string PhoneNumberShort => LC.L("Your phone number");
|
||||
public static string PhoneNumberLong => LC.L("The phone number you registered with");
|
||||
public static string AuthCodeShort => LC.L("The code you should have received (if you did)");
|
||||
public static string AuthCodeLong => LC.L("The auth code that you received. Input only if you did receive it");
|
||||
public static string PasswordShort => LC.L("2FA password (if enabled)");
|
||||
public static string PasswordLong => LC.L("The 2 step verification password. Input only if you have set it up");
|
||||
public static string ChannelName => LC.L("The channel name of the backup");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Formats
|
||||
|
||||
public const string TELEGRAM_FLOOD = "It's required to wait {0} seconds before continuing";
|
||||
public const string STARTING_EXECUTING = "Starting executing action {0}";
|
||||
public const string DONE_EXECUTING = "Done executing action {0}";
|
||||
public const string USER_INFO_EXC = "Exception thrown that should be shown on UI";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Duplicati.Library.Backend.Extensions;
|
||||
using Duplicati.Library.Interface;
|
||||
using Duplicati.Library.Logging;
|
||||
using TeleSharp.TL;
|
||||
using TeleSharp.TL.Channels;
|
||||
using TeleSharp.TL.Messages;
|
||||
using TLSharp.Core;
|
||||
using TLSharp.Core.Exceptions;
|
||||
using TLSharp.Core.Network.Exceptions;
|
||||
using TLSharp.Core.Utils;
|
||||
using TLRequestDeleteMessages = TeleSharp.TL.Channels.TLRequestDeleteMessages;
|
||||
|
||||
namespace Duplicati.Library.Backend
|
||||
{
|
||||
public class Telegram : IStreamingBackend
|
||||
{
|
||||
private readonly TelegramClient m_telegramClient;
|
||||
private readonly EncryptedFileSessionStore m_encSessionStore;
|
||||
|
||||
private static readonly object m_lockObj = new object();
|
||||
|
||||
private readonly int m_apiId;
|
||||
private readonly string m_apiHash;
|
||||
private readonly string m_authCode;
|
||||
private readonly string m_password;
|
||||
private readonly string m_channelName;
|
||||
private readonly string m_phoneNumber;
|
||||
private TLChannel m_channelCache;
|
||||
|
||||
private static string m_phoneCodeHash;
|
||||
private static readonly string m_logTag = Log.LogTagFromType(typeof(Telegram));
|
||||
private const int BYTES_IN_MEBIBYTE = 1048576;
|
||||
|
||||
public Telegram()
|
||||
{ }
|
||||
|
||||
public Telegram(string url, Dictionary<string, string> options)
|
||||
{
|
||||
if (options.TryGetValue(Strings.API_ID_KEY, out var apiId))
|
||||
{
|
||||
m_apiId = int.Parse(apiId);
|
||||
}
|
||||
|
||||
if (options.TryGetValue(Strings.API_HASH_KEY, out var apiHash))
|
||||
{
|
||||
m_apiHash = apiHash.Trim();
|
||||
}
|
||||
|
||||
if (options.TryGetValue(Strings.PHONE_NUMBER_KEY, out var phoneNumber))
|
||||
{
|
||||
m_phoneNumber = phoneNumber.Trim();
|
||||
}
|
||||
|
||||
if (options.TryGetValue(Strings.AUTH_CODE_KEY, out var authCode))
|
||||
{
|
||||
m_authCode = authCode.Trim();
|
||||
}
|
||||
|
||||
if (options.TryGetValue(Strings.AUTH_PASSWORD, out var password))
|
||||
{
|
||||
m_password = password.Trim();
|
||||
}
|
||||
|
||||
if (options.TryGetValue(Strings.CHANNEL_NAME, out var channelName))
|
||||
{
|
||||
m_channelName = channelName.Trim();
|
||||
}
|
||||
|
||||
if (m_apiId == 0)
|
||||
{
|
||||
throw new UserInformationException(Strings.NoApiIdError, nameof(Strings.NoApiIdError));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(m_apiHash))
|
||||
{
|
||||
throw new UserInformationException(Strings.NoApiHashError, nameof(Strings.NoApiHashError));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(m_phoneNumber))
|
||||
{
|
||||
throw new UserInformationException(Strings.NoPhoneNumberError, nameof(Strings.NoPhoneNumberError));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(m_channelName))
|
||||
{
|
||||
throw new UserInformationException(Strings.NoChannelNameError, nameof(Strings.NoChannelNameError));
|
||||
}
|
||||
|
||||
m_encSessionStore = new EncryptedFileSessionStore($"{m_apiHash}_{m_apiId}");
|
||||
m_telegramClient = new TelegramClient(m_apiId, m_apiHash, m_encSessionStore, m_phoneNumber);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_telegramClient?.Dispose();
|
||||
}
|
||||
|
||||
public string DisplayName { get; } = Strings.DisplayName;
|
||||
public string ProtocolKey { get; } = "telegram";
|
||||
|
||||
public IEnumerable<IFileEntry> List()
|
||||
{
|
||||
return SafeExecute<IEnumerable<IFileEntry>>(() =>
|
||||
{
|
||||
Authenticate();
|
||||
EnsureChannelCreated();
|
||||
var fileInfos = ListChannelFileInfos();
|
||||
var result = fileInfos.Select(fi => fi.ToFileEntry());
|
||||
return result;
|
||||
},
|
||||
nameof(List));
|
||||
}
|
||||
|
||||
public Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
Authenticate();
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
var channel = GetChannel();
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var sr = new StreamReader(new StreamReadHelper(stream)))
|
||||
{
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
EnsureConnected(cancelToken);
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
var file = m_telegramClient.UploadFile(remotename, sr, cancelToken).GetAwaiter().GetResult();
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
var inputPeerChannel = new TLInputPeerChannel {ChannelId = channel.Id, AccessHash = (long)channel.AccessHash};
|
||||
var fileNameAttribute = new TLDocumentAttributeFilename
|
||||
{
|
||||
FileName = remotename
|
||||
};
|
||||
|
||||
EnsureConnected(cancelToken);
|
||||
m_telegramClient.SendUploadedDocument(inputPeerChannel, file, remotename, "application/zip", new TLVector<TLAbsDocumentAttribute> {fileNameAttribute}, cancelToken).GetAwaiter().GetResult();
|
||||
}
|
||||
},
|
||||
nameof(PutAsync));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Get(string remotename, Stream stream)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
var fileInfo = ListChannelFileInfos().First(fi => fi.Name == remotename);
|
||||
var fileLocation = fileInfo.ToFileLocation();
|
||||
|
||||
var limit = BYTES_IN_MEBIBYTE;
|
||||
var currentOffset = 0;
|
||||
|
||||
while (currentOffset < fileInfo.Size)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureConnected();
|
||||
var file = m_telegramClient.GetFile(fileLocation, limit, currentOffset).GetAwaiter().GetResult();
|
||||
stream.Write(file.Bytes, 0, file.Bytes.Length);
|
||||
currentOffset += file.Bytes.Length;
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
if (e.Message.Contains("Couldn't read the packet length") == false)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
$"{nameof(Get)}");
|
||||
}
|
||||
|
||||
public Task PutAsync(string remotename, string filename, CancellationToken cancelToken)
|
||||
{
|
||||
using (var fs = File.OpenRead(filename))
|
||||
{
|
||||
PutAsync(remotename, fs, cancelToken).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Get(string remotename, string filename)
|
||||
{
|
||||
using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
Get(remotename, fs);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(string remotename)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
var channel = GetChannel();
|
||||
var fileInfo = ListChannelFileInfos().FirstOrDefault(fi => fi.Name == remotename);
|
||||
if (fileInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new TLRequestDeleteMessages
|
||||
{
|
||||
Channel = new TLInputChannel
|
||||
{
|
||||
ChannelId = channel.Id,
|
||||
AccessHash = channel.AccessHash.Value
|
||||
},
|
||||
Id = new TLVector<int> {fileInfo.MessageId}
|
||||
};
|
||||
|
||||
EnsureConnected();
|
||||
m_telegramClient.SendRequestAsync<TLAffectedMessages>(request).GetAwaiter().GetResult();
|
||||
},
|
||||
$"{nameof(Delete)}({remotename})");
|
||||
}
|
||||
|
||||
public List<ChannelFileInfo> ListChannelFileInfos()
|
||||
{
|
||||
var channel = GetChannel();
|
||||
|
||||
var inputPeerChannel = new TLInputPeerChannel {ChannelId = channel.Id, AccessHash = channel.AccessHash.Value};
|
||||
var result = new List<ChannelFileInfo>();
|
||||
var oldMinDate = 0L;
|
||||
var newMinDate = (long?)null;
|
||||
|
||||
while (oldMinDate != newMinDate)
|
||||
{
|
||||
oldMinDate = newMinDate ?? 0L;
|
||||
RetrieveMessages(inputPeerChannel, result, oldMinDate);
|
||||
if (result.Any() == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
newMinDate = result.Min(cfi => Utility.Utility.NormalizeDateTimeToEpochSeconds(cfi.Date));
|
||||
}
|
||||
|
||||
result = result.Distinct().ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void RetrieveMessages(TLInputPeerChannel inputPeerChannel, List<ChannelFileInfo> result, long offsetDate)
|
||||
{
|
||||
EnsureConnected();
|
||||
var absHistory = m_telegramClient.GetHistoryAsync(inputPeerChannel, offsetDate: (int)offsetDate).GetAwaiter().GetResult();
|
||||
var history = ((TLChannelMessages)absHistory).Messages.OfType<TLMessage>();
|
||||
|
||||
foreach (var msg in history)
|
||||
{
|
||||
if (msg.Media is TLMessageMediaDocument media &&
|
||||
media.Document is TLDocument mediaDoc)
|
||||
{
|
||||
var fileInfo = new ChannelFileInfo(
|
||||
msg.Id,
|
||||
mediaDoc.AccessHash,
|
||||
mediaDoc.Id,
|
||||
mediaDoc.Version,
|
||||
mediaDoc.Size,
|
||||
media.Caption,
|
||||
DateTimeOffset.FromUnixTimeSeconds(msg.Date).UtcDateTime);
|
||||
|
||||
result.Add(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IList<ICommandLineArgument> SupportedCommands { get; } = new List<ICommandLineArgument>
|
||||
{
|
||||
new CommandLineArgument(Strings.API_ID_KEY, CommandLineArgument.ArgumentType.Integer, Strings.ApiIdShort, Strings.ApiIdLong),
|
||||
new CommandLineArgument(Strings.API_HASH_KEY, CommandLineArgument.ArgumentType.String, Strings.ApiHashShort, Strings.ApiHashLong),
|
||||
new CommandLineArgument(Strings.PHONE_NUMBER_KEY, CommandLineArgument.ArgumentType.String, Strings.PhoneNumberShort, Strings.PhoneNumberLong),
|
||||
new CommandLineArgument(Strings.AUTH_CODE_KEY, CommandLineArgument.ArgumentType.String, Strings.AuthCodeShort, Strings.AuthCodeLong),
|
||||
new CommandLineArgument(Strings.AUTH_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.PasswordShort, Strings.PasswordLong),
|
||||
new CommandLineArgument(Strings.CHANNEL_NAME, CommandLineArgument.ArgumentType.String, Strings.ChannelName, Strings.ChannelName)
|
||||
};
|
||||
|
||||
public string Description { get; } = Strings.Description;
|
||||
|
||||
public string[] DNSName { get; }
|
||||
|
||||
public void Test()
|
||||
{
|
||||
SafeExecute(Authenticate, nameof(Authenticate));
|
||||
}
|
||||
|
||||
public void CreateFolder()
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
Authenticate();
|
||||
EnsureChannelCreated();
|
||||
},
|
||||
nameof(CreateFolder));
|
||||
}
|
||||
|
||||
private TLChannel GetChannel()
|
||||
{
|
||||
if (m_channelCache != null)
|
||||
{
|
||||
return m_channelCache;
|
||||
}
|
||||
|
||||
var absChats = GetChats();
|
||||
|
||||
var channel = (TLChannel)absChats.FirstOrDefault(chat => chat is TLChannel tlChannel && tlChannel.Title == m_channelName);
|
||||
m_channelCache = channel;
|
||||
return channel;
|
||||
}
|
||||
|
||||
private IEnumerable<TLAbsChat> GetChats()
|
||||
{
|
||||
var lastDate = 0;
|
||||
while (true)
|
||||
{
|
||||
EnsureConnected();
|
||||
var dialogs = m_telegramClient.GetUserDialogsAsync(lastDate).GetAwaiter().GetResult();
|
||||
var tlDialogs = dialogs as TLDialogs;
|
||||
var tlDialogsSlice = dialogs as TLDialogsSlice;
|
||||
|
||||
foreach (var chat in tlDialogs?.Chats ?? tlDialogsSlice?.Chats)
|
||||
{
|
||||
switch (chat)
|
||||
{
|
||||
case TLChannelForbidden _:
|
||||
case TLChatForbidden _:
|
||||
break;
|
||||
case TLChat c:
|
||||
lastDate = c.Date;
|
||||
break;
|
||||
case TLChannel c:
|
||||
lastDate = c.Date;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported chat type {chat.GetType()}");
|
||||
}
|
||||
|
||||
yield return chat;
|
||||
}
|
||||
|
||||
if (tlDialogs?.Dialogs?.Count < 100 || tlDialogsSlice?.Dialogs?.Count < 100)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureChannelCreated()
|
||||
{
|
||||
var channel = GetChannel();
|
||||
if (channel == null)
|
||||
{
|
||||
var newGroup = new TLRequestCreateChannel
|
||||
{
|
||||
Broadcast = false,
|
||||
Megagroup = false,
|
||||
Title = m_channelName,
|
||||
About = string.Empty
|
||||
};
|
||||
|
||||
EnsureConnected();
|
||||
m_telegramClient.SendRequestAsync<object>(newGroup).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
private void Authenticate()
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
if (IsAuthenticated())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m_phoneCodeHash == null)
|
||||
{
|
||||
EnsureConnected();
|
||||
var phoneCodeHash = m_telegramClient.SendCodeRequestAsync(m_phoneNumber).GetAwaiter().GetResult();
|
||||
SetPhoneCodeHash(phoneCodeHash);
|
||||
m_telegramClient.Session.Save();
|
||||
|
||||
if (string.IsNullOrEmpty(m_authCode))
|
||||
{
|
||||
throw new UserInformationException(Strings.NoAuthCodeError, nameof(Strings.NoAuthCodeError));
|
||||
}
|
||||
|
||||
throw new UserInformationException(Strings.WrongAuthCodeError, nameof(Strings.WrongAuthCodeError));
|
||||
}
|
||||
|
||||
m_telegramClient.MakeAuthAsync(m_phoneNumber, m_phoneCodeHash, m_authCode).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (CloudPasswordNeededException)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_password))
|
||||
{
|
||||
m_telegramClient.Session.Save();
|
||||
throw new UserInformationException(Strings.NoPasswordError, nameof(Strings.NoPasswordError));
|
||||
}
|
||||
|
||||
EnsureConnected();
|
||||
var passwordSetting = m_telegramClient.GetPasswordSetting().GetAwaiter().GetResult();
|
||||
m_telegramClient.MakeAuthWithPasswordAsync(passwordSetting, m_password).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
m_telegramClient.Session.Save();
|
||||
}
|
||||
|
||||
private void EnsureConnected(CancellationToken cancelToken = default(CancellationToken))
|
||||
{
|
||||
if (m_telegramClient.IsReallyConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
m_telegramClient.WrapperConnectAsync(cancelToken).GetAwaiter().GetResult();
|
||||
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (m_telegramClient.IsReallyConnected() == false)
|
||||
{
|
||||
throw new WebException("Unable to connect to telegram");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAuthenticated()
|
||||
{
|
||||
var isAuthorized = m_telegramClient.IsUserAuthorized();
|
||||
return isAuthorized;
|
||||
}
|
||||
|
||||
private static void SetPhoneCodeHash(string phoneCodeHash)
|
||||
{
|
||||
m_phoneCodeHash = phoneCodeHash;
|
||||
}
|
||||
|
||||
private void SafeExecute(Action action, string actionName)
|
||||
{
|
||||
lock (m_lockObj)
|
||||
{
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.STARTING_EXECUTING), Strings.STARTING_EXECUTING, actionName);
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (UserInformationException uiExc)
|
||||
{
|
||||
Log.WriteWarningMessage(m_logTag, nameof(Strings.USER_INFO_EXC), uiExc, Strings.USER_INFO_EXC);
|
||||
throw;
|
||||
}
|
||||
catch (FloodException floodExc)
|
||||
{
|
||||
var randSeconds = new Random().Next(2, 15);
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.TELEGRAM_FLOOD), Strings.TELEGRAM_FLOOD, floodExc.TimeToWait.TotalSeconds + randSeconds);
|
||||
Thread.Sleep(floodExc.TimeToWait + TimeSpan.FromSeconds(randSeconds));
|
||||
SafeExecute(action, actionName);
|
||||
}
|
||||
}
|
||||
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.DONE_EXECUTING), Strings.DONE_EXECUTING, actionName);
|
||||
}
|
||||
|
||||
private T SafeExecute<T>(Func<T> func, string actionName)
|
||||
{
|
||||
lock (m_lockObj)
|
||||
{
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.STARTING_EXECUTING), Strings.STARTING_EXECUTING, actionName);
|
||||
try
|
||||
{
|
||||
var res = func();
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.DONE_EXECUTING), Strings.DONE_EXECUTING, actionName);
|
||||
return res;
|
||||
}
|
||||
catch (UserInformationException uiExc)
|
||||
{
|
||||
Log.WriteWarningMessage(m_logTag, nameof(Strings.USER_INFO_EXC), uiExc, Strings.USER_INFO_EXC);
|
||||
throw;
|
||||
}
|
||||
catch (FloodException floodExc)
|
||||
{
|
||||
var randSeconds = new Random().Next(2, 15);
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.TELEGRAM_FLOOD), Strings.TELEGRAM_FLOOD, floodExc.TimeToWait.TotalSeconds + randSeconds);
|
||||
Thread.Sleep(floodExc.TimeToWait);
|
||||
var res = SafeExecute(func, actionName);
|
||||
Log.WriteInformationMessage(m_logTag, nameof(Strings.DONE_EXECUTING), Strings.DONE_EXECUTING, actionName);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="SharpAESCrypt.exe" version="1.3.3" targetFramework="net471" />
|
||||
<package id="TLSharp" version="0.1.0.574" targetFramework="net471" />
|
||||
</packages>
|
||||
@@ -90,15 +90,6 @@
|
||||
<Content Include="..\..\thirdparty\Tencentyun\LICENSE.txt">
|
||||
<Link>licenses\Tencentyun\LICENSE.txt</Link>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\TLSharp\Homepage.txt">
|
||||
<Link>licenses\TLSharp\Homepage.txt</Link>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\TLSharp\License.txt">
|
||||
<Link>licenses\TLSharp\License.txt</Link>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\TLSharp\licensedata.json">
|
||||
<Link>licenses\TLSharp\licensedata.json</Link>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\uplink.NET\Homepage.txt">
|
||||
<Link>licenses\uplink.NET\Homepage.txt</Link>
|
||||
</Content>
|
||||
|
||||
@@ -207,10 +207,6 @@
|
||||
<Project>{ae035e01-c917-4f13-a35e-78f21c1a2f17}</Project>
|
||||
<Name>Duplicati.Library.Backend.Tardigrade</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Library\Backend\Telegram\Duplicati.Library.Backend.Telegram.csproj">
|
||||
<Project>{f61679a9-e5de-468a-b5a4-05f92d0143d2}</Project>
|
||||
<Name>Duplicati.Library.Backend.Telegram</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj">
|
||||
<Project>{545dd6d4-9476-42d6-b51c-a28e000c489e}</Project>
|
||||
<Name>Duplicati.Library.Backend.TencentCOS</Name>
|
||||
|
||||
@@ -30,7 +30,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
EditUriBackendConfig.templates['dropbox'] = 'templates/backends/oauth.html';
|
||||
EditUriBackendConfig.templates['sia'] = 'templates/backends/sia.html';
|
||||
EditUriBackendConfig.templates['tardigrade'] = 'templates/backends/tardigrade.html';
|
||||
EditUriBackendConfig.templates['telegram'] = 'templates/backends/telegram.html';
|
||||
EditUriBackendConfig.templates['rclone'] = 'templates/backends/rclone.html';
|
||||
EditUriBackendConfig.templates['cos'] = 'templates/backends/cos.html';
|
||||
|
||||
@@ -504,24 +503,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
delete options[nukeopts[x]];
|
||||
};
|
||||
|
||||
EditUriBackendConfig.parsers['telegram'] = function (scope, module, server, port, path, options) {
|
||||
if (options['--api-id'])
|
||||
scope.api_id = options['--api-id'];
|
||||
if (options['--api-hash'])
|
||||
scope.api_hash = options['--api-hash'];
|
||||
if (options['--auth-code'])
|
||||
scope.auth_code = options['--auth-code'];
|
||||
if (options['--channel-name'])
|
||||
scope.channel_name = options['--channel-name'];
|
||||
if (options['--phone-number'])
|
||||
scope.phone_number = options['--phone-number'];
|
||||
if (options['--auth_password'])
|
||||
scope.auth_password = options['--auth_password'];
|
||||
|
||||
var nukeopts = ['--api-id','--api-hash', '--auth-code', '--channel-name', '--phone-number', '--auth_password'];
|
||||
for (var x in nukeopts)
|
||||
delete options[nukeopts[x]];
|
||||
};
|
||||
|
||||
EditUriBackendConfig.parsers['cos'] = function (scope, module, server, port, path, options) {
|
||||
if (options['--cos-app-id'])
|
||||
@@ -779,26 +760,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
return url;
|
||||
};
|
||||
|
||||
EditUriBackendConfig.builders['telegram'] = function (scope) {
|
||||
var opts = {
|
||||
'api-id': scope.api_id,
|
||||
'api-hash': scope.api_hash,
|
||||
'auth-code': scope.auth_code,
|
||||
'channel-name': scope.channel_name,
|
||||
'phone-number': scope.phone_number,
|
||||
'auth-password': scope.auth_password,
|
||||
};
|
||||
|
||||
EditUriBackendConfig.merge_in_advanced_options(scope, opts);
|
||||
|
||||
var url = AppUtils.format('{0}://t.me/{1}',
|
||||
scope.Backend.Key,
|
||||
AppUtils.encodeDictAsUrl(opts)
|
||||
);
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
EditUriBackendConfig.builders['rclone'] = function (scope) {
|
||||
|
||||
var opts = {
|
||||
@@ -1149,26 +1110,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
continuation();
|
||||
};
|
||||
|
||||
EditUriBackendConfig.validaters['telegram'] = function (scope, continuation) {
|
||||
var opts = {
|
||||
'api-id': scope.api_id,
|
||||
'api-hash': scope.api_hash,
|
||||
'auth-code': scope.auth_code,
|
||||
'channel-name': scope.channel_name,
|
||||
'phone-number': scope.phone_number,
|
||||
'auth-password': scope.auth_password,
|
||||
};
|
||||
|
||||
var res =
|
||||
EditUriBackendConfig.require_field(scope, 'api_id', gettextCatalog.getString('api_id')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'api_hash', gettextCatalog.getString('api_hash')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'channel_name', gettextCatalog.getString('channel_name')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'phone_number', gettextCatalog.getString('phone_number'));
|
||||
|
||||
if (res)
|
||||
continuation();
|
||||
};
|
||||
|
||||
EditUriBackendConfig.validaters['rclone'] = function (scope, continuation) {
|
||||
var res =
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Remote Repository')) &&
|
||||
|
||||
@@ -72,7 +72,6 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ
|
||||
'dropbox': null,
|
||||
'sia': null,
|
||||
'tardigrade': null,
|
||||
'telegram': null,
|
||||
'jottacloud': null,
|
||||
'rclone': null,
|
||||
'cos': null
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<div class="input text">
|
||||
<label for="api_hash" translate>API Hash</label>
|
||||
<input type="text" name="api_hash" id="api_hash" ng-model="$parent.api_hash" placeholder="{{' 0123456789abcdef... '}}" />
|
||||
</div>
|
||||
<div class="input text">
|
||||
<label for="api_id" translate>API ID</label>
|
||||
<input type="text" name="api_id" id="api_id" ng-model="$parent.api_id" placeholder="{{' 1234567 '}}" />
|
||||
</div>
|
||||
<div class="input text">
|
||||
<label for="auth_code" translate>Authentication code</label>
|
||||
<input type="text" name="auth_code" id="auth_code" ng-model="$parent.auth_code" placeholder="{{' 12345 '}}" />
|
||||
</div>
|
||||
<div class="input text">
|
||||
<label for="channel_name" translate>The channel name</label>
|
||||
<input type="text" name="channel_name" id="channel_name" ng-model="$parent.channel_name" placeholder="{{' My Backup '}}" />
|
||||
</div>
|
||||
<div class="input text">
|
||||
<label for="phone_number" translate>The phone number</label>
|
||||
<input type="text" name="phone_number" id="phone_number" ng-model="$parent.phone_number" placeholder="{{' +12223334455 '}}" />
|
||||
</div>
|
||||
<div class="input password">
|
||||
<label for="auth_password" translate>The 2FA password</label>
|
||||
<input autocomplete="new-password" type="password" name="auth_password" id="auth_password" ng-model="$parent.auth_password" />
|
||||
</div>
|
||||
@@ -21,7 +21,7 @@ Removed Gitter
|
||||
|
||||
Duplicati is a free, open source, backup client that securely stores encrypted, incremental, compressed backups on cloud storage services and remote file servers. It works with:
|
||||
|
||||
*Amazon S3, [Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage"), Box, Dropbox, FTP, Google Cloud and Drive, HubiC, MEGA, Microsoft Azure and OneDrive, Rackspace Cloud Files, OpenStack Storage (Swift), Sia, Tardigrade, SSH (SFTP), WebDAV, Tencent Cloud Object Storage (COS), Telegram, [and more!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)*
|
||||
*Amazon S3, [Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage"), Box, Dropbox, FTP, Google Cloud and Drive, HubiC, MEGA, Microsoft Azure and OneDrive, Rackspace Cloud Files, OpenStack Storage (Swift), Sia, Tardigrade, SSH (SFTP), WebDAV, Tencent Cloud Object Storage (COS), [and more!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)*
|
||||
|
||||
Duplicati is licensed under LGPL and available for Windows, OSX and Linux (.NET 4.7.1+ or Mono 5.10.0+ required).
|
||||
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
https://github.com/sochix/TLSharp
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015 Ilya Pirozhenko http://www.sochix.ru/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "TLSharp",
|
||||
"description": "Unofficial Telegram (http://telegram.org) client library implemented in C#",
|
||||
"link": "https://github.com/sochix/TLSharp",
|
||||
"license": "MIT",
|
||||
"notes": ""
|
||||
}
|
||||
Reference in New Issue
Block a user