Webshare file download URL generator from file id and name
This commit is contained in:
@@ -3,9 +3,13 @@ using JellyfinCinemaStream.Webshare;
|
|||||||
|
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
private static void Main(string[] args)
|
public static HttpClient _http = new HttpClient();
|
||||||
|
|
||||||
|
private static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
string a = LinkGenerator.CalculatePassword(downloadId: "12kRBqAYQS2", downloadName: "bIaFGdkvKiiTBo2_S3E", salt: "UX8y8Fpa");
|
CancellationToken cancel = default;
|
||||||
|
//string a = LinkGenerator.CalculatePassword(fileId: "2kRBqAYQS2", fileName: "bIaFGdkvKiiTBo2_S3E1", salt: "UX8y8Fpa");
|
||||||
|
Uri a = await LinkGenerator.GenerateDownloadLinkAsync(fileId: "2kRBqAYQS2", fileName: "bIaFGdkvKiiTBo2_S3E1", cancel);
|
||||||
Console.WriteLine(a);
|
Console.WriteLine(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,86 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
namespace JellyfinCinemaStream.Webshare;
|
namespace JellyfinCinemaStream.Webshare;
|
||||||
|
|
||||||
public class LinkGenerator
|
public class LinkGenerator
|
||||||
{
|
{
|
||||||
private static readonly BaseEncoding UnixMD5 = new BaseEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", false);
|
private static readonly BaseEncoding UnixMD5 = new BaseEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", false);
|
||||||
|
private static readonly Uri WebshareApiUri = new Uri("https://webshare.cz/api/");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a download link for the given Webshare download.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileId">Webshare file identifier.</param>
|
||||||
|
/// <param name="fileName">Webshare file name.</param>
|
||||||
|
/// <param name="cancel">Asynchronous cancellation.</param>
|
||||||
|
/// <returns>Time-limited download link.</returns>
|
||||||
|
public async static Task<Uri> GenerateDownloadLinkAsync(string fileId, string fileName, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
// Obtain the download password salt
|
||||||
|
// Example response: <?xml version="1.0" encoding="UTF-8"?><response><status>OK</status><salt>UX8y8Fpa</salt><app_version>30</app_version></response>
|
||||||
|
Uri saltUri = new Uri(WebshareApiUri, "file_password_salt/");
|
||||||
|
HttpResponseMessage saltRes = await Program._http.PostAsync(saltUri, new FormUrlEncodedContent(new [] { new KeyValuePair<string, string>("ident", fileId) }), cancel);
|
||||||
|
if (!saltRes.IsSuccessStatusCode)
|
||||||
|
throw new IOException("Failed to get password salt from Webshare API.");
|
||||||
|
|
||||||
|
XmlReader saltR = XmlReader.Create(saltRes.Content.ReadAsStream());
|
||||||
|
saltR.ReadStartElement("response");
|
||||||
|
ThrowIfStatusNotOK(saltR);
|
||||||
|
string salt = saltR.ReadElementContentAsString("salt", "");
|
||||||
|
saltR.ReadElementContentAsString("app_version", "");
|
||||||
|
saltR.ReadEndElement(); // response
|
||||||
|
|
||||||
|
// Calculate the download password
|
||||||
|
string password = CalculatePassword(fileId, fileName, salt);
|
||||||
|
|
||||||
|
// Obtain the download link
|
||||||
|
// Example response: <?xml version="1.0" encoding="UTF-8"?><response><status>OK</status><link>https://free.19.dl.wsfiles.cz/9209/...</link><app_version>30</app_version></response>
|
||||||
|
Uri linkUri = new Uri(WebshareApiUri, "file_link/");
|
||||||
|
HttpResponseMessage linkRes = await Program._http.PostAsync(linkUri, new FormUrlEncodedContent(new [] {
|
||||||
|
new KeyValuePair<string, string>("ident", fileId),
|
||||||
|
new KeyValuePair<string, string>("download_type", "video_stream"),
|
||||||
|
new KeyValuePair<string, string>("device_uuid", Guid.NewGuid().ToString("X")),
|
||||||
|
new KeyValuePair<string, string>("device_res_x", "3840"),
|
||||||
|
new KeyValuePair<string, string>("device_res_y", "2160"),
|
||||||
|
new KeyValuePair<string, string>("password", password),
|
||||||
|
}), cancel
|
||||||
|
);
|
||||||
|
if (!linkRes.IsSuccessStatusCode)
|
||||||
|
throw new IOException("Failed to get download link from Webshare API.");
|
||||||
|
|
||||||
|
XmlReader linkR = XmlReader.Create(linkRes.Content.ReadAsStream());
|
||||||
|
linkR.ReadStartElement("response");
|
||||||
|
ThrowIfStatusNotOK(linkR);
|
||||||
|
string link = linkR.ReadElementContentAsString("link", "");
|
||||||
|
linkR.ReadElementContentAsString("app_version", "");
|
||||||
|
linkR.ReadEndElement(); // response
|
||||||
|
|
||||||
|
return new Uri(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ThrowIfStatusNotOK(XmlReader r) {
|
||||||
|
string status = r.ReadElementContentAsString("status", "");
|
||||||
|
if (status == "OK")
|
||||||
|
return;
|
||||||
|
|
||||||
|
string code = r.ReadElementContentAsString("code", "");
|
||||||
|
string message = r.ReadElementContentAsString("message", "");
|
||||||
|
throw new IOException(code + ": " + message);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the Webshare download password from download identifier, name and salt.
|
/// Calculates the Webshare download password from file identifier, name and salt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="downloadId">Webshare download identifier.</param>
|
/// <param name="fileId">Webshare file identifier.</param>
|
||||||
/// <param name="downloadName">Webshare download name.</param>
|
/// <param name="fileName">Webshare file name.</param>
|
||||||
/// <param name="salt">Download salt obtained from https://webshare.cz/api/file_password_salt/.</param>
|
/// <param name="salt">Download salt obtained from https://webshare.cz/api/file_password_salt/.</param>
|
||||||
/// <returns>Password to use to obtain a download link using https://webshare.cz/api/file_link/.</returns>
|
/// <returns>Password to use to obtain a download link using https://webshare.cz/api/file_link/.</returns>
|
||||||
public static string CalculatePassword(string downloadId, string downloadName, string salt)
|
private static string CalculatePassword(string fileId, string fileName, string salt)
|
||||||
{
|
{
|
||||||
if (downloadId == null || downloadName == null || salt == null)
|
if (fileId == null || fileName == null || salt == null)
|
||||||
throw new ArgumentNullException();
|
throw new ArgumentNullException();
|
||||||
|
|
||||||
// Example
|
// Example
|
||||||
@@ -26,7 +89,7 @@ public class LinkGenerator
|
|||||||
// password = sha256(name + id) = "1e98873b57b7660cba7267c191f2d1fb7d198d062eb2fe0686502883042c4105"
|
// password = sha256(name + id) = "1e98873b57b7660cba7267c191f2d1fb7d198d062eb2fe0686502883042c4105"
|
||||||
// crypt = md5crypt(password, salt: "UX8y8Fpa") = "$1$UX8y8Fpa$stY.bUYGBsmAlc6IIxnHU0"
|
// crypt = md5crypt(password, salt: "UX8y8Fpa") = "$1$UX8y8Fpa$stY.bUYGBsmAlc6IIxnHU0"
|
||||||
// result = sha1(crypt) = "8ceff8e5abf9aa375a8a5b05871b6cd6fb7fd185")
|
// result = sha1(crypt) = "8ceff8e5abf9aa375a8a5b05871b6cd6fb7fd185")
|
||||||
string toHash = downloadName + downloadId;
|
string toHash = fileName + fileId;
|
||||||
byte[] password = SHA256.HashData(Encoding.UTF8.GetBytes(toHash));
|
byte[] password = SHA256.HashData(Encoding.UTF8.GetBytes(toHash));
|
||||||
string passwordS = Convert.ToHexStringLower(password);
|
string passwordS = Convert.ToHexStringLower(password);
|
||||||
string crypt = MD5Crypt(passwordS, salt);
|
string crypt = MD5Crypt(passwordS, salt);
|
||||||
|
|||||||
Reference in New Issue
Block a user