A virtual currency betting bot for Twitch chat. https://ddark.net/better
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

667 lines
25 KiB

#pragma warning (disable: 4996) // This function or variable may be unsafe (strcpy, sprintf, ...)
#pragma warning (disable: 4200) // nonstandard extension used: zero-sized array in struct/union
#include <cctype>
#include <cstdio>
#include <winsock2.h>
#include <malloc.h>
#include <binn.h>
#include <imgui_impl_dx11.h>
#include <stb_ds.h>
#include <fa.h>
#include <fa.c>
#include "better.h"
#include "better_alloc.h"
#include "better_func.h"
#include "better_const.h"
#include "better_Settings.h"
#include "better_UserInfo.h"
#include "better_App.h"
void free_indirect(void** p)
{
BETTER_FREE(*p);
}
bool is_digit(char c)
{
return c >= '0' && c <= '9';
}
void make_lower(char* s)
{
for(; *s; ++s) *s = tolower(*s);
}
bool str_contains(const char* const s, const char c)
{
for (const char* p = s; *p != '\0'; ++p)
if (*p == c) return true;
return false;
}
void trim_whitespace(char* s)
{
char* begin = s;
// Leading whitespace
while(str_contains(" \t\r\n", *begin)) ++begin;
if (*begin == '\0')
{
// String is all whitespace
*s = '\0';
return;
}
// Trailing whitespace
char* last = begin;
while(last[1]) ++last; // Find end of string
while(last > begin && str_contains(" \t\r\n", *last)) --last;
memmove(s, begin, last - begin + 1);
last -= begin - s;
// Write new null terminator character
last[1] = '\0';
}
void collapse_spaces(char* s)
{
char* p = s+1;
int gap = 0;
while (*p)
{
if (*(p-1) == ' ' && *p == ' ') ++gap;
if (gap > 0) *(p-gap) = *p;
++p;
}
*(p-gap) = '\0';
}
char* into_utf8(const wchar_t* str)
{
i32 len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
char* mb = (char*) BETTER_ALLOC(sizeof(len), "into_utf8");
WideCharToMultiByte(CP_UTF8, 0, str, -1, mb, len, NULL, NULL);
return mb;
}
FILE* base_open(App* app, const wchar_t* filename, const wchar_t* mode)
{
size_t filename_len = wcslen(filename);
wchar_t* path = (wchar_t*) _alloca(sizeof(wchar_t)*(app->base_dir_len+filename_len+1));
swprintf(path, L"%s%s", app->base_dir, filename);
return _wfopen(path, mode);
}
void copy_imgui_ini(App* app)
{
usize size;
const char* data = ImGui::SaveIniSettingsToMemory(&size);
size += 1; // So as to include the null terminator.
app->settings.imgui_ini = (char*) BETTER_REALLOC(app->settings.imgui_ini, size, "copy_imgui_ini");
memcpy(app->settings.imgui_ini, data, size);
}
void _add_log(App* app, const u8 log_level, const char* src_file, const i32 src_line, const char* const fmt ...)
{
va_list args;
va_start(args, fmt);
// Write the content into a buffer
char* buffer = (char*) BETTER_ALLOC(256, "add_log_buffer");
i32 write_num = vsnprintf(buffer, 256, fmt, args);
if (write_num > 256)
{
BETTER_FREE(buffer);
buffer = (char*) BETTER_ALLOC(write_num, "add_log_buffer");
i32 write_num_again = vsnprintf(buffer, write_num, fmt, args);
BETTER_ASSERT(write_num_again == write_num);
}
// Write the log to file
SYSTEMTIME timestamp;
GetLocalTime(&timestamp);
WCHAR* filename = (WCHAR*) _alloca(30);
swprintf(filename, L"log_%.4hu-%.2hu-%.2hu.txt", timestamp.wYear, timestamp.wMonth, timestamp.wDay);
FILE* file = base_open(app, filename, L"a");
assert(file);
fprintf(file, "%.2hu:%.2hu:%.2hu.%.3hu [%s] %s (%s:%i)\n", timestamp.wHour, timestamp.wMinute, timestamp.wSecond, timestamp.wMilliseconds, LOG_LEVEL_ID_STR[log_level], buffer, src_file, src_line);
fclose(file);
if (BETTER_DEBUG || log_level > LOGLEVEL_DEBUG)
{
// Add to the log buffer
LogEntry le;
le.level = log_level;
le.content = buffer;
app->log_buffer.push_back_discarding(le);
if (log_level >= LOGLEVEL_USERERROR)
app->unread_error = app->log_buffer.count-1;
}
else
{
BETTER_FREE(buffer);
}
va_end(args);
}
void open_url(const char* url)
{
ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
}
void update_imgui_style(App* app)
{
ImGuiStyle& imgui_style = ImGui::GetStyle();
imgui_style.FrameRounding = 3;
imgui_style.WindowRounding = 3;
imgui_style.PopupRounding = 3;
switch (app->settings.style.color_theme)
{
case COLOR_THEME_AUTO:
{
DWORD value, size;
LSTATUS res = RegGetValueA(
HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"AppsUseLightTheme",
RRF_RT_REG_DWORD,
NULL,
&value,
&size
);
// Registry value == 0 means dark mode is preferred. Also use dark theme if we fail to retrieve the setting.
if (res == ERROR_SUCCESS)
{
assert(size == sizeof(value));
if (value == 0)
app->color_theme_is_dark = true;
else
app->color_theme_is_dark = false;
}
else
{
add_log(app, LOGLEVEL_INFO, "Failed to retrieve dark/light theme setting. Defaulting to dark theme.", res);
app->color_theme_is_dark = true;
}
} break;
case COLOR_THEME_DARK: app->color_theme_is_dark = true; break;
case COLOR_THEME_LIGHT: app->color_theme_is_dark = false; break;
}
if (app->color_theme_is_dark)
ImGui::StyleColorsDark();
else
ImGui::StyleColorsLight();
}
template<typename T>
static bool binn_try_get_value(u8* data_ptr, T* dest, const BINN_KEY key, i32 type, i32* ptr_size = NULL)
{
T val;
if (!binn_map_get(data_ptr, key, type, (void*)&val, ptr_size))
return false;
*dest = val;
return true;
}
template<>
static bool binn_try_get_value<bool>(u8* data_ptr, bool* dest, const BINN_KEY key, i32 type, i32* ptr_size)
{
i32 val;
if (!binn_map_get(data_ptr, key, type, (void*)&val, ptr_size))
return false;
*dest = !!val;
return true;
}
static bool binn_try_get_string(u8* data_ptr, char* dest, const BINN_KEY key, usize dest_size)
{
char* val;
if (!binn_map_get(data_ptr, key, BINN_STRING, (void*)&val, NULL))
return false;
size_t len = strlen(val);
if (len >= dest_size)
return false;
memcpy(dest, val, len+1);
return true;
}
static bool binn_try_get_string_copy(u8* data_ptr, char** dest, const BINN_KEY key)
{
char* val;
if (!binn_map_get(data_ptr, key, BINN_STRING, (void*)&val, NULL))
return false;
size_t len = strlen(val);
*dest = (char*)BETTER_ALLOC(len+1, "binn_try_get_string_copy");
memcpy(*dest, val, len+1);
return true;
}
void load_settings_from_disk(App* app)
{
FILE* file = base_open(app, L"settings", L"rb");
if (!file)
add_log(app, LOGLEVEL_DEBUG, "No settings file found.");
else
{
add_log(app, LOGLEVEL_DEBUG, "Reading settings from disk.");
u32 version;
fread(&version, sizeof(u32), 1, file);
// Started using the backwards compatible format on version 7 -- we should be able to read any known version newer than that.
if (version < 7 || version > SETTINGS_VERSION)
{
add_log(app, LOGLEVEL_WARN, "Settings file version was too old or unknown (file: %i, current: %i). Resetting.", version, SETTINGS_VERSION);
}
else
{
fseek(file, 0, SEEK_END);
usize data_size = ftell(file) - sizeof(u32);
fseek(file, sizeof(u32), SEEK_SET);
u8* data_ptr = (u8*) BETTER_ALLOC(data_size, "load_settings_readbuffer");
fread(data_ptr, data_size, 1, file);
fclose(file);
i32 obj_type = BINN_MAP, count = 0, size = (i32)data_size;
if (!binn_is_valid_ex(data_ptr, &obj_type, &count, &size))
add_log(app, LOGLEVEL_USERERROR, "Settings file is corrupted. Resetting.");
else
{
binn_try_get_value(data_ptr, &app->settings.handout_amount, BINN_KEY_handout_amount, BINN_UINT64);
binn_try_get_value(data_ptr, &app->settings.timer_setting, BINN_KEY_timer_setting, BINN_INT32);
binn_try_get_value(data_ptr, &app->settings.show_window_chat, BINN_KEY_show_window_chat, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.show_window_settings, BINN_KEY_show_window_settings, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.show_window_log, BINN_KEY_show_window_log, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.show_window_points, BINN_KEY_show_window_points, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.show_window_bets, BINN_KEY_show_window_bets, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.show_window_debug, BINN_KEY_show_window_debug, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.show_window_statistics, BINN_KEY_show_window_statistics, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.auto_connect, BINN_KEY_auto_connect, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.is_mod, BINN_KEY_is_mod, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.starting_points, BINN_KEY_starting_points, BINN_UINT64);
binn_try_get_value(data_ptr, &app->settings.allow_multibets, BINN_KEY_allow_multibets, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.add_mode, BINN_KEY_add_mode, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.coyote_time, BINN_KEY_coyote_time, BINN_UINT32);
binn_try_get_value(data_ptr, &app->settings.announce_bets_open, BINN_KEY_announce_bets_open, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.announce_bets_close, BINN_KEY_announce_bets_close, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.announce_payout, BINN_KEY_announce_payout, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.confirm_handout, BINN_KEY_confirm_handout, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.confirm_leaderboard_reset, BINN_KEY_confirm_leaderboard_reset, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.confirm_refund, BINN_KEY_confirm_refund, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.confirm_payout, BINN_KEY_confirm_payout, BINN_BOOL);
binn_try_get_value(data_ptr, &app->settings.style.font_size_normal, BINN_KEY_style_font_size_normal, BINN_INT32);
binn_try_get_value(data_ptr, &app->settings.style.font_size_timer, BINN_KEY_style_font_size_timer, BINN_INT32);
binn_try_get_value(data_ptr, &app->settings.style.color_theme, BINN_KEY_style_color_theme, BINN_UINT8);
binn_try_get_value(data_ptr, &app->settings.style.auto_hide_tab_bars, BINN_KEY_style_auto_hide_tab_bars, BINN_BOOL);
binn_try_get_string(data_ptr, app->settings.channel, BINN_KEY_channel, CHANNEL_NAME_MAX);
binn_try_get_string(data_ptr, app->settings.username, BINN_KEY_username, USERNAME_MAX);
binn_try_get_string(data_ptr, app->settings.command_prefix, BINN_KEY_command_prefix, 2);
binn_try_get_string(data_ptr, app->settings.points_name, BINN_KEY_points_name, POINTS_NAME_MAX);
binn_try_get_string_copy(data_ptr, &app->settings.imgui_ini, BINN_KEY_imgui_ini);
if (binn_map_null(data_ptr, BINN_KEY_token))
app->settings.oauth_token_is_present = false;
else
{
DATA_BLOB data_in, data_out;
if (binn_try_get_value(data_ptr, &data_in.pbData, BINN_KEY_token, BINN_BLOB, (i32*)&data_in.cbData))
{
if (!CryptUnprotectData(&data_in, NULL, NULL, NULL, NULL, 0, &data_out))
{
add_log(app, LOGLEVEL_DEVERROR, "Failed to decrypt saved OAuth token. Did Windows user credentials change? (CryptUnprotectData failed: %d)", GetLastError());
}
else
{
assert(data_out.cbData == sizeof(app->settings.token));
memcpy(app->settings.token, data_out.pbData, sizeof(app->settings.token));
if (!CryptProtectMemory(app->settings.token, sizeof(app->settings.token), CRYPTPROTECTMEMORY_SAME_PROCESS))
{
SecureZeroMemory(app->settings.token, sizeof(app->settings.token));
add_log(app, LOGLEVEL_DEVERROR, "CryptProtectMemory failed: %d", GetLastError());
}
else
{
app->settings.oauth_token_is_present = true;
}
SecureZeroMemory(data_out.pbData, data_out.cbData);
LocalFree(data_out.pbData);
}
}
}
}
BETTER_FREE(data_ptr);
}
}
}
void save_settings_to_disk(App* app)
{
add_log(app, LOGLEVEL_DEBUG, "Saving settings to disk.");
FILE* file = base_open(app, L"settings", L"wb");
if (!file)
add_log(app, LOGLEVEL_DEVERROR, "Couldn't open settings file for writing.");
else
{
binn* map = binn_map();
binn_map_set_uint64 (map, BINN_KEY_handout_amount, app->settings.handout_amount);
binn_map_set_int32 (map, BINN_KEY_timer_setting, app->settings.timer_setting);
binn_map_set_bool (map, BINN_KEY_show_window_chat, app->settings.show_window_chat);
binn_map_set_bool (map, BINN_KEY_show_window_settings, app->settings.show_window_settings);
binn_map_set_bool (map, BINN_KEY_show_window_log, app->settings.show_window_log);
binn_map_set_bool (map, BINN_KEY_show_window_points, app->settings.show_window_points);
binn_map_set_bool (map, BINN_KEY_show_window_bets, app->settings.show_window_bets);
binn_map_set_bool (map, BINN_KEY_show_window_debug, app->settings.show_window_debug);
binn_map_set_bool (map, BINN_KEY_show_window_statistics, app->settings.show_window_statistics);
binn_map_set_bool (map, BINN_KEY_auto_connect, app->settings.auto_connect);
binn_map_set_str (map, BINN_KEY_channel, app->settings.channel);
binn_map_set_str (map, BINN_KEY_username, app->settings.username);
binn_map_set_bool (map, BINN_KEY_is_mod, app->settings.is_mod);
binn_map_set_str (map, BINN_KEY_command_prefix, app->settings.command_prefix);
binn_map_set_str (map, BINN_KEY_points_name, app->settings.points_name);
binn_map_set_uint64 (map, BINN_KEY_starting_points, app->settings.starting_points);
binn_map_set_bool (map, BINN_KEY_allow_multibets, app->settings.allow_multibets);
binn_map_set_bool (map, BINN_KEY_add_mode, app->settings.add_mode);
binn_map_set_uint32 (map, BINN_KEY_coyote_time, app->settings.coyote_time);
binn_map_set_bool (map, BINN_KEY_announce_bets_open, app->settings.announce_bets_open);
binn_map_set_bool (map, BINN_KEY_announce_bets_close, app->settings.announce_bets_close);
binn_map_set_bool (map, BINN_KEY_announce_payout, app->settings.announce_payout);
binn_map_set_bool (map, BINN_KEY_confirm_handout, app->settings.confirm_handout);
binn_map_set_bool (map, BINN_KEY_confirm_leaderboard_reset, app->settings.confirm_leaderboard_reset);
binn_map_set_bool (map, BINN_KEY_confirm_refund, app->settings.confirm_refund);
binn_map_set_bool (map, BINN_KEY_confirm_payout, app->settings.confirm_payout);
binn_map_set_str (map, BINN_KEY_imgui_ini, app->settings.imgui_ini);
binn_map_set_int32 (map, BINN_KEY_style_font_size_normal, app->settings.style.font_size_normal);
binn_map_set_int32 (map, BINN_KEY_style_font_size_timer, app->settings.style.font_size_timer);
binn_map_set_uint8 (map, BINN_KEY_style_color_theme, app->settings.style.color_theme);
binn_map_set_bool (map, BINN_KEY_style_auto_hide_tab_bars, app->settings.style.auto_hide_tab_bars);
if (app->settings.oauth_token_is_present)
{
DATA_BLOB data_in, data_out;
data_in.cbData = sizeof(app->settings.token);
data_in.pbData = (BYTE*) app->settings.token;
if (!CryptUnprotectMemory(app->settings.token,
sizeof(app->settings.token),
CRYPTPROTECTMEMORY_SAME_PROCESS))
{
add_log(app, LOGLEVEL_DEVERROR, "CryptUnprotectMemory failed: %i", GetLastError());
SecureZeroMemory(app->settings.token, sizeof(app->settings.token));
app->settings.oauth_token_is_present = false;
}
if (!CryptProtectData(&data_in, L"Twitch OAuth token for Better", NULL, NULL, NULL, 0, &data_out))
add_log(app, LOGLEVEL_DEVERROR, "Failed to encrypt token: %i", GetLastError());
else
{
if (!CryptProtectMemory(app->settings.token,
sizeof(app->settings.token),
CRYPTPROTECTMEMORY_SAME_PROCESS))
{
add_log(app, LOGLEVEL_DEVERROR, "CryptProtectMemory failed: %i", GetLastError());
SecureZeroMemory(app->settings.token, sizeof(app->settings.token));
app->settings.oauth_token_is_present = false;
}
binn_map_set_blob(map, BINN_KEY_token, data_out.pbData, data_out.cbData);
}
}
else
{
binn_map_set_null(map, BINN_KEY_token);
}
fwrite(&SETTINGS_VERSION, sizeof(u32), 1, file);
fwrite(binn_ptr(map), binn_size(map), 1, file);
fclose(file);
binn_free(map);
}
}
void load_leaderboard_from_disk(App* app)
{
FILE* file = base_open(app, L"leaderboard.txt", L"r");
if (!file)
add_log(app, LOGLEVEL_DEBUG, "No leaderboard file found.");
else
{
add_log(app, LOGLEVEL_DEBUG, "Reading leaderboard from disk.");
while (true)
{
char name[USERNAME_MAX];
u64 points;
i32 res = fscanf(file, "%s %llu\n", name, &points);
if (res != 2) break;
add_user(app, name, points);
}
fclose(file);
}
}
void save_leaderboard_to_disk(App* app)
{
add_log(app, LOGLEVEL_DEBUG, "Saving leaderboard to disk.");
FILE* file = base_open(app, L"leaderboard.txt", L"w");
for (i32 i = 0; i < shlen(app->users); ++i)
{
UserInfo* user = app->users[i].value;
fprintf(file, "%s %llu\n", user->name, user->points);
}
fclose(file);
}
u32 get_privmsg_interval(App* app)
{
if (app->settings.is_mod)
return PRIVMSG_MIN_INTERVAL_AS_MOD;
return PRIVMSG_MIN_INTERVAL;
}
void rebuild_fonts(App* app, ImGuiIO& io)
{
io.Fonts->Clear();
ImWchar fa_range[3] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
app->font_default = io.Fonts->AddFontFromMemoryTTF(
app->data_font_normal,
app->data_size_font_normal,
(f32)app->settings.style.font_size_normal,
&app->default_font_config);
io.Fonts->AddFontFromMemoryCompressedBase85TTF(
FONT_ICON_BUFFER_NAME_FA,
(f32)app->settings.style.font_size_normal,
&app->fa_font_config,
fa_range);
app->font_mono = io.Fonts->AddFontFromMemoryTTF(
app->data_font_mono,
app->data_size_font_mono,
(f32)app->settings.style.font_size_normal,
&app->default_font_config);
app->font_timer = io.Fonts->AddFontFromMemoryTTF(
app->data_font_mono,
app->data_size_font_mono,
(f32)app->settings.style.font_size_timer,
&app->default_font_config);
io.Fonts->Build();
ImGui_ImplDX11_InvalidateDeviceObjects();
};
int tree_comp(UserInfo* k1, UserInfo* k2)
{
if (k1->points == k2->points) return 0;
if (k1->points < k2->points) return -1;
return 1;
}
void* tree_alloc(size_t size)
{
return BETTER_ALLOC(size, "leaderboard_tree_alloc");
}
void tree_free(void* ptr)
{
BETTER_FREE(ptr);
}
UserInfo* add_user(App* app, const char* name, u64 points)
{
if (shget(app->users, name) != NULL)
{
add_log(app, LOGLEVEL_DEBUG, "Tried to re-add user: %s", name);
return NULL;
}
if (points > POINTS_MAX)
{
add_log(app, LOGLEVEL_DEBUG, "Tried to give new user (%s) more than the max amount of points (%llu)", name, points);
points = POINTS_MAX;
}
UserInfo* user = (UserInfo*)BETTER_ALLOC(sizeof(UserInfo), "add_user");
strcpy(user->name, name);
user->points = points;
shput(app->users, user->name, user);
user->tree_node = ost_insert(&app->leaderboard_tree, (void*)user);
return user;
}
void rebuild_leaderboard(App* app)
{
ost_remove_all(&app->leaderboard_tree);
for (int i = 0; i < shlen(app->users); ++i)
{
app->users[i].value->tree_node = ost_insert(&app->leaderboard_tree, (void*)app->users[i].value);
}
assert(shlen(app->users) == ost_count(&app->leaderboard_tree) && "Points table and leaderboard sizes do not match");
}
// Returns the number of users that have more points than the given user.
// TODO: When all or lots of people have the same amount of points, doing this for low-ranked nodes gets super slow because we're iterating over large parts of the tree. We should do some caching here to optimize.
u32 get_leaderboard_rank(App* app, ost_node* node)
{
void* data = node->data;
while (node && app->leaderboard_tree.comp_fn(data, node->data) == 0)
node = ost_inorder_next(node);
if (!node) return 0;
return ost_count(&app->leaderboard_tree) - ost_index_of(node);
}
#if BETTER_DEBUG
i32 spoof_message_file_index = -1;
f32 spoof_interval = 0.5f;
i32 spoof_chunk_size = 10;
static f32 last_read_time = 0;
static i32 last_read_pos[2] = {};
void start_reading_spoof_messages(App* app)
{
if (!SetTimer(app->main_wnd, TID_SPOOF_MESSAGES, (UINT)(spoof_interval*1000.0f), NULL))
add_log(app, LOGLEVEL_DEVERROR, "SetTimer failed: %d", GetLastError());
}
void stop_reading_spoof_messages(App* app)
{
if (!KillTimer(app->main_wnd, TID_SPOOF_MESSAGES))
add_log(app, LOGLEVEL_DEVERROR, "KillTimer failed: %d", GetLastError());
}
void read_spoof_messages(App* app)
{
i32 file_i = spoof_message_file_index;
WCHAR* filename = (WCHAR*) BETTER_ALLOC(sizeof(WCHAR)*(app->base_dir_len + 30), "read_spoof_messages_filename");
swprintf(filename, L"debug_chatlog%i.txt", file_i);
FILE* file = base_open(app, filename, L"r");
BETTER_FREE(filename);
if (file)
{
fseek(file, last_read_pos[file_i], SEEK_SET);
for (int i = 0; i < spoof_chunk_size; ++i)
{
char name[USERNAME_MAX+1];
char content[2100];
char timestamp[15];
i32 res = fscanf(file, "%s %s ", timestamp, name);
if (res != 2)
{
last_read_pos[file_i] = 0;
break;
}
if (!fgets(content, 2100, file))
{
last_read_pos[file_i] = 0;
break;
}
last_read_pos[file_i] = ftell(file);
name[strlen(name)-1] = '\0';
content[strlen(content)-1] = '\0';
IrcMessage msg;
msg.command = (char*) BETTER_ALLOC(8, "read_spoof_messages_ircmessage_command");
msg.name = (char*) BETTER_ALLOC(strlen(name) + 1, "read_spoof_messages_ircmessage_name");
char* m_content = (char*) BETTER_ALLOC(strlen(content) + 1, "read_spoof_messages_ircmessage_param_content");
strcpy(msg.command, "PRIVMSG");
strcpy(msg.name, name);
strcpy(m_content, content);
arrput(msg.params, m_content);
app->read_queue.push_back_growing(msg);
}
fclose(file);
}
}
#endif