Top Level Namespace

Includes:
Neo4jBolt

Defined Under Namespace

Modules: Nextcloud, UserRoleHelper Classes: AudioBotRepl, BackgroundRenderer, BitmapFont, DashboardSchema, DumpDatabase, ImageBotRepl, ImportNewsAttic, Integer, InvitationRepl, Main, Neo4jGlobal, Neo4jHelper, NodeBlock, Parser, PixelFont, RandomTag, SchemaRenderer, Script, SetupDatabase, StatsBotRepl, String, Timetable, TimetableRepl

Constant Summary collapse

DASHBOARD_SERVICE =
ENV['DASHBOARD_SERVICE']
BIB_JWT_TTL =
60
BIB_JWT_TTL_EXTRA =
20
TRESOR_SECOND_FACTOR_TTL =
DEVELOPMENT ? 60 * 15 : 60 * 15
TRESOR_JWT_TTL =
60
TRESOR_JWT_TTL_EXTRA =
20
USER_AGENT_PARSER =
UserAgentParser::Parser.new
WEEKDAYS =
['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
WEEKDAYS_LONG =
['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']
MONTHS =
['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
HOMEWORK_FEEDBACK_STATES =
['good', 'hmmm', 'lost']
HOMEWORK_FEEDBACK_EMOJIS =
{'good' => '🙂',
'hmmm' => '🤔',
'lost' => '😕'}
HOURS_FOR_KLASSE =
{}
TUNNEL =
16
DEVELOPMENT =

Diese Datei bitte unter credentials.rb speichern und Werte anpassen (bitte keine Credentials in Git committen)


ENV['DEVELOPMENT']
SCHUL_NAME =

Name der Schule

"Beispielschule"
SCHUL_NAME_AN_DATIV =

'an der' oder 'am'

"an der"
SCHUL_ICON =

Schul-Icon

"brand.png"
DARK_SCHUL_ICON =
"brand.png"
SCHULLEITUNG_EMAIL =

E-Mail-Adresse der Schulleitung für das Impressum

'schulleitung@beispielschule.de'
SCHUL_MAIL_DOMAIN =

Mail-Domain für SuS-Adressen

"mail.beispielschule.de"
SCHUL_MAIL_LOGIN_URL =

Webmail-Login-Seite, SMTP- und IMAP-Host (nur wichtig für E-Mail-Briefe)

"https://mail.beispielmailhoster.de"
SCHUL_MAIL_LOGIN_SMTP_HOST =
"smtp.beispielmailhoster.de"
SCHUL_MAIL_LOGIN_IMAP_HOST =
"imap.beispielmailhoster.de"
USE_MOCK_NAMES =

Bei Bedarf können alle Namen durch falsche Namen ersetzt werden (für Demozwecke)

false
EXCLUDE_FROM_MOCKIFICATION =

pseudonymisiert werden sollen

[]
SMTP_SERVER =

SMTP Hostname

'smtp.example.com'
IMAP_SERVER =

IMAP Hostname

'imap.example.com'
SMTP_USER =
'dashboard@beispielschule.de'
SMTP_PASSWORD =
'1234_nein_wirklich'
SMTP_DOMAIN =
'beispielschule.de'
SMTP_FROM =
'Dashboard Beispielschule <dashboard@beispielschule.de>'
DASHBOARD_SUPPORT_EMAIL =
'dashboard@beispielschule.de'
DATENTRESOR_HOST =
''
DATENTRESOR_UNLOCKED_FOR =
nil
DATENTRESOR_JWT_APPKEY =
''
SMS_GATEWAY_SECRET =
'bitte_ein_zufälliges_secret_generieren'
SMS_PHONE_NUMBER_PASSPHRASE =
'bitte_ein_zufälliges_secret_generieren'
DATENTRESOR_HOTLINE =
''
DATENTRESOR_HOTLINE_MIT_NUMMERN =
''
ZEUGNIS_USE_MOCK_NAMES =
false
MAILING_LIST_EMAIL =

Mailing-Liste

DEVELOPMENT ? 'verteiler.dev@mail.beispielschule.de' : 'verteiler@mail.beispielschule.de'
MAILING_LIST_PASSWORD =
'1234_bitte_generiere_ein_zufälliges_passwort'
MAILING_LIST_DOMAIN =
'verteiler.beispielschule.de'
VERTEILER_MAIL_HEADER =
'X-Forwarded-From-Gymnasium-Steglitz'
VERTEILER_TEST_EMAILS =
["verteiler.test@#{MAILING_LIST_DOMAIN}"]
VERTEILER_DEVELOPMENT_EMAILS =
['admin@beispielschule.de']
MAIL_SUPPORT_NAME =
'Mail-Support Beispielschule'
MAIL_SUPPORT_EMAIL =
'mailsupport@beispielschule.de'
MAILING_LIST_FORWARDER_ACCOUNTS =
{
    '@beispielschule.de' => {
        :address => SMTP_SERVER,
        :port => 587,
        :domain => SMTP_DOMAIN,
        :user_name => SMTP_USER,
        :password => SMTP_PASSWORD,
        :authentication => 'login',
        :enable_starttls_auto => true
    }
}
WEBSITE_HOST =

Domain, auf der die Live-Seite läuft

'dashboard.beispielschule.de'
WEBSITE_MAINTAINER_NAME =

Name für Unterschriften in E-Mails (Mit freundlichen Grüßen…)

'Herr Müller'
WEBSITE_MAINTAINER_NAME_AKKUSATIV =
'Herrn Müller'
WEBSITE_MAINTAINER_EMAIL =
'mueller@beispielschule.de'
VOTING_WEBSITE_URL =

Website mit Voting-System (muss noch veröffentlich werden)

'https://abstimmung.beispielschule.de'
VOTING_CONTACT_EMAIL =

Ansprechpartner für Wahlverfahren

'admin@beispielschule.de'
TECHNIK_HILFE_WEBSITE_URL =

Website für Technikhilfe (Chat und Speedtest)

'https://hilfe.beispielschule.de'
WEB_ROOT =
DEVELOPMENT ? 'http://localhost:8025' : "https://#{WEBSITE_HOST}"
ROOM_ORDER =
%w(101 102 103 104)
ENABLE_NEXTCLOUD_SANDBOX =
true
NEXTCLOUD_SANDBOX_INSTALL_ADMIN_USER =
'dashboard'
NEXTCLOUD_SANDBOX_INSTALL_ADMIN_PASSWORD =
'hunter2_bitte_etwas_anderes_waehlen'
NEXTCLOUD_URL =

Das Dashboard benötigt einen Nextcloud-Account, der Admin-Rechte hat

'http://localhost:8024'
NEXTCLOUD_URL_FROM_RUBY_CONTAINER =

keine Rolle und der Wert sollte derselbe sein wie für NEXTCLOUD_URL

'http://nextcloud'
NEXTCLOUD_USER =
'dashboard'
NEXTCLOUD_PASSWORD =
'hunter2_bitte_etwas_anderes_waehlen'
NEXTCLOUD_DASHBOARD_DATA_DIRECTORY =

NEXTCLOUD_DASHBOARD_DATA_DIRECTORY muss ein absoluter Pfad sein

__NEXTCLOUD_DASHBOARD_DATA_DIRECTORY__
NEXTCLOUD_WAIT_SECONDS =
__NEXTCLOUD_WAIT_SECONDS__
NEXTCLOUD_ALL_ACCESS_PASSWORD_BE_CAREFUL =

Das Skript share-nc-folders.rb muss sich als jeder Nutzer in der NextCloud anmelden können. Dazu kann die NC-App »External user authentication« verwendet werden, die fehlgeschlagene Anmeldeversuche an eine URL weiterleiten kann, die dann die Authentifizierung übernimmt. Wenn der folgende Wert != nil ist, ist dies das Passwort, mit dem sich das Skript als jeder Nutzer anmelden kann. Vorsicht ist angesagt.

'user_backends' => array(
    array(
        'class' => 'OC_User_BasicAuth',
        'arguments' => array('https://dashboard.beispielschule.de/nc_auth'),
    ),
),
'here_be_dragons_dont_use_this_password'
MATRIX_DOMAIN =
'matrix.example.com'
MATRIX_DOMAIN_SHORT =
'example.com'
MATRIX_ALL_ACCESS_PASSWORD_BE_CAREFUL =
nil
JITSI_HOST =

Das Dashboard vermittelt Links in Jitsi-Räume mit Hilfe von JWT (JSON Web Tokens). Dafür werden ein paar Angaben benötigt, die auf der Jitsi-Seite verifiziert werden müssen.

'meet.beispielschule.de'
JWT_APPAUD =
'jitsi'
JWT_APPISS =
'dashboard'
JWT_APPKEY =
'ein_langer_langer_richtig_langer_app_key'
JWT_SUB =
'beispielschule.de'
JWT_APPKEY_AGRAPP =
'ein_langer_langer_richtig_langer_app_key'
BIB_HOST =

BIB_HOST = 'gyst-bib.nhcham.org'

'https://bibliothek.beispielschule.de'
JWT_APPKEY_BIB =
'ein_langer_langer_richtig_langer_app_key'
EMAIL_PASSWORD_SALT =

Es folgen ein paar Salts, die bestimmen, nach welchen Regeln Passwörter und sekundäre IDs generiert werden

'bitte_jeden_salt_nur_einmal_verwenden'
NEXTCLOUD_PASSWORD_SALT =
'bitte_jeden_salt_nur_einmal_verwenden'
KLASSEN_ID_SALT =
'bitte_jeden_salt_nur_einmal_verwenden'
USER_ID_SALT =
'bitte_jeden_salt_nur_einmal_verwenden'
LESSON_ID_SALT =
'bitte_jeden_salt_nur_einmal_verwenden'
SESSION_SCRAMBLER =
'bitte_jeden_salt_nur_einmal_verwenden'
EXTERNAL_USER_EVENT_SCRAMBLER =
'bitte_jeden_salt_nur_einmal_verwenden'
LOGIN_CODE_SALT =
'bitte_jeden_salt_nur_einmal_verwenden'
WEBSITE_READ_INFO_SECRET =
'bitte_ein_zufälliges_secret_generieren'
MESSAGE_DELAY =
DEVELOPMENT ? 1 : 1
LOGIN_STATS_D =
[0, 7, 28, 1000]
VPLAN_ENCODING =
'ISO-8859-1'
JITSI_EVENT_PRE_ENTRY_TOLERANCE =

minutes

DEVELOPMENT ? 2880 : 15
JITSI_EVENT_POST_ENTRY_TOLERANCE =

minutes

DEVELOPMENT ? 2880 : 120
JITSI_LESSON_PRE_ENTRY_TOLERANCE =

minutes

DEVELOPMENT ? 5: 5
JITSI_LESSON_POST_ENTRY_TOLERANCE =

minutes

DEVELOPMENT ? 10: 10
PROVIDE_CLASS_STREAM =
false
3600 * 24 * 365
AVAILABLE_FONTS =
['Roboto', 'Alegreya']
GEN_IMAGE_WIDTHS =
[2048, 1200, 1024, 768, 512, 384, 256].sort
MAINTENANCE_MODE =
false
KLASSEN_TR =
{'8o' => ''}
TIMETABLE_ENTRIES_VISIBLE_AHEAD_DAYS =
7
AUFSICHT_ZEIT =
{1 => '08:00', 2 => '09:00', 3 => '09:55', 4 => '10:40',
6 => '12:50', 7 => '13:40', 8 => '14:30'}
PAUSENAUFSICHT_DAUER =
{1 => 25, 2 => 15, 3 => 15, 4 => 20, 6 => 40,
7 => 40, 8 => 15}
KLASSEN_ORDER =
['5a', '11', '12']
TABLET_COLORS =
{}
SCHOOL_WEBSITE_API_URL =
''
DEVELOPMENT_MAIL_DELIVERY_POSITIVE_LIST =
[]
UNTIS_VERTRETUNGSPLAN_BASE_URL =
'https://beispielschule.de/vertretungsplan_lehrer'
UNTIS_VERTRETUNGSPLAN_USERNAME =
nil
UNTIS_VERTRETUNGSPLAN_PASSWORD =
nil
nil
nil
FOTO_PASSWORD =
nil
TR =
{'String' => 'string',
  'Array' => 'list',
  'Hash' => 'hash',
  'TrueClass' => 'true',
  'FalseClass' => 'false',
  'NilClass' => 'null',
  'Integer' => 'int',
  'Float' => 'float'
}
CONFIG =
YAML::load_file('/data/config.yaml')
TIMETABLE_JSON_KEYS =
{
    # 6 => [:klasse, :stunde, :fach, :raum, :lehrer, :text],
    6 => [:stunde, :klasse, :lehrer, :raum, :fach, :text],
    7 => [:vnr, :stunde, :klasse, :lehrer, :raum, :fach, :text],
}
PING_TIME =
DEVELOPMENT ? 1 : 1
MAIL_FORWARDER_SLEEP =
DEVELOPMENT ? 10 : 60
MAIL_FORWARD_BATCH_SIZE =
30
SRSLY =
ARGV.include?('--srsly')
DELAYED_UPDATE_TIME =
DEVELOPMENT ? 0 : 0
UPDATE_INFO =
ARGV.include?('--update-info')
SKIP_COLLECT_DATA =
true
DEBUG_ARCHIVE_PATH =
'/data/debug_archives/2023-07-23.zip'
SHARE_ARCHIVED_FILES =
ARGV.include?('--share-archived')
SHARE_SOURCE_FOLDER =
SHARE_ARCHIVED_FILES ? 'Unterricht-22-23' : 'Unterricht'
SHARE_TARGET_FOLDER =
SHARE_ARCHIVED_FILES ? 'Archiv-Jahresbeginn-23-24' : 'Unterricht'
ALSO_SHARE_OS_FOLDERS =
true
SHARE_READ =
1
SHARE_UPDATE =
2
SHARE_CREATE =
4
SHARE_DELETE =
8
SHARE_SHARE =
16
HTTP_READ_TIMEOUT =
60 * 10
ALSO_CREATE_OS_FOLDERS =
true
BASE62_ALPHABET =
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
SEKRETARIAT_EMAIL =
'sekretariat@beispielschule.de'
MAX_LOGIN_TRIES =
3
MATRIX_ADMIN_USER =
nil
MATRIX_ADMIN_PASSWORD =
nil
MATRIX_CORPORAL_CALLBACK_BEARER_TOKEN =
'bitte_jedes_bearer_token_nur_einmal_verwenden'
JWT_APPAUD_STREAM =
'stream'
JWT_APPKEY_STREAM =
'ein_langer_langer_richtig_langer_app_key'
JWT_DOMAIN_STREAM =
'.beispielschule.de'
JWT_APPAUD_AGRAPP =
'agrapp'
JWT_APPISS_AGRAPP =
'dashboard'
STREAM_SITE_URL =
'https://info.beispielschule.de/'
JITSI_ALL_ROOMS_URL =

der alle Räume und Teilnehmer im JSON-Format zurückgibt.

nil
WECHSELUNTERRICHT_KLASSENSTUFEN =
[]
GROUP_AF_ICONS =
{
    ''   => "<span style='font-size:120%;'>🏠</span>",
    'it' => "<span style='font-size:120%;'>🇮🇹</span>",
    'gr' => "<span style='font-size:120%;'>🇬🇷</span>"
}
GROUP_AF_ICON_KEYS =
GROUP_AF_ICONS.keys.sort
GROUP_FT_ICONS =
{
    'nawi'   => "<span style='font-size:120%;'>🧪</span> Nawi",
    'gewi'   => "<span style='font-size:120%;'>👪</span> Gewi",
    'musik'  => "<span style='font-size:120%;'>🎹</span> Musik",
    'medien' => "<span style='font-size:120%;'>📽️</span> Medien",
}
GROUP_FT_ICON_KEYS =
GROUP_FT_ICONS.keys.sort
STREAMING_TABLET_BOOKING_TIME_PRE =

tablet booking pre and post time, in minutes

5
STREAMING_TABLET_BOOKING_TIME_POST =
15
LEHRBUCHVEREIN_JAHR =
2023
TABLET_DEFAULT_COLOR =
'#d3d7cf'
SWITCH_WEEKS =

Definition von Wechselwochen

{'2021-03-08' => ['A', 2],
'2021-03-22' => nil}
nil
DEMO_ACCOUNT_EMAIL =
nil
DEMO_ACCOUNT_INFO =
nil
DEMO_ACCOUNT_FIXED_PIN =
nil
[]
EXCLUDE_FROM_SELF_TEST_REPORT =
[]
ZEUGNISKONFERENZEN =
[]
CLING_COLORS =
[
  ["#ffe617", "daffodil"],
  ["#fad31c", "daisy"],
  ["#fdb717", "mustard"],
  ["#faaa21", "circus zest"],
  ["#f1753f", "pumpkin"],
  ["#ed5724", "tangerine"],
  ["#ef4538", "salmon"],
  ["#ea2830", "persimmon"],
  ["#bc2326", "rouge"],
  ["#8c0c03", "scarlet"],
  ["#e5185d", "hot pink"],

  ["#f384ae", "princess"],
  ["#fac6d2", "petal"],
  ["#b296c7", "lilac"],
  ["#7b67ae", "lavender"],
  ["#5f3577", "violet"],

  ["#c1d18a", "ceadon"],
  ["#799155", "olive"],
  ["#80bc42", "bamboo"],
  ["#4aa03f", "grass"],
  ["#16884a", "kelly"],
  ["#003f2e", "forrest"],

  ["#c3def1", "cloud"],
  ["#55beed", "dream"],
  ["#31a8e0", "gulf"],
  ["#238acc", "turquoise"],
  ["#0d60ae", "sky"],
  ["#143b86", "indigo"],
  ["#001b4a", "navy"],
  ["#7dcdc2", "sea foam"],
  ["#00a8a8", "teal"],
  ["#12959f", "peacock"],
  ["#094e54", "cyan"],

  ["#381e11", "chocolate"],
  ["#c05c20", "terra cotta"],
  ["#bf9b6b", "camel"],
  ["#e9d4a7", "linen"],
  ["#e7e6e1", "stone"],
  ["#cfd0d2", "smoke"],
  ["#8a8b8f", "steel"],
  ["#778590", "slate"],
  ["#474d4d", "charcoal"],
  ["#050608", "black"],
  ["#ffffff", "white"],
]
DRY_RUN =
__DRY_RUN__
LETTERS =
'BCDFHJLMNPQRSTVWYZ'
DIGITS =
'23456789'
DASH =
''
NAME_MATCH_STOP_WORDS =
Set.new(['de', 'la', 'von'])
BASE_DIR =
File.join(NEXTCLOUD_DASHBOARD_DATA_DIRECTORY, 'files', 'Unterricht')
PK5_KEYS =
[
    :themengebiet,
    :referenzfach,
    :betreuende_lehrkraft,
    :fas,
    :betreuende_lehrkraft_fas,
    :fragestellung
]
PK5_KEY_LABELS =
{
    :themengebiet => 'Themengebiet',
    :referenzfach => 'Referenzfach',
    :betreuende_lehrkraft => 'Betreuende Lehrkraft im Referenzfach',
    :fas => 'fächerübergreifender Aspekt',
    :betreuende_lehrkraft_fas => 'Betreuende Lehrkraft im fächerübergreifenden Aspekt',
    :fragestellung => 'Fragestellung',
}
PRESENCE_TOKEN_EXPIRY_TIME =

discard presence token after 600 seconds unless it's renewed in the meantime

60 * 10
LOGIN_METHODS =
{:email => 'Code per E-Mail', :sms => 'Code per SMS', :otp => 'OTP-Code'}
LOGIN_METHODS_SHORT =
{:email => 'E-Mail', :sms => 'SMS', :otp => 'OTP'}
AVAILABLE_ROLES =
{
    :teacher => 'Lehrkraft',
    :schueler => 'Schülerin / Schüler',
    :admin => 'Administrator',
    :developer => 'Entwickler',
    :sekretariat => 'Sekretariat',
    :zeugnis_admin => 'Zeugnis-Admin',
    :can_see_all_timetables => 'kann alle Stundenpläne sehen',
    # :can_manage_salzh,
    :can_upload_files => 'kann Dateien hochladen',
    :can_manage_news => 'kann News verwalten',
    :can_manage_monitors => 'kann Monitore verwalten',
    :can_manage_tablets => 'kann Tabletbuchungen verwalten',
    :can_book_tablets => 'kann Tablets buchen',
    :can_use_aula => 'Aulatechnik',
    :can_manage_antikenfahrt => 'kann Antikenfahrt verwalten',
    :can_manage_agr_app => 'kann die Altgriechisch-App verwalten',
    :can_manage_bib => 'kann die Bibliothek verwalten',
    :can_manage_bib_members => 'kann Bibliotheksnutzer verwalten',
    :can_manage_bib_payment => 'kann Bibliothekszahlungen verwalten',
    :can_manage_bib_special_access => 'Bibliothek Spezialzugriff',
    :can_manage_tech_problems => 'kann gemeldete Technikprobleme bearbeiten',
    :gev => 'GEV',
    :sv => 'Schülervertretung',
    :technikteam => 'Technikteam',
    :technikamt => 'Technikamt',
    :wants_to_receive_techpost_debug_mail => 'möchte E-Mails zu Technikproblemen erhalten',
    :datentresor_hotline => 'kann Lehrkräfte für den Datentresor freischalten',
    :schulbuchverein => 'Schulbuchverein',
    :can_receive_messages => 'kann Nachrichten empfangen',
    :can_write_messages => 'kann Nachrichten schreiben',
    :can_create_polls => 'kann Umfragen durchführen',
    :can_create_events => 'kann Termine anlegen',
    :can_use_mailing_lists => 'kann alle Mail-Verteiler verwenden',
    :can_use_nextcloud => 'kann die Nextcloud verwenden',
    :can_use_sms_gateway => 'kann die SMS-Anmeldung benutzen',
    :can_report_tech_problems => 'kann Technikprobleme melden',
    :oko => 'Oberstufenkoordination',
}
ROLE_TRANSITIONS =
<<~END_OF_STRING
    teacher => can_receive_messages can_write_messages can_book_tablets can_create_polls can_create_events can_use_mailing_lists can_use_nextcloud can_use_sms_gateway
    schueler => can_receive_messages can_use_nextcloud
    sv => can_write_messages can_create_polls can_use_mailing_lists can_create_events
    technikteam => can_write_messages can_book_tablets can_create_polls can_use_mailing_lists can_create_events can_manage_tablets can_manage_tech_problems can_report_tech_problems
    admin => can_upload_files can_book_tablets can_manage_news can_manage_monitors can_manage_tablets can_manage_tech_problems can_report_tech_problems
    schulbuchverein => can_use_nextcloud
    technikamt => can_report_tech_problems
END_OF_STRING
SALZH_MODE_COLORS =
{:contact_person => 'warning', :salzh => 'danger', :hotspot_klasse => 'pink'}
SALZH_MODE_ICONS =
{:contact_person => 'fa-exclamation', :salzh => 'fa-home', :hotspot_klasse => 'fa-fire'}
SALZH_MODE_LABEL =
{:contact_person => 'Kontaktperson', :salzh => 'saLzH', :hotspot_klasse => 'Hotspot-Klasse'}
CW_TR =

include Magick

[
    ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..",
    ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-",
    ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."
]
PROJEKT_VOTE_CODEPOINTS =
[0x1fae5, 0x1f914, 0x1f60d, 0x1f525]
PROJEKT_VOTE_LABELS =
[
    'Ich habe kein Interesse an diesem Projekt.',
    'Ich könnte mir vorstellen, an diesem Projekt teilzunehmen.',
    'Ich würde mich freuen, an diesem Projekt teilzunehmen.',
    'Ich würde wirklich sehr gern an diesem Projekt teilnehmen.',
]
TABLET_SET_WARNING_BEFORE_MINUTES =

TODO: move these to credentials

15
TABLET_SET_WARNING_AFTER_MINUTES =
15
COLOR_SCHEME_COLORS =
[
    ['l788aa3e8a598b56576', 'Dreamy'],
    ['lab8bbfa27776ab8bbe', 'Einfacher Plan', 'Thanos'],
    ['l7146749f6976cc8b79', 'Ice cream', 'Charlie'],
    ['le8a598b5657600a896', 'Pearly Purple'],
    ['l94b2a1ff7d03e0ff03', 'Orangensaft', 'Karla'],
    ['lc0c0c0ff6700ebebeb', 'Orange Justice'],
    ['d013adedf3a01f7fe2e', 'Sunset', 'Josefine'],
    ['lfcbf499e0001eeba30', 'Gryffindor'],
    ['dfad31c8c0c03381e11', 'Exotherm'],
    ['d6a00009626006a0000', 'Blutroter Sommer', 'Thanos'],
    ['d994500663300000000', 'Sandsturm'],
    ['l234567890123456789', 'Sonnenuntergang', 'Leo'],
    ['ldf0174ff0040ffffff', 'Kirscheis', 'Seraphine'],
    ['l32031ba63570ff99cc', 'BOOMBAYAH'],
    ['lffcc00c20aa10a9ec2', 'Unicorn'],
    ['l55beedf9b935e5185d', 'ソフトクリーム'],
    ['la86fd07638a15a2b7a', 'Amethyst'],
    ['lcc1ccca66aaab4bbbb', 'Lavendel', 'Leo'],
    ['l0b2f3ad0a9f5f8e0f7', 'Passive Night', 'Jack'],
    ['d0000ff6600ffcc00ff', 'Purple Valley', '𝕺𝕲 𝕭𝖎𝖌'],
    ['l073b4c118ab2946b2d', 'Ravenclaw'],
    ['la2c6e80d60aea2c6e8', 'Sky'],
    ['l000000013adfced8f8', 'In the refrigerator', 'Josefine'],
    ['d053a4d001eff00d7ff', 'Unter dem Meer', 'Linus'],
    ['d00ff000b0b6100ff00', 'Kristallmine', 'Josefine'],
    ['d401364131364799d54', 'Himmel Lila', 'HoloOmar10'],
    ['l307fdc03396cfe0e8d', 'Zoomer'],
    ['l12959f094e54e7eff6', 'Wellenbad'],
    ['d119c7503626414a19e', '20.000 Meilen unter dem Meer', 'Astrid'],
    ['d160520069960025061', 'Tiefsee'],
    ['d043402138610043402', 'Polarnacht', 'Astrid'],
    ['d4aa03f003f2e80bc42', 'Thylakoid'],
    ['l2a9134054a29e7ecef', 'Slytherin'],
    ['ldf01010b6121a9f5bc', 'Watermelon'],
    ['lf7fe2e088a08fa5882', 'Kaktus'],
    ['le8e33b6ca705f8f8df', 'Zitronen & Limetten', 'Astrid'],
    ['lceecf5f3f781f7819f', 'Dream', 'Jack'],
    ['daaaaaa777777cccccc', 'Shadows', 'Leo'],
    ['l0c0f0a463f3affbd00', 'Hufflepuff'],
    ['l9afe2e1c1c1c3c74c7', 'Libelle', 'Elias'],
    ['d290040232323290040', 'Aschgrauer Himmel', 'Thanos'],
    ['d151131222526333383', 'Midnight Sky', 'Leo'],
    ['d00f7000a0a0a00f700', 'H4CK3Я', 'Karla'],
    ['d990000000000000099', 'Spooner'],
    ['d000099000000009900', 'FreeRot']
]
STANDARD_COLOR_SCHEME =
'la2c6e80d60aea2c6e80'

Instance Method Summary collapse

Instance Method Details

#assert(condition, message = 'assertion failed', suppress_backtrace = false, delay = nil) ⇒ Object



37
38
39
40
41
42
43
44
45
# File 'src/ruby/main.rb', line 37

def assert(condition, message = 'assertion failed', suppress_backtrace = false, delay = nil)
    unless condition
        debug_error message
        e = StandardError.new(message)
        e.set_backtrace([]) if suppress_backtrace
        sleep delay unless delay.nil?
        raise e
    end
end

#assert_with_delay(condition, message = 'assertion failed', suppress_backtrace = false) ⇒ Object



47
48
49
# File 'src/ruby/main.rb', line 47

def assert_with_delay(condition, message = 'assertion failed', suppress_backtrace = false)
    assert(condition, message, suppress_backtrace, DEVELOPMENT ? 0.0 : 3.0)
end

#color_palette_for_color_scheme(color_scheme) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'src/ruby/create-cling-color-palette.rb', line 170

def color_palette_for_color_scheme(color_scheme)
    primary_color = '#' + color_scheme[7, 6]
    light = luminance(primary_color) > 160
    primary_color_darker = darken(primary_color, 0.8)
    desaturated_color = darken(desaturate(primary_color), 0.9)
    if light
        desaturated_color = rgb_to_hex(mix(hex_to_rgb(desaturate(primary_color)), hex_to_rgb('#ffffff'), 0.1))
    end
    desaturated_color_darker = darken(desaturate(primary_color), 0.3)
    disabled_color = light ? rgb_to_hex(mix(hex_to_rgb(primary_color), [255, 255, 255], 0.5)) : rgb_to_hex(mix(hex_to_rgb(primary_color), [192, 192, 192], 0.5))
    darker_color = rgb_to_hex(mix(hex_to_rgb(primary_color), [0, 0, 0], 0.6))
    shifted_color = shift_hue(primary_color, 350)
    main_text_color = light ? rgb_to_hex(mix(hex_to_rgb(primary_color), [0, 0, 0], 0.7)) : rgb_to_hex(mix(hex_to_rgb(primary_color), [255, 255, 255], 0.8))
    contrast_color = rgb_to_hex(mix(hex_to_rgb(primary_color), color_scheme[0] == 'l' ? [0, 0, 0] : [255, 255, 255], 0.7))
    color_palette = {
        :is_light => light,
        :primary => primary_color, 
        :primary_color_darker => primary_color_darker,
        :disabled => disabled_color, 
        :darker => darker_color, 
        :shifted => desaturated_color,
        :left => '#' + color_scheme[1, 6],
        :right => '#' + color_scheme[13, 6],
        :main_text => main_text_color,
        :contrast => contrast_color
    }
    color_palette
end

#cw_pattern(word) ⇒ Object



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
# File 'src/ruby/include/cypher.rb', line 12

def cw_pattern(word)
    pattern = ''
    word.each_char do |c|
        c = c.upcase
        ci = c.ord - 'A'.ord
        if c == ' '
            pattern += '       '
            while pattern.length % 8 != 0
                pattern += ' '
            end
        else
            cw = CW_TR[ci]
            (0...cw.size).each do |cwi|
                if cw[cwi] == '.'
                    pattern += '.'
                else
                    pattern += '...'
                end
                if cwi < cw.size - 1
                    pattern += ' '
                else
                    pattern += '   '
                end
            end
        end
    end
    pattern
end

#darken(c, f = 0.2) ⇒ Object



146
147
148
149
150
# File 'src/ruby/create-cling-color-palette.rb', line 146

def darken(c, f = 0.2)
    hsv = rgb_to_hsv(hex_to_rgb(c))
    hsv[2] *= f
    rgb_to_hex(hsv_to_rgb(hsv))
end

#debug(message, index = 0) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'src/ruby/main.rb', line 130

def debug(message, index = 0)
    index = 0
    begin
        while index < caller_locations.size - 1 && ['transaction', 'neo4j_query', 'neo4j_query_expect_one'].include?(caller_locations[index].base_label)
            index += 1
        end
    rescue
        index = 0
    end
    l = caller_locations[index]
    ls = ''
    begin
        ls = "#{l.path.sub('/app/', '').sub('.rb', '')}:#{l.lineno}"
    rescue
        ls = "#{l[0].sub('/app/', '').sub('.rb', '')}:#{l[1]}"
    end
    STDERR.puts "#{DateTime.now.strftime('%H:%M:%S')} [#{ls}] #{message}"
end

#debug_error(message) ⇒ Object



149
150
151
152
153
154
155
156
157
158
# File 'src/ruby/main.rb', line 149

def debug_error(message)
    l = caller_locations.first
    ls = ''
    begin
        ls = "#{l.path.sub('/app/', '').sub('.rb', '')}:#{l.lineno} @ #{l.base_label}"
    rescue
        ls = "#{l[0].sub('/app/', '').sub('.rb', '')}:#{l[1]}"
    end
    STDERR.puts "#{DateTime.now.strftime('%H:%M:%S')} [ERROR] [#{ls}] #{message}"
end

#deliver_mail(plain_text = nil, &block) ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'src/ruby/main.rb', line 206

def deliver_mail(plain_text = nil, &block)
    mail = Mail.new do
        charset = 'UTF-8'
        message = self.instance_eval(&block)
        if plain_text.nil?
            html_part do
                content_type 'text/html; charset=UTF-8'
                body message
            end

            text_part do
                content_type 'text/plain; charset=UTF-8'
                body mail_html_to_plain_text(message)
            end
        else
            text_part do
                content_type 'text/plain; charset=UTF-8'
                body plain_text
            end
        end
    end
    if DEVELOPMENT
        if DEVELOPMENT_MAIL_DELIVERY_POSITIVE_LIST.include?(mail.to.first)
            debug "Sending mail to #{mail.to.join(' / ')} because first recipient is included in DEVELOPMENT_MAIL_DELIVERY_POSITIVE_LIST..."
            mail.deliver!
        else
            debug "Not sending mail to because we're in development: #{mail.subject} => #{mail.to.join(' / ')}"
            debug mail.to_s
        end
    else
        mail.deliver!
    end
end

#desaturate(c) ⇒ Object



133
134
135
136
137
138
# File 'src/ruby/create-cling-color-palette.rb', line 133

def desaturate(c)
    hsv = rgb_to_hsv(hex_to_rgb(c))
    hsv[1] *= 0.7
    hsv[2] *= 0.9
    rgb_to_hex(hsv_to_rgb(hsv))
end

#find_available_target_paths(target_filename, cp_target_dir, mv_target_dir) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'src/ruby/collect-nextcloud-files-from-sus.script.rb', line 22

def find_available_target_paths(target_filename, cp_target_dir, mv_target_dir)
    n = 1
    while true do
        temp_target_filename = target_filename.dup
        if n > 1
            temp_parts = target_filename.split('.')
            inject_index = temp_parts.size - 2
            inject_index = 0 if inject_index < 0
            temp_parts[inject_index] += " (#{n})"
            temp_target_filename = temp_parts.join('.')
        end
        cp_path = File.join(cp_target_dir, temp_target_filename)
        mv_path = File.join(mv_target_dir, temp_target_filename)
        unless File.exist?(cp_path) || File.exist?(mv_path)
            return cp_path, mv_path
        end
        n += 1
    end
end

#find_email_for_name(first_name, last_name, first_name_to_email, last_name_to_email) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'src/ruby/import-projekte.rb', line 40

def find_email_for_name(first_name, last_name, first_name_to_email, last_name_to_email)
    a = first_name_to_email[first_name.split('-').first.downcase.strip] || Set.new()
    b = last_name_to_email[last_name.split('-').first.downcase.strip] || Set.new()
    c = a & b
    if c.empty?
        STDERR.puts a.to_a.to_yaml
        STDERR.puts b.to_a.to_yaml
        raise "No email found for #{first_name} #{last_name}!"
    end
    if c.size > 1
        raise "Multiple emails found for #{first_name} #{last_name}!"
    end
    return c.to_a.first
end

#fix_h_to_hh(s) ⇒ Object



160
161
162
163
164
165
166
167
# File 'src/ruby/main.rb', line 160

def fix_h_to_hh(s)
    return nil if s.nil?
    if s =~ /^\d:\d\d$/
        '0' + s
    else
        s
    end
end

#gen_reverse_index(users, key) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
# File 'src/ruby/import-projekte.rb', line 24

def gen_reverse_index(users, key)
    index = {}
    users.each_pair do |email, user|
        value = user[key].split('-').first
        if value
            value = value.downcase.strip
            index[value] ||= Set.new()
            index[value] << email
        end
    end
    index
end

#get_file_from_url(url, &block) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'src/ruby/vplan-watcher.rb', line 243

def get_file_from_url(url, &block)
    # STDERR.puts "Getting #{url}..."
    uri = URI.parse(url)
    req = Net::HTTP::Get.new(uri)
    if UNTIS_VERTRETUNGSPLAN_USERNAME && UNTIS_VERTRETUNGSPLAN_PASSWORD
        req.basic_auth UNTIS_VERTRETUNGSPLAN_USERNAME, UNTIS_VERTRETUNGSPLAN_PASSWORD
    end

    res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
        response = http.request(req)
        if response.code.to_i == 200
            body = response.body
            body.force_encoding('iso-8859-1')
            body = body.encode('utf-8')
            yield(response.header, body)
        else
            STDERR.puts "WARNING: page not found: #{url}"
            # raise "page not found: #{url}"
        end
    end
end

#get_gradient(colors, t) ⇒ Object



160
161
162
163
164
165
166
167
168
# File 'src/ruby/create-cling-color-palette.rb', line 160

def get_gradient(colors, t)
    i = (t * (colors.size - 1)).to_i
    i = colors.size - 2 if i == colors.size - 1
    f = (t * (colors.size - 1)) - i
    f1 = 1.0 - f
    a = html_to_rgb(colors[i])
    b = html_to_rgb(colors[i + 1])
    rgb_to_html([a[0] * f1 + b[0] * f, a[1] * f1 + b[1] * f, a[2] * f1 + b[2] * f])
end

#handle_html_batch(bodies) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'src/ruby/vplan-watcher.rb', line 205

def handle_html_batch(bodies)
    datum_list = Set.new()
    removed_days = Set.new()
    bodies.each do |body|
        temp = handle_vplan_html_file(body, removed_days)
        if temp
            datum_list |= temp
        end
    end
    datum_list.each do |datum|
        File.open("/vplan/#{datum}.json", 'w') do |fout|
            data = {:entries => {}, :entry_ref => {}, :timetables => {}}
            Dir["/vplan/#{datum}/*.json"].each do |path|
                temp = JSON.parse(File.read(path))
                (temp['entries'] || []).each do |sha1|
                    data[:entries][sha1] = JSON.parse(File.read("/vplan/#{datum}/entries/#{sha1}.json"))
                end
                (temp['day_messages'] || []).each do |sha1|
                    data[:entries][sha1] = JSON.parse(File.read("/vplan/#{datum}/entries/#{sha1}.json"))
                end
            end
            Dir["/vplan/#{datum}/*.json"].each do |path|
                id = File.basename(path).sub('.json', '')
                entry = JSON.parse(File.read(path))
                unless entry.empty?
                    data[:timetables][id] = entry
                    (entry['entries'] || []).each do |e|
                        data[:entry_ref][e] ||= []
                        data[:entry_ref][e] << id
                    end
                end
            end
            fout.write(data.to_json)
        end
    end
    # trigger_update('all')
end

#handle_vplan_html_file(contents, removed_days) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'src/ruby/vplan-watcher.rb', line 73

def handle_vplan_html_file(contents, removed_days)
    dom = Nokogiri::HTML.parse(contents)
    dom = strip_past_tables(dom)
    return if dom.at_css('h2').nil?
    return if dom.at_css('#vertretung').nil?
    heading = dom.at_css('h2').text
    heading.gsub!('8?', '8o')
    heading.gsub!('9?', '9o')
    heading.gsub!('J11', '11')
    heading.gsub!('J12', '12')
    klasse = heading.split(' ').first
    if KLASSEN_ORDER.include?(klasse)
        heading = klasse
    end
    datum_list = Set.new()
    datum = nil
    result = {}
    dom.at_css('#vertretung').children.each do |child|
        if child.name == 'table' && datum
            table_mode = nil
            classes = child.attribute('class').to_s.split(' ')
            # STDERR.puts "[#{heading}] [#{datum}] [#{classes.join(' ')}]"
            if classes.include?('subst')
                # STDERR.print "(Vertretungsplan)"
                table_mode = :vplan
            else
                if child.css('tr').first.text == 'Nachrichten zum Tag'
                    # STDERR.print "(Nachrichten zum Tag)"
                    table_mode = :day_message
                else
                    # STDERR.puts "classes: #{classes.to_json}"
                    # STDERR.puts child.to_s
                    raise 'unexpected table'
                end
            end
            assert(!table_mode.nil?)
            # STDERR.puts child.to_s
            child.css('tr').each do |row|
                row.search('s').each do |n|
                    n.content = "__STRIKE_BEGIN__#{n.content}__STRIKE_END__"
                end
                row.search('br').each do |n|
                    n.content = "__LINE_BREAK__"
                end
                row.search('td/*').each do |n|
                    n.replace(n.content) unless n.name == 's'
                end

                tr = row.css('th')
                if tr.size == 6
                    # Klassenvertretungsplan:
                    headings = tr.map { |x| x.text }.join(' / ')
                    # assert(headings == 'Klasse(n) / Stunde / Fach / Raum / (Lehrer) / Text')
                    assert(headings == 'Stunde / Klasse(n) / (Lehrer) / (Raum) / (Fach) / Text')
                elsif tr.size == 7
                    # Lehrervertretungsplan: Vtr-Nr.	Stunde	Klasse(n)	(Lehrer)	(Raum)	(Fach)	Text
                    headings = tr.map { |x| x.text }.join(' / ')
                    assert(headings == 'Vtr-Nr. / Stunde / Klasse(n) / (Lehrer) / (Raum) / (Fach) / Text')
                end
                cells = row.css('td')
                if cells.size == 1 && table_mode == :day_message
                    result[datum] ||= {}
                    day_message = cells.first.text.gsub('__LINE_BREAK__', "\n").strip
                    sha1 = Digest::SHA1.hexdigest(day_message.to_json)[0, 8]
                    path = "/vplan/#{datum}/entries/#{sha1}.json"
                    FileUtils.mkpath(File.dirname(path))
                    File.open(path, 'w') { |f| f.write(day_message.to_json) }
                    result[datum][:day_messages] ||= []
                    result[datum][:day_messages] << sha1
                elsif (cells.size == 6 || cells.size == 7) && table_mode == :vplan
                    # Klassenvertretungsplan: Klasse(n)	Stunde	Fach	Raum	(Lehrer)	Text
                    # Lehrervertretungsplan: Vtr-Nr.	Stunde	Klasse(n)	(Lehrer)	(Raum)	(Fach)	Text
                    result[datum] ||= {}
                    result[datum][:entries] ||= []
                    entry = {}
                    cells.each.with_index do |x, index|
                        key = TIMETABLE_JSON_KEYS[cells.size][index]
                        text = x.content || ''
                        # replace &nbsp; with normal space and strip
                        text.gsub!(/[[:space:]]+/, ' ')
                        text.strip!
                        entry_del = nil
                        entry_add = nil
                        if text.include?('__STRIKE_BEGIN__')
                            text.gsub!('__STRIKE_BEGIN__', '')
                            parts = text.split('__STRIKE_END__')
                            entry_del = (parts[0] || '').strip
                            entry_add = (parts[1] || '').strip
                        else
                            entry_add = (text || '').strip
                        end
                        entry_add = nil if entry_add && entry_add.empty?
                        entry_del = nil if entry_del && entry_del.empty?
                        entry_add = entry_add[1, entry_add.size - 1] if entry_add && entry_add[0] == '?'
                        entry[key] = [entry_del, entry_add]
                    end
                    fixed_entry = [entry[:stunde][1], entry[:klasse], entry[:lehrer], entry[:fach], entry[:raum], entry[:text][1]]
                    sha1 = Digest::SHA1.hexdigest(fixed_entry.to_json)[0, 8]
                    path = "/vplan/#{datum}/entries/#{sha1}.json"
                    FileUtils.mkpath(File.dirname(path))
                    File.open(path, 'w') { |f| f.write(fixed_entry.to_json) }
                    result[datum][:entries] << sha1
                end
                # STDERR.print " #{cells.size}"
            end
            # STDERR.puts
            # STDERR.puts '-' * 40
        else
            b = nil
            b = child.text if child.name == 'b'
            child.css('b').each { |c2| b = c2.text }
            if b
                datum = parse_html_datum(b)
                next if datum.nil?
                datum_list << datum
                unless removed_days.include?(datum)
                    Dir["/vplan/#{datum}/*.json"].each do |path|
                        FileUtils::rm_f(path)
                    end
                    removed_days << datum
                end
            end
        end
    end
    result.each_pair do |datum, info|
        path = "/vplan/#{datum}/#{heading.gsub('/', '-')}.json"
        FileUtils.mkpath(File.dirname(path))
        File.open(path, 'w') { |f| f.write(result[datum].to_json) }
    end
    return datum_list
end

#head_file_from_url(url, &block) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'src/ruby/vplan-watcher.rb', line 265

def head_file_from_url(url, &block)
    uri = URI.parse(url)
    req = Net::HTTP::Head.new(uri)
    if UNTIS_VERTRETUNGSPLAN_USERNAME && UNTIS_VERTRETUNGSPLAN_PASSWORD
        req.basic_auth UNTIS_VERTRETUNGSPLAN_USERNAME, UNTIS_VERTRETUNGSPLAN_PASSWORD
    end
    
    res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') do |http|
        response = http.request(req)
        if response.code.to_i == 200
            yield(response.header)
        else
            raise "page not found: #{url}"
        end
    end
end

#hex_to_rgb(c) ⇒ Object



110
111
112
113
114
115
# File 'src/ruby/create-cling-color-palette.rb', line 110

def hex_to_rgb(c)
    r = c[1, 2].downcase.to_i(16)
    g = c[3, 2].downcase.to_i(16)
    b = c[5, 2].downcase.to_i(16)
    [r, g, b]
end

#hsv_to_rgb(c) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'src/ruby/create-cling-color-palette.rb', line 57

def hsv_to_rgb(c)
    h, s, v = c[0].to_f / 360, c[1].to_f / 100, c[2].to_f / 100
    h_i = (h * 6).to_i
    f = h * 6 - h_i
    p = v * (1 - s)
    q = v * (1 - f * s)
    t = v * (1 - (1 - f) * s)
    r, g, b = v, t, p if h_i == 0
    r, g, b = q, v, p if h_i == 1
    r, g, b = p, v, t if h_i == 2
    r, g, b = p, q, v if h_i == 3
    r, g, b = t, p, v if h_i == 4
    r, g, b = v, p, q if h_i == 5
    [(r * 255).to_i, (g * 255).to_i, (b * 255).to_i]
end

#html_to_rgb(x) ⇒ Object



152
153
154
# File 'src/ruby/create-cling-color-palette.rb', line 152

def html_to_rgb(x)
    [x[1, 2].to_i(16), x[3, 2].to_i(16), x[5, 2].to_i(16)]
end

#i_to_b62(n) ⇒ Object



5
6
7
8
9
10
11
12
13
# File 'src/ruby/background-renderer.rb', line 5

def i_to_b62(n)
    s = ''
    while n > 0
        d = n % 62
        s = BASE62_ALPHABET[d] + s
        n /= 62
    end
    s
end

#join_with_sep(list, a, b) ⇒ Object



246
247
248
# File 'src/ruby/main.rb', line 246

def join_with_sep(list, a, b)
    list.size == 1 ? list.first : [list[0, list.size - 1].join(a), list.last].join(b)
end

#luminance(color) ⇒ Object



117
118
119
120
# File 'src/ruby/create-cling-color-palette.rb', line 117

def luminance(color) 
    r, g, b = hex_to_rgb(color)
    return r * 0.299 + g * 0.587 + b * 0.114
end

#mail_html_to_plain_text(s) ⇒ Object



202
203
204
# File 'src/ruby/main.rb', line 202

def mail_html_to_plain_text(s)
    s.gsub('<p>', "\n\n").gsub(/<br\s*\/?>/, "\n").gsub(/<\/?[^>]*>/, '').strip
end

#mix(a, b, t) ⇒ Object



122
123
124
125
126
127
# File 'src/ruby/create-cling-color-palette.rb', line 122

def mix(a, b, t)
    t1 = 1.0 - t
    return [a[0] * t1 + b[0] * t,
            a[1] * t1 + b[1] * t,
            a[2] * t1 + b[2] * t]
end

#override_email_login_recipient_for_chat(email) ⇒ Object



231
232
233
# File 'src/ruby/credentials.template.rb', line 231

def (email)
    email
end

#parse_html_datum(s) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'src/ruby/vplan-watcher.rb', line 36

def parse_html_datum(s)
    parts = s.split('.')
    d = parts[0].to_i
    m = parts[1].to_i
    _ = CONFIG[:first_school_day][0, 4].to_i
    (_ .. (_ + 1)).each do |y|
        ds = sprintf('%04d-%02d-%02d', y, m, d)
        if ds >= CONFIG[:first_school_day] && ds <= CONFIG[:last_day]
            return ds
        end
    end
    return nil
    # raise 'nope'
end

#parse_markdown(s) ⇒ Object



240
241
242
243
244
# File 'src/ruby/main.rb', line 240

def parse_markdown(s)
    s ||= ''
    s.gsub!(/\w\*in/) { |x| x.sub('*', '\\*') }
    Kramdown::Document.new(s, :smart_quotes => %w{sbquo lsquo bdquo ldquo}).to_html.strip
end

#perform_refreshObject



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'src/ruby/vplan-watcher.rb', line 282

def perform_refresh
    last_update_timestamp = ''
    last_update_timestamp_path = '/vplan/timestamp.txt'
    if File.exist?(last_update_timestamp_path)
        last_update_timestamp = File.read(last_update_timestamp_path).strip
    end
    last_modified = last_update_timestamp
    head_file_from_url("#{UNTIS_VERTRETUNGSPLAN_BASE_URL}/frames/navbar.htm") do |header|
        last_modified = DateTime.parse(header['last-modified']).strftime('%Y-%m-%d-%H-%M-%S')
    end
    return unless last_modified > last_update_timestamp

    STDERR.puts "Fetching vplan from #{UNTIS_VERTRETUNGSPLAN_BASE_URL}!"

    get_file_from_url("#{UNTIS_VERTRETUNGSPLAN_BASE_URL}/frames/navbar.htm") do |header, body|
        file_count = 0
        bodies = []
        dom = Nokogiri::HTML.parse(body)
        weeks = []
        dom.css('select').each do |element|
            if element.attr('name') == 'week'
                element.css('option').each do |option|
                    weeks << option.attr('value').to_i
                end
            end
        end
        classes = JSON.parse(body.match(/var classes\s*=\s*([^;]+);/)[1])
        teachers = JSON.parse(body.match(/var teachers\s*=\s*([^;]+);/)[1])
        weeks.each do |week|
            (0...teachers.size).each do |index|
                file_count += 1
                get_file_from_url("#{UNTIS_VERTRETUNGSPLAN_BASE_URL}/#{sprintf('%02d', week)}/v/#{sprintf('v%05d', index + 1)}.htm") do |header, body|
                    bodies << body
                end
            end
            (0...classes.size).each do |index|
                file_count += 1
                get_file_from_url("#{UNTIS_VERTRETUNGSPLAN_BASE_URL}/#{sprintf('%02d', week)}/w/#{sprintf('w%05d', index + 1)}.htm") do |header, body|
                    bodies << body
                end
            end
        end
        STDERR.puts "Fetched #{file_count} HTML files, parsing..."
        handle_html_batch(bodies)
    end
    File.open(last_update_timestamp_path, 'w') { |f| f.puts(last_modified) }
    STDERR.puts "Triggering timetable update..."
    system("curl -s http://timetable:8080/api/update/all")
end

#remove_accents(s) ⇒ Object

Faye::WebSocket.load_adapter('puma')



126
127
128
# File 'src/ruby/main.rb', line 126

def remove_accents(s)
    I18n.transliterate(s.gsub('ä', 'ae').gsub('ö', 'oe').gsub('ü', 'ue').gsub('Ä', 'Ae').gsub('Ö', 'Oe').gsub('Ü', 'Ue').gsub('ß', 'ss').gsub('ė', 'e').gsub('š', 's'))
end

#rgb_to_hex(c) ⇒ Object



129
130
131
# File 'src/ruby/create-cling-color-palette.rb', line 129

def rgb_to_hex(c)
    sprintf('#%02x%02x%02x', c[0].to_i, c[1].to_i, c[2].to_i)
end

#rgb_to_hsv(c) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'src/ruby/create-cling-color-palette.rb', line 75

def rgb_to_hsv(c)
    r = c[0] / 255.0
    g = c[1] / 255.0
    b = c[2] / 255.0
    max = [r, g, b].max
    min = [r, g, b].min
    delta = max - min
    v = max * 100

    if (max != 0.0)
        s = delta / max *100
    else
        s = 0.0
    end

    if (s == 0.0)
        h = 0.0
    else
        if (r == max)
            h = (g - b) / delta
        elsif (g == max)
            h = 2 + (b - r) / delta
        elsif (b == max)
            h = 4 + (r - g) / delta
        end

        h *= 60.0

        if (h < 0)
            h += 360.0
        end
    end
    [h, s, v]
end

#rgb_to_html(x) ⇒ Object



156
157
158
# File 'src/ruby/create-cling-color-palette.rb', line 156

def rgb_to_html(x)
    sprintf('#%02x%02x%02x', x[0], x[1], x[2])
end

#shift_hue(c, f = 60) ⇒ Object



140
141
142
143
144
# File 'src/ruby/create-cling-color-palette.rb', line 140

def shift_hue(c, f = 60)
    hsv = rgb_to_hsv(hex_to_rgb(c))
    hsv[0] = (hsv[0] + f) % 360.0
    rgb_to_hex(hsv_to_rgb(hsv))
end

#strip_past_tables(dom) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'src/ruby/vplan-watcher.rb', line 51

def strip_past_tables(dom)
    dom.css('td').each do |td|
        if (td.text || '').strip == 'Vertretungen sind nicht freigegeben'
            n = td.parent.parent
            deleted_table = false
            while n != nil
                p = n.previous
                n.remove()
                if n && n.name == 'table'
                    if deleted_table
                        break
                    else
                        deleted_table = true
                    end
                end
                n = p
            end
        end
    end
    dom
end