Browse Source

* Мейлер теперь позволяет использование в качестве фетчера или сендера.
* Упразднён атавизм в виде разных мейлеров для разных форматов базы. Теперь он берёт нужные функции из api.
* Добавлены описания форматов баз сообщений и краткая справка по мейлеру.

Andrew Lobanov 1 year ago
parent
commit
137572e6af
13 changed files with 130 additions and 979 deletions
  1. 1 3
      README.md
  2. 3 0
      ait_readme.txt
  3. 1 1
      api/aio.py
  4. 2 2
      api/ait.py
  5. 3 2
      api/txt.py
  6. 0 8
      caesium.def.cfg
  7. 23 0
      docs/formats.md
  8. 23 0
      docs/mailer.md
  9. 0 251
      fetcher.py
  10. 0 263
      fetcher_aio.py
  11. 74 34
      mailer.py
  12. 0 315
      mailer_aio.py
  13. 0 100
      sender.py

File diff suppressed because it is too large
+ 1 - 3
README.md


+ 3 - 0
ait_readme.txt

@@ -0,0 +1,3 @@
1
+ait (all in two)
2
+
3
+Представляет собой формат aio, расширенный дополнительным файлом индекса, что существенно ускоряет работу.

+ 1 - 1
api/aio.py

@@ -46,7 +46,7 @@ def add_to_carbonarea(msgid, msgbody):
46 46
     codecs.open("aio/carbonarea.aio", "a", "utf-8").write(msgid + ":" + chr(15).join(msg) + "\n")
47 47
 
48 48
 def save_message(msgid, msgbody):
49
-    codecs.open("aio/" + msgbody[1] + ".aio", "w", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
49
+    codecs.open("aio/" + msgbody[1] + ".aio", "a", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
50 50
 
51 51
 def get_favorites_list():
52 52
     return codecs.open("aio/favorites.aio", "r", "utf-8").read().split("\n")

+ 2 - 2
api/ait.py

@@ -49,8 +49,8 @@ def add_to_carbonarea(msgid, msgbody):
49 49
     codecs.open("ait/carbonarea.mat", "a", "utf-8").write(msgid + ":" + chr(15).join(msg) + "\n")
50 50
 
51 51
 def save_message(msgid, msgbody):
52
-    codecs.open("ait/" + msgbody[1] + ".iat", "w", "utf-8").write(msgid + "\n")
53
-    codecs.open("ait/" + msgbody[1] + ".mat", "w", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
52
+    codecs.open("ait/" + msgbody[1] + ".iat", "a", "utf-8").write(msgid + "\n")
53
+    codecs.open("ait/" + msgbody[1] + ".mat", "a", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
54 54
 
55 55
 def get_favorites_list():
56 56
     return codecs.open("ait/favorites.mat", "r", "utf-8").read().split("\n")

+ 3 - 2
api/txt.py

@@ -1,4 +1,4 @@
1
-import os
1
+import os, codecs
2 2
 
3 3
 def get_echo_length(echo):
4 4
     if os.path.exists("echo/" + echo):
@@ -38,7 +38,8 @@ def add_to_carbonarea(msgid, msgbody):
38 38
     codecs.open("echo/carbonarea", "a", "utf-8").write(msgid + "\n")
39 39
 
40 40
 def save_message(msgid, msgbody):
41
-    codecs.open("msg/" + msgid, "w", "utf-8").write(msgbody)
41
+    codecs.open("echo/" + msgbody[1], "a", "utf-8").write(msgid + "\n")
42
+    codecs.open("msg/" + msgid, "w", "utf-8").write("\n".join(msgbody))
42 43
 
43 44
 def get_favorites_list():
44 45
     return open("echo/favorites", "r").read().split("\n")

+ 0 - 8
caesium.def.cfg

@@ -1,14 +1,6 @@
1 1
 editor nano
2 2
 theme default
3 3
 
4
-# Если вы хотите использовать отправку и получение сообщений раздельно, то
5
-# раскомментируйте строки вызова фетчера и сендера и закомментируйте строки
6
-# вызова мейлера
7
-
8
-#fetch ./fetcher.py -w -n %node -e %echoareas -to %to
9
-#clone ./fetcher.py -w -n %node -e %echoareas -c %clone -to %to
10
-#send ./sender.py -w -n %node -m %nodename -a %auth
11
-
12 4
 fetch ./mailer.py -w -n %node -m %nodename -a %auth -e %echoareas -to %to
13 5
 clone ./mailer.py -w -n %node -m %nodename -a %auth -e %echoareas -c %clone -to %to
14 6
 

File diff suppressed because it is too large
+ 23 - 0
docs/formats.md


+ 23 - 0
docs/mailer.md

@@ -0,0 +1,23 @@
1
+mailer
2
+======
3
+
4
+Мейлер — отдельная программа для обмена сообщениями с нодой. В цезии отправка и получение почты осуществляется посредством вызоава мейлера.
5
+
6
+mailer.py позволяет совмещать отправку и получение сообщений либо же провоизводить их раздельно. Для работы требует наличия в своей директории копии api/.
7
+
8
+Задавать параметры работы мейлера можно как через параметры командной строки, так и через конфигурационный файл.
9
+
10
+Параметры конфигурационного файла:
11
+
12
+  * node — адрес ноды с учётом схем u/. Например node http://idec.spline-online.tk/u/
13
+  * nodename — название ноды. Необходимо для отправки сообщения, так как все исходящие находятся в директории out/<nodename>. Для адекватной работы с цезием параметр должен совпадать с одноимённым параметром из конфигурационного файла цезия.
14
+  * auth — строка авторизации. Необходима для отправки сообщений.
15
+  * depth — глубина сканирования индекса на ноде. Позволяет экономить трафик при обмене с нодой. Работает только с узлами idec-сетей, так как требует поддержку расширенной схемы u/e на их стороне.
16
+  * echo — имя эхоконференции для фетчинга.
17
+  * db — тип базы сообщений (txt, aio, ait). Для подробной информации см. файл formats.md.
18
+  * oldmode — принудительное игнорирование расширенной схемы u/e. Может быть полезно для ускорения обмена с узлами ii-сетей.
19
+  * to — имя для определения сообщений, попадающих в карбонку.
20
+  * fetchonly — заставляет мейлер пропускать поиск исходящих сообщений и их отправку.
21
+  * sendonly — заставляет мейлер пропускать поиск новых сообщений у аплинка и их получение.
22
+
23
+Параметры командной строки, можно узнать, вызвав мейлер с опцией -h.

+ 0 - 251
fetcher.py

@@ -1,251 +0,0 @@
1
-#!/usr/bin/env python3
2
-
3
-import urllib.request, base64, codecs, re, os, sys, pickle
4
-
5
-clone = []
6
-counts = {}
7
-remote_counts = {}
8
-full = False
9
-h = False
10
-features = []
11
-ue = False
12
-xc = False
13
-to = False
14
-depth = "200"
15
-
16
-def load_config():
17
-    node = ""
18
-    depth = "200"
19
-    echoareas = []
20
-    f = open(config, "r").read().split("\n")
21
-    for line in f:
22
-        param = line.split(" ")
23
-        if param[0] == "node":
24
-            node = param[1]
25
-        elif param[0] == "depth":
26
-            depth = param[1]
27
-        elif param[0] == "echo":
28
-            echoareas.append(param[1])
29
-    return node, depth, echoareas
30
-
31
-def check_directories():
32
-    if not os.path.exists("echo"):
33
-        os.makedirs("echo")
34
-    if not os.path.exists("msg"):
35
-        os.makedirs("msg")
36
-
37
-def separate(l, step=40):
38
-    for x in range(0, len(l), step):
39
-        yield l[x:x+step]
40
-
41
-def get_features():
42
-    global features
43
-    try:
44
-        r = urllib.request.Request(node + "x/features")
45
-        with urllib.request.urlopen(r) as f:
46
-            features = f.read().decode("utf-8").split("\n")
47
-    except:
48
-        features = []
49
-
50
-def check_features():
51
-    global ue, xc
52
-    ue = "u/e" in features
53
-    xc = "x/c" in features
54
-
55
-def load_counts():
56
-    global counts
57
-    if os.path.exists("counts.lst"):
58
-        f = open("counts.lst", "rb")
59
-        counts = pickle.load(f)
60
-        f.close()
61
-    else:
62
-        counts[node] = {}
63
-    if not node in counts:
64
-        counts[node] = {}
65
-
66
-def save_counts():
67
-    counts[node] = remote_counts
68
-    f = open("counts.lst", "wb")
69
-    pickle.dump(counts, f)
70
-    f.close()
71
-
72
-def get_remote_counts():
73
-    counts = {}
74
-    r = urllib.request.Request(node + "x/c/" + "/".join(echoareas))
75
-    with urllib.request.urlopen(r) as f:
76
-        c = f.read().decode("utf-8").split("\n")
77
-    for count in c:
78
-        echoarea = count.split(":")
79
-        if len(echoarea) > 1:
80
-            counts[echoarea[0]] = echoarea[1]
81
-    return counts
82
-
83
-def calculate_offset():
84
-    global depth
85
-    n = False
86
-    offset = 0
87
-    for echoarea in echoareas:
88
-        if not echoarea in counts[node]:
89
-            n = True
90
-        else:
91
-            if not echoarea in clone and int(remote_counts[echoarea]) - int(counts[node][echoarea]) > offset:
92
-                offset = int(remote_counts[echoarea]) - int(counts[node][echoarea])
93
-    if not n:
94
-        depth = offset
95
-
96
-def get_echoarea(echoarea):
97
-    try:
98
-        return open("echo/" + echoarea, "r").read().split("\n")
99
-    except:
100
-        return []
101
-
102
-def get_msg_list():
103
-    global clone
104
-    msg_list = []
105
-    fetch_echoareas = []
106
-    if not full and ue:
107
-        for echoarea in echoareas:
108
-            if not echoarea in clone and (not echoarea in counts[node] or int(counts[node][echoarea]) < int(remote_counts[echoarea])):
109
-                fetch_echoareas.append(echoarea)
110
-    else:
111
-        clone = echoareas
112
-    if len(clone) > 0:
113
-        r = urllib.request.Request(node + "u/e/" + "/".join(clone))
114
-        with urllib.request.urlopen(r) as f:
115
-            lines = f.read().decode("utf-8").split("\n")
116
-            for line in lines:
117
-                if len(line) > 0:
118
-                    msg_list.append(line)
119
-    if len(fetch_echoareas) > 0 and int(depth) > 0:
120
-        r = urllib.request.Request(node + "u/e/" + "/".join(fetch_echoareas) + "/-%s:%s" %(depth, depth))
121
-        with urllib.request.urlopen(r) as f:
122
-            lines = f.read().decode("utf-8").split("\n")
123
-            for line in lines:
124
-                if len(line) > 0:
125
-                    msg_list.append(line)
126
-    return msg_list
127
-
128
-def get_bundle(node, msgids):
129
-    bundle = []
130
-    r = urllib.request.Request(node + "u/m/" + msgids)
131
-    with urllib.request.urlopen(r) as f:
132
-        bundle = f.read().decode("utf-8").split("\n")
133
-    return bundle
134
-
135
-def debundle(bundle):
136
-    for msg in bundle:
137
-        if msg:
138
-            m = msg.split(":")
139
-            msgid = m[0]
140
-            if len(msgid) == 20 and m[1]:
141
-                msgbody = base64.b64decode(m[1].encode("ascii")).decode("utf8")
142
-                codecs.open("msg/" + msgid, "w", "utf-8").write(msgbody)
143
-                codecs.open("echo/" + msgbody.split("\n")[1], "a", "utf-8").write(msgid + "\n")
144
-                if to:
145
-                    try:
146
-                        carbonarea = open("echo/carbonarea", "r").read().split("\n")
147
-                    except:
148
-                        carbonarea = []
149
-                    if msgbody.split("\n")[5] in to and not msgid in carbonarea:
150
-                        codecs.open("echo/carbonarea", "a", "utf-8").write(msgid + "\n")
151
-
152
-def echo_filter(ea):
153
-    rr = re.compile(r'^[a-z0-9_!.-]{1,60}\.[a-z0-9_!.-]{1,60}$')
154
-    if rr.match(ea): return True
155
-
156
-def get_mail():
157
-    fetch_msg_list = []
158
-    print("Получение индекса от ноды...")
159
-    remote_msg_list = get_msg_list()
160
-    print("Построение разностного индекса...")
161
-    for line in remote_msg_list:
162
-        if echo_filter(line):
163
-            if line in clone and ue:
164
-                try:
165
-                    os.remove("echo/" + line)
166
-                except:
167
-                    None
168
-            local_index = get_echoarea(line)
169
-        else:
170
-            if not line in local_index:
171
-                fetch_msg_list.append(line)
172
-    msg_list_len = str(len(fetch_msg_list))
173
-    if len(fetch_msg_list) > 0:
174
-        count = 0
175
-        for get_list in separate(fetch_msg_list):
176
-            count = count + len(get_list)
177
-            print("\rПолучение сообщений: " + str(count) + "/"  + msg_list_len, end="")
178
-            debundle(get_bundle(node, "/".join(get_list)))
179
-    else:
180
-        print("Новых сообщений не обнаружено.", end="")
181
-    print()
182
-
183
-def check_new_echoareas():
184
-    local_base = os.listdir("echo/")
185
-    n = False
186
-    for echoarea in echoareas:
187
-        if not echoarea in local_base:
188
-            n = True
189
-    return n
190
-
191
-def show_help():
192
-    print("Usage: fetcher.py [-f filename] [-n node] [-e echoarea1,echoarea2,...] [-d depth] [-c echoarea1,echoarea2,...] [-o] [-to name1,name2...] [-h].")
193
-    print()
194
-    print("  -f filename  load config file. Default idec-fetcher.cfg.")
195
-    print("  -n node      node address.")
196
-    print("  -e echoareas echoareas for fetch.")
197
-    print("  -d depth     fetch messages with an offset to a predetermined depth. Default 200.")
198
-    print("  -c echoareas clone echoareas from node.")
199
-    print("  -o           old mode. Get full index from nore.")
200
-    print("  -to names    names for put messages to carbonarea.")
201
-    print("  -h           this message.")
202
-    print()
203
-    print("If -f not exist, script will load config from current directory with name\nfetcher.cfg.")
204
-
205
-args = sys.argv[1:]
206
-
207
-conf = "-f" in args
208
-if conf:
209
-    config = args[args.index("-f") + 1]
210
-else:
211
-    config = "fetcher.cfg"
212
-if "-c" in args:
213
-    clone = args[args.index("-c") + 1].split(",")
214
-full = "-o" in args
215
-if "-d" in args:
216
-    depth = args[args.index("-d") + 1]
217
-h = "-h" in args
218
-if "-n" in args:
219
-    node = args[args.index("-n") + 1]
220
-if "-e" in args:
221
-    echoareas = args[args.index("-e") + 1].split(",")
222
-if "-to" in args:
223
-    to = args[args.index("-to") + 1].split(",")
224
-wait = "-w" in args
225
-
226
-if h:
227
-    show_help()
228
-    quit()
229
-
230
-if not "-n" in args and not "-e" in args and not os.path.exists(config):
231
-    print("Config file not found.")
232
-    quit()
233
-
234
-check_directories()
235
-if not "-n" in args or not "-e" in args:
236
-    node, depth, echoareas = load_config()
237
-print("Работа с " + node)
238
-print("Получение списка возможностей ноды...")
239
-get_features()
240
-check_features()
241
-if xc:
242
-    load_counts()
243
-    print("Получение количества сообщений в конференциях...")
244
-    remote_counts = get_remote_counts()
245
-    calculate_offset()
246
-get_mail()
247
-if xc:
248
-    save_counts()
249
-if wait:
250
-    input("Нажмите Enter для продолжения.")
251
-    print()

+ 0 - 263
fetcher_aio.py

@@ -1,263 +0,0 @@
1
-#!/usr/bin/env python3
2
-
3
-import urllib.request, base64, codecs, re, os, sys, pickle
4
-
5
-clone = []
6
-counts = {}
7
-remote_counts = {}
8
-full = False
9
-h = False
10
-features = []
11
-ue = False
12
-xc = False
13
-to = False
14
-depth = "200"
15
-
16
-def load_config():
17
-    node = ""
18
-    depth = "200"
19
-    echoareas = []
20
-    f = open(config, "r").read().split("\n")
21
-    for line in f:
22
-        param = line.split(" ")
23
-        if param[0] == "node":
24
-            node = param[1]
25
-        elif param[0] == "depth":
26
-            depth = param[1]
27
-        elif param[0] == "echo":
28
-            echoareas.append(param[1])
29
-    return node, depth, echoareas
30
-
31
-def check_directories():
32
-    if not os.path.exists("aio"):
33
-        os.makedirs("aio")
34
-
35
-def separate(l, step=40):
36
-    for x in range(0, len(l), step):
37
-        yield l[x:x+step]
38
-
39
-def get_features():
40
-    global features
41
-    try:
42
-        r = urllib.request.Request(node + "x/features")
43
-        with urllib.request.urlopen(r) as f:
44
-            features = f.read().decode("utf-8").split("\n")
45
-    except:
46
-        features = []
47
-
48
-def check_features():
49
-    global ue, xc
50
-    ue = "u/e" in features
51
-    xc = "x/c" in features
52
-
53
-def load_counts():
54
-    global counts
55
-    if os.path.exists("counts.lst"):
56
-        f = open("counts.lst", "rb")
57
-        counts = pickle.load(f)
58
-        f.close()
59
-    else:
60
-        counts[node] = {}
61
-    if not node in counts:
62
-        counts[node] = {}
63
-
64
-def save_counts():
65
-    counts[node] = remote_counts
66
-    f = open("counts.lst", "wb")
67
-    pickle.dump(counts, f)
68
-    f.close()
69
-
70
-def get_remote_counts():
71
-    counts = {}
72
-    r = urllib.request.Request(node + "x/c/" + "/".join(echoareas))
73
-    with urllib.request.urlopen(r) as f:
74
-        c = f.read().decode("utf-8").split("\n")
75
-    for count in c:
76
-        echoarea = count.split(":")
77
-        if len(echoarea) > 1:
78
-            counts[echoarea[0]] = echoarea[1]
79
-    return counts
80
-
81
-def calculate_offset():
82
-    global depth
83
-    n = False
84
-    offset = 0
85
-    for echoarea in echoareas:
86
-        if not echoarea in counts[node]:
87
-            n = True
88
-        else:
89
-            if not echoarea in clone and int(remote_counts[echoarea]) - int(counts[node][echoarea]) > offset:
90
-                offset = int(remote_counts[echoarea]) - int(counts[node][echoarea])
91
-    if not n:
92
-        depth = offset
93
-
94
-def get_echoarea(echo):
95
-    if os.path.exists("aio/" + echo + ".aio"):
96
-        f = codecs.open("aio/" + echo + ".aio", "r", "utf-8").read().split("\n")
97
-        msgids = []
98
-        for line in f:
99
-            if len(line) > 0:
100
-                msgids.append(line.split(":")[0])
101
-    else:
102
-        msgids = []
103
-    return msgids
104
-
105
-def get_msg_list():
106
-    global clone
107
-    msg_list = []
108
-    fetch_echoareas = []
109
-    if not full and ue:
110
-        for echoarea in echoareas:
111
-            if not echoarea in clone and (not echoarea in counts[node] or int(counts[node][echoarea]) < int(remote_counts[echoarea])):
112
-                fetch_echoareas.append(echoarea)
113
-    else:
114
-        clone = echoareas
115
-    if len(clone) > 0:
116
-        r = urllib.request.Request(node + "u/e/" + "/".join(clone))
117
-        with urllib.request.urlopen(r) as f:
118
-            lines = f.read().decode("utf-8").split("\n")
119
-            for line in lines:
120
-                if len(line) > 0:
121
-                    msg_list.append(line)
122
-    if len(fetch_echoareas) > 0 and int(depth) > 0:
123
-        r = urllib.request.Request(node + "u/e/" + "/".join(fetch_echoareas) + "/-%s:%s" %(depth, depth))
124
-        with urllib.request.urlopen(r) as f:
125
-            lines = f.read().decode("utf-8").split("\n")
126
-            for line in lines:
127
-                if len(line) > 0:
128
-                    msg_list.append(line)
129
-    return msg_list
130
-
131
-def get_bundle(node, msgids):
132
-    bundle = []
133
-    r = urllib.request.Request(node + "u/m/" + msgids)
134
-    with urllib.request.urlopen(r) as f:
135
-        bundle = f.read().decode("utf-8").split("\n")
136
-    return bundle
137
-
138
-def get_carbonarea():
139
-    try:
140
-        f = open("aio/carbonarea.aio", "r").read().split("\n")
141
-        carbonarea = []
142
-        for line in f:
143
-            carbonarea.append(line.split(":")[0])
144
-        return carbonarea
145
-    except:
146
-        return []
147
-
148
-def add_to_carbonarea(msgid, msgbody):
149
-    codecs.open("aio/carbonarea.aio", "a", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
150
-
151
-def debundle(bundle):
152
-    for msg in bundle:
153
-        if msg:
154
-            m = msg.split(":")
155
-            msgid = m[0]
156
-            if len(msgid) == 20 and m[1]:
157
-                msgbody = base64.b64decode(m[1].encode("ascii")).decode("utf8").split("\n")
158
-                codecs.open("aio/" + msgbody[1] + ".aio", "a", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
159
-                if to:
160
-                    carbonarea = get_carbonarea()
161
-                    if msgbody[5] in to and not msgid in carbonarea:
162
-                        add_to_carbonarea(msgid, msgbody)
163
-
164
-def echo_filter(ea):
165
-    rr = re.compile(r'^[a-z0-9_!.-]{1,60}\.[a-z0-9_!.-]{1,60}$')
166
-    if rr.match(ea): return True
167
-
168
-def get_mail():
169
-    fetch_msg_list = []
170
-    print("Получение индекса от ноды...")
171
-    remote_msg_list = get_msg_list()
172
-    print("Построение разностного индекса...")
173
-    for line in remote_msg_list:
174
-        if echo_filter(line):
175
-            if line in clone and ue:
176
-                try:
177
-                    os.remove("aio/" + line + ".aio")
178
-                except:
179
-                    None
180
-            local_index = get_echoarea(line)
181
-        else:
182
-            if not line in local_index:
183
-                fetch_msg_list.append(line)
184
-    msg_list_len = str(len(fetch_msg_list))
185
-    if len(fetch_msg_list) > 0:
186
-        count = 0
187
-        for get_list in separate(fetch_msg_list):
188
-            count = count + len(get_list)
189
-            print("\rПолучение сообщений: " + str(count) + "/"  + msg_list_len, end="")
190
-            debundle(get_bundle(node, "/".join(get_list)))
191
-    else:
192
-        print("Новых сообщений не обнаружено.", end="")
193
-    print()
194
-
195
-def check_new_echoareas():
196
-    local_base = os.listdir("echo/")
197
-    n = False
198
-    for echoarea in echoareas:
199
-        if not echoarea in local_base:
200
-            n = True
201
-    return n
202
-
203
-def show_help():
204
-    print("Usage: fetcher.py [-f filename] [-n node] [-e echoarea1,echoarea2,...] [-d depth] [-c echoarea1,echoarea2,...] [-o] [-to name1,name2...] [-h].")
205
-    print()
206
-    print("  -f filename  load config file. Default idec-fetcher.cfg.")
207
-    print("  -n node      node address.")
208
-    print("  -e echoareas echoareas for fetch.")
209
-    print("  -d depth     fetch messages with an offset to a predetermined depth. Default 200.")
210
-    print("  -c echoareas clone echoareas from node.")
211
-    print("  -o           old mode. Get full index from nore.")
212
-    print("  -to names    names for put messages to carbonarea.")
213
-    print("  -h           this message.")
214
-    print()
215
-    print("If -f not exist, script will load config from current directory with name\nfetcher.cfg.")
216
-
217
-args = sys.argv[1:]
218
-
219
-conf = "-f" in args
220
-if conf:
221
-    config = args[args.index("-f") + 1]
222
-else:
223
-    config = "fetcher.cfg"
224
-if "-c" in args:
225
-    clone = args[args.index("-c") + 1].split(",")
226
-full = "-o" in args
227
-if "-d" in args:
228
-    depth = args[args.index("-d") + 1]
229
-h = "-h" in args
230
-if "-n" in args:
231
-    node = args[args.index("-n") + 1]
232
-if "-e" in args:
233
-    echoareas = args[args.index("-e") + 1].split(",")
234
-if "-to" in args:
235
-    to = args[args.index("-to") + 1].split(",")
236
-wait = "-w" in args
237
-
238
-if h:
239
-    show_help()
240
-    quit()
241
-
242
-if not "-n" in args and not "-e" in args and not os.path.exists(config):
243
-    print("Config file not found.")
244
-    quit()
245
-
246
-check_directories()
247
-if not "-n" in args or not "-e" in args:
248
-    node, depth, echoareas = load_config()
249
-print("Работа с " + node)
250
-print("Получение списка возможностей ноды...")
251
-get_features()
252
-check_features()
253
-if xc:
254
-    load_counts()
255
-    print("Получение количества сообщений в конференциях...")
256
-    remote_counts = get_remote_counts()
257
-    calculate_offset()
258
-get_mail()
259
-if xc:
260
-    save_counts()
261
-if wait:
262
-    input("Нажмите Enter для продолжения.")
263
-    print()

+ 74 - 34
mailer.py

@@ -13,9 +13,11 @@ xc = False
13 13
 to = False
14 14
 depth = "200"
15 15
 auth = False
16
+db = 0
17
+fetchonly = False
18
+sendonly = False
16 19
 
17 20
 def load_config():
18
-    global node, depth, echoareas, nodename
19 21
     node = ""
20 22
     depth = "200"
21 23
     echoareas = []
@@ -33,15 +35,38 @@ def load_config():
33 35
             depth = param[1]
34 36
         elif param[0] == "echo":
35 37
             echoareas.append(param[1])
36
-    return node, nodename, auth, depth, echoareas
38
+        elif param[0] == "db":
39
+            db = param[1]
40
+        elif param[0] == "oldmode":
41
+            full = True
42
+        elif param[0] == "to":
43
+            to = param[1].split(",")
44
+        elif param[0] == "fetchonly":
45
+            fetchonly = True
46
+        elif param[0] == "sendonly":
47
+            sendonly = True
48
+    return node, nodename, auth, depth, echoareas, db, oldmode, to, fetchonly, sendonly
37 49
 
38 50
 def check_directories():
39
-    if not os.path.exists("echo"):
40
-        os.makedirs("echo")
41
-    if not os.path.exists("msg"):
42
-        os.makedirs("msg")
43 51
     if not os.path.exists("out"):
44
-        os.makedirs("out")
52
+        os.mkdir("out")
53
+    if not fetchonly and not os.path.exists("out/" + nodename):
54
+        os.mkdir("out/" + nodename)
55
+    if db == 0:
56
+        if not os.path.exists("echo"):
57
+            os.mkdir("echo")
58
+        if not os.path.exists("msg"):
59
+            os.mkdir("msg")
60
+        if not os.path.exists("echo/favorites"):
61
+            open("echo/favorites", "w")
62
+        if not os.path.exists("echo/carbonarea"):
63
+            open("echo/carbonarea", "w")
64
+    elif db == 1:
65
+        if not os.path.exists("aio"):
66
+            os.mkdir("aio")
67
+    elif db == 2:
68
+        if not os.path.exists("ait"):
69
+            os.mkdir("ait")
45 70
 
46 71
 def make_toss():
47 72
     lst = [x for x in os.listdir("out/" + nodename) if x.endswith(".out")]
@@ -135,12 +160,6 @@ def calculate_offset():
135 160
     if not n:
136 161
         depth = offset
137 162
 
138
-def get_echoarea(echoarea):
139
-    try:
140
-        return open("echo/" + echoarea, "r").read().split("\n")
141
-    except:
142
-        return []
143
-
144 163
 def get_msg_list():
145 164
     global clone
146 165
     msg_list = []
@@ -180,16 +199,15 @@ def debundle(bundle):
180 199
             m = msg.split(":")
181 200
             msgid = m[0]
182 201
             if len(msgid) == 20 and m[1]:
183
-                msgbody = base64.b64decode(m[1].encode("ascii")).decode("utf8")
184
-                codecs.open("msg/" + msgid, "w", "utf-8").write(msgbody)
185
-                codecs.open("echo/" + msgbody.split("\n")[1], "a", "utf-8").write(msgid + "\n")
202
+                msgbody = base64.b64decode(m[1].encode("ascii")).decode("utf8").split("\n")
203
+                save_message(msgid, msgbody)
186 204
                 if to:
187 205
                     try:
188
-                        carbonarea = open("echo/carbonarea", "r").read().split("\n")
206
+                        carbonarea = get_carbonarea()
189 207
                     except:
190 208
                         carbonarea = []
191
-                    if msgbody.split("\n")[5] in to and not msgid in carbonarea:
192
-                        codecs.open("echo/carbonarea", "a", "utf-8").write(msgid + "\n")
209
+                    if msgbody[5] in to and not msgid in carbonarea:
210
+                        add_to_carbonarea(msgid, msgbody)
193 211
 
194 212
 def echo_filter(ea):
195 213
     rr = re.compile(r'^[a-z0-9_!.-]{1,60}\.[a-z0-9_!.-]{1,60}$')
@@ -207,7 +225,7 @@ def get_mail():
207 225
                     os.remove("echo/" + line)
208 226
                 except:
209 227
                     None
210
-            local_index = get_echoarea(line)
228
+            local_index = get_echo_msgids(line)
211 229
         else:
212 230
             if not line in local_index:
213 231
                 fetch_msg_list.append(line)
@@ -234,6 +252,7 @@ def show_help():
234 252
     print("Usage: mailer.py [-f filename] [-n node] [-e echoarea1,echoarea2,...] [-d depth] [-c echoarea1,echoarea2,...] [-o] [-to name1,name2...] [-h].")
235 253
     print()
236 254
     print("  -f filename  load config file. Default idec-fetcher.cfg.")
255
+    print("  -db db type  set database type (txt, aio or ait).")
237 256
     print("  -n node      node address.")
238 257
     print("  -m nodename  nodename for search .out messages.")
239 258
     print("  -a authkey   authkey.")
@@ -242,6 +261,8 @@ def show_help():
242 261
     print("  -c echoareas clone echoareas from node.")
243 262
     print("  -o           old mode. Get full index from nore.")
244 263
     print("  -to names    names for put messages to carbonarea.")
264
+    print("  -fetchonly   fetch messages only.")
265
+    print("  -sendonly    send messages only.")
245 266
     print("  -h           this message.")
246 267
     print()
247 268
     print("If -f not exist, script will load config from current directory with name\nmailer.cfg.")
@@ -269,7 +290,19 @@ if "-e" in args:
269 290
     echoareas = args[args.index("-e") + 1].split(",")
270 291
 if "-to" in args:
271 292
     to = args[args.index("-to") + 1].split(",")
293
+if "-db" in args:
294
+    db = args[args.index("-db") + 1]
295
+if db == "txt":
296
+        db = 0
297
+    elif db == "aio":
298
+        db = 1
299
+    elif db == "ait":
300
+        db = 2
272 301
 wait = "-w" in args
302
+if "-fetchonly" in args:
303
+    fetchonly = True
304
+if "-sendonly" in args:
305
+    sendonly = True
273 306
 
274 307
 if h:
275 308
     show_help()
@@ -280,23 +313,30 @@ if not "-n" in args and not "-e" in args and not os.path.exists(config):
280 313
     quit()
281 314
 
282 315
 check_directories()
283
-if not "-n" in args or not "-e" in args:
284
-    node, nodename, auth, depth, echoareas = load_config()
316
+if (not sendonly and (not "-n" in args or not "-e" in args)) or (not fetchonly and not "-n" in args and not "-a" in args):
317
+    node, nodename, auth, depth, echoareas, db, oldmode, to, fetchonly, sendonly = load_config()
318
+if db == 0:
319
+    from api.txt import *
320
+elif db == 1:
321
+    from api.aio import *
322
+elif db == 2:
323
+    from api.ait import *
285 324
 print("Работа с " + node)
286
-if auth:
325
+if auth and not fetchonly:
287 326
     make_toss()
288 327
     send_mail()
289
-print("Получение списка возможностей ноды...")
290
-get_features()
291
-check_features()
292
-if xc:
293
-    load_counts()
294
-    print("Получение количества сообщений в конференциях...")
295
-    remote_counts = get_remote_counts()
296
-    calculate_offset()
297
-get_mail()
298
-if xc:
299
-    save_counts()
328
+if not sendonly:
329
+    print("Получение списка возможностей ноды...")
330
+    get_features()
331
+    check_features()
332
+    if xc:
333
+        load_counts()
334
+        print("Получение количества сообщений в конференциях...")
335
+        remote_counts = get_remote_counts()
336
+        calculate_offset()
337
+        get_mail()
338
+    if xc:
339
+        save_counts()
300 340
 if wait:
301 341
     input("Нажмите Enter для продолжения.")
302 342
     print()

+ 0 - 315
mailer_aio.py

@@ -1,315 +0,0 @@
1
-#!/usr/bin/env python3
2
-
3
-import urllib.request, base64, codecs, re, os, sys, pickle
4
-
5
-clone = []
6
-counts = {}
7
-remote_counts = {}
8
-full = False
9
-h = False
10
-features = []
11
-ue = False
12
-xc = False
13
-to = False
14
-depth = "200"
15
-auth = False
16
-
17
-def load_config():
18
-    global node, depth, echoareas, nodename
19
-    node = ""
20
-    depth = "200"
21
-    echoareas = []
22
-    nodename = "unknown"
23
-    f = open(config, "r").read().split("\n")
24
-    for line in f:
25
-        param = line.split(" ")
26
-        if param[0] == "node":
27
-            node = param[1]
28
-        elif param[0] == "nodename":
29
-            nodename = param[1:]
30
-        elif param[0] == "auth":
31
-            auth = param[1]
32
-        elif param[0] == "depth":
33
-            depth = param[1]
34
-        elif param[0] == "echo":
35
-            echoareas.append(param[1])
36
-    return node, nodename, auth, depth, echoareas
37
-
38
-def check_directories():
39
-    if not os.path.exists("aio"):
40
-        os.makedirs("aio")
41
-    if not os.path.exists("out"):
42
-        os.makedirs("out")
43
-
44
-def make_toss():
45
-    lst = [x for x in os.listdir("out/" + nodename) if x.endswith(".out")]
46
-    for msg in lst:
47
-        text = codecs.open("out/" + nodename + "/%s" % msg, "r", "utf-8").read()
48
-        coded_text = base64.b64encode(text.encode("utf-8"))
49
-        codecs.open("out/" + nodename + "/%s.toss" % msg, "w", "utf-8").write(coded_text.decode("utf-8"))
50
-        os.rename("out/" + nodename + "/%s" % msg, "out/" + nodename + "/%s%s" % (msg, "msg"))
51
-
52
-def send_mail():
53
-    lst = [x for x in sorted(os.listdir("out/" + nodename)) if x.endswith(".toss")]
54
-    max = len(lst)
55
-    n = 1
56
-    try:
57
-        for msg in lst:
58
-            print("\rОтправка сообщения: " + str(n) + "/" + str(max), end="")
59
-            text = codecs.open("out/" + nodename + "/%s" % msg, "r", "utf-8").read()
60
-            data = urllib.parse.urlencode({"tmsg": text,"pauth": auth}).encode("utf-8")
61
-            request = urllib.request.Request(node + "u/point")
62
-            result = urllib.request.urlopen(request, data).read().decode("utf-8")
63
-            if result.startswith("msg ok"):
64
-                os.remove("out/" + nodename + "/%s" % msg)
65
-                n = n + 1
66
-            elif result == "msg big!":
67
-                print("\nERROR: very big message (limit 64K)!")
68
-            elif result == "auth error!":
69
-                print("\nERROR: unknown auth!")
70
-            else:
71
-                print("\nERROR: unknown error!")
72
-        if len(lst) > 0:
73
-            print()
74
-    except:
75
-        print("\nОшибка: не удаётся связаться с нодой.")
76
-
77
-def separate(l, step=40):
78
-    for x in range(0, len(l), step):
79
-        yield l[x:x+step]
80
-
81
-def get_features():
82
-    global features
83
-    try:
84
-        r = urllib.request.Request(node + "x/features")
85
-        with urllib.request.urlopen(r) as f:
86
-            features = f.read().decode("utf-8").split("\n")
87
-    except:
88
-        features = []
89
-
90
-def check_features():
91
-    global ue, xc
92
-    ue = "u/e" in features
93
-    xc = "x/c" in features
94
-
95
-def load_counts():
96
-    global counts
97
-    if os.path.exists("counts.lst"):
98
-        f = open("counts.lst", "rb")
99
-        counts = pickle.load(f)
100
-        f.close()
101
-    else:
102
-        counts[node] = {}
103
-    if not node in counts:
104
-        counts[node] = {}
105
-
106
-def save_counts():
107
-    counts[node] = remote_counts
108
-    f = open("counts.lst", "wb")
109
-    pickle.dump(counts, f)
110
-    f.close()
111
-
112
-def get_remote_counts():
113
-    counts = {}
114
-    r = urllib.request.Request(node + "x/c/" + "/".join(echoareas))
115
-    with urllib.request.urlopen(r) as f:
116
-        c = f.read().decode("utf-8").split("\n")
117
-    for count in c:
118
-        echoarea = count.split(":")
119
-        if len(echoarea) > 1:
120
-            counts[echoarea[0]] = echoarea[1]
121
-    return counts
122
-
123
-def calculate_offset():
124
-    global depth
125
-    n = False
126
-    offset = 0
127
-    for echoarea in echoareas:
128
-        if not echoarea in counts[node]:
129
-            n = True
130
-        else:
131
-            if not echoarea in clone and int(remote_counts[echoarea]) - int(counts[node][echoarea]) > offset:
132
-                offset = int(remote_counts[echoarea]) - int(counts[node][echoarea])
133
-    if not n:
134
-        depth = offset
135
-
136
-def get_echoarea(echo):
137
-    if os.path.exists("aio/" + echo + ".aio"):
138
-        f = codecs.open("aio/" + echo + ".aio", "r", "utf-8").read().split("\n")
139
-        msgids = []
140
-        for line in f:
141
-            if len(line) > 0:
142
-                msgids.append(line.split(":")[0])
143
-    else:
144
-        msgids = []
145
-    return msgids
146
-
147
-def get_msg_list():
148
-    global clone
149
-    msg_list = []
150
-    fetch_echoareas = []
151
-    if not full and ue:
152
-        for echoarea in echoareas:
153
-            if not echoarea in clone and (not echoarea in counts[node] or int(counts[node][echoarea]) < int(remote_counts[echoarea])):
154
-                fetch_echoareas.append(echoarea)
155
-    else:
156
-        clone = echoareas
157
-    if len(clone) > 0:
158
-        r = urllib.request.Request(node + "u/e/" + "/".join(clone))
159
-        with urllib.request.urlopen(r) as f:
160
-            lines = f.read().decode("utf-8").split("\n")
161
-            for line in lines:
162
-                if len(line) > 0:
163
-                    msg_list.append(line)
164
-    if len(fetch_echoareas) > 0 and int(depth) > 0:
165
-        r = urllib.request.Request(node + "u/e/" + "/".join(fetch_echoareas) + "/-%s:%s" %(depth, depth))
166
-        with urllib.request.urlopen(r) as f:
167
-            lines = f.read().decode("utf-8").split("\n")
168
-            for line in lines:
169
-                if len(line) > 0:
170
-                    msg_list.append(line)
171
-    return msg_list
172
-
173
-def get_bundle(node, msgids):
174
-    bundle = []
175
-    r = urllib.request.Request(node + "u/m/" + msgids)
176
-    with urllib.request.urlopen(r) as f:
177
-        bundle = f.read().decode("utf-8").split("\n")
178
-    return bundle
179
-
180
-def get_carbonarea():
181
-    try:
182
-        f = open("aio/carbonarea.aio", "r").read().split("\n")
183
-        carbonarea = []
184
-        for line in f:
185
-            carbonarea.append(line.split(":")[0])
186
-        return carbonarea
187
-    except:
188
-        return []
189
-
190
-def add_to_carbonarea(msgid, msgbody):
191
-    codecs.open("aio/carbonarea.aio", "a", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
192
-
193
-def debundle(bundle):
194
-    for msg in bundle:
195
-        if msg:
196
-            m = msg.split(":")
197
-            msgid = m[0]
198
-            if len(msgid) == 20 and m[1]:
199
-                msgbody = base64.b64decode(m[1].encode("ascii")).decode("utf8").split("\n")
200
-                codecs.open("aio/" + msgbody[1] + ".aio", "a", "utf-8").write(msgid + ":" + chr(15).join(msgbody) + "\n")
201
-                if to:
202
-                    carbonarea = get_carbonarea()
203
-                    if msgbody[5] in to and not msgid in carbonarea:
204
-                        add_to_carbonarea(msgid, msgbody)
205
-
206
-def echo_filter(ea):
207
-    rr = re.compile(r'^[a-z0-9_!.-]{1,60}\.[a-z0-9_!.-]{1,60}$')
208
-    if rr.match(ea): return True
209
-
210
-def get_mail():
211
-    fetch_msg_list = []
212
-    print("Получение индекса от ноды...")
213
-    remote_msg_list = get_msg_list()
214
-    print("Построение разностного индекса...")
215
-    local_index = []
216
-    for line in remote_msg_list:
217
-        if echo_filter(line):
218
-            if line in clone and ue:
219
-                try:
220
-                    os.remove("aio/" + line + ".aio")
221
-                except:
222
-                    None
223
-            local_index = get_echoarea(line)
224
-        else:
225
-            if not line in local_index:
226
-                fetch_msg_list.append(line)
227
-    msg_list_len = str(len(fetch_msg_list))
228
-    if len(fetch_msg_list) > 0:
229
-        count = 0
230
-        for get_list in separate(fetch_msg_list):
231
-            count = count + len(get_list)
232
-            print("\rПолучение сообщений: " + str(count) + "/"  + msg_list_len, end="")
233
-            debundle(get_bundle(node, "/".join(get_list)))
234
-    else:
235
-        print("Новых сообщений не обнаружено.", end="")
236
-    print()
237
-
238
-def check_new_echoareas():
239
-    local_base = os.listdir("echo/")
240
-    n = False
241
-    for echoarea in echoareas:
242
-        if not echoarea in local_base:
243
-            n = True
244
-    return n
245
-
246
-def show_help():
247
-    print("Usage: mailer_aio.py [-f filename] [-n node] [-e echoarea1,echoarea2,...] [-d depth] [-c echoarea1,echoarea2,...] [-o] [-to name1,name2...] [-h].")
248
-    print()
249
-    print("  -f filename  load config file. Default idec-fetcher.cfg.")
250
-    print("  -n node      node address.")
251
-    print("  -m nodename  nodename for search .out messages.")
252
-    print("  -a authkey   authkey.")
253
-    print("  -e echoareas echoareas for fetch.")
254
-    print("  -d depth     fetch messages with an offset to a predetermined depth. Default 200.")
255
-    print("  -c echoareas clone echoareas from node.")
256
-    print("  -o           old mode. Get full index from nore.")
257
-    print("  -to names    names for put messages to carbonarea.")
258
-    print("  -h           this message.")
259
-    print()
260
-    print("If -f not exist, script will load config from current directory with name\nfetcher.cfg.")
261
-
262
-args = sys.argv[1:]
263
-
264
-conf = "-f" in args
265
-if conf:
266
-    config = args[args.index("-f") + 1]
267
-else:
268
-    config = "fetcher.cfg"
269
-if "-c" in args:
270
-    clone = args[args.index("-c") + 1].split(",")
271
-full = "-o" in args
272
-if "-d" in args:
273
-    depth = args[args.index("-d") + 1]
274
-h = "-h" in args
275
-if "-n" in args:
276
-    node = args[args.index("-n") + 1]
277
-if "-m" in args:
278
-    nodename = args[args.index("-m") + 1]
279
-if "-a" in args:
280
-    auth = args[args.index("-a") + 1]
281
-if "-e" in args:
282
-    echoareas = args[args.index("-e") + 1].split(",")
283
-if "-to" in args:
284
-    to = args[args.index("-to") + 1].split(",")
285
-wait = "-w" in args
286
-
287
-if h:
288
-    show_help()
289
-    quit()
290
-
291
-if not "-n" in args and not "-e" in args and not os.path.exists(config):
292
-    print("Config file not found.")
293
-    quit()
294
-
295
-check_directories()
296
-if not "-n" in args or not "-e" in args:
297
-    node, nodename, auth, depth, echoareas = load_config()
298
-print("Работа с " + node)
299
-if auth:
300
-    make_toss()
301
-    send_mail()
302
-print("Получение списка возможностей ноды...")
303
-get_features()
304
-check_features()
305
-if xc:
306
-    load_counts()
307
-    print("Получение количества сообщений в конференциях...")
308
-    remote_counts = get_remote_counts()
309
-    calculate_offset()
310
-get_mail()
311
-if xc:
312
-    save_counts()
313
-if wait:
314
-    input("Нажмите Enter для продолжения.")
315
-    print()

+ 0 - 100
sender.py

@@ -1,100 +0,0 @@
1
-#!/usr/bin/env python3
2
-
3
-import urllib.request, base64, codecs, os, sys
4
-
5
-def check_directories():
6
-    if not os.path.exists("out"):
7
-        os.makedirs("out")
8
-
9
-def load_config():
10
-    node = ""
11
-    auth = False
12
-    nodename = "unknown"
13
-    f = open(config, "r").read().split("\n")
14
-    for line in f:
15
-        param = line.split(" ")
16
-        if param[0] == "node":
17
-            node = param[1]
18
-        elif param[0] == "nodename":
19
-            nodename = param[1:]
20
-        elif param[0] == "auth":
21
-            auth = param[1]
22
-    return node, nodename, auth
23
-
24
-def make_toss():
25
-    lst = [x for x in os.listdir("out/" + nodename) if x.endswith(".out")]
26
-    for msg in lst:
27
-        text = codecs.open("out/" + nodename + "/%s" % msg, "r", "utf-8").read()
28
-        coded_text = base64.b64encode(text.encode("utf-8"))
29
-        codecs.open("out/" + nodename + "/%s.toss" % msg, "w", "utf-8").write(coded_text.decode("utf-8"))
30
-        os.rename("out/" + nodename + "/%s" % msg, "out/" + nodename + "/%s%s" % (msg, "msg"))
31
-
32
-def send_mail():
33
-    lst = [x for x in sorted(os.listdir("out/" + nodename)) if x.endswith(".toss")]
34
-    max = len(lst)
35
-    n = 1
36
-    try:
37
-        for msg in lst:
38
-            print("\rОтправка сообщения: " + str(n) + "/" + str(max), end="")
39
-            text = codecs.open("out/" + nodename + "/%s" % msg, "r", "utf-8").read()
40
-            data = urllib.parse.urlencode({"tmsg": text,"pauth": auth}).encode("utf-8")
41
-            request = urllib.request.Request(node + "u/point")
42
-            result = urllib.request.urlopen(request, data).read().decode("utf-8")
43
-            if result.startswith("msg ok"):
44
-                os.remove("out/" + nodename + "/%s" % msg)
45
-                n = n + 1
46
-            elif result == "msg big!":
47
-                print("\nERROR: very big message (limit 64K)!")
48
-            elif result == "auth error!":
49
-                print("\nERROR: unknown auth!")
50
-            else:
51
-                print("\nERROR: unknown error!")
52
-        if len(lst) > 0:
53
-            print()
54
-    except:
55
-        print("\nОшибка: не удаётся связаться с нодой.")
56
-
57
-def show_help():
58
-    print("Usage: sender.py [-f filename] [-n node] [-o] [-to name1,name2...] [-h].")
59
-    print()
60
-    print("  -f filename  load config file. Default idec-fetcher.cfg.")
61
-    print("  -n node      node address.")
62
-    print("  -m nodename  nodename for search .out messages.")
63
-    print("  -a authkey   authkey.")
64
-    print("  -h           this message.")
65
-    print()
66
-    print("If -f not exist, script will load config from current directory with name\nsender.cfg.")
67
-
68
-args = sys.argv[1:]
69
-
70
-conf = "-f" in args
71
-if conf:
72
-    config = args[args.index("-f") + 1]
73
-else:
74
-    config = "sender.cfg"
75
-h = "-h" in args
76
-if "-n" in args:
77
-    node = args[args.index("-n") + 1]
78
-if "-m" in args:
79
-    nodename = args[args.index("-m") + 1]
80
-if "-a" in args:
81
-    auth = args[args.index("-a") + 1]
82
-wait = "-w" in args
83
-
84
-if h:
85
-    show_help()
86
-    quit()
87
-
88
-if not "-n" in args and not "-a" in args and not "-m" in args and not os.path.exists(config):
89
-    print("Config file not found.")
90
-    quit()
91
-
92
-check_directories()
93
-if not "-n" in args or not "-a" in args and not "-m" in args:
94
-    node, nodename, auth = load_config()
95
-print("Работа с " + node)
96
-make_toss()
97
-send_mail()
98
-if wait:
99
-    input("Нажмите Enter для продолжения.")
100
-    print()