/*
* CSB MOTD News
* Copyright (C) 2026 counter-strike-boost.com
*
* Shows a news / changelog MOTD to players, but only when it actually changed.
* The news file's first line carries a version number; that number is stored per
* SteamID in an nVault, so a returning player is not shown the same news twice.
* The MOTD itself is rendered from a local file (or a URL, if one is set).
* Players can re-open the latest news any time with /news.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version. It is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See for details.
*/
#include
#include
new const PLUGIN[] = "CSB MOTD News"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
new g_pEnabled, g_pFile, g_pUrl, g_pTitle
new g_iVault
new g_iNewsVersion
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_news_enabled", "1")
g_pFile = register_cvar("csb_news_file", "csb_news.txt")
g_pUrl = register_cvar("csb_news_url", "")
g_pTitle = register_cvar("csb_news_title", "Server News")
register_clcmd("say /news", "cmdNews")
register_clcmd("say_team /news", "cmdNews")
g_iVault = nvault_open("csb_news")
if (g_iVault == INVALID_HANDLE)
set_fail_state("Could not open csb_news nvault")
g_iNewsVersion = readNewsVersion()
}
public plugin_end()
{
if (g_iVault != INVALID_HANDLE)
nvault_close(g_iVault)
}
readNewsVersion()
{
new file[64]
get_pcvar_string(g_pFile, file, charsmax(file))
if (!file[0] || !file_exists(file))
return 0
new f = fopen(file, "rt")
if (!f)
return 0
new line[64]
fgets(f, line, charsmax(line))
fclose(f)
trim(line)
/* accept "version 7" or a bare "7" on the first line */
new digits[16], j = 0
for (new i = 0; line[i] && j < charsmax(digits); i++)
{
if (line[i] >= '0' && line[i] <= '9')
digits[j++] = line[i]
}
digits[j] = 0
return str_to_num(digits)
}
public client_authorized(id)
{
if (!get_pcvar_num(g_pEnabled) || is_user_bot(id))
return
if (g_iNewsVersion <= 0)
return
new authid[35]
get_user_authid(id, authid, charsmax(authid))
new seen = getSeen(authid)
if (g_iNewsVersion > seen)
{
set_task(3.0, "taskShow", id)
setSeen(authid, g_iNewsVersion)
}
}
getSeen(const authid[])
{
new data[16], timestamp
if (nvault_lookup(g_iVault, authid, data, charsmax(data), timestamp))
return str_to_num(data)
return 0
}
setSeen(const authid[], version)
{
new data[16]
num_to_str(version, data, charsmax(data))
nvault_set(g_iVault, authid, data)
}
public taskShow(id)
{
if (is_user_connected(id))
showNews(id)
}
public cmdNews(id)
{
showNews(id)
return PLUGIN_HANDLED
}
showNews(id)
{
new title[48]
get_pcvar_string(g_pTitle, title, charsmax(title))
new url[128]
get_pcvar_string(g_pUrl, url, charsmax(url))
if (url[0])
{
show_motd(id, url, title)
return
}
new file[64]
get_pcvar_string(g_pFile, file, charsmax(file))
if (file[0] && file_exists(file))
{
show_motd(id, file, title)
}
else
{
client_print(id, print_chat, "[CSB] No news file is configured.")
}
}