Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ranking data #3

Merged
merged 4 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/scrape.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Daily Scrape

on:
workflow_dispatch:
schedule:
- cron: "30 5 * * *"

jobs:
scrape:
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run scraper
run: |
# Add your scraping command here

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.x'

- name: Restore NuGet packages
shell: bash
run: |
dotnet restore

- name: Build solution
shell: bash
run: |
dotnet build -c Release

- name: Install Playwright
shell: pwsh
run: |
$playwright = Get-ChildItem -File Microsoft.Playwright.dll -Path . -Recurse
$installer = "$($playwright[0].Directory.FullName)/playwright.ps1"
& "$installer" install

- name: Run scraper app - Daily 100
shell: pwsh
run: |
$date = (Get-Date).ToUniversalTime().AddHours(9).ToString("yyyyMMdd")
$result = dotnet run --project ./samples/MelonChart.ConsoleApp/ -- -c Daily100 --json | ConvertFrom-Json

mkdir -p ./data
pushd ./data
$result | ConvertTo-Json -Depth 100 | Out-File -FilePath "daily100-$date.json" -Force
popd

- name: Upload data
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update data"
branch: "main"
commit_user_name: "GitHub Actions"
commit_user_email: "scraper+github-actions[bot]@users.noreply.github.com"
commit_author: "GitHub Actions <scraper+github-actions[bot]@users.noreply.github.com>"
123 changes: 66 additions & 57 deletions samples/MelonChart.ConsoleApp/Options/ArgumentOptions.cs
Original file line number Diff line number Diff line change
@@ -1,57 +1,66 @@
namespace MelonChart.ConsoleApp.Options;

/// <summary>
/// This represents the options entity for arguments.
/// </summary>
public class ArgumentOptions
{
/// <summary>
/// Gets or sets the <see cref="ChartTypes"/> value.
/// </summary>
public ChartTypes ChartType { get; set; } = ChartTypes.Top100;

/// <summary>
/// Gets or sets the value indicating whether to display help or not.
/// </summary>
public bool Help { get; set; } = false;

/// <summary>
/// Parses the arguments and returns the <see cref="ArgumentOptions"/> instance.
/// </summary>
/// <param name="args">List of arguments.</param>
/// <returns>Returns the <see cref="ArgumentOptions"/> instance.</returns>
public static ArgumentOptions Parse(string[] args)
{
var options = new ArgumentOptions();
if (args.Length == 0)
{
return options;
}

for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
switch (arg)
{
case "-c":
case "-t":
case "--chart":
case "--type":
case "--chart-type":
options.ChartType = i < args.Length - 1
? Enum.TryParse<ChartTypes>(args[++i], ignoreCase: true, out var result)
? result
: throw new ArgumentException("Invalid chart type. It should be 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.")
: throw new ArgumentException("Invalid chart type. It should be 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.");
break;

case "-h":
case "--help":
options.Help = true;
break;
}
}

return options;
}
}
namespace MelonChart.ConsoleApp.Options;

/// <summary>
/// This represents the options entity for arguments.
/// </summary>
public class ArgumentOptions
{
/// <summary>
/// Gets or sets the <see cref="ChartTypes"/> value.
/// </summary>
public ChartTypes ChartType { get; set; } = ChartTypes.Top100;

/// <summary>
/// Gets or sets the value indicating whether to output as JSON or not.
/// </summary>
public bool OutputAsJson { get; set; } = false;

/// <summary>
/// Gets or sets the value indicating whether to display help or not.
/// </summary>
public bool Help { get; set; } = false;

/// <summary>
/// Parses the arguments and returns the <see cref="ArgumentOptions"/> instance.
/// </summary>
/// <param name="args">List of arguments.</param>
/// <returns>Returns the <see cref="ArgumentOptions"/> instance.</returns>
public static ArgumentOptions Parse(string[] args)
{
var options = new ArgumentOptions();
if (args.Length == 0)
{
return options;
}

for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
switch (arg)
{
case "-c":
case "-t":
case "--chart":
case "--type":
case "--chart-type":
options.ChartType = i < args.Length - 1
? Enum.TryParse<ChartTypes>(args[++i], ignoreCase: true, out var result)
? result
: throw new ArgumentException("Invalid chart type. It should be 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.")
: throw new ArgumentException("Invalid chart type. It should be 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.");
break;

case "--json":
options.OutputAsJson = true;
break;

case "-h":
case "--help":
options.Help = true;
break;
}
}

return options;
}
}
199 changes: 115 additions & 84 deletions samples/MelonChart.ConsoleApp/Services/MelonChartService.cs
Original file line number Diff line number Diff line change
@@ -1,84 +1,115 @@
using MelonChart.Abstractions;
using MelonChart.ConsoleApp.Options;
using MelonChart.Models;

namespace MelonChart.ConsoleApp.Services;

/// <summary>
/// This represents the service entity for Melon chart.
/// </summary>
/// <param name="charts">List of <see cref="IChart"/> instances.</param>
public class MelonChartService(IEnumerable<IChart> charts) : IMelonChartService
{
private readonly IEnumerable<IChart> _charts = charts ?? throw new ArgumentNullException(nameof(charts));

/// <inheritdoc />
public async Task RunAsync(string[] args)
{
var options = ArgumentOptions.Parse(args);
if (options.Help)
{
this.DisplayHelp();
return;
}

try
{
var chart = this._charts.SingleOrDefault(p => p.ChartType.Equals(options.ChartType));
if (chart is null)
{
throw new ArgumentException("Invalid chart type. It should be 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.");
}

var collection = await chart.GetChartAsync().ConfigureAwait(false);
this.DisplayDetails(collection);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
this.DisplayHelp();
}
}

private void DisplayDetails(ChartItemCollection collection)
{
Console.WriteLine($"Chart Type: {collection.ChartType}");
switch (collection.ChartType)
{
case ChartTypes.Top100:
case ChartTypes.Hot100:
default:
Console.WriteLine($"Date/Time: {collection.DateLastUpdated} {collection.TimeLastUpdated}");
break;

case ChartTypes.Daily100:
Console.WriteLine($"Date: {collection.DateLastUpdated}");
break;

case ChartTypes.Weekly100:
Console.WriteLine($"Week: {collection.PeriodFrom} - {collection.PeriodTo}");
break;

case ChartTypes.Monthly100:
Console.WriteLine($"Month: {collection.Year}-{collection.Month}");
break;
}
Console.WriteLine();

var items = collection.Items;

Console.WriteLine("Rank\tTitle\tArtist\tAlbum");
Console.WriteLine("----\t-----\t------\t-----");
foreach (var item in items)
{
Console.WriteLine($"{item.Rank}\t{item.Title}\t{item.Artist}\t{item.Album}");
}
}

private void DisplayHelp()
{
Console.WriteLine("Usage:");
Console.WriteLine(" -c, -t, --chart, --type, --chart-type <chart-type> Chart type - 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.");
Console.WriteLine(" -h, --help Display help");
}
}
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;

using MelonChart.Abstractions;
using MelonChart.ConsoleApp.Options;
using MelonChart.Models;

namespace MelonChart.ConsoleApp.Services;

/// <summary>
/// This represents the service entity for Melon chart.
/// </summary>
/// <param name="charts">List of <see cref="IChart"/> instances.</param>
public class MelonChartService(IEnumerable<IChart> charts) : IMelonChartService
{
private readonly IEnumerable<IChart> _charts = charts ?? throw new ArgumentNullException(nameof(charts));

private static JsonSerializerOptions jso = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
};

/// <inheritdoc />
public async Task RunAsync(string[] args)
{
var options = ArgumentOptions.Parse(args);
if (options.Help)
{
this.DisplayHelp();
return;
}

try
{
var chart = this._charts.SingleOrDefault(p => p.ChartType.Equals(options.ChartType));
if (chart is null)
{
throw new ArgumentException("Invalid chart type. It should be 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.");
}

var collection = await chart.GetChartAsync().ConfigureAwait(false);
if (options.OutputAsJson)
{
Console.WriteLine(JsonSerializer.Serialize(collection, jso));
return;
}

this.DisplayDetails(collection);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
this.DisplayHelp();
}
}

private void DisplayDetails(ChartItemCollection collection)
{
Console.WriteLine($"Chart Type: {collection.ChartType}");
switch (collection.ChartType)
{
case ChartTypes.Top100:
case ChartTypes.Hot100:
default:
Console.WriteLine($"Date/Time: {collection.DateLastUpdated} {collection.TimeLastUpdated}");
break;

case ChartTypes.Daily100:
Console.WriteLine($"Date: {collection.DateLastUpdated}");
break;

case ChartTypes.Weekly100:
Console.WriteLine($"Week: {collection.PeriodFrom} - {collection.PeriodTo}");
break;

case ChartTypes.Monthly100:
Console.WriteLine($"Month: {collection.Year}-{collection.Month}");
break;
}
Console.WriteLine();

var items = collection.Items;

Console.WriteLine("Rank\tStatus\tTitle\tArtist\tAlbum");
Console.WriteLine("----\t-----\t-----\t------\t-----");
foreach (var item in items)
{
Console.WriteLine($"{item.Rank}\t{this.GetRankStatus(item)}\t{item.Title}\t{item.Artist}\t{item.Album}");
}
}

private void DisplayHelp()
{
Console.WriteLine("Usage:");
Console.WriteLine(" -c, -t, --chart, --type, --chart-type <chart-type> Chart type - 'Top100', 'Hot100', 'Daily100', 'Weekly100' or 'Monthly100'.");
Console.WriteLine(" --json Output in JSON format");
Console.WriteLine(" -h, --help Display help");
}

private string GetRankStatus(ChartItem item)
{
return item.RankStatus switch
{
RankStatus.None => "--",
RankStatus.Up => $"+{item.RankStatusValue}",
RankStatus.Down => $"-{item.RankStatusValue}",
RankStatus.New => "new",
_ => "Unknown",
};
}
}
Loading