2022-01-06 07:38:20 +00:00
|
|
|
const std = @import("std");
|
|
|
|
const json = std.json;
|
|
|
|
|
|
|
|
const twitch = @import("twitch.zig");
|
|
|
|
const Client = @import("client.zig").Client;
|
|
|
|
const webhook = @import("webhook.zig");
|
2022-01-08 14:09:44 +00:00
|
|
|
const sqlite = @import("sqlite.zig");
|
2022-01-06 07:38:20 +00:00
|
|
|
|
2022-01-08 16:13:41 +00:00
|
|
|
const DATABASE_VERSION_CODE = 1;
|
|
|
|
|
2022-01-08 14:58:10 +00:00
|
|
|
const CREATE_TABLES =
|
|
|
|
\\ CREATE TABLE VERSION
|
|
|
|
\\ (
|
|
|
|
\\ versionCode INTEGER
|
|
|
|
\\ );
|
|
|
|
\\
|
|
|
|
\\ INSERT INTO VERSION(versionCode)
|
|
|
|
\\ VALUES (1);
|
|
|
|
\\
|
|
|
|
\\ CREATE TABLE STREAMER
|
|
|
|
\\ (
|
|
|
|
\\ idStreamer TEXT PRIMARY KEY NOT NULL,
|
|
|
|
\\ loginStreamer TEXT NOT NULL,
|
|
|
|
\\ nameStreamer TEXT NOT NULL
|
|
|
|
\\ );
|
|
|
|
\\
|
|
|
|
\\ CREATE TABLE STREAM
|
|
|
|
\\ (
|
|
|
|
\\ idStream TEXT PRIMARY KEY NOT NULL,
|
|
|
|
\\ idStreamer TEXT NOT NULL,
|
|
|
|
\\ isMatureStream BOOLEAN NOT NULL DEFAULT 'F',
|
|
|
|
\\ CONSTRAINT FK_STREAM_STREAMER_ID FOREIGN KEY (idStreamer) REFERENCES STREAMER (idStreamer)
|
|
|
|
\\ );
|
|
|
|
\\
|
|
|
|
\\ CREATE TABLE VIEWER_COUNT_STREAM
|
|
|
|
\\ (
|
|
|
|
\\ viewerCount INTEGER NOT NULL,
|
|
|
|
\\ dateViewerCount DATE NOT NULL,
|
|
|
|
\\ idStream TEXT NOT NULL,
|
|
|
|
\\ PRIMARY KEY (dateViewerCount, idStream),
|
|
|
|
\\ CONSTRAINT FK_VIEWER_COUNT_STREAM_ID FOREIGN KEY (idStream) REFERENCES STREAM (idStream)
|
|
|
|
\\ );
|
|
|
|
\\
|
|
|
|
\\ CREATE TABLE NAME_STREAM
|
|
|
|
\\ (
|
|
|
|
\\ nameStream TEXT NOT NULL,
|
|
|
|
\\ dateNameStream DATE NOT NULL,
|
|
|
|
\\ idStream TEXT NOT NULL,
|
|
|
|
\\ PRIMARY KEY (dateNameStream, idStream),
|
|
|
|
\\ CONSTRAINT FK_NAME_STREAM_STREAM_ID FOREIGN KEY (idStream) REFERENCES STREAM (idStream)
|
|
|
|
\\ );
|
|
|
|
\\
|
|
|
|
\\ CREATE TABLE GAME
|
|
|
|
\\ (
|
|
|
|
\\ gameId TEXT NOT NULL PRIMARY KEY,
|
|
|
|
\\ gameName TEXT
|
|
|
|
\\ );
|
|
|
|
\\
|
|
|
|
\\ CREATE TABLE IS_STREAMING_GAME
|
|
|
|
\\ (
|
|
|
|
\\ gameId TEXT NOT NULL,
|
|
|
|
\\ streamId TEXT NOT NULL,
|
|
|
|
\\ dateGameStream DATE NOT NULL,
|
|
|
|
\\ PRIMARY KEY (gameId, streamId, dateGameStream),
|
|
|
|
\\ CONSTRAINT FK_GAME_STREAM_GAME_ID FOREIGN KEY (gameId) REFERENCES GAME (gameId),
|
|
|
|
\\ CONSTRAINT FK_GAME_STREAM_STREAM_ID FOREIGN KEY (streamId) REFERENCES STREAM (idStream)
|
|
|
|
\\ );
|
|
|
|
;
|
|
|
|
|
2022-01-06 07:38:20 +00:00
|
|
|
const Config = struct {
|
|
|
|
token: []const u8,
|
|
|
|
client_id: []const u8,
|
|
|
|
user_logins: []const User,
|
|
|
|
webhook_url: []const u8,
|
|
|
|
|
|
|
|
pub fn fromFile(allocator: std.mem.Allocator, path: []const u8) anyerror!@This() {
|
|
|
|
var file = try std.fs.cwd().openFile(path, .{
|
|
|
|
.read = true,
|
|
|
|
.write = false,
|
|
|
|
});
|
|
|
|
|
|
|
|
var stat = try file.stat();
|
|
|
|
const file_buffer = try allocator.alloc(u8, stat.size);
|
|
|
|
_ = try file.readAll(file_buffer);
|
|
|
|
|
|
|
|
var stream = json.TokenStream.init(file_buffer);
|
|
|
|
|
|
|
|
return json.parse(@This(), &stream, .{ .allocator = allocator });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const User = struct {
|
|
|
|
user_login: []u8,
|
|
|
|
user_icon: []u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn main() anyerror!void {
|
2022-01-08 14:58:10 +00:00
|
|
|
var db = try sqlite.Database.open("data.db");
|
2022-01-08 14:09:44 +00:00
|
|
|
defer {
|
|
|
|
_ = db.close() catch |e| {
|
|
|
|
std.log.err("Failed to close db : {}", .{e});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-08 16:13:41 +00:00
|
|
|
try createTables(&db);
|
2022-01-08 14:58:10 +00:00
|
|
|
|
2022-01-06 07:38:20 +00:00
|
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
|
|
var allocator = arena.allocator();
|
|
|
|
|
|
|
|
var config = try Config.fromFile(allocator, "config.json");
|
|
|
|
|
|
|
|
try Client.globalInit();
|
|
|
|
|
|
|
|
var client = try Client.init(&allocator) orelse error.FailedInitClient;
|
|
|
|
|
|
|
|
var headers = std.StringHashMap([]const u8).init(allocator);
|
|
|
|
try headers.put("Authorization", config.token);
|
|
|
|
try headers.put("Client-Id", config.client_id);
|
|
|
|
|
|
|
|
try updateAlert(allocator, &client, &config, &headers);
|
|
|
|
|
|
|
|
client.deinit();
|
|
|
|
|
|
|
|
try Client.cleanup();
|
|
|
|
arena.deinit();
|
|
|
|
}
|
|
|
|
|
2022-01-08 16:13:41 +00:00
|
|
|
pub fn createTables(db: *sqlite.Database) anyerror!void {
|
|
|
|
var stm = db.prepare("SELECT versionCode FROM VERSION ORDER BY versionCode DESC") catch {
|
|
|
|
std.log.debug("Creating database", .{});
|
|
|
|
|
|
|
|
try db.exec(CREATE_TABLES);
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (stm.next()) {
|
|
|
|
var code: isize = 0;
|
|
|
|
|
|
|
|
try stm.fetch(.{&code});
|
|
|
|
|
|
|
|
if (DATABASE_VERSION_CODE == code) {
|
|
|
|
std.log.debug("Database already created", .{});
|
|
|
|
stm.finalize();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stm.finalize();
|
|
|
|
}
|
|
|
|
|
2022-01-08 14:09:44 +00:00
|
|
|
pub fn updateAlert(allocator: std.mem.Allocator, client: *Client, config: *Config, headers: *std.StringHashMap([]const u8)) anyerror!void {
|
2022-01-06 07:38:20 +00:00
|
|
|
var request = std.ArrayList(u8).init(allocator);
|
|
|
|
|
|
|
|
try request.appendSlice("https://api.twitch.tv/helix/streams?");
|
|
|
|
|
|
|
|
{
|
|
|
|
var i: u8 = 0;
|
|
|
|
while (i < config.user_logins.len) : (i += 1) {
|
|
|
|
if (i != 0)
|
|
|
|
try request.append('&');
|
|
|
|
try request.appendSlice("user_login=");
|
|
|
|
try request.appendSlice(config.user_logins[i].user_login);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try request.append(0);
|
|
|
|
|
|
|
|
const streams: twitch.TwitchRes([]const twitch.Stream) = try client.getJSON(twitch.TwitchRes([]const twitch.Stream), @ptrCast([*:0]const u8, request.items), headers);
|
|
|
|
|
|
|
|
request.deinit();
|
|
|
|
|
|
|
|
std.log.info("{s}", .{streams});
|
|
|
|
|
|
|
|
if (streams.data.len > 0) {
|
|
|
|
var embeds = try allocator.alloc(webhook.Embed, streams.data.len);
|
|
|
|
|
|
|
|
for (streams.data) |s, i| {
|
|
|
|
var viewer = std.ArrayList(u8).init(allocator);
|
|
|
|
try std.fmt.format(viewer.writer(), "{}", .{s.viewer_count});
|
|
|
|
var fields = [_]webhook.Field{
|
|
|
|
.{
|
|
|
|
.name = "Viewer count",
|
|
|
|
.value = viewer.items,
|
|
|
|
.@"inline" = true,
|
|
|
|
},
|
|
|
|
.{
|
|
|
|
.name = "Game name",
|
|
|
|
.value = s.game_name,
|
|
|
|
.@"inline" = true,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var thumbnail = try std.mem.replaceOwned(u8, allocator, s.thumbnail_url, "{width}", "1920");
|
|
|
|
thumbnail = try std.mem.replaceOwned(u8, allocator, thumbnail, "{height}", "1080");
|
|
|
|
|
|
|
|
var stream_url = std.ArrayList(u8).init(allocator);
|
|
|
|
_ = try stream_url.appendSlice("https://twitch.tv/");
|
|
|
|
_ = try stream_url.appendSlice(s.user_login);
|
|
|
|
|
|
|
|
var icon_url: []u8 = "";
|
|
|
|
for (config.user_logins) |u| {
|
|
|
|
if (std.mem.eql(u8, u.user_login, s.user_login)) {
|
|
|
|
icon_url = u.user_icon;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
embeds[i] = .{
|
|
|
|
.title = s.title,
|
|
|
|
.image = .{
|
|
|
|
.url = thumbnail,
|
|
|
|
},
|
|
|
|
.author = .{
|
|
|
|
.name = s.user_name,
|
|
|
|
.url = stream_url.items,
|
|
|
|
.icon_url = icon_url,
|
|
|
|
},
|
|
|
|
.color = 0xa970ff,
|
|
|
|
.fields = fields[0..],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = try client.postJSON(config.webhook_url, webhook.Webhook{
|
|
|
|
.username = "Twitch",
|
|
|
|
.content = "Live alert",
|
|
|
|
.embeds = embeds,
|
|
|
|
}, null);
|
|
|
|
}
|
|
|
|
}
|