Sanitized public release v1.0.3
Source-Tag: v1.0.3 Manifest-SHA256: b7b09bf48f9e092ed8ad82f99c0319d6c3837d0a1549bd0cc4a374276c0f3896
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
:root { color-scheme: dark; --bg: #09040f; --panel: #170c22; --line: #62377d; --pink: #f45bd2; --cyan: #62e7ee; --text: #f5edf8; --muted: #b9a8c2; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; min-height: 100vh; background: radial-gradient(circle at 80% 0, #281139, transparent 42%), var(--bg); color: var(--text); font: 16px/1.5 "Atkinson Hyperlegible", monospace; }
|
||||
main { width: min(1120px, calc(100% - 32px)); margin: 0 auto; padding: 48px 0 72px; }
|
||||
header { border-left: 4px solid var(--pink); padding-left: 20px; margin-bottom: 36px; }
|
||||
h1, h2, p { margin-top: 0; }
|
||||
h1 { margin-bottom: 8px; font-size: clamp(2rem, 6vw, 4rem); letter-spacing: -.04em; }
|
||||
h1 span { color: var(--pink); }
|
||||
.kicker { color: var(--cyan); letter-spacing: .16em; font-size: .78rem; }
|
||||
section { background: color-mix(in srgb, var(--panel) 90%, transparent); border: 1px solid var(--line); padding: 24px; margin-top: 22px; box-shadow: 8px 8px 0 #050208; }
|
||||
form { display: grid; grid-template-columns: minmax(220px, 1fr) minmax(220px, .6fr) auto; gap: 16px; align-items: end; }
|
||||
label { display: grid; gap: 7px; color: var(--muted); font-size: .85rem; text-transform: uppercase; letter-spacing: .08em; }
|
||||
input, select, button { min-height: 44px; border: 1px solid var(--line); background: #0c0612; color: var(--text); padding: 9px 12px; font: inherit; }
|
||||
button { cursor: pointer; border-color: var(--pink); }
|
||||
button:hover, button:focus-visible { background: var(--pink); color: #16051a; outline: none; }
|
||||
button:disabled { opacity: .55; cursor: wait; }
|
||||
.progress { height: 8px; margin: 20px 0 12px; background: #08030c; border: 1px solid #3d254b; }
|
||||
.progress span { display: block; width: 0; height: 100%; background: linear-gradient(90deg, var(--pink), var(--cyan)); transition: width .15s; }
|
||||
#status { margin-bottom: 0; color: var(--cyan); }
|
||||
#status.error { color: #ff829c; }
|
||||
#retry { margin-top: 12px; }
|
||||
.section-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
||||
.table-wrap { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; min-width: 760px; }
|
||||
th, td { padding: 12px 10px; border-top: 1px solid #3e294a; text-align: left; vertical-align: middle; }
|
||||
th { color: var(--cyan); font-size: .78rem; text-transform: uppercase; letter-spacing: .09em; }
|
||||
.actions { display: flex; gap: 8px; }
|
||||
.actions button { min-height: 36px; padding: 5px 10px; }
|
||||
.actions .danger { border-color: #ff607f; }
|
||||
.muted { color: var(--muted); }
|
||||
.hidden { display: none !important; }
|
||||
@media (max-width: 760px) { main { width: min(100% - 20px, 1120px); padding-top: 24px; } section { padding: 16px; } form { grid-template-columns: 1fr; } .section-heading { align-items: flex-start; } }
|
||||
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="theme-color" content="#150a24">
|
||||
<title>NeoDrop Management</title>
|
||||
<link rel="stylesheet" href="/manage/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<p class="kicker">{{OPERATOR_LABEL}}</p>
|
||||
<h1>NeoDrop <span>Management</span></h1>
|
||||
<p>Files are encrypted in this browser. The storage service receives ciphertext only.</p>
|
||||
</header>
|
||||
|
||||
<section class="upload-panel" aria-labelledby="upload-title">
|
||||
<h2 id="upload-title">New encrypted drop</h2>
|
||||
<form id="upload-form">
|
||||
<label class="file-picker">File <input id="file" type="file" required></label>
|
||||
<label>Retention
|
||||
<select id="profile">
|
||||
<option value="0">30 days, up to 50 MiB</option>
|
||||
<option value="1">7 days, up to 1 GiB</option>
|
||||
</select>
|
||||
</label>
|
||||
<button id="upload" type="submit">Encrypt and upload</button>
|
||||
</form>
|
||||
<div class="progress" aria-hidden="true"><span id="progress"></span></div>
|
||||
<p id="status" role="status">Ready.</p>
|
||||
<button id="retry" type="button" class="hidden">Retry catalog registration</button>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="inventory-title">
|
||||
<div class="section-heading">
|
||||
<h2 id="inventory-title">Managed drops</h2>
|
||||
<button id="refresh" type="button">Refresh</button>
|
||||
</div>
|
||||
<p id="empty" class="muted hidden">No managed drops.</p>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Name</th><th>Type</th><th>Size</th><th>Expires</th><th>Actions</th></tr></thead>
|
||||
<tbody id="drops"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="/manage/neodrop-crypto.js"></script>
|
||||
<script src="/manage/manage.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
"use strict";
|
||||
(function() {
|
||||
var pendingRegistration = null;
|
||||
var busy = false;
|
||||
var $ = function(id) { return document.getElementById(id); };
|
||||
|
||||
function formatSize(value) {
|
||||
var units = ["B", "KiB", "MiB", "GiB"];
|
||||
var size = Number(value);
|
||||
var index = 0;
|
||||
while (size >= 1024 && index < units.length - 1) { size /= 1024; index++; }
|
||||
return size.toFixed(index ? 1 : 0) + " " + units[index];
|
||||
}
|
||||
|
||||
function setStatus(message, error) {
|
||||
$("status").textContent = message;
|
||||
$("status").classList.toggle("error", Boolean(error));
|
||||
}
|
||||
|
||||
async function api(path, options) {
|
||||
var response = await fetch(path, Object.assign({cache: "no-store"}, options || {}));
|
||||
var value;
|
||||
try { value = await response.json(); } catch (_) { value = null; }
|
||||
if (!response.ok) throw new Error("Management request failed.");
|
||||
return value;
|
||||
}
|
||||
|
||||
async function copyUrl(url) {
|
||||
await navigator.clipboard.writeText(url);
|
||||
setStatus("Share URL copied.", false);
|
||||
}
|
||||
|
||||
function actionButton(label, handler, className) {
|
||||
var button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = label;
|
||||
if (className) button.className = className;
|
||||
button.addEventListener("click", handler);
|
||||
return button;
|
||||
}
|
||||
|
||||
function render(rows) {
|
||||
var body = $("drops");
|
||||
body.textContent = "";
|
||||
$("empty").classList.toggle("hidden", rows.length !== 0);
|
||||
rows.forEach(function(row) {
|
||||
var tr = document.createElement("tr");
|
||||
[row.name, row.type || "application/octet-stream", formatSize(row.size),
|
||||
new Date(row.expiresAt * 1000).toLocaleString()].forEach(function(value) {
|
||||
var td = document.createElement("td");
|
||||
td.textContent = value;
|
||||
tr.appendChild(td);
|
||||
});
|
||||
var actions = document.createElement("td");
|
||||
actions.className = "actions";
|
||||
actions.appendChild(actionButton("Copy", function() { copyUrl(row.readUrl).catch(function() {
|
||||
setStatus("Unable to copy the share URL.", true);
|
||||
}); }));
|
||||
actions.appendChild(actionButton("Open", function() {
|
||||
window.open(row.readUrl, "_blank", "noopener,noreferrer");
|
||||
}));
|
||||
actions.appendChild(actionButton("Delete", async function() {
|
||||
if (!window.confirm("Permanently delete " + row.name + "?")) return;
|
||||
try {
|
||||
await api("/manage/api/v1/drops/" + row.id, {method: "DELETE"});
|
||||
await load();
|
||||
setStatus("Drop deleted.", false);
|
||||
} catch (_) { setStatus("Delete failed.", true); }
|
||||
}, "danger"));
|
||||
tr.appendChild(actions);
|
||||
body.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
async function load() {
|
||||
var value = await api("/manage/api/v1/list", {
|
||||
method: "POST", headers: {"Content-Type": "application/json"}, body: "{}"
|
||||
});
|
||||
render(Array.isArray(value.drops) ? value.drops : []);
|
||||
}
|
||||
|
||||
async function register(result) {
|
||||
await api("/manage/api/v1/register", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({readUrl: result.readUrl, deleteCapability: result.deleteCapability})
|
||||
});
|
||||
pendingRegistration = null;
|
||||
$("retry").classList.add("hidden");
|
||||
await load();
|
||||
}
|
||||
|
||||
$("upload-form").addEventListener("submit", async function(event) {
|
||||
event.preventDefault();
|
||||
if (busy) return;
|
||||
var file = $("file").files[0];
|
||||
if (!file) return setStatus("Select a file.", true);
|
||||
busy = true;
|
||||
$("upload").disabled = true;
|
||||
$("progress").style.width = "0";
|
||||
setStatus("Encrypting and uploading...", false);
|
||||
try {
|
||||
var result = await NeoDropCrypto.upload(file, {
|
||||
base: location.origin,
|
||||
profile: Number($("profile").value),
|
||||
onProgress: function(value) { $("progress").style.width = Math.round(value * 100) + "%"; }
|
||||
});
|
||||
pendingRegistration = result;
|
||||
setStatus("Upload complete. Registering encrypted catalog entry...", false);
|
||||
try {
|
||||
await register(result);
|
||||
$("upload-form").reset();
|
||||
setStatus("Encrypted drop uploaded and registered.", false);
|
||||
} catch (_) {
|
||||
$("retry").classList.remove("hidden");
|
||||
setStatus("Upload succeeded, but catalog registration must be retried in this tab.", true);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name !== "AbortError") setStatus(error.message || "Encrypted upload failed.", true);
|
||||
} finally {
|
||||
busy = false;
|
||||
$("upload").disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
$("retry").addEventListener("click", async function() {
|
||||
if (!pendingRegistration || busy) return;
|
||||
busy = true;
|
||||
try {
|
||||
await register(pendingRegistration);
|
||||
setStatus("Catalog registration completed.", false);
|
||||
} catch (_) { setStatus("Catalog registration failed; retry from this tab.", true); }
|
||||
finally { busy = false; }
|
||||
});
|
||||
|
||||
$("refresh").addEventListener("click", function() {
|
||||
load().then(function() { setStatus("Managed drops refreshed.", false); })
|
||||
.catch(function() { setStatus("Unable to load managed drops.", true); });
|
||||
});
|
||||
|
||||
load().catch(function() { setStatus("Unable to load managed drops.", true); });
|
||||
}());
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,327 @@
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <weechat/weechat-plugin.h>
|
||||
|
||||
WEECHAT_PLUGIN_NAME("neodrop_manager")
|
||||
WEECHAT_PLUGIN_DESCRIPTION("NeoDrop manager control buffer")
|
||||
WEECHAT_PLUGIN_AUTHOR("{{PRODUCT_NAME}}")
|
||||
WEECHAT_PLUGIN_VERSION("1.1")
|
||||
WEECHAT_PLUGIN_LICENSE("GPL3")
|
||||
|
||||
#define CONTROL_SOCKET "{{MANAGER_CONTROL_SOCKET_PATH}}"
|
||||
#define SEND_LIMIT 8192U
|
||||
#define RECEIVE_LIMIT 16384U
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
struct t_weechat_plugin *weechat_plugin;
|
||||
static struct t_gui_buffer *manager_buffer;
|
||||
static struct t_hook *manager_fd_hook;
|
||||
static int manager_fd = -1;
|
||||
static char receive_buffer[RECEIVE_LIMIT];
|
||||
static size_t receive_length;
|
||||
|
||||
static void print_error(const char *message)
|
||||
{
|
||||
if (manager_buffer != NULL)
|
||||
weechat_printf(manager_buffer, "%s%s", weechat_color("lightred"), message);
|
||||
}
|
||||
|
||||
static void disconnect_manager(int announce)
|
||||
{
|
||||
int was_connected = manager_fd >= 0;
|
||||
|
||||
if (manager_fd_hook != NULL) {
|
||||
weechat_unhook(manager_fd_hook);
|
||||
manager_fd_hook = NULL;
|
||||
}
|
||||
if (manager_fd >= 0)
|
||||
close(manager_fd);
|
||||
manager_fd = -1;
|
||||
receive_length = 0;
|
||||
|
||||
if (announce && was_connected)
|
||||
print_error("NeoDrop manager disconnected; reconnecting in the background.");
|
||||
}
|
||||
|
||||
static void print_protocol_line(const char *line, size_t length)
|
||||
{
|
||||
const char *payload;
|
||||
const char *color = NULL;
|
||||
size_t payload_length;
|
||||
size_t index;
|
||||
char output[RECEIVE_LIMIT];
|
||||
|
||||
if (manager_buffer == NULL)
|
||||
return;
|
||||
|
||||
if (length > 0 && line[length - 1] == '\r')
|
||||
length--;
|
||||
|
||||
if (length >= 5 && memcmp(line, "LINE\t", 5) == 0) {
|
||||
payload = line + 5;
|
||||
payload_length = length - 5;
|
||||
} else if (length >= 6 && memcmp(line, "ERROR\t", 6) == 0) {
|
||||
payload = line + 6;
|
||||
payload_length = length - 6;
|
||||
color = "lightred";
|
||||
} else {
|
||||
print_error("NeoDrop manager sent an invalid response.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (index = 0; index < payload_length; index++) {
|
||||
unsigned char byte = (unsigned char)payload[index];
|
||||
output[index] = (byte < 0x20U || byte == 0x7fU) ? ' ' : (char)byte;
|
||||
}
|
||||
output[payload_length] = '\0';
|
||||
|
||||
if (color != NULL)
|
||||
weechat_printf(manager_buffer, "%s%s", weechat_color(color), output);
|
||||
else
|
||||
weechat_printf(manager_buffer, "%s", output);
|
||||
}
|
||||
|
||||
static int manager_read_cb(const void *pointer, void *data, int fd)
|
||||
{
|
||||
ssize_t count;
|
||||
size_t consumed = 0;
|
||||
char *newline;
|
||||
|
||||
(void)pointer;
|
||||
(void)data;
|
||||
(void)fd;
|
||||
|
||||
if (receive_length >= RECEIVE_LIMIT - 1U) {
|
||||
print_error("NeoDrop manager response exceeded the line limit.");
|
||||
disconnect_manager(0);
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
count = recv(manager_fd, receive_buffer + receive_length,
|
||||
RECEIVE_LIMIT - receive_length - 1U, 0);
|
||||
if (count < 0 && errno == EINTR)
|
||||
return WEECHAT_RC_OK;
|
||||
if (count <= 0) {
|
||||
disconnect_manager(1);
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
receive_length += (size_t)count;
|
||||
|
||||
while (consumed < receive_length) {
|
||||
size_t available = receive_length - consumed;
|
||||
|
||||
newline = memchr(receive_buffer + consumed, '\n', available);
|
||||
if (newline == NULL)
|
||||
break;
|
||||
print_protocol_line(receive_buffer + consumed,
|
||||
(size_t)(newline - (receive_buffer + consumed)));
|
||||
consumed = (size_t)(newline - receive_buffer) + 1U;
|
||||
}
|
||||
|
||||
if (consumed > 0) {
|
||||
receive_length -= consumed;
|
||||
memmove(receive_buffer, receive_buffer + consumed, receive_length);
|
||||
}
|
||||
if (receive_length >= RECEIVE_LIMIT - 1U) {
|
||||
print_error("NeoDrop manager response exceeded the line limit.");
|
||||
disconnect_manager(0);
|
||||
}
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
static int connect_manager(void)
|
||||
{
|
||||
struct sockaddr_un address;
|
||||
int fd;
|
||||
|
||||
if (manager_fd >= 0)
|
||||
return 1;
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
|
||||
#ifdef SO_NOSIGPIPE
|
||||
{
|
||||
int enabled = 1;
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled)) != 0) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(&address, 0, sizeof(address));
|
||||
address.sun_family = AF_UNIX;
|
||||
memcpy(address.sun_path, CONTROL_SOCKET, sizeof(CONTROL_SOCKET));
|
||||
if (connect(fd, (struct sockaddr *)&address, sizeof(address)) != 0) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
manager_fd = fd;
|
||||
manager_fd_hook = weechat_hook_fd(manager_fd, 1, 0, 0,
|
||||
&manager_read_cb, NULL, NULL);
|
||||
if (manager_fd_hook == NULL) {
|
||||
disconnect_manager(0);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void send_command(const char *command)
|
||||
{
|
||||
static const char prefix[] = "COMMAND\t";
|
||||
char message[SEND_LIMIT];
|
||||
size_t command_length;
|
||||
size_t message_length;
|
||||
size_t sent = 0;
|
||||
|
||||
if (command == NULL || command[0] == '\0')
|
||||
return;
|
||||
|
||||
command_length = strlen(command);
|
||||
if (command_length > SEND_LIMIT - sizeof("COMMAND\t\n")) {
|
||||
print_error("NeoDrop manager command exceeded the line limit.");
|
||||
return;
|
||||
}
|
||||
if (strpbrk(command, "\t\r\n") != NULL) {
|
||||
print_error("NeoDrop manager commands cannot contain tabs or newlines.");
|
||||
return;
|
||||
}
|
||||
if (!connect_manager()) {
|
||||
print_error("Unable to connect to the NeoDrop manager.");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(message, prefix, sizeof(prefix) - 1U);
|
||||
memcpy(message + sizeof(prefix) - 1U, command, command_length);
|
||||
message_length = sizeof(prefix) - 1U + command_length;
|
||||
message[message_length++] = '\n';
|
||||
|
||||
while (sent < message_length) {
|
||||
ssize_t count = send(manager_fd, message + sent, message_length - sent,
|
||||
MSG_NOSIGNAL);
|
||||
|
||||
if (count < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (count <= 0) {
|
||||
disconnect_manager(1);
|
||||
return;
|
||||
}
|
||||
sent += (size_t)count;
|
||||
}
|
||||
}
|
||||
|
||||
static int reconnect_cb(const void *pointer, void *data, int remaining_calls)
|
||||
{
|
||||
(void)pointer;
|
||||
(void)data;
|
||||
(void)remaining_calls;
|
||||
(void)connect_manager();
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
static int input_cb(const void *pointer, void *data, struct t_gui_buffer *buffer,
|
||||
const char *input)
|
||||
{
|
||||
(void)pointer;
|
||||
(void)data;
|
||||
(void)buffer;
|
||||
send_command(input);
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
static int close_cb(const void *pointer, void *data, struct t_gui_buffer *buffer)
|
||||
{
|
||||
(void)pointer;
|
||||
(void)data;
|
||||
(void)buffer;
|
||||
manager_buffer = NULL;
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
static int ensure_buffer(void)
|
||||
{
|
||||
if (manager_buffer == NULL)
|
||||
manager_buffer = weechat_buffer_search("neodrop_manager", "control");
|
||||
if (manager_buffer == NULL) {
|
||||
manager_buffer = weechat_buffer_new("control", &input_cb, NULL, NULL,
|
||||
&close_cb, NULL, NULL);
|
||||
if (manager_buffer == NULL)
|
||||
return 0;
|
||||
}
|
||||
weechat_buffer_set_pointer(manager_buffer, "input_callback", &input_cb);
|
||||
weechat_buffer_set_pointer(manager_buffer, "close_callback", &close_cb);
|
||||
weechat_buffer_set(manager_buffer, "short_name", "NeoDrop");
|
||||
weechat_buffer_set(manager_buffer, "title",
|
||||
"NeoDrop | list | put-text NAME TEXT | show/read/download/delete ID | edit ID | help");
|
||||
weechat_buffer_set(manager_buffer, "hidden", "0");
|
||||
weechat_buffer_set(manager_buffer, "notify", "3");
|
||||
weechat_buffer_set(manager_buffer, "localvar_set_no_log", "1");
|
||||
weechat_buffer_set(manager_buffer, "localvar_set_pinned", "true");
|
||||
weechat_buffer_set(manager_buffer, "localvar_set_type", "other");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int command_cb(const void *pointer, void *data, struct t_gui_buffer *buffer,
|
||||
int argc, char **argv, char **argv_eol)
|
||||
{
|
||||
const char *command = NULL;
|
||||
int background_registration;
|
||||
|
||||
(void)pointer;
|
||||
(void)data;
|
||||
(void)buffer;
|
||||
(void)argv;
|
||||
|
||||
if (argc > 1 && argv_eol != NULL && argv_eol[1] != NULL && argv_eol[1][0] != '\0')
|
||||
command = argv_eol[1];
|
||||
background_registration = command != NULL && strncmp(command, "register ", 9) == 0;
|
||||
|
||||
if (!ensure_buffer())
|
||||
return WEECHAT_RC_ERROR;
|
||||
|
||||
if (!background_registration)
|
||||
weechat_buffer_set(manager_buffer, "display", "1");
|
||||
if (command != NULL)
|
||||
send_command(command);
|
||||
else
|
||||
send_command("list");
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[])
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
weechat_plugin = plugin;
|
||||
|
||||
if (!ensure_buffer())
|
||||
return WEECHAT_RC_ERROR;
|
||||
|
||||
if (weechat_hook_command("drops", "open/control NeoDrop manager",
|
||||
"list || status || help || put-text <name> <text> || show <selector> || read <selector> || download <selector> || delete <selector> || edit <selector> || line <number> <text> || insert <number> <text> || delete-line <number> || save || cancel",
|
||||
"Manage encrypted NeoDrop files and text revisions",
|
||||
"list|status|help|put-text|show|read|download|delete|edit|line|insert|delete-line|save|cancel",
|
||||
&command_cb, NULL, NULL) == NULL)
|
||||
return WEECHAT_RC_ERROR;
|
||||
if (weechat_hook_timer(5000, 0, 0, &reconnect_cb, NULL, NULL) == NULL)
|
||||
return WEECHAT_RC_ERROR;
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
|
||||
int weechat_plugin_end(struct t_weechat_plugin *plugin)
|
||||
{
|
||||
(void)plugin;
|
||||
disconnect_manager(0);
|
||||
return WEECHAT_RC_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user