You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-11 16:00:49 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
985f6e57c3 | ||
![]() |
d9eea2e80f | ||
![]() |
0cc4a87a4c | ||
![]() |
66d0864d72 | ||
![]() |
965ac2b513 | ||
![]() |
a95334ec57 | ||
![]() |
e05a21a034 | ||
![]() |
13cd5d0870 | ||
![]() |
8d9ad7af41 | ||
![]() |
0a11816acf | ||
![]() |
81ba4ff7d3 | ||
![]() |
f5a9ed08a1 | ||
![]() |
6b8bfde19f | ||
![]() |
ff738f2849 | ||
![]() |
3d35c24a2e | ||
![]() |
41d7151e7e | ||
![]() |
94246ef163 | ||
![]() |
9c118631cf | ||
![]() |
2ee9ab234f | ||
![]() |
606f2184df |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://nilaoda.github.io/N_m3u8DL-CLI/source/images/alipay.png','https://www.buymeacoffee.com/nilaoda']
|
661
N_m3u8DL-CLI/CSChaCha20.cs
Normal file
661
N_m3u8DL-CLI/CSChaCha20.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using CSChaCha20;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
@@ -45,6 +47,40 @@ namespace N_m3u8DL_CLI
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static byte[] CHACHA20Decrypt(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
|
||||
{
|
||||
if (keyBytes.Length != 32)
|
||||
throw new Exception("Key must be 32 bytes!");
|
||||
if (nonceBytes.Length != 12 && nonceBytes.Length != 8)
|
||||
throw new Exception("Key must be 12 or 8 bytes!");
|
||||
if (nonceBytes.Length == 8)
|
||||
nonceBytes = (new byte[4] { 0, 0, 0, 0 }).Concat(nonceBytes).ToArray();
|
||||
|
||||
var decStream = new MemoryStream();
|
||||
using (BinaryReader reader = new BinaryReader(new MemoryStream(encryptedBuff)))
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(decStream))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var buffer = reader.ReadBytes(1024);
|
||||
byte[] dec = new byte[buffer.Length];
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
ChaCha20 forDecrypting = new ChaCha20(keyBytes, nonceBytes, 0);
|
||||
forDecrypting.DecryptBytes(dec, buffer);
|
||||
writer.Write(dec, 0, dec.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return decStream.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] HexStringToBytes(string hexStr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hexStr))
|
||||
|
@@ -47,6 +47,10 @@ namespace N_m3u8DL_CLI
|
||||
public long ExpectByte { get => expectByte; set => expectByte = value; }
|
||||
public long StartByte { get => startByte; set => startByte = value; }
|
||||
public double SegDur { get => segDur; set => segDur = value; }
|
||||
|
||||
public static bool EnableChaCha20 { get; set; } = false;
|
||||
public static string ChaCha20KeyBase64 { get; set; }
|
||||
public static string ChaCha20NonceBase64 { get; set; }
|
||||
|
||||
//重写WebClinet
|
||||
//private class WebClient : System.Net.WebClient
|
||||
@@ -179,9 +183,18 @@ namespace N_m3u8DL_CLI
|
||||
if (File.Exists(savePath) && Global.ShouldStop == false)
|
||||
{
|
||||
FileInfo fi = new FileInfo(savePath);
|
||||
if (Method == "NONE" || method.Contains("NOTSUPPORTED"))
|
||||
if (File.Exists(fi.FullName) && EnableChaCha20)
|
||||
{
|
||||
fi.MoveTo(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts");
|
||||
byte[] decryptBuff = Decrypter.CHACHA20Decrypt(File.ReadAllBytes(fi.FullName), Convert.FromBase64String(ChaCha20KeyBase64), Convert.FromBase64String(ChaCha20NonceBase64));
|
||||
FileStream fs = new FileStream(Path.GetDirectoryName(SavePath) + "\\" + Path.GetFileNameWithoutExtension(SavePath) + ".ts", FileMode.Create);
|
||||
fs.Write(decryptBuff, 0, decryptBuff.Length);
|
||||
fs.Close();
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
fi.Delete();
|
||||
}
|
||||
else if (Method == "NONE" || Method.Contains("NOTSUPPORTED"))
|
||||
{
|
||||
fi.MoveTo(Path.GetDirectoryName(SavePath) + "\\" + Path.GetFileNameWithoutExtension(SavePath) + ".ts");
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
//Console.WriteLine(Path.GetFileNameWithoutExtension(savePath) + " Completed.");
|
||||
}
|
||||
@@ -249,4 +262,4 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ namespace N_m3u8DL_CLI
|
||||
/*===============================================================================*/
|
||||
static Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
static string nowVer = $"{ver.Major}.{ver.Minor}.{ver.Build}";
|
||||
static string nowDate = "20220524";
|
||||
static string nowDate = "20220711";
|
||||
public static void WriteInit()
|
||||
{
|
||||
Console.WriteLine($"N_m3u8DL-CLI version {nowVer} 2018-2022");
|
||||
|
@@ -61,7 +61,7 @@ namespace N_m3u8DL_CLI
|
||||
string m3u8Url = initJson["m3u8"].Value<string>();
|
||||
targetduration = initJson["m3u8Info"]["targetDuration"].Value<double>();
|
||||
TotalDuration = initJson["m3u8Info"]["totalDuration"].Value<double>();
|
||||
timer.Interval = (TotalDuration - targetduration) * 1000;//设置定时器运行间隔
|
||||
timer.Interval = Math.Abs(TotalDuration - targetduration) * 1000;//设置定时器运行间隔
|
||||
if (timer.Interval <= 1000) timer.Interval = 10000;
|
||||
JArray lastSegments = JArray.Parse(initJson["m3u8Info"]["segments"][0].ToString().Trim()); //上次的分段,用于比对新分段
|
||||
ArrayList tempList = new ArrayList(); //所有待下载的列表
|
||||
@@ -79,6 +79,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
|
||||
Parser parser = new Parser();
|
||||
parser.Headers = Headers;
|
||||
parser.DownDir = Path.GetDirectoryName(jsonFile);
|
||||
parser.M3u8Url = m3u8Url;
|
||||
parser.LiveStream = true;
|
||||
@@ -128,6 +129,7 @@ namespace N_m3u8DL_CLI
|
||||
sd.Iv = info["iv"].Value<string>();
|
||||
}
|
||||
sd.TimeOut = (int)timer.Interval - 1000;//超时时间不超过下次执行时间
|
||||
if (sd.TimeOut <= 0) sd.TimeOut = (int)timer.Interval;
|
||||
sd.SegIndex = index;
|
||||
sd.Headers = Headers;
|
||||
sd.SegDur = info["duration"].Value<double>();
|
||||
|
@@ -563,12 +563,19 @@ namespace N_m3u8DL_CLI
|
||||
Console.Write("".PadRight(13) + "Enter Numbers Separated By A Space: ");
|
||||
var input = Console.ReadLine();
|
||||
cursorIndex += 2;
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
try
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
;
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
bestVideo = new Dictionary<string, dynamic>() { ["Tbr"] = 0 };
|
||||
|
@@ -97,5 +97,14 @@ namespace N_m3u8DL_CLI
|
||||
[Option("unregisterUrlProtocol", HelpText = "Help_unregisterUrlProtocol", ResourceType = typeof(strings))]
|
||||
public bool UnregisterUrlProtocol { get; set; }
|
||||
|
||||
[Option("enableChaCha20", HelpText = "enableChaCha20")]
|
||||
public bool EnableChaCha20 { get; set; }
|
||||
|
||||
[Option("chaCha20KeyBase64", HelpText = "ChaCha20KeyBase64")]
|
||||
public string ChaCha20KeyBase64 { get; set; }
|
||||
|
||||
[Option("chaCha20NonceBase64", HelpText = "ChaCha20NonceBase64")]
|
||||
public string ChaCha20NonceBase64 { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -90,6 +90,7 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CSChaCha20.cs" />
|
||||
<Compile Include="Decode51CtoKey.cs" />
|
||||
<Compile Include="DecodeCdeledu.cs" />
|
||||
<Compile Include="DecodeDdyun.cs" />
|
||||
|
@@ -271,6 +271,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
expectByte = Convert.ToInt64(t[0]);
|
||||
segInfo.Add("expectByte", expectByte);
|
||||
segInfo.Add("startByte", segments.Last["startByte"].Value<long>() + segments.Last["expectByte"].Value<long>());
|
||||
}
|
||||
if (t.Length == 2)
|
||||
{
|
||||
@@ -326,7 +327,7 @@ namespace N_m3u8DL_CLI
|
||||
continue;
|
||||
}
|
||||
//常规情况的#EXT-X-DISCONTINUITY标记,新建part
|
||||
if (!hasAd && segments.Count > 1)
|
||||
if (!hasAd && segments.Count >= 1)
|
||||
{
|
||||
parts.Add(segments);
|
||||
segments = new JArray();
|
||||
@@ -361,20 +362,20 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
string[] tmp = line.Replace(HLSTags.extinf + ":", "").Split(',');
|
||||
segDuration = Convert.ToDouble(tmp[0]);
|
||||
segInfo.Add("index", segIndex);
|
||||
segInfo.Add("method", m3u8CurrentKey[0]);
|
||||
segInfo["index"] = segIndex;
|
||||
segInfo["method"] = m3u8CurrentKey[0];
|
||||
//是否有加密,有的话写入KEY和IV
|
||||
if (m3u8CurrentKey[0] != "NONE")
|
||||
{
|
||||
segInfo.Add("key", m3u8CurrentKey[1]);
|
||||
segInfo["key"] = m3u8CurrentKey[1];
|
||||
//没有读取到IV,自己生成
|
||||
if (m3u8CurrentKey[2] == "")
|
||||
segInfo.Add("iv", "0x" + Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
||||
segInfo["iv"] = "0x" + Convert.ToString(segIndex, 16).PadLeft(32, '0');
|
||||
else
|
||||
segInfo.Add("iv", m3u8CurrentKey[2]);
|
||||
segInfo["iv"] = m3u8CurrentKey[2];
|
||||
}
|
||||
totalDuration += segDuration;
|
||||
segInfo.Add("duration", segDuration);
|
||||
segInfo["duration"] = segDuration;
|
||||
expectSegment = true;
|
||||
segIndex++;
|
||||
}
|
||||
@@ -635,12 +636,19 @@ namespace N_m3u8DL_CLI
|
||||
Console.Write("".PadRight(13) + "Enter Number: ");
|
||||
var input = Console.ReadLine();
|
||||
cursorIndex += 2;
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
try
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
;
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
audioUrl = MEDIA_AUDIO_GROUP[bestUrlAudio][int.Parse(input)].Uri;
|
||||
}
|
||||
}
|
||||
@@ -665,12 +673,19 @@ namespace N_m3u8DL_CLI
|
||||
Console.Write("".PadRight(13) + "Enter Number: ");
|
||||
var input = Console.ReadLine();
|
||||
cursorIndex += 2;
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
try
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
;
|
||||
}
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
subUrl = MEDIA_SUB_GROUP[bestUrlSub][int.Parse(input)].Uri;
|
||||
}
|
||||
}
|
||||
|
@@ -77,8 +77,14 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
{
|
||||
if (args[0].ToLower().StartsWith("m3u8dl:"))
|
||||
{
|
||||
var valueBytes = Convert.FromBase64String(args[0].Substring(7));
|
||||
var cmd = Encoding.UTF8.GetString(valueBytes);
|
||||
var base64 = args[0].Replace("m3u8dl://", "").Replace("m3u8dl:", "");
|
||||
var cmd = "";
|
||||
try { cmd = Encoding.UTF8.GetString(Convert.FromBase64String(base64)); }
|
||||
catch (FormatException) { cmd = Encoding.UTF8.GetString(Convert.FromBase64String(base64.TrimEnd('/'))); }
|
||||
//修正参数转义符
|
||||
cmd = cmd.Replace("\\\"", "\"");
|
||||
//修正工作目录
|
||||
Environment.CurrentDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
|
||||
args = Global.ParseArguments(cmd).ToArray(); //解析命令行
|
||||
}
|
||||
else if (args[0] == "--registerUrlProtocol")
|
||||
@@ -221,6 +227,13 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
workDir = Environment.ExpandEnvironmentVariables(o.WorkDir);
|
||||
DownloadManager.HasSetDir = true;
|
||||
}
|
||||
//CHACHA20
|
||||
if (o.EnableChaCha20 && !string.IsNullOrEmpty(o.ChaCha20KeyBase64) && !string.IsNullOrEmpty(o.ChaCha20NonceBase64))
|
||||
{
|
||||
Downloader.EnableChaCha20 = true;
|
||||
Downloader.ChaCha20KeyBase64 = o.ChaCha20KeyBase64;
|
||||
Downloader.ChaCha20NonceBase64 = o.ChaCha20NonceBase64;
|
||||
}
|
||||
|
||||
//Proxy
|
||||
if (!string.IsNullOrEmpty(o.ProxyAddress))
|
||||
|
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
|
||||
// 方法是按如下所示使用“*”: :
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("3.0.0.0")]
|
||||
[assembly: AssemblyVersion("3.0.2.0")]
|
||||
[assembly: AssemblyFileVersion("3.0.2.0")]
|
||||
|
15
README.md
15
README.md
@@ -22,7 +22,9 @@
|
||||
本项目已于2019年10月9日开源,采用MIT许可证,各取所需。
|
||||
|
||||
# 关于跨平台
|
||||
未来可期
|
||||
* N_m3u8DL-CLI `(本项目)`: 基于 .NET Framework, 不具备跨平台能力. 目前已进入维护阶段.
|
||||
|
||||
* [N_m3u8DL-RE](https://github.com/nilaoda/N_m3u8DL-RE) : 抛弃历史包袱从零做起, 支持Win/Linux/Mac, 更丰富的功能会在这里出现 ...
|
||||
|
||||
# N_m3u8DL-CLI
|
||||
一个**简单易用的**m3u8下载器,下载地址:https://github.com/nilaoda/N_m3u8DL-CLI/releases
|
||||
@@ -89,6 +91,9 @@ OPTIONS:
|
||||
--noProxy 不自动使用系统代理
|
||||
--registerUrlProtocol 注册m3u8dl链接协议
|
||||
--unregisterUrlProtocol 取消注册m3u8dl链接协议
|
||||
--enableChaCha20 enableChaCha20
|
||||
--chaCha20KeyBase64 ChaCha20KeyBase64
|
||||
--chaCha20NonceBase64 ChaCha20NonceBase64
|
||||
--help Display this help screen.
|
||||
--version Display version information.
|
||||
```
|
||||
@@ -102,24 +107,24 @@ OPTIONS:
|
||||
|
||||
URI格式:
|
||||
```
|
||||
m3u8dl:<base64编码的客户端命令行文本>
|
||||
m3u8dl://<base64编码的客户端命令行文本>
|
||||
```
|
||||
|
||||
URI示例:
|
||||
```
|
||||
m3u8dl:Imh0dHBzOi8vZXhhbXBsZS5jb20vYWJjLm0zdTgiIC0td29ya0RpciAiJVVTRVJQUk9GSUxFJVxEb3dubG9hZHNcbTN1OGRsIiAtLXNhdmVOYW1lICJhYmMiIC0tZW5hYmxlRGVsQWZ0ZXJEb25lIC0tZGlzYWJsZURhdGVJbmZvIC0tbm9Qcm94eQ==
|
||||
m3u8dl://Imh0dHBzOi8vZXhhbXBsZS5jb20vYWJjLm0zdTgiIC0td29ya0RpciAiJVVTRVJQUk9GSUxFJVxEb3dubG9hZHNcbTN1OGRsIiAtLXNhdmVOYW1lICJhYmMiIC0tZW5hYmxlRGVsQWZ0ZXJEb25lIC0tZGlzYWJsZURhdGVJbmZvIC0tbm9Qcm94eQ==
|
||||
```
|
||||
|
||||
URI解码结果:
|
||||
```
|
||||
m3u8dl:"https://example.com/abc.m3u8" --workDir "%USERPROFILE%\Downloads\m3u8dl" --saveName "abc" --enableDelAfterDone --disableDateInfo --noProxy
|
||||
"https://example.com/abc.m3u8" --workDir "%USERPROFILE%\Downloads\m3u8dl" --saveName "abc" --enableDelAfterDone --disableDateInfo --noProxy
|
||||
```
|
||||
|
||||
# 用户文档
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
|
||||
# 聊聊
|
||||
https://discord.gg/W5tvcRJDPs
|
||||
https://discord.gg/SSGwKrjC44
|
||||
|
||||
# 赞赏
|
||||

|
||||
|
@@ -68,6 +68,9 @@ OPTIONS:
|
||||
--noProxy Disable use system proxy
|
||||
--registerUrlProtocol Register m3u8dl URL protocol
|
||||
--unregisterUrlProtocol Unregister m3u8dl URL protocol
|
||||
--enableChaCha20 enableChaCha20
|
||||
--chaCha20KeyBase64 ChaCha20KeyBase64
|
||||
--chaCha20NonceBase64 ChaCha20NonceBase64
|
||||
--help Display this help screen.
|
||||
--version Display version information.
|
||||
```
|
||||
@@ -81,21 +84,21 @@ New commandline options:
|
||||
|
||||
URI Format:
|
||||
```
|
||||
m3u8dl:<base64 encoded params>
|
||||
m3u8dl://<base64 encoded params>
|
||||
```
|
||||
|
||||
URI Example:
|
||||
```
|
||||
m3u8dl:Imh0dHBzOi8vZXhhbXBsZS5jb20vYWJjLm0zdTgiIC0td29ya0RpciAiJVVTRVJQUk9GSUxFJVxEb3dubG9hZHNcbTN1OGRsIiAtLXNhdmVOYW1lICJhYmMiIC0tZW5hYmxlRGVsQWZ0ZXJEb25lIC0tZGlzYWJsZURhdGVJbmZvIC0tbm9Qcm94eQ==
|
||||
m3u8dl://Imh0dHBzOi8vZXhhbXBsZS5jb20vYWJjLm0zdTgiIC0td29ya0RpciAiJVVTRVJQUk9GSUxFJVxEb3dubG9hZHNcbTN1OGRsIiAtLXNhdmVOYW1lICJhYmMiIC0tZW5hYmxlRGVsQWZ0ZXJEb25lIC0tZGlzYWJsZURhdGVJbmZvIC0tbm9Qcm94eQ==
|
||||
```
|
||||
|
||||
URI Decode Result:
|
||||
```
|
||||
m3u8dl:"https://example.com/abc.m3u8" --workDir "%USERPROFILE%\Downloads\m3u8dl" --saveName "abc" --enableDelAfterDone --disableDateInfo --noProxy
|
||||
"https://example.com/abc.m3u8" --workDir "%USERPROFILE%\Downloads\m3u8dl" --saveName "abc" --enableDelAfterDone --disableDateInfo --noProxy
|
||||
```
|
||||
|
||||
## Document
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
|
||||
## Chit-chat
|
||||
https://discord.gg/W5tvcRJDPs
|
||||
https://discord.gg/RscAJZv3Yq
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user