Compare commits

..

5 commits

8 changed files with 165 additions and 63 deletions

View file

@ -6,7 +6,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
</Project>

View file

@ -1,7 +1,8 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using InfoferScraper;
using InfoferScraper.Scrapers;
@ -39,11 +40,13 @@ async Task PrintTrain() {
}
Console.WriteLine(
JsonSerializer.Serialize(
JsonConvert.SerializeObject(
await new TrainScraper().Scrape(trainNumber),
new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
new JsonSerializerSettings {
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver {
NamingStrategy = new CamelCaseNamingStrategy(),
},
}
)
);
@ -57,11 +60,13 @@ async Task PrintStation() {
}
Console.WriteLine(
JsonSerializer.Serialize(
JsonConvert.SerializeObject(
await new StationScraper().Scrape(stationName),
new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
new JsonSerializerSettings {
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver {
NamingStrategy = new CamelCaseNamingStrategy(),
},
}
)
);

View file

@ -1,5 +1,5 @@
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /source
# copy csproj and restore as distinct layers
@ -14,10 +14,10 @@ COPY server/. ./server/
COPY scraper/. ./scraper/
COPY ConsoleTest/. ./ConsoleTest/
WORKDIR /source/server
RUN dotnet publish -f net8.0 -c release -o /app --no-restore
RUN dotnet publish -f net9.0 -c release -o /app --no-restore
# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build /app ./
ENV INSIDE_DOCKER=true

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<Nullable>enable</Nullable>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

40
scraper/src/Models/Train.cs Normal file → Executable file
View file

@ -40,6 +40,11 @@ namespace InfoferScraper.Models.Train {
public int Delay { get; }
public string Station { get; }
public StatusKind State { get; }
/// <summary>
/// The time when the real time report was introduced in the system
/// </summary>
public DateTimeOffset? ReportTime { get; }
public ITrainRoute? Between { get; }
}
public interface ITrainStopDescription {
@ -102,6 +107,9 @@ namespace InfoferScraper.Models.Train {
DetachingWagons,
ReceivingWagons,
DepartsAs,
BusReplacementStartingHere,
BusReplacement,
BusReplacementEndingHere,
}
#region Implementations
@ -166,6 +174,14 @@ namespace InfoferScraper.Models.Train {
public int Delay { get; set; }
public string Station { get; set; } = "";
public StatusKind State { get; set; }
public DateTimeOffset? ReportTime { get; set; }
public ITrainRoute? Between { get; set; }
internal void MakeBetween(Action<TrainRoute> configurator) {
TrainRoute newRoute = new();
configurator(newRoute);
Between = newRoute;
}
}
internal record TrainStopDescription : ITrainStopDescription {
@ -214,6 +230,18 @@ namespace InfoferScraper.Models.Train {
public string Station { get; set; } = "";
}
class BusReplacementStartingHereNote : ITrainStopNote {
public NoteKind Kind => NoteKind.BusReplacementStartingHere;
}
class BusReplacementNote : ITrainStopNote {
public NoteKind Kind => NoteKind.BusReplacement;
}
class BusReplacementEndingHereNote : ITrainStopNote {
public NoteKind Kind => NoteKind.BusReplacementEndingHere;
}
internal void AddDepartsAsNote(string rank, string number, DateTimeOffset departureDate) {
ModifyableNotes.Add(new DepartsAsNote { Rank = rank, Number = number, DepartureDate = departureDate });
}
@ -229,6 +257,18 @@ namespace InfoferScraper.Models.Train {
internal void AddDetachingWagonsNote(string station) {
ModifyableNotes.Add(new DetachingWagonsNote { Station = station });
}
internal void AddBusReplacementStartingHereNote() {
ModifyableNotes.Add(new BusReplacementStartingHereNote {});
}
internal void AddBusReplacementNote() {
ModifyableNotes.Add(new BusReplacementNote {});
}
internal void AddBusReplacementEndingHereNote() {
ModifyableNotes.Add(new BusReplacementEndingHereNote {});
}
}
public record TrainStopArrDep : ITrainStopArrDep {

90
scraper/src/Scrapers/Train.cs Normal file → Executable file
View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -28,6 +28,10 @@ namespace InfoferScraper.Scrapers {
new(
@"^(?:Fără|([0-9]+)\smin)\s(întârziere|mai\sdevreme)\sla\s(trecerea\sfără\soprire\sprin|sosirea\sîn|plecarea\sdin)\s(.+)(?:\s\(Raportat\sla\s([0-9]+):([0-9]+)\))?\.");
private static readonly Regex SlExpandedRegex =
new(
@"^(?:Fără|([0-9]+)\smin)\s(întârziere|mai\sdevreme)\sla\s(trecerea\sfără\soprire\sprin|sosirea\sîn|plecarea\sdin)\s(.+)(?:\s\(Raportat\sla\s([0-9]+):([0-9]+)\))?\.\sConform\sitinerariului,\strenul\sse\saflă\sîntre\sstațiile\s(.+)\s-\s(.+)\.\sPuteți\sapăsa\spe\sbutonul\s”Hartă”\spentru\sa\svedea\slocația.");
private static readonly Dictionary<char, StatusKind> SlStateMap = new() {
{ 't', StatusKind.Passing },
{ 's', StatusKind.Arrival },
@ -49,6 +53,12 @@ namespace InfoferScraper.Scrapers {
new(@"^Trenul primește vagoane de la\s(.+)\.$");
private static readonly Regex DetachingWagonsNoteRegex =
new(@"^Trenul detașează vagoane pentru stația\s(.+)\.$");
private static readonly Regex BusReplacementStartingHereNoteRegex =
new(@"^\s*Transfer\scu\sautobuzul\sîncepând\scu\saceastă\sstație\s*$");
private static readonly Regex BusReplacementNoteRegex =
new(@"^Transfer cu autobuzul$");
private static readonly Regex BusReplacementEndingHereNoteRegex =
new(@"^\s*Transfer\scu\sautobuzul\spână\sla\saceastă\sstație\s*$");
private static readonly DateTimeZone BucharestTz = DateTimeZoneProviders.Tzdb["Europe/Bucharest"];
@ -144,22 +154,6 @@ namespace InfoferScraper.Scrapers {
.Select(group => group.Value);
});
try {
var statusLineMatch =
SlRegex.Match(statusDiv.QuerySelector(":scope > div")!.Text().WithCollapsedSpaces());
var (slmDelay, (slmLate, (slmArrival, (slmStation, _)))) =
(statusLineMatch.Groups as IEnumerable<Group>).Skip(1).Select(group => group.Value);
group.MakeStatus(status => {
status.Delay = string.IsNullOrEmpty(slmDelay) ? 0 :
slmLate == "întârziere" ? int.Parse(slmDelay) : -int.Parse(slmDelay);
status.Station = slmStation;
status.State = SlStateMap[slmArrival[0]];
});
}
catch {
// ignored
}
Utils.DateTimeSequencer dtSeq = new(date.Year, date.Month, date.Day);
var stations = statusDiv.QuerySelectorAll(":scope > ul > li");
foreach (var station in stations) {
@ -242,7 +236,7 @@ namespace InfoferScraper.Scrapers {
foreach (var noteDiv in stopNotes.QuerySelectorAll(":scope > div > div")) {
var noteText = noteDiv.Text().WithCollapsedSpaces();
Match trainNumberChangeMatch, departsAsMatch, detachingWagons, receivingWagons;
Match trainNumberChangeMatch, departsAsMatch, detachingWagons, receivingWagons, busReplacementStart, busReplacement, busReplacementEnd;
if ((trainNumberChangeMatch = TrainNumberChangeNoteRegex.Match(noteText)).Success) {
stopDescription.AddTrainNumberChangeNote(trainNumberChangeMatch.Groups[1].Value, trainNumberChangeMatch.Groups[2].Value);
}
@ -257,9 +251,69 @@ namespace InfoferScraper.Scrapers {
else if ((receivingWagons = ReceivingWagonsNoteRegex.Match(noteText)).Success) {
stopDescription.AddReceivingWagonsNote(receivingWagons.Groups[1].Value);
}
else if ((busReplacementStart = BusReplacementStartingHereNoteRegex.Match(noteText)).Success) {
stopDescription.AddBusReplacementStartingHereNote();
}
else if ((busReplacement = BusReplacementNoteRegex.Match(noteText)).Success) {
stopDescription.AddBusReplacementNote();
}
else if ((busReplacementEnd = BusReplacementEndingHereNoteRegex.Match(noteText)).Success) {
stopDescription.AddBusReplacementEndingHereNote();
}
}
});
}
try {
var statusLineText = statusDiv.QuerySelector(":scope > div")!.Text().WithCollapsedSpaces();
var statusLineExpandedMatch = SlExpandedRegex.Match(statusLineText);
var statusLineMatch = SlRegex.Match(statusLineText);
var realMatch = statusLineExpandedMatch.Success ? statusLineExpandedMatch
: statusLineMatch.Success ? statusLineMatch
: null;
if (realMatch != null) {
var (slmDelay, (slmLate, (slmArrival, (slmStation,
(slmReportH, (slmReportM, (slmBetweenF, (slmBetweenT, _)))))))) =
(realMatch.Groups as IEnumerable<Group>).Skip(1).Select(group => group.Value);
group.MakeStatus(status => {
status.Delay = string.IsNullOrEmpty(slmDelay) ? 0 :
slmLate == "întârziere" ? int.Parse(slmDelay) : -int.Parse(slmDelay);
status.Station = slmStation;
status.State = SlStateMap[slmArrival[0]];
if (!string.IsNullOrEmpty(slmReportH) && !string.IsNullOrEmpty(slmReportM)) {
var firstDeparture = group.Stations[0].Departure!.ScheduleTime;
var potentialReportTime = BucharestTz
.AtLeniently(
new DateTime(firstDeparture.Year, firstDeparture.Month, firstDeparture.Day, int.Parse(slmReportH), int.Parse(slmReportM), 0)
.ToLocalDateTime()
)
.ToDateTimeOffset();
if (potentialReportTime < firstDeparture) {
// Assume no reports come in before the train departs
var nextDay = firstDeparture.AddDays(1);
potentialReportTime = BucharestTz
.AtLeniently(
new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, potentialReportTime.Hour, potentialReportTime.Minute, 0)
.ToLocalDateTime()
)
.ToDateTimeOffset();
}
status.ReportTime = potentialReportTime;
}
if (!string.IsNullOrEmpty(slmBetweenF) && !string.IsNullOrEmpty(slmBetweenT)) {
status.MakeBetween(between => {
between.From = slmBetweenF;
between.To = slmBetweenT;
});
}
});
}
}
catch {
// ignored
}
});
}
return result;

5
scraper/src/Utils/DateTimeSequencer.cs Normal file → Executable file
View file

@ -16,7 +16,10 @@ namespace InfoferScraper {
public DateTime Next(int hour, int minute = 0, int second = 0) {
DateTime potentialNewDate = new(_current.Year, _current.Month, _current.Day, hour, minute, second);
if (_current > potentialNewDate) potentialNewDate = potentialNewDate.AddDays(1);
if (_current >= potentialNewDate) {
_current = potentialNewDate.AddDays(1);
potentialNewDate = new(_current.Year, _current.Month, _current.Day, hour, minute, second);
}
_current = potentialNewDate;
return _current;
}

View file

@ -5,7 +5,7 @@
<AssemblyName>Server</AssemblyName>
<RootNamespace>Server</RootNamespace>
<LangVersion>11</LangVersion>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>