removing half baked music link page

This commit is contained in:
Hamid Reza Mohammadi 2026-09-15 12:05:22 +03:30
parent 3d94c92620
commit ca84dab8d5
8 changed files with 13 additions and 412 deletions

View File

@ -794,7 +794,7 @@
{
<div class="poemtabcontent">
<p>
<a href="@Html.Raw($"/musiclink?p={Model.GanjoorPage.Id}")" onclick="wpopen(this.href); return false" class="pagebutton comments-link">
<a href="@Html.Raw($"/golha?p={Model.GanjoorPage.Id}")" onclick="wpopen(this.href); return false" class="pagebutton comments-link">
معرفی ترانه‌هایی که در متن آنها از این شعر استفاده شده است<i class="notranslate info-buttons music_note"></i>
</a>
</p>
@ -819,7 +819,7 @@
</div>
}
<p>
<a href="@Html.Raw($"/musiclink?p={Model.GanjoorPage.Id}")" onclick="wpopen(this.href); return false" class="pagebutton comments-link">
<a href="@Html.Raw($"/golha?p={Model.GanjoorPage.Id}")" onclick="wpopen(this.href); return false" class="pagebutton comments-link">
معرفی ترانه‌های دیگر<i class="notranslate info-buttons music_note"></i>
</a>
</p>

View File

@ -92,11 +92,11 @@
<h3><strong>انتخاب منبع جستجو:</strong></h3>
</div>
<div style="width:100%;text-align:center">
<input type="radio" id="golha" name="golha" value="گلها">
<input type="radio" id="golha" name="source" value="گلها">
<label for="golha"><a href="@Html.Raw($"/golha/?p={Model.PoemId}")"><img src="/image/golha.png" alt="گلها" height="32" /></a></label>
<input type="radio" id="beeptunes" name="beeptunes" value="بیپ‌تونز" checked>
<input type="radio" id="beeptunes" name="source" value="بیپ‌تونز" checked>
<label for="beeptunes"><a href="@Html.Raw($"/bp/?p={Model.PoemId}")"><img src="/image/beeptunes.png" alt="بیپ‌تونز" height="32" /></a></label>
<input type="radio" id="spotify" name="spotify" value="اسپاتیفای">
<input type="radio" id="spotify" name="source" value="اسپاتیفای">
<label for="spotify"><a href="@Html.Raw($"/spotify/?p={Model.PoemId}")"><img src="/image/spotify.png" alt="اسپاتیفای" height="32" /></a></label>
</div>
</div>

View File

@ -83,11 +83,11 @@
<h3><strong>انتخاب منبع جستجو:</strong></h3>
</div>
<div style="width:100%;text-align:center">
<input type="radio" id="golha" name="golha" value="گلها" checked>
<input type="radio" id="golha" name="source" value="گلها" checked>
<label for="golha"><a href="@Html.Raw($"/golha/?p={Model.PoemId}")"><img src="/image/golha.png" alt="گلها" height="32" /></a></label>
<input type="radio" id="beeptunes" name="beeptunes" value="بیپ‌تونز">
<input type="radio" id="beeptunes" name="source" value="بیپ‌تونز">
<label for="beeptunes"><a href="@Html.Raw($"/bp/?p={Model.PoemId}")"><img src="/image/beeptunes.png" alt="بیپ‌تونز" height="32" /></a></label>
<input type="radio" id="spotify" name="spotify" value="اسپاتیفای">
<input type="radio" id="spotify" name="source" value="اسپاتیفای">
<label for="spotify"><a href="@Html.Raw($"/spotify/?p={Model.PoemId}")"><img src="/image/spotify.png" alt="اسپاتیفای" height="32" /></a></label>
</div>
</div>

View File

@ -1,155 +0,0 @@
@page
@model GanjooRazor.Pages.MusicLinkModel
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="fa-IR" dir="rtl">
<head>
<meta charset="utf-8" />
<title>پیشنهاد پیوند قطعهٔ موسیقی مرتبط با شعر</title>
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href="/favicon.ico?version=1" type="image/x-icon" />
<link href="/css/p8.css?version=89" rel="stylesheet" type="text/css" />
<link href="/css/spotify.css" rel="stylesheet" type="text/css" />
<script src="/lib/jquery/dist/jquery.min.js"></script>
<script src="/js/utils.js"></script>
<script src="/js/musiclink.js?version=1"></script>
</head>
<body>
<div id="fa">
<div class="poem">
@if (Model.ReadOnlyMode)
{
<div class="width-100per">
<div class="width-100per text-align-center">
<p class="alert-text">
امکان پیشنهاد قطعه موقتا به دلیل تغییرات سایت و یا انتقال سرور وجود ندارد. لطفاً ساعاتی دیگر مجدداً تلاش کنید.
</p>
</div>
</div>
}
else
@if (Model.PoemId == 0)
{
<div class="width-100per">
<div class="width-100per text-align-center">
<p class="alert-text">
خطا: شعری انتخاب نشده است.
</p>
</div>
</div>
}
else
if (!Model.LoggedIn)
{
<div class="width-100per">
<div class="width-100per text-align-center">
<p class="alert-text">
برای پیشنهاد آهنگهای مرتبط با اشعار لازم است ابتدا با نام کاربری خود وارد گنجور شوید.
</p>
<p class="text-align-center">
<a href=@Html.Raw($"/login/?redirect=/musiclink/?p={Model.PoemId}")>ورود به گنجور</a>
</p>
</div>
</div>
}
else
{
@if (!string.IsNullOrEmpty(Model.LastError))
{
<div class="width-100per">
<div class="width-100per text-align-center">
<p class="alert-text">
@Model.LastError
</p>
</div>
</div>
}
<div class="width-100per">
<div class="width-100per text-align-center">
<h3><strong>انتخاب منبع:</strong></h3>
</div>
<div style="width:100%;text-align:center">
<input type="radio" id="golha" name="source" value="گلها">
<label for="golha"><a href="@($"/golha/?p={Model.PoemId}")"><img src="/image/golha.png" alt="گلها" height="32" /></a></label>
<input type="radio" id="beeptunes" name="source" value="بیپ‌تونز">
<label for="beeptunes"><a href="@($"/bp/?p={Model.PoemId}")"><img src="/image/beeptunes.png" alt="بیپ‌تونز" height="32" /></a></label>
<input type="radio" id="musiclink" name="source" value="پیوند آهنگ" checked>
<label for="musiclink"><a href="@($"/musiclink/?p={Model.PoemId}")"><img src="/image/note.png" alt="پیوند آهنگ" height="32" /></a></label>
</div>
</div>
<p>
با استفاده از این صفحه می‌توانید آهنگهایی را که در متن آنها از شعر جاری استفاده شده است معرفی کنید. نشانی صفحهٔ آهنگ را از سرویس پخش یا فروش موسیقی کپی کرده و در جعبهٔ «نشانی آهنگ» بچسبانید. نام خواننده و عنوان آهنگ در صورت امکان به صورت خودکار پر می‌شود و می‌توانید آن را اصلاح کنید. پس از «پیشنهاد قطعه» آهنگ پیشنهادی در صف بررسی قرار می‌گیرد و پس از بازبینی و تأیید به صفحهٔ اصلی شعر اضافه می‌شود. از این که کمک می‌کنید که گنجور به نمایه‌ای برای موسیقی ایرانی بدل شود سپاسگزاریم.
</p>
<p>
<span style="color:red">تنها نشانی از این سرویس‌ها پذیرفته می‌شود:</span> @RMuseum.Utils.MusicUrlValidator.PlatformNames. نشانی فایلهای صوتی تکثیر شده و سایتهای دانلود غیرقانونی پذیرفته نمی‌شود.
</p>
<p>
<span style="color:red">تذکر مهم:</span> فقط مواردی تأیید می‌شود که <span style="color:red">در آهنگ متناظر همین شعر خوانده شود</span>. تعیین نام خواننده به تنهایی کارایی ندارد و لطفا جهت کاهش زمان مورد نیاز برای بررسی موارد پیشنهادی از اعلام موارد اینچنینی خودداری فرمایید. فهرست کامل آهنگهای مرتبط در <a href="https://ganjoor.net/mundex/" target="_blank">این صفحه</a> در دسترس است.
</p>
@if (Model.SuggestedSongs.Length > 0)
{
<p>موارد زیر شامل قطعه‌هایی هستند که پیشتر توسط دوستان دیگر پیشنهاد شده و در صف بررسی قرار دارند:</p>
@foreach (var suggestedSong in Model.SuggestedSongs)
{
<p style="@(suggestedSong.Id == Model.InsertedSongId ? "color:green" : "")">
@if (!string.IsNullOrEmpty(suggestedSong.ArtistName))
{
<text>@suggestedSong.ArtistName » </text>
}
@if (!string.IsNullOrEmpty(suggestedSong.AlbumName))
{
<text>@suggestedSong.AlbumName » </text>
}
<a target="_blank" rel="noopener nofollow" href="@suggestedSong.TrackUrl">@suggestedSong.TrackName</a>
</p>
}
}
@if (Model.PostSuccess)
{
<div class="width-100per">
<div class="width-100per text-align-center">
<p style="color:green">
با سپاس! پیشنهاد شما به فهرست اضافه شد.
</p>
</div>
</div>
}
}
</div>
@if (Model.PoemId != 0 && Model.LoggedIn && !Model.ReadOnlyMode)
{
<div class="content">
<form method="post">
<span class="inputlabel">نشانی آهنگ: </span>
<input type="url" dir="ltr" class="albumtrack" id="trackurl" placeholder="https://" required asp-for="PoemMusicTrackViewModel.TrackUrl" />
<div id="fetching-metadata" style="display:none"><img src="/image/loading.gif" alt="در حال دریافت مشخصات" /></div>
<br />
<span class="inputlabel">خواننده: </span>
<input type="text" class="albumtrack" placeholder="خواننده" required asp-for="PoemMusicTrackViewModel.ArtistName" />
<br />
<span class="inputlabel">آلبوم: </span>
<input type="text" class="albumtrack" placeholder="آلبوم (اختیاری)" asp-for="PoemMusicTrackViewModel.AlbumName" />
<br />
<span class="inputlabel">عنوان آهنگ: </span>
<input type="text" class="albumtrack" placeholder="عنوان آهنگ" required asp-for="PoemMusicTrackViewModel.TrackName" />
<br />
<span class="inputlabel">توضیح: </span>
<input type="text" class="albumtrack" placeholder="توضیح" asp-for="PoemMusicTrackViewModel.Description" />
<br />
<input type="submit" name="submit" id="submit" value="پیشنهاد قطعه" />
</form>
</div>
}
</div>
</body>
</html>

View File

@ -1,243 +0,0 @@
using GanjooRazor.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RMuseum.Models.Ganjoor;
using RMuseum.Models.Ganjoor.ViewModels;
using RMuseum.Utils;
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace GanjooRazor.Pages
{
[IgnoreAntiforgeryToken(Order = 1001)]
public class MusicLinkModel : PageModel
{
/// <summary>
/// HttpClient instance
/// </summary>
private readonly HttpClient _httpClient;
/// <summary>
/// configuration
/// </summary>
private readonly IConfiguration Configuration;
/// <summary>
/// constructor
/// </summary>
/// <param name="httpClient"></param>
/// <param name="configuration"></param>
public MusicLinkModel(HttpClient httpClient, IConfiguration configuration)
{
_httpClient = httpClient;
Configuration = configuration;
}
/// <summary>
/// is logged on
/// </summary>
public bool LoggedIn { get; set; }
/// <summary>
/// PoemId
/// </summary>
public int PoemId { get; set; }
/// <summary>
/// Last Error
/// </summary>
public string LastError { get; set; }
/// <summary>
/// Post Success
/// </summary>
public bool PostSuccess { get; set; }
/// <summary>
/// Inserted song Id
/// </summary>
public int InsertedSongId { get; set; }
/// <summary>
/// readonly mode
/// </summary>
public bool ReadOnlyMode
{
get
{
return bool.Parse(Configuration["ReadOnlyMode"]);
}
}
/// <summary>
/// suggested (unapproved) songs
/// </summary>
public PoemMusicTrackViewModel[] SuggestedSongs { get; set; }
/// <summary>
/// api model
/// </summary>
[BindProperty]
public PoemMusicTrackViewModel PoemMusicTrackViewModel { get; set; }
/// <summary>
/// every pending suggestion of the poem is listed, whatever source it came from
/// </summary>
private async Task _GetSuggestedSongs()
{
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/poem/{PoemId}/songs/?approved=false&trackType={(int)PoemMusicTrackType.All}");
if (response.StatusCode == HttpStatusCode.OK)
{
SuggestedSongs = JsonConvert.DeserializeObject<PoemMusicTrackViewModel[]>(await response.Content.ReadAsStringAsync());
}
else
{
SuggestedSongs = new PoemMusicTrackViewModel[] { };
}
}
public async Task OnGetAsync()
{
PostSuccess = false;
LastError = "";
InsertedSongId = 0;
LoggedIn = !string.IsNullOrEmpty(Request.Cookies["Token"]);
if (!string.IsNullOrEmpty(Request.Query["p"]))
{
PoemId = int.Parse(Request.Query["p"]);
}
else
{
PoemId = 0;
}
await _GetSuggestedSongs();
}
public async Task<IActionResult> OnPostAsync()
{
PostSuccess = false;
LastError = "";
LoggedIn = !string.IsNullOrEmpty(Request.Cookies["Token"]);
PoemId = PoemMusicTrackViewModel.PoemId = int.Parse(Request.Query["p"]);
PoemMusicTrackViewModel.TrackType = PoemMusicTrackType.MusicUrl;
InsertedSongId = 0;
if (!MusicUrlValidator.TryNormalize(PoemMusicTrackViewModel.TrackUrl, out string normalizedUrl, out _, out string urlError))
{
LastError = urlError;
await _GetSuggestedSongs();
return Page();
}
PoemMusicTrackViewModel.TrackUrl = normalizedUrl;
using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response)))
{
if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response))
{
var stringContent = new StringContent(JsonConvert.SerializeObject(PoemMusicTrackViewModel), Encoding.UTF8, "application/json");
var methodUrl = $"{APIRoot.Url}/api/ganjoor/song";
var response = await secureClient.PostAsync(methodUrl, stringContent);
if (!response.IsSuccessStatusCode)
{
LastError = await _ReadErrorAsync(response);
}
else
{
InsertedSongId = JsonConvert.DeserializeObject<PoemMusicTrackViewModel>(await response.Content.ReadAsStringAsync()).Id;
PostSuccess = true;
}
}
else
{
LastError = "لطفاً از گنجور خارج و مجددا به آن وارد شوید.";
}
}
await _GetSuggestedSongs();
return Page();
}
/// <summary>
/// the API returns its errors as a JSON string, but 401s and framework level failures
/// come back as an empty or non-JSON body
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
private static async Task<string> _ReadErrorAsync(HttpResponseMessage response)
{
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
return "نشست شما معتبر نیست. لطفاً از گنجور خارج و مجدداً وارد شوید.";
string body = await response.Content.ReadAsStringAsync();
if (string.IsNullOrWhiteSpace(body))
return $"خطا در ارتباط با گنجور ({(int)response.StatusCode}).";
try
{
return JsonConvert.DeserializeObject<string>(body) ?? body;
}
catch (JsonException)
{
return body;
}
}
/// <summary>
/// best effort track title/artist lookup through the platform's public oEmbed endpoint
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostFetchMetadataAsync(string url) {
if (!MusicUrlValidator.TryNormalize(url, out string normalizedUrl, out MusicPlatform platform, out string error))
{
return new BadRequestObjectResult(error);
}
if (string.IsNullOrEmpty(platform.OEmbedEndpoint))
{
return new JsonResult(new { trackName = "", artistName = "" });
}
try
{
// the pasted url is only ever handed to the platform's own fixed endpoint, and redirects
// are refused, so this cannot be steered at an arbitrary host
using var handler = new HttpClientHandler() { AllowAutoRedirect = false };
using var oEmbedClient = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(5), MaxResponseContentBufferSize = 128 * 1024 };
var response = await oEmbedClient.GetAsync($"{platform.OEmbedEndpoint}?url={WebUtility.UrlEncode(normalizedUrl)}&format=json");
if (!response.IsSuccessStatusCode)
{
return new JsonResult(new { trackName = "", artistName = "" });
}
var parsed = JObject.Parse(await response.Content.ReadAsStringAsync());
return new JsonResult
(
new
{
trackName = parsed.SelectToken("title")?.Value<string>() ?? "",
artistName = parsed.SelectToken("author_name")?.Value<string>() ?? ""
}
);
}
catch
{
return new JsonResult(new { trackName = "", artistName = "" });
}
}
}
}

View File

@ -91,11 +91,11 @@
<h3><strong>انتخاب منبع جستجو:</strong></h3>
</div>
<div style="width:100%;text-align:center">
<input type="radio" id="golha" name="golha" value="گلها">
<input type="radio" id="golha" name="source" value="گلها">
<label for="golha"><a href="@Html.Raw($"/golha/?p={Model.PoemId}")"><img src="/image/golha.png" alt="گلها" height="32" /></a></label>
<input type="radio" id="beeptunes" name="beeptunes" value="بیپ‌تونز">
<input type="radio" id="beeptunes" name="source" value="بیپ‌تونز">
<label for="beeptunes"><a href="@Html.Raw($"/bp/?p={Model.PoemId}")"><img src="/image/beeptunes.png" alt="بیپ‌تونز" height="32" /></a></label>
<input type="radio" id="spotify" name="spotify" value="اسپاتیفای" checked>
<input type="radio" id="spotify" name="source" value="اسپاتیفای" checked>
<label for="spotify"><a href="@Html.Raw($"/spotify/?p={Model.PoemId}")"><img src="/image/spotify.png" alt="اسپاتیفای" height="32" /></a></label>
</div>
</div>

View File

@ -1,4 +1,4 @@
using GanjooRazor.Utils;
using GanjooRazor.Utils;
using GSpotifyProxy.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -136,7 +136,7 @@ namespace GanjooRazor.Pages
// the search flow stays in place for the day the Spotify API works again
if (!SpotifyWorking)
{
return Redirect($"/musiclink/?p={PoemId}");
LastError = "جست‌وجوی خودکار قطعات اسپاتیفای موقتاً در دسترس نیست. لطفاً بعداً دوباره تلاش کنید.";
}
await _GetSuggestedSongs();

View File

@ -87,7 +87,6 @@ namespace GanjooRazor
options.Conventions.AddPageRoute("/SongRecommendation/Bp", "/Bp");
options.Conventions.AddPageRoute("/SongRecommendation/Golha", "/Golha");
options.Conventions.AddPageRoute("/SongRecommendation/Spotify", "/Spotify");
options.Conventions.AddPageRoute("/SongRecommendation/MusicLink", "/MusicLink");
options.Conventions.AddPageRoute("/ImageRecommendation/Pin", "/Pin");