📄
BfiScreeningCheckerJob.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Quartz; [DisallowConcurrentExecution] internal sealed class BfiScreeningCheckerJob( PlaywrightBrowserService browserService, ScreeningRepository repository, IScheduler scheduler, IOptions<MonitorOptions> options, ILogger<BfiScreeningCheckerJob> logger ) : IJob { public async Task Execute(IJobExecutionContext context) { var opts = options.Value; var page = await browserService.NewPageAsync(); try { await page.GotoAsync(opts.Url); // Booking links on BFI What's On pages have "book" in the href. // Each href is unique per performance and encodes the date/time/seat map. var bookingLinks = await page.QuerySelectorAllAsync("a[href*='book' i], a[href*='seatmap' i]"); var available = new List<string>(); foreach (var link in bookingLinks) { var href = await link.GetAttributeAsync("href"); if (!string.IsNullOrWhiteSpace(href)) available.Add(href); } logger.LogInformation("Check complete — {Count} booking link(s) found on {Url}", available.Count, opts.Url); foreach (var href in available) { if (await repository.HasBeenNotifiedAsync(href)) continue; await repository.MarkNotifiedAsync(href); var message = $"BFI IMAX tickets available! Book now: {href}"; foreach (var phone in opts.PhoneNumbers) { var dataMap = new JobDataMap { { "phoneNumber", phone }, { "message", message } }; var job = JobBuilder .Create<SendSmsJob>() .WithIdentity(Guid.NewGuid().ToString(), "sms") .UsingJobData(dataMap) .Build(); var trigger = TriggerBuilder.Create().StartNow().Build(); await scheduler.ScheduleJob(job, trigger, context.CancellationToken); logger.LogInformation("Scheduled SMS to {Phone} for screening {Href}", phone, href); } } } finally { await page.CloseAsync(); } } }