forked from menubboi/HerokuShell-Rclone
-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy patheditor.js
More file actions
132 lines (114 loc) · 4.35 KB
/
Copy patheditor.js
File metadata and controls
132 lines (114 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* Implements a simple select-replace file editor in Telegram.
* It works as follows:
*
* 1. The user invokes the editor with a non-empty file.
* 2. The contents of the file are posted as a message.
* 3. The user replies to that message with (part of) the text.
* The bot will locate that substring in the file contents and track the message.
* 4. The user edits his message.
* The bot will then replace the original substring, save the file and edit its message.
* If there are any problems with saving the file, the editor may detach.
*
* NOTE: sync I/O is used for simplicity; be careful! (TODO)
**/
var fs = require("fs");
var escapeHtml = require("escape-html");
var utils = require("./utils");
function ChunkedString(text) {
this.text = text;
this.chunks = [];
}
ChunkedString.prototype.findAcquire = function findAcquire(text) {
if (text.length == 0) throw Error("Empty find text not allowed");
var index = this.text.indexOf(text);
if (index == -1)
throw Error("The substring was not found. Wrapping in tildes may be necessary.");
if (index != this.text.lastIndexOf(text))
throw Error("There are multiple instances of the passed substring");
return this.acquire(index, text.length);
};
ChunkedString.prototype.acquire = function acquire(offset, length) {
if (offset < 0 || length <= 0 || offset + length > this.text.length)
throw Error("Invalid coordinates");
for (var i = 0; i < this.chunks.length; i++) {
var c = this.chunks[i];
if (offset + length > c.offset || c.offset + c.text.length > offset)
throw Error("Chunk overlaps");
}
var chunk = { offset: offset, text: this.text.substring(offset, offset + length) };
this.chunks.push(chunk);
return chunk;
};
ChunkedString.prototype.release = function release(chunk) {
if (this.chunks.indexOf(chunk) == -1) throw Error("Invalid chunk given");
this.chunks.splice(index, 1);
};
ChunkedString.prototype.modify = function modify(chunk, text) {
if (this.chunks.indexOf(chunk) == -1) throw Error("Invalid chunk given");
if (text.length == 0) throw Error("Empty replacement not allowed");
var end = chunk.offset + chunk.text.length;
this.text = this.text.substring(0, chunk.offset) + text + this.text.substring(end);
var diff = text.length - chunk.text.length;
chunk.text = text;
this.chunks.forEach(function (c) {
if (c.offset > chunk.offset) c.offset += diff;
});
};
function Editor(reply, file, encoding) {
if (!encoding) encoding = "utf-8";
this.reply = reply;
this.file = file;
this.encoding = encoding;
// TODO: support for longer files (paginated, etc.)
// FIXME: do it correctly using fd, keeping it open
var contents = fs.readFileSync(file, encoding);
if (contents.length > 1500 || contents.split("\n") > 50)
throw Error("The file is too long");
this.contents = new ChunkedString(contents);
this.chunks = {}; // associates each message ID to an active chunk
this.message = new utils.EditedMessage(reply, this._render(), "HTML");
this.fileTouched = false;
}
Editor.prototype._render = function _render() {
if (!this.contents.text.trim()) return "<em>(empty file)</em>";
return "<pre>" + escapeHtml(this.contents.text) + "</pre>";
};
Editor.prototype.handleReply = function handleReply(msg) {
this.message.idPromise.then(function (id) {
if (this.detached) return;
if (msg.reply.id != id) return;
try {
this.chunks[msg.id] = this.contents.findAcquire(msg.text);
} catch (e) {
this.reply.html("%s", e.message);
}
}.bind(this));
};
Editor.prototype.handleEdit = function handleEdit(msg) {
if (this.detached) return false;
if (!Object.hasOwnProperty.call(this.chunks, msg.id)) return false;
this.contents.modify(this.chunks[msg.id], msg.text);
this.attemptSave();
return true;
};
Editor.prototype.attemptSave = function attemptSave() {
this.fileTouched = true;
process.nextTick(function () {
if (!this.fileTouched) return;
if (this.detached) return;
this.fileTouched = false;
// TODO: check for file external modification, fail then
try {
fs.writeFileSync(this.file, this.contents.text, this.encoding);
} catch (e) {
this.reply.html("Couldn't save file: %s", e.message);
return;
}
this.message.edit(this._render());
}.bind(this));
};
Editor.prototype.detach = function detach() {
this.detached = true;
};
module.exports.Editor = Editor;