diff --git a/src/main.zig b/src/main.zig index f001823..92620e1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,7 +10,7 @@ const Airports = @import("location/Airports.zig"); const Resolver = @import("location/resolver.zig").Resolver; const GeoLite2 = @import("location/GeoLite2.zig"); -pub fn main() !void { +pub fn main() !u8 { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -67,7 +67,10 @@ pub fn main() !void { }); defer rate_limiter.deinit(); - var metno = try MetNo.init(allocator); + var metno = MetNo.init(allocator, null) catch |err| { + if (err == MetNo.MissingIdentificationError) return 1; + return err; + }; defer metno.deinit(); var server = try Server.init(allocator, cfg.listen_host, cfg.listen_port, .{ @@ -77,6 +80,7 @@ pub fn main() !void { }, &rate_limiter); try server.listen(); + return 0; } test { diff --git a/src/weather/MetNo.zig b/src/weather/MetNo.zig index 26a85fb..a455b32 100644 --- a/src/weather/MetNo.zig +++ b/src/weather/MetNo.zig @@ -7,6 +7,8 @@ const zeit = @import("zeit"); const MetNo = @This(); +pub const MissingIdentificationError = error.MetNoIdentificationRequired; + const MetNoOpenWeatherEntry = struct { []const u8, types.WeatherCode }; // symbol codes: https://github.com/metno/weathericons/tree/main/weather // they also have _day, _night and _polartwilight variants @@ -63,10 +65,24 @@ const WeatherCodeMap = std.StaticStringMap(types.WeatherCode); const weather_code_map = WeatherCodeMap.initComptime(weather_code_entries); allocator: std.mem.Allocator, +identifying_email: []const u8, + +pub fn init(allocator: std.mem.Allocator, identifying_email: ?[]const u8) !MetNo { + const email = identifying_email orelse blk: { + const env_email = std.process.getEnvVarOwned(allocator, "METNO_TOS_IDENTIFYING_EMAIL") catch |err| { + if (err == error.EnvironmentVariableNotFound) { + std.log.err("Met.no Terms of Service require identification. Set METNO_TOS_IDENTIFYING_EMAIL environment variable. See https://api.met.no/doc/TermsOfService", .{}); + std.log.err("See \x1b]8;;https://api.met.no/doc/TermsOfService\x1b\\https://api.met.no/doc/TermsOfService\x1b]8;;\x1b\\ for more information", .{}); + return MissingIdentificationError; + } + return err; + }; + break :blk env_email; + }; -pub fn init(allocator: std.mem.Allocator) !MetNo { return MetNo{ .allocator = allocator, + .identifying_email = email, }; } @@ -100,12 +116,20 @@ fn fetchRaw(ptr: *anyopaque, allocator: std.mem.Allocator, coords: Coordinates) var response_buf: [1024 * 1024]u8 = undefined; var writer = std.Io.Writer.fixed(&response_buf); + + const user_agent = try std.fmt.allocPrint( + self.allocator, + "wttr/1.0 git.lerch.org/lobo/wttr {s}", + .{self.identifying_email}, + ); + defer self.allocator.free(user_agent); + const result = try client.fetch(.{ .location = .{ .uri = uri }, .method = .GET, .response_writer = &writer, .extra_headers = &.{ - .{ .name = "User-Agent", .value = "wttr.in-zig/1.0 github.com/chubin/wttr.in" }, + .{ .name = "User-Agent", .value = user_agent }, }, }); @@ -145,7 +169,7 @@ fn deinitProvider(ptr: *anyopaque) void { } pub fn deinit(self: *MetNo) void { - _ = self; + self.allocator.free(self.identifying_email); } fn parseMetNoResponse(allocator: std.mem.Allocator, coords: Coordinates, json: std.json.Value) !types.WeatherData {