module main import net.http import sources import os import time import strconv // apply_filters checks all filter rules and returns false if the event should be dropped. fn (mut app App) apply_filters(mut event sources.Event) bool { for rule in app.cfg.filters.rules { if rule.match_source != '' && rule.match_source != event.source { continue } if rule.match_name != '' && !glob_match(event.name, rule.match_name) { continue } if rule.match_topic != '' && rule.match_topic != event.topic { continue } match rule.action { 'drop' { return false } 'set_priority' { new_prio := strconv.atoi(rule.value) or { 0 } if new_prio > 0 { event.priority = new_prio } } 'add_tag' { if rule.value != '' { event.tags << rule.value } } else {} } } return true } // glob_match performs simple wildcard matching (* matches anything). fn glob_match(s string, pattern string) bool { if pattern == '*' { return true } if !pattern.contains('*') { return s == pattern } parts := pattern.split('*') mut offset := 0 for i, part in parts { if part == '' { if i == parts.len - 1 { return true } continue } idx := s.index_after(part, offset) idx_val := idx or { -1 } if idx_val < 0 { return false } offset = idx_val + part.len if i == 0 && !s.starts_with(part) && offset != part.len { return false } } return true } // dispatch_outgoing sends the event to all configured outgoing webhooks. fn (mut app App) dispatch_outgoing(e sources.Event) { mut hdr := http.Header{} hdr.add_custom('Content-Type', 'application/json') or { return } for wh in app.cfg.outgoing.webhooks { body := format_outgoing(e, wh.format) resp := http.fetch(http.FetchConfig{ url: wh.url method: .post header: hdr data: body }) or { app.logger.warn('server', 'outgoing', 'failed to dispatch: ${err}') continue } app.logger.debug('server', 'outgoing', 'dispatched to ${wh.url}: ${resp.status_code}') } } // format_outgoing formats an event for an outgoing webhook platform. fn format_outgoing(e sources.Event, format string) string { match format { 'slack' { emoji := match e.priority { 5 { '🔴' } 4 { '🟠' } 3 { '🟡' } else { '🔵' } } return '{"text":"${emoji} *${e.source}:${e.name}*\\n${e.message}"}' } 'discord' { return '{"content":"**[${e.source}]** ${e.message}"}' } else { return '{"source":"${e.source}","name":"${e.name}","topic":"${e.topic}","priority":${e.priority},"message":"${e.message}"}' } } } // save_state persists poll_state and silence_until to a JSON file. fn (mut app App) save_state() { if app.cfg.state.file == '' { return } mut data := map[string]string{} for url, is_up in app.poll_state { data[url] = is_up.str() } data['_silence_until'] = app.silence_until.unix().str() json_str := json_encode_map(data) os.write_file(app.cfg.state.file, json_str) or { app.logger.warn('server', 'state', 'failed to save state: ${err}') return } app.logger.debug('server', 'state', 'state saved to ${app.cfg.state.file}') } // load_state restores poll_state and silence_until from a JSON file. fn (mut app App) load_state() { if app.cfg.state.file == '' { return } if !os.exists(app.cfg.state.file) { return } raw := os.read_file(app.cfg.state.file) or { app.logger.warn('server', 'state', 'failed to read state file: ${err}') return } mut parsed := json_decode_map(raw) or { app.logger.warn('server', 'state', 'failed to parse state file: ${err}') return } for key, val in parsed { if key == '_silence_until' { ts := strconv.atoi(val) or { 0 } app.silence_until = time.unix(i64(ts)) continue } app.poll_state[key] = val == 'true' } app.logger.info('server', 'state', 'restored ${app.poll_state.len} poll states') } // json_encode_map encodes a map[string]string as JSON. fn json_encode_map(m map[string]string) string { mut parts := []string{} for k, v in m { parts << '"${k}":"${v}"' } return '{' + parts.join(',') + '}' } // json_decode_map parses a simple JSON object of string→string. fn json_decode_map(raw string) !map[string]string { mut result := map[string]string{} trimmed := raw.trim_space() if !trimmed.starts_with('{') || !trimmed.ends_with('}') { return result } inner := trimmed[1..trimmed.len - 1] if inner == '' { return result } pairs := inner.split(',') for pair in pairs { kv := pair.split(':') if kv.len >= 2 { key := kv[0].trim_space().trim('"') val := kv[1].trim_space().trim('"') result[key] = val } } return result }