ssb: Add a publish command that can be used to publish messages from the command-line.
This commit is contained in:
		
							
								
								
									
										123
									
								
								src/main.c
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								src/main.c
									
									
									
									
									
								
							| @@ -48,11 +48,12 @@ struct backtrace_state* g_backtrace_state; | ||||
| const char* k_db_path_default = "db.sqlite"; | ||||
|  | ||||
| #if !TARGET_OS_IPHONE && !defined(__ANDROID__) | ||||
| static int _tf_command_test(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_import(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_export(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_import(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_publish(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_run(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_sandbox(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_test(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_verify(const char* file, int argc, char* argv[]); | ||||
| static int _tf_command_usage(const char* file); | ||||
|  | ||||
| @@ -68,6 +69,7 @@ const command_t k_commands[] = { | ||||
| 	{ "sandbox", _tf_command_sandbox, "Run a sandboxed tildefriends sandbox process (used internally)." }, | ||||
| 	{ "import", _tf_command_import, "Import apps to SSB." }, | ||||
| 	{ "export", _tf_command_export, "Export apps from SSB." }, | ||||
| 	{ "publish", _tf_command_publish, "Append a message to a feed." }, | ||||
| 	{ "verify", _tf_command_verify, "Verify a feed." }, | ||||
| 	{ "test", _tf_command_test, "Test SSB." }, | ||||
| }; | ||||
| @@ -274,6 +276,121 @@ static int _tf_command_export(const char* file, int argc, char* argv[]) | ||||
| 	return EXIT_SUCCESS; | ||||
| } | ||||
|  | ||||
| static void _tf_published_callback(const char* id, bool verified, bool stored, void* user_data) | ||||
| { | ||||
| 	if (verified) | ||||
| 	{ | ||||
| 		if (stored) | ||||
| 		{ | ||||
| 			tf_printf("Message %s stored.\n", id); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			tf_printf("Unable to store the message.\n"); | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		tf_printf("Failed to verify the message.\n"); | ||||
| 	} | ||||
| 	*(int*)user_data = stored ? EXIT_SUCCESS : EXIT_FAILURE; | ||||
| } | ||||
|  | ||||
| static int _tf_command_publish(const char* file, int argc, char* argv[]) | ||||
| { | ||||
| 	const char* user = NULL; | ||||
| 	const char* identity = NULL; | ||||
| 	const char* db_path = k_db_path_default; | ||||
| 	const char* content = NULL; | ||||
| 	bool show_usage = false; | ||||
|  | ||||
| 	while (!show_usage) | ||||
| 	{ | ||||
| 		static const struct option k_options[] = { | ||||
| 			{ "user", required_argument, NULL, 'u' }, | ||||
| 			{ "id", required_argument, NULL, 'i' }, | ||||
| 			{ "db-path", required_argument, NULL, 'd' }, | ||||
| 			{ "content", required_argument, NULL, 'c' }, | ||||
| 			{ "help", no_argument, NULL, 'h' }, | ||||
| 			{ 0 }, | ||||
| 		}; | ||||
| 		int c = getopt_long(argc, argv, "u:i:d:c:h", k_options, NULL); | ||||
| 		if (c == -1) | ||||
| 		{ | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 		switch (c) | ||||
| 		{ | ||||
| 		case '?': | ||||
| 		case 'h': | ||||
| 		default: | ||||
| 			show_usage = true; | ||||
| 			break; | ||||
| 		case 'u': | ||||
| 			user = optarg; | ||||
| 			break; | ||||
| 		case 'i': | ||||
| 			identity = optarg; | ||||
| 			break; | ||||
| 		case 'd': | ||||
| 			db_path = optarg; | ||||
| 			break; | ||||
| 		case 'c': | ||||
| 			content = optarg; | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if (show_usage || !user || !identity || !content) | ||||
| 	{ | ||||
| 		tf_printf("\n%s publish [options]\n\n", file); | ||||
| 		tf_printf("options:\n"); | ||||
| 		tf_printf("  -y, --user user          User owning identity with which to publish.\n"); | ||||
| 		tf_printf("  -i, --identity identity  Identity with which to publish message.\n"); | ||||
| 		tf_printf("  -d, --db-path db_path    SQLite database path (default: %s).\n", k_db_path_default); | ||||
| 		tf_printf("  -c, --content json       JSON content of message to publish.\n"); | ||||
| 		tf_printf("  -h, --help               Show this usage information.\n"); | ||||
| 		return EXIT_FAILURE; | ||||
| 	} | ||||
|  | ||||
| 	int result = EXIT_FAILURE; | ||||
| 	tf_printf("Posting %s as account %s belonging to %s...\n", content, identity, user); | ||||
| 	tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); | ||||
| 	uint8_t private_key[512] = { 0 }; | ||||
| 	if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key))) | ||||
| 	{ | ||||
| 		JSContext* context = tf_ssb_get_context(ssb); | ||||
| 		int64_t sequence = 0; | ||||
| 		char previous[k_id_base64_len] = { 0 }; | ||||
| 		tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, previous, sizeof(previous)); | ||||
| 		JSValue content_value = JS_ParseJSON(context, content, strlen(content), NULL); | ||||
| 		if (!JS_IsException(content_value)) | ||||
| 		{ | ||||
| 			JSValue message = tf_ssb_sign_message(ssb, identity, private_key, content_value, previous, sequence); | ||||
| 			JSValue message_value = JS_JSONStringify(context, message, JS_NULL, JS_NULL); | ||||
| 			const char* message_str = JS_ToCString(context, message_value); | ||||
| 			tf_printf("Posting: %s.\n", message_str); | ||||
| 			tf_ssb_verify_strip_and_store_message(ssb, message, _tf_published_callback, &result); | ||||
| 			JS_FreeCString(context, message_str); | ||||
| 			JS_FreeValue(context, message_value); | ||||
| 			JS_FreeValue(context, message); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			tf_printf("Unable to parse content as JSON: "); | ||||
| 			tf_util_report_error(context, content_value); | ||||
| 		} | ||||
| 		JS_FreeValue(context, content_value); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user); | ||||
| 	} | ||||
| 	tf_ssb_destroy(ssb); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| static int _tf_command_verify(const char* file, int argc, char* argv[]) | ||||
| { | ||||
| 	const char* identity = NULL; | ||||
| @@ -283,7 +400,7 @@ static int _tf_command_verify(const char* file, int argc, char* argv[]) | ||||
| 	while (!show_usage) | ||||
| 	{ | ||||
| 		static const struct option k_options[] = { | ||||
| 			{ "id", required_argument, NULL, 'u' }, | ||||
| 			{ "id", required_argument, NULL, 'i' }, | ||||
| 			{ "db-path", required_argument, NULL, 'd' }, | ||||
| 			{ "help", no_argument, NULL, 'h' }, | ||||
| 			{ 0 }, | ||||
|   | ||||
| @@ -1530,7 +1530,7 @@ static void _tf_task_run_jobs_async(uv_async_t* async) | ||||
|  | ||||
| void tf_task_check_jobs(tf_task_t* task) | ||||
| { | ||||
| 	if (JS_IsJobPending(task->_runtime)) | ||||
| 	if (task && JS_IsJobPending(task->_runtime)) | ||||
| 	{ | ||||
| 		uv_async_send(&task->run_jobs_async); | ||||
| 	} | ||||
|   | ||||
| @@ -44,7 +44,6 @@ void tf_taskstub_startup() | ||||
| 		JS_NewClassID(&_classId); | ||||
| 		size_t size = sizeof(_executable); | ||||
| 		uv_exepath(_executable, &size); | ||||
| 		tf_printf("exepath is %s\n", _executable); | ||||
| 		initialized = true; | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user