Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save PJUllrich/a7805692469f317f9e8ee8a4c09a61ad to your computer and use it in GitHub Desktop.
Save PJUllrich/a7805692469f317f9e8ee8a4c09a61ad to your computer and use it in GitHub Desktop.
Checks for available appointments in the "Bürgeramt" in Cologne and notifies via Pushover
defmodule Terminvergabe do
@moduledoc """
This script checks for available appointments in the "Bürgeramt" (city hall)
every 5min and notifies a user via Pushover if new appointments become were added.
In order to use Pushover, you must configure the following environment variables:
PUSHOVER_USER
PUSHOVER_TOKEN
Unfortunately, the "book the appointment"-link received from the website does
not work because the website is not stateless, but uses some PHP-state in order
to save which type of appointment you want to book.
After receiving a notification through this script, you must then go yourself
to https://termine-online.stadt-koeln.de and book the appointment.
This script is simply a notifier.
"""
@base_url "https://termine-online.stadt-koeln.de"
@request_path "/index.php"
@file_path "./termine"
@company "stadtkoeln"
@year Date.utc_today().year
@month Date.utc_today().month
@step "2"
@aemter %{
1 => "Chorweiler",
2 => "Ehrenfeld",
3 => "Innenstadt",
4 => "Kalk",
5 => "Lindenthal",
6 => "Mülheim",
7 => "Nippes",
8 => "Porz",
9 => "Rodenkirchen"
}
# 10sec
@sleep_between_requests 10_000
# 5min
@sleep_between_checks 5 * 60 * 1000
def check_appointments do
maybe_create_termine_dir()
for amt_nummer <- Map.keys(@aemter) do
req_url = @base_url <> @request_path
HTTPoison.get!(req_url, [],
params: %{
company: @company,
step: @step,
year: @year,
month: @month,
cur_cause: amt_nummer
}
)
|> find_appointments(amt_nummer)
:timer.sleep(@sleep_between_requests)
end
IO.inspect(
"""
-------------------------------------------------------------------------------------
"""
)
:timer.sleep(@sleep_between_checks)
check_appointments()
end
defp find_appointments(%{body: html}, amt_nummer) do
{:ok, document} = Floki.parse_document(html)
appointments = Floki.find(document, "a.nat_calendar_weekday_bookable")
if appointments != [] do
for appointment <- appointments do
parse_appointment(appointment, amt_nummer)
end
end
end
defp parse_appointment(appointment, amt_nummer) do
amt_name = Map.get(@aemter, amt_nummer)
[link] = Floki.attribute(appointment, "href")
[_, day, date] = Floki.children(appointment)
day = String.trim(day)
date_text = Floki.text(date)
datum = day <> date_text
full_link = @base_url <> link
maybe_send_notification(amt_name, datum, full_link)
end
defp maybe_send_notification(amt_name, datum, link) do
if not termin_bekannt?(amt_name, datum) do
IO.inspect("#{amt_name}: #{datum} - #{link}")
push_notification(amt_name, datum)
save_termin(amt_name, datum)
end
end
defp push_notification(amt_name, datum) do
message = %{
data: "#{amt_name}: #{datum}",
title: "Neuer Termin in Köln",
priority: 0
}
Pushover.Api.Messages.send(message)
end
defp termin_bekannt?(amt_name, datum) do
path = filename(amt_name)
if File.exists?(path) do
path
|> File.stream!()
|> CSV.decode!()
|> Enum.reduce(false, fn [item], exists? -> exists? || item == datum end)
else
false
end
end
defp maybe_create_termine_dir do
File.mkdir_p(@file_path)
end
defp save_termin(amt_name, datum) do
path = filename(amt_name)
File.write!(path, "#{datum}\n", [:append])
end
defp filename(amt_name) do
"#{@file_path}/#{amt_name}.csv"
end
end
Mix.install([:csv, :httpoison, :floki, :pushover])
Terminvergabe.check_appointments()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment