2021-08-04 00:40:09 +02:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2021-08-07 03:44:11 +02:00
using System.Net.Http ;
2021-08-20 14:45:39 +02:00
using System.Text ;
2021-08-04 00:40:09 +02:00
using System.Text.Json ;
2021-08-07 03:44:11 +02:00
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2021-08-04 00:40:09 +02:00
namespace WTelegram
{
2021-08-07 03:44:11 +02:00
public class Generator
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
readonly Dictionary < int , string > ctorToTypes = new ( ) ;
readonly HashSet < string > allTypes = new ( ) ;
2021-08-09 11:41:50 +02:00
readonly Dictionary < string , int > knownStyles = new ( ) { [ "InitConnection" ] = 1 , [ "Help_GetConfig" ] = 0 , [ "HttpWait" ] = - 1 } ;
2021-09-26 05:07:17 +02:00
readonly Dictionary < string , TypeInfo > typeInfos = new ( ) ;
2021-09-18 02:11:23 +02:00
readonly HashSet < string > enumTypes = new ( ) ;
2021-08-07 03:44:11 +02:00
int currentLayer ;
string tabIndent ;
2021-08-12 11:01:15 +02:00
private string currentJson ;
2021-08-04 00:40:09 +02:00
2021-08-07 03:44:11 +02:00
public async Task FromWeb ( )
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
Console . WriteLine ( "Fetch web pages..." ) ;
2021-08-12 11:01:15 +02:00
#if DEBUG
2021-09-17 04:53:02 +02:00
currentLayer = await Task . FromResult ( TL . Layer . Version ) ;
2021-08-12 11:01:15 +02:00
#else
2021-08-10 03:12:33 +02:00
using var http = new HttpClient ( ) ;
2021-08-20 14:45:39 +02:00
//var html = await http.GetStringAsync("https://core.telegram.org/api/layers");
//currentLayer = int.Parse(Regex.Match(html, @"#layer-(\d+)").Groups[1].Value);
//File.WriteAllBytes("TL.MTProto.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/mtproto-json"));
//File.WriteAllBytes("TL.Schema.json", await http.GetByteArrayAsync("https://core.telegram.org/schema/json"));
File . WriteAllBytes ( "TL.MTProto.tl" , await http . GetByteArrayAsync ( "https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/mtproto.tl" ) ) ;
File . WriteAllBytes ( "TL.Schema.tl" , await http . GetByteArrayAsync ( "https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/api.tl" ) ) ;
2021-08-16 22:52:33 +02:00
File . WriteAllBytes ( "TL.Secret.json" , await http . GetByteArrayAsync ( "https://core.telegram.org/schema/end-to-end-json" ) ) ;
2021-08-12 11:01:15 +02:00
#endif
2021-08-20 14:45:39 +02:00
//FromJson("TL.MTProto.json", "TL.MTProto.cs", @"TL.Table.cs");
//FromJson("TL.Schema.json", "TL.Schema.cs", @"TL.Table.cs");
FromTL ( "TL.MTProto.tl" , "TL.MTProto.cs" ) ;
FromTL ( "TL.Schema.tl" , "TL.Schema.cs" ) ;
FromJson ( "TL.Secret.json" , "TL.Secret.cs" ) ;
2021-08-07 03:44:11 +02:00
}
2021-08-20 14:45:39 +02:00
private void FromTL ( string tlPath , string outputCs )
{
using var sr = new StreamReader ( tlPath ) ;
var schema = new SchemaJson { constructors = new ( ) , methods = new ( ) } ;
string line ;
bool inFunctions = false ;
while ( ( line = sr . ReadLine ( ) ) ! = null )
{
line = line . Trim ( ) ;
if ( line = = "---functions---" )
inFunctions = true ;
else if ( line = = "---types---" )
inFunctions = false ;
else if ( line . StartsWith ( "// LAYER " ) )
currentLayer = int . Parse ( line [ 9. . ] ) ;
else if ( line ! = "" & & ! line . StartsWith ( "//" ) )
{
if ( ! line . EndsWith ( ";" ) ) System . Diagnostics . Debugger . Break ( ) ;
var words = line . Split ( ' ' ) ;
int hash = words [ 0 ] . IndexOf ( '#' ) ;
if ( hash = = - 1 ) { Console . WriteLine ( line ) ; continue ; }
if ( words [ ^ 2 ] ! = "=" ) { Console . WriteLine ( line ) ; continue ; }
string name = words [ 0 ] [ 0. . hash ] ;
int id = int . Parse ( words [ 0 ] [ ( hash + 1 ) . . ] , System . Globalization . NumberStyles . HexNumber ) ;
string type = words [ ^ 1 ] . TrimEnd ( ';' ) ;
var @params = words [ 1. . ^ 2 ] . Where ( word = > word ! = "{X:Type}" ) . Select ( word = >
{
int colon = word . IndexOf ( ':' ) ;
string name = word [ 0. . colon ] ;
string type = word [ ( colon + 1 ) . . ] ;
if ( type = = "string" & & outputCs = = "TL.MTProto.cs" & & ! name . Contains ( "message" ) ) type = "bytes" ;
return new Param { name = name , type = type } ;
} ) . ToArray ( ) ;
if ( inFunctions )
schema . methods . Add ( new Method { id = id . ToString ( ) , method = name , type = type , @params = @params } ) ;
else
schema . constructors . Add ( new Constructor { id = id . ToString ( ) , predicate = name , type = type , @params = @params } ) ;
}
}
FromSchema ( schema , outputCs ) ;
}
public void FromJson ( string jsonPath , string outputCs )
2021-08-07 03:44:11 +02:00
{
Console . WriteLine ( "Parsing " + jsonPath ) ;
2021-08-04 00:40:09 +02:00
var schema = JsonSerializer . Deserialize < SchemaJson > ( File . ReadAllText ( jsonPath ) ) ;
2021-08-20 14:45:39 +02:00
FromSchema ( schema , outputCs ) ;
}
public void FromSchema ( SchemaJson schema , string outputCs )
{
currentJson = Path . GetFileNameWithoutExtension ( outputCs ) ;
using var sw = new StreamWriter ( outputCs , false , Encoding . UTF8 ) ;
2021-09-17 04:53:02 +02:00
sw . WriteLine ( "// This file is generated automatically using the Generator class" ) ;
2021-08-04 00:40:09 +02:00
sw . WriteLine ( "using System;" ) ;
2021-08-09 11:41:50 +02:00
if ( schema . methods . Count ! = 0 ) sw . WriteLine ( "using System.Threading.Tasks;" ) ;
2021-08-04 00:40:09 +02:00
sw . WriteLine ( ) ;
sw . WriteLine ( "namespace TL" ) ;
2021-09-17 04:53:02 +02:00
sw . WriteLine ( "{" ) ;
sw . WriteLine ( "\tusing BinaryWriter = System.IO.BinaryWriter;" ) ;
sw . WriteLine ( "\tusing Client = WTelegram.Client;" ) ;
2021-08-07 03:44:11 +02:00
tabIndent = "\t" ;
2021-09-26 05:07:17 +02:00
foreach ( var ctor in schema . constructors )
2021-08-04 00:40:09 +02:00
{
2021-09-26 05:07:17 +02:00
if ( ctorToTypes . ContainsKey ( ctor . ID ) ) continue ;
if ( ctor . type = = "Vector t" ) continue ;
var structName = CSharpName ( ctor . predicate ) ;
ctorToTypes [ ctor . ID ] = ctor . layer = = 0 ? structName : $"Layer{ctor.layer}.{structName}" ;
var typeInfo = typeInfos . GetOrCreate ( ctor . type ) ;
if ( ctor . ID = = 0x5BB8E511 ) { ctorToTypes [ ctor . ID ] = structName = ctor . predicate = ctor . type = "_Message" ; }
2021-09-30 03:40:08 +02:00
else if ( ctor . ID = = TL . Layer . NullCtor ) { ctorToTypes [ ctor . ID ] + = "=null" ; typeInfo . Nullable = ctor ; }
2021-09-26 05:07:17 +02:00
if ( typeInfo . ReturnName = = null ) typeInfo . ReturnName = CSharpName ( ctor . type ) ;
typeInfo . Structs . Add ( ctor ) ;
if ( structName = = typeInfo . ReturnName ) typeInfo . MainClass = ctor ;
}
foreach ( var ( name , typeInfo ) in typeInfos )
{
if ( allTypes . Contains ( typeInfo . ReturnName ) )
2021-08-07 03:44:11 +02:00
{
2021-09-26 05:07:17 +02:00
if ( typeInfos . TryGetValue ( typeInfo . ReturnName , out var existingType ) )
{
typeInfo . ReturnName = existingType . ReturnName ;
typeInfo . MainClass = existingType . MainClass ;
}
continue ;
2021-08-07 03:44:11 +02:00
}
2021-09-30 03:40:08 +02:00
if ( typeInfo . Structs . All ( ctor = > ctor . @params . Length = = 0 ) )
typeInfo . AsEnum = true ;
var nullable = typeInfo . Structs . Where ( c = > c . predicate . EndsWith ( "Empty" ) | | c . predicate . EndsWith ( "Unknown" ) ) . ToList ( ) ;
if ( nullable . Count = = 1 & & nullable [ 0 ] . @params . Length = = 0 & & ! typeInfo . AsEnum )
{
typeInfo . Nullable = nullable [ 0 ] ;
typeInfo . Structs . Remove ( typeInfo . Nullable ) ;
ctorToTypes [ typeInfo . Nullable . ID ] + = "=null" ;
}
2021-09-26 05:07:17 +02:00
if ( typeInfo . MainClass = = null )
2021-08-04 00:40:09 +02:00
{
2021-09-26 05:07:17 +02:00
List < Param > fakeCtorParams = new ( ) ;
if ( typeInfo . Structs . Count > 1 )
2021-08-20 14:45:39 +02:00
{
2021-09-26 05:07:17 +02:00
while ( typeInfo . Structs [ 0 ] . @params . Length > fakeCtorParams . Count )
2021-09-26 01:19:32 +02:00
{
2021-09-26 05:07:17 +02:00
fakeCtorParams . Add ( typeInfo . Structs [ 0 ] . @params [ fakeCtorParams . Count ] ) ;
if ( ! typeInfo . Structs . All ( ctor = > HasPrefix ( ctor , fakeCtorParams ) ) )
{
fakeCtorParams . RemoveAt ( fakeCtorParams . Count - 1 ) ;
break ;
}
2021-09-26 01:19:32 +02:00
}
2021-09-26 05:07:17 +02:00
if ( fakeCtorParams . Count = = 0 )
2021-08-07 03:44:11 +02:00
while ( typeInfo . Structs [ 0 ] . @params . Length > fakeCtorParams . Count )
2021-08-04 00:40:09 +02:00
{
2021-09-26 05:07:17 +02:00
fakeCtorParams . Insert ( 0 , typeInfo . Structs [ 0 ] . @params [ ^ ( fakeCtorParams . Count + 1 ) ] ) ;
if ( ! typeInfo . Structs . All ( ctor = > HasSuffix ( ctor , fakeCtorParams ) ) )
2021-08-07 03:44:11 +02:00
{
2021-09-26 05:07:17 +02:00
fakeCtorParams . RemoveAt ( 0 ) ;
2021-08-07 03:44:11 +02:00
break ;
}
}
}
2021-09-26 05:07:17 +02:00
typeInfo . MainClass = new Constructor { id = null , @params = fakeCtorParams . ToArray ( ) , predicate = typeInfo . ReturnName , type = name } ;
typeInfo . Structs . Insert ( 0 , typeInfo . MainClass ) ;
typeInfo . CommonFields = fakeCtorParams . Count ; // generation of abstract main class with some common fields
}
else if ( typeInfo . Structs . Count > 1 )
{
if ( typeInfo . Structs . All ( ctor = > ctor = = typeInfo . MainClass | | HasPrefix ( ctor , typeInfo . MainClass . @params ) | | HasSuffix ( ctor , typeInfo . MainClass . @params ) ) )
typeInfo . CommonFields = typeInfo . MainClass . @params . Length ;
else
2021-08-07 03:44:11 +02:00
{
2021-09-26 05:07:17 +02:00
// the previous MainClass (ctor have the same name as ReturnName) is incompatible with other classes fields
typeInfo . MainClass = new Constructor { id = null , @params = Array . Empty < Param > ( ) , predicate = typeInfo . ReturnName + "Base" , type = name } ;
typeInfo . Structs . Insert ( 0 , typeInfo . MainClass ) ;
typeInfo . ReturnName = typeInfo . MainClass . predicate ;
2021-08-04 00:40:09 +02:00
}
}
2021-09-26 05:07:17 +02:00
}
var layers = schema . constructors . Select ( c = > c . layer ) . Distinct ( ) . ToList ( ) ;
if ( layers . Count > 1 ) // multi-layer file => generate abstract classes out of layer namespaces first
foreach ( var typeInfo in typeInfos . Values )
WriteTypeInfo ( sw , typeInfo , 0 ) ;
foreach ( var layer in layers )
{
if ( layer ! = 0 )
{
sw . WriteLine ( ) ;
sw . WriteLine ( "\tnamespace Layer" + layer ) ;
sw . Write ( "\t{" ) ;
tabIndent + = "\t" ;
}
2021-08-07 03:44:11 +02:00
foreach ( var typeInfo in typeInfos . Values )
2021-09-26 05:07:17 +02:00
WriteTypeInfo ( sw , typeInfo , layer ) ;
if ( layer ! = 0 )
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
sw . WriteLine ( "\t}" ) ;
tabIndent = tabIndent [ 1. . ] ;
2021-08-04 00:40:09 +02:00
}
}
2021-09-26 05:07:17 +02:00
if ( typeInfos . GetValueOrDefault ( "Message" ) ? . MainClass . ID = = 0x5BB8E511 ) typeInfos . Remove ( "Message" ) ;
2021-08-04 00:40:09 +02:00
2021-08-09 11:41:50 +02:00
if ( schema . methods . Count ! = 0 )
2021-08-04 00:40:09 +02:00
{
2021-08-14 15:15:41 +02:00
var ping = schema . methods . FirstOrDefault ( m = > m . method = = "ping" ) ;
if ( ping ! = null )
{
2021-09-26 01:19:32 +02:00
var typeInfo = new TypeInfo { ReturnName = ping . type , MainClass =
new Constructor { id = ping . id , @params = ping . @params , predicate = ping . method , type = ping . type } } ;
typeInfo . Structs . Add ( typeInfo . MainClass ) ;
2021-08-14 15:15:41 +02:00
ctorToTypes [ int . Parse ( ping . id ) ] = CSharpName ( ping . method ) ;
2021-09-26 05:07:17 +02:00
WriteTypeInfo ( sw , typeInfo , 0 ) ;
2021-08-14 15:15:41 +02:00
}
2021-08-09 11:41:50 +02:00
sw . WriteLine ( ) ;
2021-09-17 04:53:02 +02:00
sw . WriteLine ( "\t// ---functions---" ) ;
sw . WriteLine ( ) ;
sw . WriteLine ( $"\tpublic static class {currentJson[3..]}" ) ;
2021-08-09 11:41:50 +02:00
//sw.WriteLine("\tpublic static partial class Fn // ---functions---");
2021-08-07 03:44:11 +02:00
sw . Write ( "\t{" ) ;
tabIndent = "\t\t" ;
foreach ( var method in schema . methods )
{
2021-08-09 11:41:50 +02:00
WriteMethod ( sw , method ) ;
//var typeInfo = new TypeInfo { ReturnName = method.type };
//typeInfo.Structs.Add(new Constructor { id = method.id, @params = method.@params, predicate = method.method, type = method.type });
//methods.Add(typeInfo);
//WriteTypeInfo(sw, typeInfo, "", true);
2021-08-07 03:44:11 +02:00
}
sw . WriteLine ( "\t}" ) ;
2021-08-04 00:40:09 +02:00
}
sw . WriteLine ( "}" ) ;
2021-08-20 14:45:39 +02:00
UpdateTable ( "TL.Table.cs" ) ;
2021-08-07 03:44:11 +02:00
}
2021-08-06 20:17:19 +02:00
2021-09-26 05:07:17 +02:00
void WriteTypeInfo ( StreamWriter sw , TypeInfo typeInfo , int layer )
2021-08-07 03:44:11 +02:00
{
var genericType = typeInfo . ReturnName . Length = = 1 ? $"<{typeInfo.ReturnName}>" : null ;
bool needNewLine = true ;
2021-09-26 05:07:17 +02:00
int commonFields = 0 ;
2021-09-27 00:39:30 +02:00
foreach ( var ctor in typeInfo . Structs )
2021-08-07 03:44:11 +02:00
{
2021-09-26 05:07:17 +02:00
if ( ctor . layer ! = layer ) continue ;
2021-09-26 01:19:32 +02:00
int ctorId = ctor . ID ;
2021-08-07 03:44:11 +02:00
string className = CSharpName ( ctor . predicate ) + genericType ;
2021-09-26 05:07:17 +02:00
if ( ! allTypes . Add ( ( layer = = 0 ? "" : $"Layer{layer}." ) + className ) ) continue ;
2021-08-07 03:44:11 +02:00
if ( needNewLine ) { needNewLine = false ; sw . WriteLine ( ) ; }
2021-09-26 05:07:17 +02:00
var parentClass = ctor = = typeInfo . MainClass ? "ITLObject" : typeInfo . ReturnName ;
var parms = ctor . @params ;
2021-09-30 03:40:08 +02:00
if ( ctorId = = 0 ) // abstract parent
2021-08-12 11:01:15 +02:00
{
if ( currentJson ! = "TL.MTProto" )
sw . WriteLine ( $"{tabIndent}///<summary>See <a href=\" https : //core.telegram.org/type/{typeInfo.Structs[0].type}\"/></summary>");
2021-09-30 03:40:08 +02:00
if ( typeInfo . Nullable ! = null )
sw . WriteLine ( $"{tabIndent}///<remarks>a <c>null</c> value means <a href=\" https : //core.telegram.org/constructor/{typeInfo.Nullable.predicate}\">{typeInfo.Nullable.predicate}</a></remarks>");
if ( typeInfo . AsEnum )
2021-09-26 01:19:32 +02:00
{
WriteTypeAsEnum ( sw , typeInfo ) ;
return ;
}
sw . Write ( $"{tabIndent}public abstract partial class {ctor.predicate}" ) ;
2021-08-12 11:01:15 +02:00
}
2021-08-07 03:44:11 +02:00
else
2021-08-04 00:40:09 +02:00
{
2021-09-26 05:07:17 +02:00
string tldefReverse = null ;
if ( commonFields ! = 0 )
{
if ( ctor . @params [ 0 ] . name = = typeInfo . MainClass . @params [ 0 ] . name )
parms = ctor . @params . Skip ( commonFields ) . ToArray ( ) ;
else
{
parms = ctor . @params . Take ( ctor . @params . Length - commonFields ) . ToArray ( ) ;
tldefReverse = ", inheritAfter = true" ;
}
}
else
{
2021-09-27 00:39:30 +02:00
foreach ( var other in typeInfo . Structs )
2021-09-26 05:07:17 +02:00
{
2021-09-27 00:39:30 +02:00
if ( other = = ctor ) continue ;
var otherParams = other . @params ;
2021-09-26 05:07:17 +02:00
if ( otherParams . Length < = commonFields ) continue ;
2021-09-27 00:39:30 +02:00
if ( ! IsDerivedName ( ctor . predicate , other . predicate ) ) continue ;
2021-09-26 05:07:17 +02:00
if ( HasPrefix ( ctor , otherParams ) )
{
parms = ctor . @params . Skip ( otherParams . Length ) . ToArray ( ) ;
tldefReverse = null ;
}
else if ( HasSuffix ( ctor , otherParams ) )
{
parms = ctor . @params . Take ( ctor . @params . Length - otherParams . Length ) . ToArray ( ) ;
tldefReverse = ", inheritAfter = true" ;
}
else continue ;
commonFields = otherParams . Length ;
2021-09-27 00:39:30 +02:00
parentClass = CSharpName ( other . predicate ) + genericType ;
2021-09-26 05:07:17 +02:00
}
}
2021-08-12 11:01:15 +02:00
if ( currentJson ! = "TL.MTProto" )
{
sw . WriteLine ( $"{tabIndent}///<summary>See <a href=\" https : //core.telegram.org/constructor/{ctor.predicate}\"/></summary>");
2021-09-30 03:40:08 +02:00
if ( typeInfo . Nullable ! = null & & ctor = = typeInfo . MainClass )
sw . WriteLine ( $"{tabIndent}///<remarks>a <c>null</c> value means <a href=\" https : //core.telegram.org/constructor/{typeInfo.Nullable.predicate}\">{typeInfo.Nullable.predicate}</a></remarks>");
2021-09-26 05:07:17 +02:00
sw . WriteLine ( $"{tabIndent}[TLDef(0x{ctor.ID:X8}{tldefReverse})]" ) ;
2021-08-12 11:01:15 +02:00
}
else
{
2021-09-26 05:07:17 +02:00
sw . Write ( $"{tabIndent}[TLDef(0x{ctor.ID:X8}{tldefReverse})] //{ctor.predicate}#{ctor.ID:x8} " ) ;
2021-08-12 11:01:15 +02:00
if ( genericType ! = null ) sw . Write ( $"{{{typeInfo.ReturnName}:Type}} " ) ;
foreach ( var parm in ctor . @params ) sw . Write ( $"{parm.name}:{parm.type} " ) ;
sw . WriteLine ( $"= {ctor.type}" ) ;
}
2021-09-26 01:19:32 +02:00
sw . Write ( $"{tabIndent}public partial class {className}" ) ;
//sw.Write(skipParams == 0 && typeInfo.NeedAbstract > 0 ? "ITLObject" : parentClass);
2021-08-07 03:44:11 +02:00
}
2021-09-26 01:19:32 +02:00
sw . Write ( " : " ) ;
2021-09-26 05:07:17 +02:00
sw . Write ( parentClass ) ;
2021-08-07 03:44:11 +02:00
if ( parms . Length = = 0 )
{
sw . WriteLine ( " { }" ) ;
2021-09-26 05:07:17 +02:00
commonFields = typeInfo . CommonFields ;
2021-08-07 03:44:11 +02:00
continue ;
}
var hasFlagEnum = parms . Any ( p = > p . type . StartsWith ( "flags." ) ) ;
bool multiline = hasFlagEnum | | parms . Length > 1 ;
if ( multiline )
{
sw . WriteLine ( ) ;
sw . WriteLine ( tabIndent + "{" ) ;
}
else
sw . Write ( " { " ) ;
if ( hasFlagEnum )
{
var list = new SortedList < int , string > ( ) ;
foreach ( var parm in parms )
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
if ( ! parm . type . StartsWith ( "flags." ) | | ! parm . type . EndsWith ( "?true" ) ) continue ;
var mask = 1 < < int . Parse ( parm . type [ 6. . parm . type . IndexOf ( '?' ) ] ) ;
if ( ! list . ContainsKey ( mask ) ) list [ mask ] = MapName ( parm . name ) ;
2021-08-04 00:40:09 +02:00
}
2021-08-07 03:44:11 +02:00
foreach ( var parm in parms )
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
if ( ! parm . type . StartsWith ( "flags." ) | | parm . type . EndsWith ( "?true" ) ) continue ;
var mask = 1 < < int . Parse ( parm . type [ 6. . parm . type . IndexOf ( '?' ) ] ) ;
if ( list . ContainsKey ( mask ) ) continue ;
var name = MapName ( "has_" + parm . name ) ;
if ( list . Values . Contains ( name ) ) name + = "_field" ;
list [ mask ] = name ;
2021-08-04 00:40:09 +02:00
}
2021-08-07 03:44:11 +02:00
string line = tabIndent + "\t[Flags] public enum Flags { " ;
foreach ( var ( mask , name ) in list )
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
var str = $"{name} = 0x{mask:X}, " ;
if ( line . Length + str . Length + tabIndent . Length * 3 > = 134 ) { sw . WriteLine ( line ) ; line = tabIndent + "\t\t" ; }
line + = str ;
2021-08-04 00:40:09 +02:00
}
2021-08-07 03:44:11 +02:00
sw . WriteLine ( line . TrimEnd ( ',' , ' ' ) + " }" ) ;
}
foreach ( var parm in parms )
{
if ( parm . type . EndsWith ( "?true" ) ) continue ;
if ( multiline ) sw . Write ( tabIndent + "\t" ) ;
if ( parm . type = = "#" )
sw . Write ( $"public {(hasFlagEnum ? " Flags " : " int ")} {parm.name};" ) ;
2021-08-06 07:28:54 +02:00
else
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
if ( parm . type . StartsWith ( "flags." ) )
2021-08-04 00:40:09 +02:00
{
2021-08-07 03:44:11 +02:00
int qm = parm . type . IndexOf ( '?' ) ;
sw . Write ( $"[IfFlag({parm.type[6..qm]})] public {MapType(parm.type[(qm + 1)..], parm.name)} {MapName(parm.name)};" ) ;
2021-08-04 00:40:09 +02:00
}
else
2021-08-07 03:44:11 +02:00
sw . Write ( $"public {MapType(parm.type, parm.name)} {MapName(parm.name)};" ) ;
2021-08-04 00:40:09 +02:00
}
2021-08-07 03:44:11 +02:00
if ( multiline ) sw . WriteLine ( ) ;
2021-08-04 00:40:09 +02:00
}
2021-08-07 03:44:11 +02:00
if ( multiline )
sw . WriteLine ( tabIndent + "}" ) ;
else
sw . WriteLine ( " }" ) ;
2021-09-26 05:07:17 +02:00
commonFields = typeInfo . CommonFields ;
2021-08-07 03:44:11 +02:00
}
2021-08-09 11:41:50 +02:00
}
2021-09-26 05:07:17 +02:00
private static bool IsDerivedName ( string derived , string basename )
{
int left , right ;
if ( basename . Length > = derived . Length ) return false ;
for ( left = 0 ; left < basename . Length ; left + + )
if ( basename [ left ] ! = derived [ left ] )
break ;
if ( left = = 0 ) return false ;
for ( right = 1 ; left + right < = basename . Length ; right + + )
if ( basename [ ^ right ] ! = derived [ ^ right ] )
break ;
return left + right > basename . Length ;
}
2021-09-18 02:11:23 +02:00
private void WriteTypeAsEnum ( StreamWriter sw , TypeInfo typeInfo )
{
enumTypes . Add ( typeInfo . ReturnName ) ;
2021-09-23 13:13:36 +02:00
bool lowercase = typeInfo . ReturnName = = "Storage_FileType" ;
2021-09-18 02:11:23 +02:00
sw . WriteLine ( $"{tabIndent}public enum {typeInfo.ReturnName} : uint" ) ;
sw . WriteLine ( $"{tabIndent}{{" ) ;
string prefix = "" ;
2021-09-26 01:19:32 +02:00
while ( ( prefix + = typeInfo . Structs [ 1 ] . predicate [ prefix . Length ] ) ! = null )
if ( ! typeInfo . Structs . All ( ctor = > ctor . id = = null | | ctor . predicate . StartsWith ( prefix ) ) )
2021-09-18 02:11:23 +02:00
break ;
int prefixLen = CSharpName ( prefix ) . Length - 1 ;
foreach ( var ctor in typeInfo . Structs )
{
2021-09-26 01:19:32 +02:00
if ( ctor . id = = null ) continue ;
2021-09-18 02:11:23 +02:00
string className = CSharpName ( ctor . predicate ) ;
if ( ! allTypes . Add ( className ) ) continue ;
2021-09-23 13:13:36 +02:00
if ( lowercase ) className = className . ToLowerInvariant ( ) ;
2021-09-18 02:11:23 +02:00
ctorToTypes . Remove ( ctor . ID ) ;
sw . WriteLine ( $"{tabIndent}\t///<summary>See <a href=\" https : //core.telegram.org/constructor/{ctor.predicate}\"/></summary>");
sw . WriteLine ( $"{tabIndent}\t{className[prefixLen..]} = 0x{ctor.ID:X8}," ) ;
}
sw . WriteLine ( $"{tabIndent}}}" ) ;
}
2021-08-09 11:41:50 +02:00
private static string MapName ( string name ) = > name switch
{
"out" = > "out_" ,
"static" = > "static_" ,
"long" = > "long_" ,
"default" = > "default_" ,
"public" = > "public_" ,
"params" = > "params_" ,
"private" = > "private_" ,
_ = > name
} ;
private string MapType ( string type , string name )
{
if ( type . StartsWith ( "Vector<" , StringComparison . OrdinalIgnoreCase ) )
return MapType ( type [ 7. . ^ 1 ] , name ) + "[]" ;
else if ( type = = "Bool" )
return "bool" ;
else if ( type = = "bytes" )
return "byte[]" ;
else if ( type = = "int128" )
return "Int128" ;
else if ( type = = "int256" )
return "Int256" ;
else if ( type = = "Object" )
return "ITLObject" ;
else if ( type = = "!X" )
2021-08-12 12:37:56 +02:00
return "ITLFunction" ;
2021-08-09 11:41:50 +02:00
else if ( type = = "int" )
{
var name2 = '_' + name + '_' ;
if ( name2 . EndsWith ( "_date_" ) | | name2 . EndsWith ( "_time_" ) | | name2 . StartsWith ( "_valid_" ) | |
name2 = = "_expires_" | | name2 = = "_expires_at_" | | name2 = = "_now_" )
return "DateTime" ;
else
return "int" ;
}
else if ( type = = "string" )
return name . StartsWith ( "md5" ) ? "byte[]" : "string" ;
2021-08-20 14:45:39 +02:00
else if ( type = = "long" | | type = = "double" | | type = = "X" )
2021-08-09 11:41:50 +02:00
return type ;
2021-08-20 14:45:39 +02:00
else if ( typeInfos . TryGetValue ( type , out var typeInfo ) )
return typeInfo . ReturnName ;
else
{ // try to find type in a lower layer
2021-09-26 05:07:17 +02:00
/ * foreach ( var layer in typeInfosByLayer . OrderByDescending ( kvp = > kvp . Key ) )
2021-08-20 14:45:39 +02:00
if ( layer . Value . TryGetValue ( type , out typeInfo ) )
2021-09-26 05:07:17 +02:00
return layer . Key = = 0 ? typeInfo . ReturnName : $"Layer{layer.Key}.{typeInfo.ReturnName}" ; * /
2021-08-20 14:45:39 +02:00
return CSharpName ( type ) ;
}
2021-08-09 11:41:50 +02:00
}
private string MapOptionalType ( string type , string name )
{
if ( type = = "Bool" )
return "bool?" ;
else if ( type = = "long" )
return "long?" ;
else if ( type = = "double" )
return "double?" ;
else if ( type = = "int128" )
return "Int128?" ;
else if ( type = = "int256" )
return "Int256?" ;
else if ( type = = "int" )
2021-08-07 03:44:11 +02:00
{
2021-08-09 11:41:50 +02:00
var name2 = '_' + name + '_' ;
if ( name2 . EndsWith ( "_date_" ) | | name2 . EndsWith ( "_time_" ) | | name2 = = "_expires_" | | name2 = = "_now_" | | name2 . StartsWith ( "_valid_" ) )
return "DateTime?" ;
else
return "int?" ;
}
else
return MapType ( type , name ) ;
}
private void WriteMethod ( StreamWriter sw , Method method )
{
int ctorNb = int . Parse ( method . id ) ;
var funcName = CSharpName ( method . method ) ;
string returnType = MapType ( method . type , "" ) ;
int style = knownStyles . GetValueOrDefault ( funcName , 2 ) ;
// styles: 0 = static string, 1 = static ITLFunction<>, 2 = Task<>, -1 = skip method
if ( style = = - 1 ) return ;
sw . WriteLine ( ) ;
2021-08-07 03:44:11 +02:00
2021-08-14 15:15:41 +02:00
var callAsync = "CallAsync" ;
2021-08-12 12:37:56 +02:00
if ( method . type . Length = = 1 & & style ! = 1 ) funcName + = $"<{returnType}>" ;
2021-08-12 11:01:15 +02:00
if ( currentJson ! = "TL.MTProto" )
sw . WriteLine ( $"{tabIndent}///<summary>See <a href=\" https : //core.telegram.org/method/{method.method}\"/></summary>");
else
{
2021-08-14 15:15:41 +02:00
if ( method . type is not "FutureSalts" and not "Pong" ) callAsync = "CallBareAsync" ;
2021-08-12 11:01:15 +02:00
sw . Write ( $"{tabIndent}//{method.method}#{ctorNb:x8} " ) ;
if ( method . type . Length = = 1 ) sw . Write ( $"{{{method.type}:Type}} " ) ;
foreach ( var parm in method . @params ) sw . Write ( $"{parm.name}:{parm.type} " ) ;
sw . WriteLine ( $"= {method.type}" ) ;
}
2021-08-09 11:41:50 +02:00
2021-09-17 04:53:02 +02:00
if ( style = = 0 ) sw . WriteLine ( $"{tabIndent}public static Task<{returnType}> {funcName}(this Client client) => client.{callAsync}<{returnType}>({funcName});" ) ;
2021-08-09 11:41:50 +02:00
if ( style = = 0 ) sw . Write ( $"{tabIndent}public static string {funcName}(BinaryWriter writer" ) ;
2021-08-12 12:37:56 +02:00
if ( style = = 1 ) sw . Write ( $"{tabIndent}public static ITLFunction {funcName}(" ) ;
2021-09-17 04:53:02 +02:00
if ( style = = 2 ) sw . Write ( $"{tabIndent}public static Task<{returnType}> {funcName}(this Client client" ) ;
bool first = style = = 1 ;
2021-08-09 11:41:50 +02:00
foreach ( var parm in method . @params ) // output non-optional parameters
{
if ( parm . type = = "#" | | parm . type . StartsWith ( "flags." ) ) continue ;
if ( first ) first = false ; else sw . Write ( ", " ) ;
sw . Write ( $"{MapType(parm.type, parm.name)} {MapName(parm.name)}" ) ;
}
string flagExpr = null ;
foreach ( var parm in method . @params ) // output optional parameters
2021-08-07 03:44:11 +02:00
{
2021-08-09 11:41:50 +02:00
if ( ! parm . type . StartsWith ( "flags." ) ) continue ;
var parmName = MapName ( parm . name ) ;
int qm = parm . type . IndexOf ( '?' ) ;
int bit = int . Parse ( parm . type [ 6. . qm ] ) ;
if ( first ) first = false ; else sw . Write ( ", " ) ;
if ( parm . type . EndsWith ( "?true" ) )
2021-08-04 00:40:09 +02:00
{
2021-08-09 11:41:50 +02:00
sw . Write ( $"bool {parmName} = false" ) ;
flagExpr + = $" | ({parmName} ? 0x{1 << bit:X} : 0)" ;
2021-08-04 00:40:09 +02:00
}
2021-08-07 03:44:11 +02:00
else
2021-08-09 11:41:50 +02:00
{
sw . Write ( $"{MapOptionalType(parm.type[(qm + 1)..], parm.name)} {parmName} = null" ) ;
flagExpr + = $" | ({parmName} != null ? 0x{1 << bit:X} : 0)" ;
}
}
if ( flagExpr ! = null ) flagExpr = flagExpr . IndexOf ( '|' , 3 ) > = 0 ? flagExpr [ 3. . ] : flagExpr [ 4. . ^ 1 ] ;
sw . WriteLine ( ")" ) ;
if ( style ! = 0 ) tabIndent + = "\t" ;
if ( style = = 1 ) sw . WriteLine ( $"{tabIndent}=> writer =>" ) ;
2021-09-17 04:53:02 +02:00
if ( style = = 2 ) sw . WriteLine ( $"{tabIndent}=> client.{callAsync}<{returnType}>(writer =>" ) ;
2021-08-09 11:41:50 +02:00
sw . WriteLine ( tabIndent + "{" ) ;
sw . WriteLine ( $"{tabIndent}\twriter.Write(0x{ctorNb:X8});" ) ;
foreach ( var parm in method . @params ) // serialize request
{
var parmName = MapName ( parm . name ) ;
var parmType = parm . type ;
if ( parmType . StartsWith ( "flags." ) )
{
if ( parmType . EndsWith ( "?true" ) ) continue ;
int qm = parmType . IndexOf ( '?' ) ;
parmType = parmType [ ( qm + 1 ) . . ] ;
sw . WriteLine ( $"{tabIndent}\tif ({parmName} != null)" ) ;
sw . Write ( '\t' ) ;
2021-08-16 22:30:45 +02:00
if ( MapOptionalType ( parmType , parm . name ) . EndsWith ( "?" ) )
2021-08-09 11:41:50 +02:00
parmName + = ".Value" ;
}
switch ( parmType )
{
case "Bool" :
sw . WriteLine ( $"{tabIndent}\twriter.Write({parmName} ? 0x997275B5 : 0xBC799737);" ) ;
break ;
case "bytes" :
sw . WriteLine ( $"{tabIndent}\twriter.WriteTLBytes({parmName});" ) ;
break ;
case "long" : case "int128" : case "int256" : case "double" :
sw . WriteLine ( $"{tabIndent}\twriter.Write({parmName});" ) ;
break ;
case "int" :
if ( MapType ( parmType , parm . name ) = = "int" )
sw . WriteLine ( $"{tabIndent}\twriter.Write({parmName});" ) ;
else
sw . WriteLine ( $"{tabIndent}\twriter.WriteTLStamp({parmName});" ) ;
break ;
case "string" :
if ( parm . name . StartsWith ( "md5" ) )
sw . WriteLine ( $"{tabIndent}\twriter.WriteTLBytes({parmName});" ) ;
else
sw . WriteLine ( $"{tabIndent}\twriter.WriteTLString({parmName});" ) ;
break ;
case "#" :
sw . WriteLine ( $"{tabIndent}\twriter.Write({flagExpr});" ) ;
break ;
case "!X" :
sw . WriteLine ( $"{tabIndent}\t{parmName}(writer);" ) ;
break ;
default :
if ( parmType . StartsWith ( "Vector<" , StringComparison . OrdinalIgnoreCase ) )
sw . WriteLine ( $"{tabIndent}\twriter.WriteTLVector({parmName});" ) ;
2021-09-18 02:11:23 +02:00
else if ( enumTypes . Contains ( parmType ) )
sw . WriteLine ( $"{tabIndent}\twriter.Write((uint){parmName});" ) ;
2021-08-09 11:41:50 +02:00
else
sw . WriteLine ( $"{tabIndent}\twriter.WriteTLObject({parmName});" ) ;
break ;
}
2021-08-04 00:40:09 +02:00
}
2021-08-09 11:41:50 +02:00
sw . WriteLine ( $"{tabIndent}\treturn \" { funcName } \ ";" ) ;
if ( style = = 0 ) sw . WriteLine ( tabIndent + "}" ) ;
if ( style = = 1 ) sw . WriteLine ( tabIndent + "};" ) ;
if ( style = = 2 ) sw . WriteLine ( tabIndent + "});" ) ;
if ( style ! = 0 ) tabIndent = tabIndent [ 0. . ^ 1 ] ;
2021-08-07 03:44:11 +02:00
}
2021-08-06 20:17:19 +02:00
2021-08-14 15:15:41 +02:00
void UpdateTable ( string tableCs )
2021-08-07 03:44:11 +02:00
{
2021-09-30 03:40:08 +02:00
int tableId = 0 ;
2021-08-12 11:01:15 +02:00
var myTag = $"\t\t\t// from {currentJson}:" ;
2021-08-07 03:44:11 +02:00
var seen_ids = new HashSet < int > ( ) ;
2021-09-30 03:40:08 +02:00
var seen_nullables = new HashSet < string > ( ) ;
2021-08-07 03:44:11 +02:00
using ( var sr = new StreamReader ( tableCs ) )
2021-08-20 14:45:39 +02:00
using ( var sw = new StreamWriter ( tableCs + ".new" , false , Encoding . UTF8 ) )
2021-08-06 20:17:19 +02:00
{
2021-08-07 03:44:11 +02:00
string line ;
while ( ( line = sr . ReadLine ( ) ) ! = null )
2021-08-06 20:17:19 +02:00
{
2021-08-07 03:44:11 +02:00
if ( currentLayer ! = 0 & & line . StartsWith ( "\t\tpublic const int Layer" ) )
2021-08-07 06:23:13 +02:00
sw . WriteLine ( $"\t\tpublic const int Layer = {currentLayer};\t\t\t\t\t// fetched {DateTime.UtcNow}" ) ;
2021-08-07 03:44:11 +02:00
else
sw . WriteLine ( line ) ;
if ( line = = myTag )
2021-08-06 20:17:19 +02:00
{
2021-09-30 03:40:08 +02:00
switch ( + + tableId )
{
case 1 :
foreach ( var ctor in ctorToTypes )
if ( seen_ids . Add ( ctor . Key ) )
if ( ctor . Value . EndsWith ( "=null" ) )
sw . WriteLine ( $"\t\t\t[0x{ctor.Key:X8}] = null,//{ctor.Value[..^5]}" ) ;
else
sw . WriteLine ( $"\t\t\t[0x{ctor.Key:X8}] = typeof({ctor.Value})," ) ;
break ;
case 2 :
foreach ( var typeInfo in typeInfos . Values )
if ( typeInfo . Nullable ! = null & & seen_nullables . Add ( typeInfo . ReturnName ) )
sw . WriteLine ( $"\t\t\t[typeof({typeInfo.ReturnName})]{new string(' ', 30 - typeInfo.ReturnName.Length)} = 0x{typeInfo.Nullable.ID:X8}, //{typeInfo.Nullable.predicate}" ) ;
break ;
}
2021-08-07 03:44:11 +02:00
while ( ( line = sr . ReadLine ( ) ) ! = null )
if ( line . StartsWith ( "\t\t\t// " ) )
break ;
2021-08-06 20:17:19 +02:00
sw . WriteLine ( line ) ;
}
2021-08-07 03:44:11 +02:00
else if ( line . StartsWith ( "\t\t\t[0x" ) )
seen_ids . Add ( int . Parse ( line [ 6. . 14 ] , System . Globalization . NumberStyles . HexNumber ) ) ;
2021-09-30 03:40:08 +02:00
else if ( line . StartsWith ( "\t\t\t[typeof(" ) )
seen_nullables . Add ( line [ 11. . line . IndexOf ( ')' ) ] ) ;
2021-08-06 20:17:19 +02:00
}
}
2021-08-07 03:44:11 +02:00
File . Replace ( tableCs + ".new" , tableCs , null ) ;
2021-08-04 00:40:09 +02:00
}
private static bool HasPrefix ( Constructor ctor , IList < Param > prefixParams )
{
if ( ctor . @params . Length < prefixParams . Count ) return false ;
for ( int i = 0 ; i < prefixParams . Count ; i + + )
if ( ctor . @params [ i ] . name ! = prefixParams [ i ] . name | | ctor . @params [ i ] . type ! = prefixParams [ i ] . type )
return false ;
return true ;
}
2021-09-26 05:07:17 +02:00
private static bool HasSuffix ( Constructor ctor , IList < Param > prefixParams )
{
if ( ctor . @params . Length < prefixParams . Count ) return false ;
for ( int i = 1 ; i < = prefixParams . Count ; i + + )
if ( ctor . @params [ ^ i ] . name ! = prefixParams [ ^ i ] . name | | ctor . @params [ ^ i ] . type ! = prefixParams [ ^ i ] . type )
return false ;
return true ;
}
2021-08-04 00:40:09 +02:00
private static string CSharpName ( string name )
{
name = char . ToUpper ( name [ 0 ] ) + name [ 1. . ] ;
int i ;
while ( ( i = name . IndexOf ( '_' ) ) > 0 )
name = name [ . . i ] + char . ToUpper ( name [ i + 1 ] ) + name [ ( i + 2 ) . . ] ;
while ( ( i = name . IndexOf ( '.' ) ) > 0 )
name = name [ . . i ] + '_' + char . ToUpper ( name [ i + 1 ] ) + name [ ( i + 2 ) . . ] ;
return name ;
}
class TypeInfo
{
public string ReturnName ;
2021-09-26 01:19:32 +02:00
public Constructor MainClass ;
2021-09-30 03:40:08 +02:00
public Constructor Nullable ;
2021-08-04 00:40:09 +02:00
public List < Constructor > Structs = new ( ) ;
2021-09-26 01:19:32 +02:00
internal int CommonFields ; // n fields are common among all those classes
2021-09-30 03:40:08 +02:00
internal bool AsEnum ;
2021-08-04 00:40:09 +02:00
}
#pragma warning disable IDE1006 // Naming Styles
public class SchemaJson
{
2021-08-09 11:41:50 +02:00
public List < Constructor > constructors { get ; set ; }
public List < Method > methods { get ; set ; }
2021-08-04 00:40:09 +02:00
}
public class Constructor
{
public string id { get ; set ; }
public string predicate { get ; set ; }
public Param [ ] @params { get ; set ; }
public string type { get ; set ; }
2021-08-07 03:44:11 +02:00
public int layer { get ; set ; }
2021-09-26 01:19:32 +02:00
public int ID = > string . IsNullOrEmpty ( id ) ? 0 : int . Parse ( id ) ;
2021-08-04 00:40:09 +02:00
}
public class Param
{
public string name { get ; set ; }
public string type { get ; set ; }
}
public class Method
{
public string id { get ; set ; }
public string method { get ; set ; }
public Param [ ] @params { get ; set ; }
public string type { get ; set ; }
}
#pragma warning restore IDE1006 // Naming Styles
}
}