Compare commits
2
Commits
ae5389a7d5
...
d7b9582cc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7b9582cc0
|
||
|
|
ee045cd1dd
|
+17
-2
@@ -27,6 +27,10 @@
|
||||
// The current longest string in the qcodes table is 42; 128 is pretty generous. TODO: Change this if the qcodes table ever changes
|
||||
#define HAMMY_QCODE_MAX 128
|
||||
|
||||
// Abbreviations
|
||||
#define HAMMY_ABBR_LONGEST_STR 96
|
||||
#define HAMMY_ABBR_LONGEST_CTX 16
|
||||
|
||||
// A country has at most a handful of licence classes, each with at most a
|
||||
// couple of segments covering one exact frequency. 24 is generous.
|
||||
#define HAMMY_FREQ_PRIVS_MAX 24
|
||||
@@ -41,6 +45,7 @@ struct hammy_refdb_t {
|
||||
sqlite3_stmt* stMorse;
|
||||
sqlite3_stmt* stQCode;
|
||||
sqlite3_stmt* stPhonetic;
|
||||
sqlite3_stmt* stAbbr;
|
||||
sqlite3_stmt* stFreqMain;
|
||||
sqlite3_stmt* stFreqIaru;
|
||||
sqlite3_stmt* stFreqNearest;
|
||||
@@ -53,6 +58,9 @@ struct hammy_refdb_t {
|
||||
char phoneticCode[HAMMY_PHONETIC_MAX];
|
||||
char phoneticCodePronunciation[HAMMY_PHONETIC_MAX];
|
||||
|
||||
char abbrStr[HAMMY_ABBR_LONGEST_STR];
|
||||
char abbrCtx[HAMMY_ABBR_LONGEST_CTX];
|
||||
|
||||
char qcodeQuestion[HAMMY_QCODE_MAX]; // Scratch for the last hammy_refdb_get_qcode() hit
|
||||
char qcodeAnswer[HAMMY_QCODE_MAX]; // Scratch for the last hammy_refdb_get_qcode() hit
|
||||
|
||||
@@ -162,19 +170,26 @@ bool hammy_refdb_dxcc(hammy_refdb_t* db, const char* callsign, hammy_dxcc_t* out
|
||||
bool hammy_refdb_get_morse(hammy_refdb_t* db, char c, const char** out);
|
||||
|
||||
// Looks up a character in the Phonetic table. Case-insensitive; non-ASCII bytes
|
||||
// never match. Returns false if not found, leaving *out untouched.
|
||||
// never match. Returns false if not found, leaving *out(s) untouched.
|
||||
//
|
||||
// On a hit *out points at storage owned by the refdb and is only valid until
|
||||
// the NEXT call on the same handle - copy it if it has to outlive that.
|
||||
bool hammy_refdb_get_phonetic(hammy_refdb_t* db, char c, const char** out, const char** outPronunciation);
|
||||
|
||||
// Looks up a QSO code in the qcodes table. Case-insensitive; non-ASCII bytes
|
||||
// never match. Returns false if not found, leaving *out untouched.
|
||||
// never match. Returns false if not found, leaving *out(s) untouched.
|
||||
//
|
||||
// On a hit *out points at the storage owned by refdb and is only valid until
|
||||
// the NEXT call on the same handle - copy it if it has to outlive that.
|
||||
bool hammy_refdb_get_qcode(hammy_refdb_t* db, const char* code, const char** outQuestion, const char** outAnswer);
|
||||
|
||||
// Looks up an abbreviation in the abbreviations table. Case-insensitive; non-ASCII bytes
|
||||
// never match. Returns false if not found, leaving *out(s) untouched.
|
||||
//
|
||||
// On a hit *out points at the storage owned by refdb and is only valid until
|
||||
// the NEXT call on the same handle - copy it if it has to outlive that.
|
||||
bool hammy_refdb_get_abbr(hammy_refdb_t* db, const char* code, const char** outStr, const char** outContext);
|
||||
|
||||
// What band is freq_hz in, and who may transmit there.
|
||||
//
|
||||
// country is an ISO 3166-1 alpha-2 code; NULL or empty means "US". Always
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <concord/discord.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <hammy/command.h>
|
||||
#include <hammy/job.h>
|
||||
#include <hammy/refdb.h>
|
||||
#include <hammy/utils.h>
|
||||
|
||||
#define HAMMY_ABBR_ECHO_MAX 16
|
||||
#define HAMMY_ABBR_CODE_MAX 3072
|
||||
#define HAMMY_ABBR_TRUNCATED " ... (truncated)"
|
||||
|
||||
void hammy_cmd_abbr(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb) {
|
||||
// Get the qcode argument from the job
|
||||
const char* abbr = hammy_job_get_arg(job, "abbreviation");
|
||||
if (!abbr) {
|
||||
hammy_job_respond(job, client, "Error", "No Abbreviation provided for Abbreviation conversion.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
char body[HAMMY_ABBR_CODE_MAX + sizeof(HAMMY_ABBR_TRUNCATED)]; // Generally, since the bottom pointers can only point to max 128-character strings (set as preprocesor header)
|
||||
// So yeah - if we ever change the above for some reason to stupid values... yeah. Technically "unsafe" but yeah.
|
||||
|
||||
const char* str = NULL;
|
||||
const char* context = NULL;
|
||||
|
||||
if (!hammy_refdb_get_abbr(refdb, abbr, &str, &context)) {
|
||||
hammy_job_respond(job, client, "Abbreviation Not Found!", "Failed to find the Abbreviation specified. Please check your query!", true);
|
||||
return;
|
||||
}
|
||||
|
||||
hammy_to_uppercase(abbr);
|
||||
|
||||
snprintf(body, sizeof(body), "Abbreviation: `%s`\nMeaning: `%s`\nUsage Context: `%s`", abbr, (str ? str : "Not Specified"), (context ? context : "Not Specified"));
|
||||
|
||||
hammy_job_respond(job, client, "Abbreviation Conversion", body, false);
|
||||
}
|
||||
+13
-7
@@ -53,18 +53,20 @@ static bool phonetic_is_gap(char c) {
|
||||
|
||||
// Instant command: runs on the gateway thread, sends a fresh response.
|
||||
void hammy_cmd_phonetic(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb) {
|
||||
// Get the callsign argument from the job
|
||||
const char* text = hammy_job_get_arg(job, "callsign");
|
||||
// Get the text argument from the job
|
||||
const char* text = hammy_job_get_arg(job, "text");
|
||||
if (!text) {
|
||||
hammy_job_respond(job, client, "Callsign Missing!", "No Callsign provided for Phonetic conversion.", true);
|
||||
hammy_job_respond(job, client, "Text Missing!", "No Text provided for Phonetic conversion.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(text) > 12) { // Arbitrary limit to prevent abuse and ensure reasonable output size
|
||||
hammy_job_respond(job, client, "Callsign Too Long!", "The provided Callsign exceeds the maximum allowed length.", true);
|
||||
if (strlen(text) >= 128) {
|
||||
hammy_job_respond(job, client, "Text Too Long!", "Text provided is too long! Max. 128 characters.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
bool showPronunciation = strlen(text) < 12;
|
||||
|
||||
char phonetic[HAMMY_PHONETIC_CODE_MAX + sizeof(HAMMY_PHONETIC_TRUNCATED)];
|
||||
char pronunciation[HAMMY_PHONETIC_PRONUNCIATION_MAX + sizeof(HAMMY_PHONETIC_TRUNCATED)];
|
||||
|
||||
@@ -131,7 +133,11 @@ void hammy_cmd_phonetic(const hammy_job_t* job, struct discord* client, hammy_re
|
||||
|
||||
// Reply with the phonetic words and how each of them is spoken.
|
||||
char body[sizeof(phonetic) + sizeof(pronunciation) + 48];
|
||||
snprintf(body, sizeof(body), "Callsign: `%s`\nPhonetics: `%s`\nPronunciation: `%s`", text, phonetic, pronunciation);
|
||||
if (showPronunciation) {
|
||||
snprintf(body, sizeof(body), "Text: `%s`\nPhonetics: `%s`\nPronunciation: `%s`", text, phonetic, pronunciation);
|
||||
} else {
|
||||
snprintf(body, sizeof(body), "Text: `(truncated)`\nPhonetics: `%s`", phonetic);
|
||||
}
|
||||
|
||||
hammy_job_respond(job, client, "Phonetic Code Conversion", body, false);
|
||||
hammy_job_respond(job, client, "Text to Phonetics Conversion", body, false);
|
||||
}
|
||||
|
||||
+20
-8
@@ -6,11 +6,12 @@
|
||||
// Handlers live in src/hammy/commands/. Declared here rather than in a header
|
||||
// so adding a command touches exactly two places: its own .c file and this
|
||||
// table.
|
||||
void hammy_cmd_ping(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb);
|
||||
void hammy_cmd_morse(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb);
|
||||
void hammy_cmd_freq(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb);
|
||||
void hammy_cmd_q(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb);
|
||||
void hammy_cmd_phonetic(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb);
|
||||
void hammy_cmd_ping(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb); // Ping
|
||||
void hammy_cmd_morse(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb); // Text -> Morse
|
||||
void hammy_cmd_freq(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb); // Frequency lookup
|
||||
void hammy_cmd_q(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb); // Q-Code lookup
|
||||
void hammy_cmd_abbr(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb); // Abbreviation lookup
|
||||
void hammy_cmd_phonetic(const hammy_job_t* job, struct discord* client, hammy_refdb_t* refdb); // Callsign -> Phonetics
|
||||
|
||||
// TODO: Temporary - move to a proper options system; e.g. a vector of heap-allocated options or something. For now, just point at static storage.
|
||||
static struct discord_application_command_option morse_opts[] = {
|
||||
@@ -45,9 +46,19 @@ static struct discord_application_command_options q_opts_struct = {
|
||||
.array = q_opts
|
||||
};
|
||||
|
||||
static struct discord_application_command_option abbr_opts[] = {
|
||||
{ .type = DISCORD_APPLICATION_OPTION_STRING, .name = "abbreviation",
|
||||
.description = "Abbreviation to convert to Meaning", .required = true },
|
||||
};
|
||||
|
||||
static struct discord_application_command_options abbr_opts_struct = {
|
||||
.size = 1,
|
||||
.array = abbr_opts
|
||||
};
|
||||
|
||||
static struct discord_application_command_option phonetic_opts[] = {
|
||||
{ .type = DISCORD_APPLICATION_OPTION_STRING, .name = "callsign",
|
||||
.description = "Callsign to convert to Phonetics", .required = true },
|
||||
{ .type = DISCORD_APPLICATION_OPTION_STRING, .name = "text",
|
||||
.description = "Text to convert to Phonetics", .required = true },
|
||||
};
|
||||
|
||||
static struct discord_application_command_options phonetic_opts_struct = {
|
||||
@@ -65,7 +76,8 @@ static const hammy_command_t hammy_builtin_commands[] = {
|
||||
{ "morse", "Text to and from Morse code", &morse_opts_struct, &hammy_cmd_morse, true },
|
||||
{ "freq", "Band, segment and who can transmit", &freq_opts_struct, &hammy_cmd_freq, true },
|
||||
{ "q", "Convert Q-Code to the corresponding question and answer", &q_opts_struct, &hammy_cmd_q, true },
|
||||
{ "phonetic", "Convert Callsign to Phonetics", &phonetic_opts_struct, &hammy_cmd_phonetic, true }
|
||||
{ "phonetic", "Convert Text to Phonetics", &phonetic_opts_struct, &hammy_cmd_phonetic, true },
|
||||
{ "abbr", "Convert Abbreviation to Meaning and Context", &abbr_opts_struct, &hammy_cmd_abbr, true }
|
||||
|
||||
// Tier 0 commands go here as they land. All pure computation, so instant:
|
||||
// { "grid", "Maidenhead locator conversions", &grid_opts, &hammy_cmd_grid, true },
|
||||
|
||||
@@ -94,6 +94,10 @@ static const char SQL_QCODE[] =
|
||||
static const char SQL_PHONETIC[] =
|
||||
"SELECT word, pronunciation FROM phonetics WHERE letter = UPPER(?1) LIMIT 1";
|
||||
|
||||
// Resolve abbr to meaning
|
||||
static const char SQL_ABBR[] =
|
||||
"SELECT meaning, context FROM abbreviations WHERE abbr = UPPER(?1) LIMIT 1";
|
||||
|
||||
// Authorizer: the connection may read, and nothing else.
|
||||
//
|
||||
// SQLITE_OPEN_READONLY protects the MAIN database file. It does not stop
|
||||
@@ -148,6 +152,7 @@ static const hammy_stmt_def_t HAMMY_STATEMENTS[] = {
|
||||
HAMMY_STMT(stMorse, SQL_MORSE),
|
||||
HAMMY_STMT(stQCode, SQL_QCODE),
|
||||
HAMMY_STMT(stPhonetic, SQL_PHONETIC),
|
||||
HAMMY_STMT(stAbbr, SQL_ABBR),
|
||||
HAMMY_STMT(stFreqMain, SQL_FREQ_MAIN),
|
||||
HAMMY_STMT(stFreqIaru, SQL_FREQ_IARU),
|
||||
HAMMY_STMT(stFreqNearest, SQL_FREQ_NEAREST),
|
||||
@@ -479,6 +484,12 @@ bool hammy_refdb_get_phonetic(hammy_refdb_t* db, char c, const char** out, const
|
||||
bool hit = false;
|
||||
|
||||
if (sqlite3_step(db->stPhonetic) == SQLITE_ROW) {
|
||||
if (sqlite3_column_count(db->stPhonetic) < 2) {
|
||||
// Error
|
||||
sqlite3_reset(db->stPhonetic);
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned char* code = sqlite3_column_text(db->stPhonetic, 0);
|
||||
const unsigned char* codePronunciation = sqlite3_column_text(db->stPhonetic, 1);
|
||||
|
||||
@@ -543,6 +554,51 @@ bool hammy_refdb_get_qcode(hammy_refdb_t* db, const char* code, const char** out
|
||||
return hit;
|
||||
}
|
||||
|
||||
bool hammy_refdb_get_abbr(hammy_refdb_t* db, const char* code, const char** outStr, const char** outContext) {
|
||||
if (!db || !code || !outStr || !outContext) { return false; }
|
||||
|
||||
sqlite3_stmt* const needed[] = { db->stAbbr };
|
||||
if (!stmts_ready("qcodes", needed, 1)) { return false; }
|
||||
|
||||
// Read the get_morse comment, I ain't writing this again (entire string edition)
|
||||
for (const char* p = code; *p != '\0'; p++) {
|
||||
if ((unsigned char)*p & 0x80u) { return false; }
|
||||
}
|
||||
|
||||
// Query the string
|
||||
sqlite3_reset(db->stAbbr);
|
||||
sqlite3_clear_bindings(db->stAbbr);
|
||||
sqlite3_bind_text(db->stAbbr, 1, code, -1, SQLITE_TRANSIENT); // -1 tells SQLite to figure out the length itself (strlen() call - requires NULL term)
|
||||
|
||||
bool hit = false;
|
||||
|
||||
if (sqlite3_step(db->stAbbr) == SQLITE_ROW) {
|
||||
if (sqlite3_column_count(db->stAbbr) < 2) { // Error
|
||||
sqlite3_reset(db->stAbbr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned char* meaningStr = sqlite3_column_text(db->stAbbr, 0);
|
||||
const unsigned char* ctxStr = sqlite3_column_text(db->stAbbr, 1);
|
||||
|
||||
if (meaningStr) {
|
||||
snprintf(db->abbrStr, sizeof(db->abbrStr), "%s", (const char*)meaningStr);
|
||||
*outStr = db->abbrStr;
|
||||
hit = true;
|
||||
}
|
||||
|
||||
if (ctxStr) {
|
||||
snprintf(db->abbrCtx, sizeof(db->abbrCtx), "%s", (const char*)ctxStr);
|
||||
*outContext = db->abbrCtx;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_reset(db->stAbbr);
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frequency parsing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Vendored
+1122
-2
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user