#114 spotify authorization process

This commit is contained in:
Hamid Reza Mohammadi 2021-07-21 19:48:57 +04:30
parent 7526d6af1d
commit b3af039a25
7 changed files with 496 additions and 12 deletions

View File

@ -0,0 +1,34 @@
@page
@model GanjooRazor.Areas.Admin.Pages.SpotifyCallbackModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_AdminLayout";
ViewData["Title"] = "بازگشت از اسپاتیفای";
}
<form method="post">
<input asp-for="@Model.Code" type="hidden" />
<div>
<button type="submit">ذخیره</button>
</div>
</form>
<p>
<strong>Code:</strong> <code>@Model.Code</code>
</p>
<p>
<strong>access_token:</strong> <code>@Model.access_token</code>
</p>
<p>
<strong>token_type:</strong> <code>@Model.token_type</code>
</p>
<p>
<strong>expires_in:</strong> <code>@Model.expires_in</code>
</p>
<p>
<strong>refresh_token:</strong> <code>@Model.refresh_token</code>
</p>
<p>
<strong>Error:</strong> <code>@Model.Error</code>
</p>

View File

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using GanjooRazor.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
namespace GanjooRazor.Areas.Admin.Pages
{
public class SpotifyCallbackModel : PageModel
{
public SpotifyCallbackModel(IHttpClientFactory clientFactory, IConfiguration configuration)
{
_clientFactory = clientFactory;
_configuration = configuration;
}
public void OnGet(string code = "code", string state = "none")
{
Code = code;
State = state;
}
public async Task<ActionResult> OnPostAsync(string code)
{
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
nvc.Add(new KeyValuePair<string, string>("code", code));
string callbackUrl = $"{_configuration["SiteUrl"]}/Admin/SpotifyCallback";
nvc.Add(new KeyValuePair<string, string>("redirect_uri", callbackUrl));
var formContent = new FormUrlEncodedContent(nvc);
var client = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Post,
"https://accounts.spotify.com/api/token");
request.Content = formContent;
string authValue = Convert.ToBase64String(new ASCIIEncoding().GetBytes($"{SpotifyOptions.Options["client_id"]}:{SpotifyOptions.Options["client_secret"]}"));
request.Headers.Add("Authorization", $"Basic {authValue}");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var parsed = JObject.Parse(json);
access_token = parsed.SelectToken("access_token").Value<string>();
token_type = parsed.SelectToken("token_type").Value<string>();
expires_in = parsed.SelectToken("expires_in").Value<string>();
refresh_token = parsed.SelectToken("refresh_token").Value<string>();
Dictionary<string, string> options = new Dictionary<string, string>();
options.Add("client_id", SpotifyOptions.Options["client_id"]);
options.Add("client_secret", SpotifyOptions.Options["client_secret"]);
options.Add("access_token", access_token);
options.Add("refresh_token", refresh_token);
SpotifyOptions.Options = options;
}
else
{
Error = await response.Content.ReadAsStringAsync();
}
return new OkResult();
}
public string Code { get; set; }
public string State { get; set; }
public string access_token { get; set; }
public string token_type { get; set; }
public string expires_in { get; set; }
public string refresh_token { get; set; }
public string Error { get; set; }
private readonly IHttpClientFactory _clientFactory;
private readonly IConfiguration _configuration;
}
}

View File

@ -0,0 +1,11 @@
@page
@model GanjooRazor.Areas.Admin.Pages.SpotifyLoginModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_AdminLayout";
ViewData["Title"] = "ورود به اسپاتیفای";
}
<h1>@ViewData["Title"]</h1>
<p>Click <a href="@Model.SpotifyUrl">ورود</a></p>

View File

@ -0,0 +1,31 @@
using System.Net;
using GanjooRazor.Utils;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
namespace GanjooRazor.Areas.Admin.Pages
{
public class SpotifyLoginModel : PageModel
{
private readonly IConfiguration _configuration;
public SpotifyLoginModel(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// url
/// </summary>
public string SpotifyUrl
{
get
{
return
$"https://accounts.spotify.com/authorize?client_id={SpotifyOptions.Options["client_id"]}&response_type=code&redirect_uri=" +
WebUtility.UrlEncode($"{_configuration["SiteUrl"]}/Admin/SpotifyCallback") +
"&scope=&state=34fFs29kd09";
}
}
}
}

View File

@ -5,13 +5,15 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RMuseum.Models.Ganjoor;
using RMuseum.Models.Ganjoor.ViewModels;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace GanjooRazor.Pages
{
@ -23,14 +25,17 @@ namespace GanjooRazor.Pages
/// </summary>
private readonly HttpClient _httpClient;
/// <summary>
/// constructor
/// </summary>
/// <param name="httpClient"></param>
/// <param name="ganjoorService"></param>
public SpotifyModel(HttpClient httpClient)
{
_httpClient = httpClient;
}
/// <summary>
@ -146,9 +151,52 @@ namespace GanjooRazor.Pages
/// search by artists name
/// </summary>
/// <param name="search"></param>
/// <param name="secondtime"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostSearchByArtistNameAsync(string search)
public async Task<IActionResult> OnPostSearchByArtistNameAsync(string search, bool secondtime = false)
{
string spotifyToken = $"Bearer {SpotifyOptions.Options["access_token"]}";
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://api.spotify.com/v1/search?q={search}&type=artist");
request.Headers.Add("Authorization", spotifyToken);
var response = await _httpClient.SendAsync(request);
List<NameIdUrlImage> artists = new List<NameIdUrlImage>();
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var parsed = JObject.Parse(json);
foreach (JToken artist in parsed.SelectTokens("artists.items[*]"))
{
string imageUrl = "";
foreach (JToken image in artist.SelectTokens("images[*].url"))
{
imageUrl = image.Value<string>();
break;
}
artists.Add(
new NameIdUrlImage()
{
Name = artist.SelectToken("name").Value<string>(),
Id = artist.SelectToken("id").Value<string>(),
Url = artist.SelectToken("external_urls.spotify").Value<string>(),
Image = imageUrl
}
);
}
}
else
{
if (!secondtime)
{
await _RefreshSpotifyToken();
return await OnPostSearchByArtistNameAsync(search, true);
}
return BadRequest(response.ToString());
}
/*
//Warning: This is a private wrapper around the spotify API, created only for this project and incapable of
// responding large number of requests (both server and Spotify user limitations),
// so please do not use this proxy in other projects because you will cause this proxy to become unavailable for me
@ -161,6 +209,7 @@ namespace GanjooRazor.Pages
{
artists = JsonConvert.DeserializeObject<NameIdUrlImage[]>(await response.Content.ReadAsStringAsync());
}
*/
return new PartialViewResult()
@ -170,19 +219,103 @@ namespace GanjooRazor.Pages
{
Model = new _SpotifySearchPartialModel()
{
Artists = artists
Artists = artists.ToArray()
}
}
};
}
private async Task _RefreshSpotifyToken()
{
string refresh_token = SpotifyOptions.Options["refresh_token"];
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", "refresh_token"));
nvc.Add(new KeyValuePair<string, string>("refresh_token", refresh_token));
var formContent = new FormUrlEncodedContent(nvc);
var request = new HttpRequestMessage(HttpMethod.Post,
"https://accounts.spotify.com/api/token");
request.Content = formContent;
string authValue = Convert.ToBase64String(new ASCIIEncoding().GetBytes($"{SpotifyOptions.Options["client_id"]}:{SpotifyOptions.Options["client_secret"]}"));
request.Headers.Add("Authorization", $"Basic {authValue}");
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var parsed = JObject.Parse(json);
string access_token = parsed.SelectToken("access_token").Value<string>();
var options = SpotifyOptions.Options;
options["access_token"] = access_token;
SpotifyOptions.Options = options;
}
}
/// <summary>
/// fill artist albums
/// </summary>
/// <param name="artist">is an ID and consists of numeric and non-numeric characters</param>
/// <returns></returns>
public async Task<IActionResult> OnPostFillAlbumsAsync(string artist)
public async Task<IActionResult> OnPostFillAlbumsAsync(string artist, bool secondtime = false)
{
string spotifyToken = $"Bearer {SpotifyOptions.Options["access_token"]}";
List<NameIdUrlImage> albums = new List<NameIdUrlImage>();
int offest = 0;
int limit = 50;
bool newItems;
do
{
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://api.spotify.com/v1/artists/{artist}/albums?limit={limit}&offset={offest}");
request.Headers.Add("Authorization", spotifyToken);
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var parsed = JObject.Parse(json);
newItems = false;
foreach (JToken album in parsed.SelectTokens("items[*]"))
{
newItems = true;
string imageUrl = "";
foreach (JToken image in album.SelectTokens("images[*].url"))
{
imageUrl = image.Value<string>();
break;
}
string album_type = album.SelectToken("album_type").Value<string>();
if (album_type == "album" || album_type == "single")
{
albums.Add(
new NameIdUrlImage()
{
Name = album.SelectToken("name").Value<string>(),
Id = album.SelectToken("id").Value<string>(),
Url = album.SelectToken("external_urls.spotify").Value<string>(),
Image = imageUrl
}
);
}
}
}
else
{
if (!secondtime && offest == 0)
{
await _RefreshSpotifyToken();
return await OnPostFillAlbumsAsync(artist, true);
}
return BadRequest(response.ToString());
}
offest += limit;
}
while (newItems);
albums.Sort((a, b) => a.Name.CompareTo(b.Name));
/*
//Warning: This is a private wrapper around the spotify API, created only for this project and incapable of
// responding large number of requests (both server and Spotify user limitations),
// so please do not use this proxy in other projects because you will cause this proxy to become unavailable for me
@ -195,7 +328,8 @@ namespace GanjooRazor.Pages
{
albums = JsonConvert.DeserializeObject<NameIdUrlImage[]>(await response.Content.ReadAsStringAsync());
}
return new OkObjectResult(albums);
*/
return new OkObjectResult(albums.ToArray());
}
/// <summary>
@ -203,8 +337,51 @@ namespace GanjooRazor.Pages
/// </summary>
/// <param name="album">is an ID and consists of numeric and non-numeric characters</param>
/// <returns></returns>
public async Task<IActionResult> OnPostFillTracksAsync(string album)
public async Task<IActionResult> OnPostFillTracksAsync(string album, bool secondtime = false)
{
string spotifyToken = $"Bearer {SpotifyOptions.Options["access_token"]}";
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://api.spotify.com/v1/albums/{album}/tracks?limit=50");
request.Headers.Add("Authorization", spotifyToken);
List<NameIdUrlImage> tracks = new List<NameIdUrlImage>();
var response = await _httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var parsed = JObject.Parse(json);
foreach (JToken track in parsed.SelectTokens("items[*]"))
{
string imageUrl = "";
foreach (JToken image in track.SelectTokens("images[*].url"))
{
imageUrl = image.Value<string>();
break;
}
tracks.Add(
new NameIdUrlImage()
{
Name = track.SelectToken("name").Value<string>(),
Id = track.SelectToken("id").Value<string>(),
Url = track.SelectToken("external_urls.spotify").Value<string>(),
Image = imageUrl
}
);
}
}
else
{
if (!secondtime)
{
await _RefreshSpotifyToken();
return await OnPostFillTracksAsync(album, true);
}
return BadRequest(response.ToString());
}
/*
//Warning: This is a private wrapper around the spotify API, created only for this project and incapable of
// responding large number of requests (both server and Spotify user limitations),
// so please do not use this proxy in other projects because you will cause this proxy to become unavailable for me
@ -217,7 +394,8 @@ namespace GanjooRazor.Pages
{
tracks = JsonConvert.DeserializeObject<NameIdUrlImage[]>(await response.Content.ReadAsStringAsync());
}
return new OkObjectResult(tracks);
*/
return new OkObjectResult(tracks.ToArray());
}
/// <summary>
@ -225,8 +403,90 @@ namespace GanjooRazor.Pages
/// </summary>
/// <param name="search"></param>
/// <returns></returns>
public async Task<IActionResult> OnPostSearchByTrackTitleAsync(string search)
public async Task<IActionResult> OnPostSearchByTrackTitleAsync(string search, bool secondtime = false)
{
string spotifyToken = $"Bearer {SpotifyOptions.Options["access_token"]}";
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://api.spotify.com/v1/search?q={search}&type=track");
request.Headers.Add("Authorization", spotifyToken);
var response = await _httpClient.SendAsync(request);
List<TrackQueryResult> tracks = new List<TrackQueryResult>();
if (response.IsSuccessStatusCode)
{
string json = await response.Content.ReadAsStringAsync();
var parsed = JObject.Parse(json);
foreach (JToken track in parsed.SelectTokens("tracks.items[*]"))
{
string imageUrl = "";
NameIdUrlImage artistInfo = new NameIdUrlImage()
{
Id = "0",
Name = "",
Url = "",
Image = ""
};
foreach (JToken artist in track.SelectTokens("artists[*]"))
{
artistInfo.Name = artist.SelectToken("name").Value<string>();
artistInfo.Id = artist.SelectToken("id").Value<string>();
artistInfo.Url = artist.SelectToken("external_urls.spotify").Value<string>();
break;
}
NameIdUrlImage albumInfo = new NameIdUrlImage()
{
Id = "0",
Name = "",
Url = "",
Image = ""
};
JToken album = track.SelectToken("album");
if (album != null)
{
albumInfo.Name = album.SelectToken("name").Value<string>();
albumInfo.Id = album.SelectToken("id").Value<string>();
albumInfo.Url = album.SelectToken("external_urls.spotify").Value<string>();
foreach (JToken image in album.SelectTokens("images[*].url"))
{
imageUrl = image.Value<string>();
break;
}
}
tracks.Add(
new TrackQueryResult()
{
Name = track.SelectToken("name").Value<string>(),
Id = track.SelectToken("id").Value<string>(),
Url = track.SelectToken("external_urls.spotify").Value<string>(),
Image = imageUrl,
ArtistName = artistInfo.Name,
ArtistId = artistInfo.Id,
ArtistUrl = artistInfo.Url,
AlbumName = albumInfo.Name,
AlbumId = albumInfo.Id,
AlbunUrl = albumInfo.Url
}
);
}
}
else
{
if (!secondtime)
{
await _RefreshSpotifyToken();
return await OnPostSearchByTrackTitleAsync(search, true);
}
return BadRequest(response.ToString());
}
/*
//Warning: This is a private wrapper around the spotify API, created only for this project and incapable of
// responding large number of requests (both server and Spotify user limitations),
// so please do not use this proxy in other projects because you will cause this proxy to become unavailable for me
@ -239,6 +499,7 @@ namespace GanjooRazor.Pages
{
tracks = JsonConvert.DeserializeObject<TrackQueryResult[]>(await response.Content.ReadAsStringAsync());
}
*/
return new PartialViewResult()
{
@ -247,7 +508,7 @@ namespace GanjooRazor.Pages
{
Model = new _SpotifySearchPartialModel()
{
Tracks = tracks
Tracks = tracks.ToArray()
}
}
};

View File

@ -0,0 +1,64 @@
using System.Collections.Generic;
namespace GanjooRazor.Utils
{
/// <summary>
/// spotify options
/// </summary>
public class SpotifyOptions
{
private const string OptionFilePath = @"C:\Tools\NewSpotifyOptions\config.txt";
/// <summary>
/// options
/// </summary>
public static Dictionary<string, string> Options
{
get
{
if (_cachedOptions != null)
return _cachedOptions;
Dictionary<string, string> options = new Dictionary<string, string>();
options.Add("access_token", "");
options.Add("refresh_token", "");
options.Add("client_id", "");
options.Add("client_secret", "");
if (System.IO.File.Exists(OptionFilePath))
{
string[] lines = System.IO.File.ReadAllLines(OptionFilePath);
int i = 0;
while (i < lines.Length)
{
if (options.ContainsKey(lines[i]))
{
if ((i + 1) < lines.Length)
{
options[lines[i]] = lines[i + 1];
}
i += 2;
}
else
{
i++;
}
}
}
_cachedOptions = options;
return options;
}
set
{
_cachedOptions = null;
List<string> lines = new List<string>();
foreach (string key in value.Keys)
{
lines.Add(key);
lines.Add(value[key]);
}
System.IO.File.WriteAllLines(OptionFilePath, lines);
}
}
private static Dictionary<string, string> _cachedOptions = null;
}
}

View File

@ -8,6 +8,7 @@
},
"AllowedHosts": "*",
"GoogleAnalyticsCode": "",
"APIRoot": "http://localhost:3439",
"GlobalAPIRoot": "http://localhost:3439"
"APIRoot": "https://ganjgah.ir",
"GlobalAPIRoot": "https://ganjgah.ir",
"SiteUrl": "http://localhost:33081"
}