diff --git a/include/hammy/refdb.h b/include/hammy/refdb.h index 84e801a..b939a7a 100644 --- a/include/hammy/refdb.h +++ b/include/hammy/refdb.h @@ -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 diff --git a/src/commands/abbr.c b/src/commands/abbr.c index e6207dc..e913c21 100644 --- a/src/commands/abbr.c +++ b/src/commands/abbr.c @@ -14,26 +14,26 @@ 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* qcode = hammy_job_get_arg(job, "q-code"); - if (!qcode) { - hammy_job_respond(job, client, "Error", "No text provided for Q-Code conversion.", true); + 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* questionStr = NULL; - const char* answerStr = NULL; + const char* str = NULL; + const char* context = NULL; - if (!hammy_refdb_get_qcode(refdb, qcode, &questionStr, &answerStr)) { - hammy_job_respond(job, client, "Q-Code Not Found!", "Failed to find the Q-Code specified. Please check your query!", true); + 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(qcode); + hammy_to_uppercase(abbr); - snprintf(body, sizeof(body), "Q-Code: `%s`\nQuestion: `%s`\nAnswer: `%s`", qcode, (questionStr ? questionStr : "Not Specified"), (answerStr ? answerStr : "Not Specified")); + 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, "Q-Code Conversion", body, false); + hammy_job_respond(job, client, "Abbreviation Conversion", body, false); } diff --git a/src/commands/phonetic.c b/src/commands/phonetic.c index 9d60771..8d6ffbc 100644 --- a/src/commands/phonetic.c +++ b/src/commands/phonetic.c @@ -60,6 +60,11 @@ void hammy_cmd_phonetic(const hammy_job_t* job, struct discord* client, hammy_re return; } + 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)]; diff --git a/src/hammy/command.c b/src/hammy/command.c index dc22d01..98007e6 100644 --- a/src/hammy/command.c +++ b/src/hammy/command.c @@ -46,6 +46,16 @@ 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 = "text", .description = "Text to convert to Phonetics", .required = true }, @@ -66,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 Text 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 }, diff --git a/src/hammy/refdb.c b/src/hammy/refdb.c index 5c088a7..272352d 100644 --- a/src/hammy/refdb.c +++ b/src/hammy/refdb.c @@ -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 // ---------------------------------------------------------------------------