Code splitting for specific usages
Also add a script to update a file containing SMSdb.
This commit is contained in:
parent
076610caf6
commit
87ffd22d35
|
@ -0,0 +1,412 @@
|
||||||
|
#!/data/data/com.termux/files/usr/bin/python3
|
||||||
|
|
||||||
|
from subprocess import Popen, DEVNULL, PIPE
|
||||||
|
from json import loads as read_json
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer as http4_server_do_not_use
|
||||||
|
from urllib.parse import quote, unquote
|
||||||
|
from socket import AF_INET6
|
||||||
|
from cgi import FieldStorage
|
||||||
|
from os import kill, getpid
|
||||||
|
|
||||||
|
from sms import SMS_Service
|
||||||
|
|
||||||
|
contacts_list_program = '/data/data/com.termux/files/usr/bin/termux-contact-list'
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPServer (http4_server_do_not_use):
|
||||||
|
'''
|
||||||
|
This block is just to have a IPv6 web server
|
||||||
|
This is not IPv6 only, the server also accepts IPv4 clients
|
||||||
|
'''
|
||||||
|
address_family = AF_INET6
|
||||||
|
|
||||||
|
|
||||||
|
def html_header (title = 'No title'):
|
||||||
|
return ('<html>\n<head>\n\t<title>' + title + '</title>\n' + '''\t<style>
|
||||||
|
body {
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
border: 2px solid #dedede;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darker {
|
||||||
|
border-color: #ccc;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message::after {
|
||||||
|
content: "";
|
||||||
|
clear: both;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message img {
|
||||||
|
float: left;
|
||||||
|
max-width: 60px;
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message img.right {
|
||||||
|
float: right;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-right:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-right {
|
||||||
|
float: right;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-left {
|
||||||
|
float: left;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
</style>''' + '<head>\n<body>')
|
||||||
|
|
||||||
|
|
||||||
|
def html_title (text):
|
||||||
|
return ('\n\t<h1>' + text + '</h1>')
|
||||||
|
|
||||||
|
|
||||||
|
def html_iframe (src, height = '150', width = '300', name = None, style = 'border:none;'):
|
||||||
|
r = '<iframe src="' + src + '" width="' + width + '" height="' + height + '" style="' + style + '"'
|
||||||
|
if name is not None:
|
||||||
|
r += ' name="' + name + '"'
|
||||||
|
r += '></iframe>'
|
||||||
|
return (r)
|
||||||
|
|
||||||
|
|
||||||
|
def html_paragraph (text):
|
||||||
|
return ('\n\t<p>' + text + '</p>')
|
||||||
|
|
||||||
|
|
||||||
|
def html_list (lst):
|
||||||
|
t = '\n\t<ul>'
|
||||||
|
for line in lst:
|
||||||
|
t += '\n\t\t<li>' + line + '</li>'
|
||||||
|
t += '\n\t</ul>'
|
||||||
|
return (t)
|
||||||
|
|
||||||
|
|
||||||
|
def html_link (url, text = None, target = '_self'):
|
||||||
|
if text is None:
|
||||||
|
text = url
|
||||||
|
return ('<a href="' + url + '" target="' + target + '">' + text + '</a>')
|
||||||
|
|
||||||
|
|
||||||
|
def html_table (body, table_style = None, cell_style = None):
|
||||||
|
t = '\n\t<table' + ((' style="' + table_style + '"') if table_style is not None else '') + '>'
|
||||||
|
for line in body:
|
||||||
|
t += '\n\t\t<tr>'
|
||||||
|
for cell in line:
|
||||||
|
t += '\n\t\t\t<td' + ((' style="' + cell_style + '"') if cell_style is not None else '') + '>' + cell + '</td>'
|
||||||
|
t += '\n\t\t</tr>'
|
||||||
|
t += '\n\t</table>'
|
||||||
|
return (t)
|
||||||
|
|
||||||
|
|
||||||
|
def html_message (msg, sent, time, name, avatar = None):
|
||||||
|
r = '\n\t<div class="message'
|
||||||
|
if sent:
|
||||||
|
r += ' darker'
|
||||||
|
r += '">\n\t\t<img src="' + str (avatar) + '" alt="' + name + '"'
|
||||||
|
if sent:
|
||||||
|
r += ' class="right"'
|
||||||
|
r += 'style"width:100%;">\n\t\t<p>' + msg.replace ('\n', '<br />') + '</p>\n\t\t<spam class="time-'
|
||||||
|
if sent:
|
||||||
|
r += 'left'
|
||||||
|
else:
|
||||||
|
r += 'right'
|
||||||
|
r += '">' + time + '</span>\n\t</div>'
|
||||||
|
return (r)
|
||||||
|
|
||||||
|
|
||||||
|
def html_form (body, url, post = True):
|
||||||
|
return ('\n\t<form action="' + url + '"' + (' method="post"' if post else '') + '>' + body + '\n\t</form>')
|
||||||
|
|
||||||
|
|
||||||
|
def html_form_text (id, value = '', textarea = False):
|
||||||
|
if textarea:
|
||||||
|
return ('\n\t<textarea name="' + id + '" rows="8" cols="100">' + value + '</textarea>')
|
||||||
|
return ('\n\t<input type="text" name="' + id + '" value="' + value + '" />')
|
||||||
|
|
||||||
|
|
||||||
|
def html_form_hidden (id, value):
|
||||||
|
return ('\n\t<input type="hidden" name="' + id + '" value="' + value + '" />')
|
||||||
|
|
||||||
|
|
||||||
|
def html_form_submit (label):
|
||||||
|
return ('\n\t<input type="submit" value="' + label + '" />')
|
||||||
|
|
||||||
|
|
||||||
|
def html_footer ():
|
||||||
|
return ('\n</body>\n</html>')
|
||||||
|
|
||||||
|
|
||||||
|
def contact_list ():
|
||||||
|
p = Popen (
|
||||||
|
[contacts_list_program, ],
|
||||||
|
stdin = DEVNULL,
|
||||||
|
stdout = PIPE
|
||||||
|
)
|
||||||
|
return (read_json (p.communicate () [0]))
|
||||||
|
|
||||||
|
|
||||||
|
class Web_Service (BaseHTTPRequestHandler):
|
||||||
|
def do_GET (self):
|
||||||
|
global locked_client
|
||||||
|
client = self.client_address [0]
|
||||||
|
if locked_client is None:
|
||||||
|
locked_client = client
|
||||||
|
print ('Locking on ' + str (client) + '…')
|
||||||
|
if locked_client == client:
|
||||||
|
global sms
|
||||||
|
if self.path == '/':
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Termux WebSMS') +
|
||||||
|
html_iframe ('/list', height = '100%', width = '30%', name = 'list') + html_iframe ('about:blank', height = '100%', width = '70%', name = 'thread') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path == '/up.svg':
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'image/svg+xml; charset=UTF-8')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">\n' +
|
||||||
|
'\t<g>\n' +
|
||||||
|
'\t\t<path\n' +
|
||||||
|
'\t\t\tstyle="fill:#000000;stroke:#000000;stroke-width:0.264583"\n' +
|
||||||
|
'\t\t\td="M 42,128 86,128 86,64 128,64 64,0 0,64 42,64 Z"\n' +
|
||||||
|
'\t\t/>\n' +
|
||||||
|
'\t</g>\n' +
|
||||||
|
'</svg>'
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path == '/down.svg':
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'image/svg+xml; charset=UTF-8')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">\n' +
|
||||||
|
'\t<g>\n' +
|
||||||
|
'\t\t<path\n' +
|
||||||
|
'\t\t\tstyle="fill:#000000;stroke:#000000;stroke-width:0.264583"\n' +
|
||||||
|
'\t\t\td="M 42,0 86,0 86,64 128,64 64,128 0,64 42,64 Z"\n' +
|
||||||
|
'\t\t/>\n' +
|
||||||
|
'\t</g>\n' +
|
||||||
|
'</svg>'
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path == '/list':
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
||||||
|
self.send_header ('Refresh', '20')
|
||||||
|
self.end_headers ()
|
||||||
|
ls = sms.thread_list ()
|
||||||
|
lst = []
|
||||||
|
for i in sorted (ls, key = lambda item: ls [item] [1], reverse = True):
|
||||||
|
s = ls [i] [0] + ' (' + str (ls [i] [3]) + ')'
|
||||||
|
if len (ls [i]) == 5:
|
||||||
|
s = ls [i] [4] + ' (' + str (ls [i] [3]) + ')'
|
||||||
|
lst.append (html_link ('/?thread=' + str (i), text = s, target = 'thread'))
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Conversations list') +
|
||||||
|
html_title ('Conversation') +
|
||||||
|
html_paragraph (html_link ('/new', text = 'New conversation', target = 'thread')) +
|
||||||
|
html_paragraph (html_link ('/list', text = 'Refresh list')) +
|
||||||
|
html_paragraph ('List of conversations follows:') +
|
||||||
|
html_list (lst) +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path == '/new':
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
||||||
|
self.end_headers ()
|
||||||
|
ls = contact_list ()
|
||||||
|
lst = []
|
||||||
|
for i in sorted (ls, key = lambda item: item ['name']):
|
||||||
|
lst.append (html_link ('/new?num=' + quote (i ['number']), text = i ['name'] + ' (' + i ['number'] + ')'))
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'New conversation') +
|
||||||
|
html_title ('Create a new conversation') +
|
||||||
|
html_paragraph ('Put number here:') +
|
||||||
|
html_form (
|
||||||
|
html_form_text ('num') +
|
||||||
|
html_form_submit ('Validate'),
|
||||||
|
'/new',
|
||||||
|
post = False
|
||||||
|
) +
|
||||||
|
html_paragraph ('List of contacts follows:') +
|
||||||
|
html_list (lst) +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path [0:9] == '/new?num=':
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
||||||
|
self.end_headers ()
|
||||||
|
number = self.path [9:]
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'New conversation') +
|
||||||
|
html_paragraph (html_link ('/new', text = '← Back to new message')) +
|
||||||
|
html_title ('Create a new conversation with ' + unquote (number)) +
|
||||||
|
html_paragraph ('Message:') +
|
||||||
|
html_form (
|
||||||
|
html_form_text ('msg', textarea = True) +
|
||||||
|
html_form_hidden ('number', unquote (number)) +
|
||||||
|
html_form_submit ('Send'),
|
||||||
|
'/send'
|
||||||
|
) +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path [0:9] == '/?thread=':
|
||||||
|
try:
|
||||||
|
id = self.path [9:]
|
||||||
|
number, name, conv = sms.get_thread (id)
|
||||||
|
if number is None:
|
||||||
|
raise (IndexError)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
self.send_response (404)
|
||||||
|
self.send_header ('Content-type', 'text/html')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Page not found') +
|
||||||
|
html_title ('Not found') +
|
||||||
|
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
return ()
|
||||||
|
if name is None:
|
||||||
|
name = number
|
||||||
|
else:
|
||||||
|
name = name + ' (' + number + ')'
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Conversation with ' + name) +
|
||||||
|
html_title (name) +
|
||||||
|
html_form (
|
||||||
|
html_form_text ('msg', textarea = True) +
|
||||||
|
html_form_hidden ('number', number) +
|
||||||
|
html_form_submit ('Send'),
|
||||||
|
'/send'
|
||||||
|
) +
|
||||||
|
html_iframe ('/messages?thread=' + str (id), height = '80%', width = '100%', name = 'messages') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
elif self.path [0:17] == '/messages?thread=':
|
||||||
|
try:
|
||||||
|
id = self.path [17:]
|
||||||
|
number, name, conv = sms.get_thread (id)
|
||||||
|
if number is None:
|
||||||
|
raise (IndexError)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
self.send_response (404)
|
||||||
|
self.send_header ('Content-type', 'text/html')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Page not found') +
|
||||||
|
html_title ('Not found') +
|
||||||
|
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
return ()
|
||||||
|
self.send_response (200)
|
||||||
|
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
||||||
|
self.send_header ('Refresh', '10')
|
||||||
|
self.end_headers ()
|
||||||
|
msgs = ''
|
||||||
|
for i in sorted (conv, key = lambda item: item [0], reverse = True) [0:2000]:
|
||||||
|
msgs += html_message (i [2], not i [1], i [0], '↓' if i [1] else '↑', avatar = 'down.svg' if i [1] else 'up.svg')
|
||||||
|
if name is None:
|
||||||
|
name = number
|
||||||
|
else:
|
||||||
|
name = name + ' (' + number + ')'
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Conversation with ' + name) +
|
||||||
|
html_link (self.path, text = 'Refresh') +
|
||||||
|
msgs +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
else:
|
||||||
|
self.send_response (404)
|
||||||
|
self.send_header ('Content-type', 'text/html')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Page not found') +
|
||||||
|
html_title ('Not found') +
|
||||||
|
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
else:
|
||||||
|
self.send_response (403)
|
||||||
|
self.send_header ('Content-type', 'text/html')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Access denied') +
|
||||||
|
html_title ('Access denied') +
|
||||||
|
html_paragraph ('Access have allready been granted to another host.') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
|
||||||
|
def do_POST (self):
|
||||||
|
global locked_client
|
||||||
|
client = self.client_address [0]
|
||||||
|
if locked_client is None:
|
||||||
|
locked_client = client
|
||||||
|
print ('Locking on ' + str (client) + '…')
|
||||||
|
if locked_client == client:
|
||||||
|
global sms
|
||||||
|
if self.path == '/send':
|
||||||
|
post_data = FieldStorage (fp = self.rfile, headers = self.headers, environ = {'REQUEST_METHOD': 'POST'})
|
||||||
|
sms.send_sms (post_data.getvalue ('number'), post_data.getvalue ('msg'))
|
||||||
|
self.send_response (301)
|
||||||
|
self.send_header ('Location', self.headers.get ('Referer'))
|
||||||
|
self.end_headers ()
|
||||||
|
else:
|
||||||
|
self.send_response (404)
|
||||||
|
self.send_header ('Content-type', 'text/html')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Page not found') +
|
||||||
|
html_title ('Not found') +
|
||||||
|
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
else:
|
||||||
|
self.send_response (403)
|
||||||
|
self.send_header ('Content-type', 'text/html')
|
||||||
|
self.end_headers ()
|
||||||
|
self.wfile.write ((
|
||||||
|
html_header (title = 'Access denied') +
|
||||||
|
html_title ('Access denied') +
|
||||||
|
html_paragraph ('Access have allready been granted to another host.') +
|
||||||
|
html_footer ()
|
||||||
|
).encode ('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sms = SMS_Service ()
|
||||||
|
server = HTTPServer (('::', 8080), Web_Service)
|
||||||
|
locked_client = None
|
||||||
|
print ('Started!')
|
||||||
|
try:
|
||||||
|
sms.start ()
|
||||||
|
server.serve_forever ()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print ('Interrupt received, closing process…')
|
||||||
|
server.server_close ()
|
||||||
|
# BUG: the program must kill itself to end or the user must send Ctrl+C several time to close
|
||||||
|
# TODO: find another way to close the program in one action, suicide is never the solution
|
||||||
|
kill (getpid (), 15)
|
|
@ -2,167 +2,23 @@
|
||||||
|
|
||||||
from subprocess import Popen, DEVNULL, PIPE
|
from subprocess import Popen, DEVNULL, PIPE
|
||||||
from json import loads as read_json
|
from json import loads as read_json
|
||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer as http4_server_do_not_use
|
|
||||||
from urllib.parse import quote, unquote
|
|
||||||
from socket import AF_INET6
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from cgi import FieldStorage
|
|
||||||
from os import kill, getpid
|
|
||||||
|
|
||||||
sms_list_program = '/data/data/com.termux/files/usr/bin/termux-sms-list'
|
sms_list_program = '/data/data/com.termux/files/usr/bin/termux-sms-list'
|
||||||
sms_send_program = '/data/data/com.termux/files/usr/bin/termux-sms-send'
|
sms_send_program = '/data/data/com.termux/files/usr/bin/termux-sms-send'
|
||||||
contacts_list_program = '/data/data/com.termux/files/usr/bin/termux-contact-list'
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPServer (http4_server_do_not_use):
|
|
||||||
'''
|
|
||||||
This block is just to have a IPv6 web server
|
|
||||||
This is not IPv6 only, the server also accepts IPv4 clients
|
|
||||||
'''
|
|
||||||
address_family = AF_INET6
|
|
||||||
|
|
||||||
|
|
||||||
def html_header (title = 'No title'):
|
|
||||||
return ('<html>\n<head>\n\t<title>' + title + '</title>\n' + '''\t<style>
|
|
||||||
body {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
border: 2px solid #dedede;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.darker {
|
|
||||||
border-color: #ccc;
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message::after {
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message img {
|
|
||||||
float: left;
|
|
||||||
max-width: 60px;
|
|
||||||
width: 100%;
|
|
||||||
margin-right: 20px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message img.right {
|
|
||||||
float: right;
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-right:0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-right {
|
|
||||||
float: right;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-left {
|
|
||||||
float: left;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
</style>''' + '<head>\n<body>')
|
|
||||||
|
|
||||||
|
|
||||||
def html_title (text):
|
|
||||||
return ('\n\t<h1>' + text + '</h1>')
|
|
||||||
|
|
||||||
|
|
||||||
def html_iframe (src, height = '150', width = '300', name = None, style = 'border:none;'):
|
|
||||||
r = '<iframe src="' + src + '" width="' + width + '" height="' + height + '" style="' + style + '"'
|
|
||||||
if name is not None:
|
|
||||||
r += ' name="' + name + '"'
|
|
||||||
r += '></iframe>'
|
|
||||||
return (r)
|
|
||||||
|
|
||||||
|
|
||||||
def html_paragraph (text):
|
|
||||||
return ('\n\t<p>' + text + '</p>')
|
|
||||||
|
|
||||||
|
|
||||||
def html_list (lst):
|
|
||||||
t = '\n\t<ul>'
|
|
||||||
for line in lst:
|
|
||||||
t += '\n\t\t<li>' + line + '</li>'
|
|
||||||
t += '\n\t</ul>'
|
|
||||||
return (t)
|
|
||||||
|
|
||||||
|
|
||||||
def html_link (url, text = None, target = '_self'):
|
|
||||||
if text is None:
|
|
||||||
text = url
|
|
||||||
return ('<a href="' + url + '" target="' + target + '">' + text + '</a>')
|
|
||||||
|
|
||||||
|
|
||||||
def html_table (body, table_style = None, cell_style = None):
|
|
||||||
t = '\n\t<table' + ((' style="' + table_style + '"') if table_style is not None else '') + '>'
|
|
||||||
for line in body:
|
|
||||||
t += '\n\t\t<tr>'
|
|
||||||
for cell in line:
|
|
||||||
t += '\n\t\t\t<td' + ((' style="' + cell_style + '"') if cell_style is not None else '') + '>' + cell + '</td>'
|
|
||||||
t += '\n\t\t</tr>'
|
|
||||||
t += '\n\t</table>'
|
|
||||||
return (t)
|
|
||||||
|
|
||||||
|
|
||||||
def html_message (msg, sent, time, name, avatar = None):
|
|
||||||
r = '\n\t<div class="message'
|
|
||||||
if sent:
|
|
||||||
r += ' darker'
|
|
||||||
r += '">\n\t\t<img src="' + str (avatar) + '" alt="' + name + '"'
|
|
||||||
if sent:
|
|
||||||
r += ' class="right"'
|
|
||||||
r += 'style"width:100%;">\n\t\t<p>' + msg.replace ('\n', '<br />') + '</p>\n\t\t<spam class="time-'
|
|
||||||
if sent:
|
|
||||||
r += 'left'
|
|
||||||
else:
|
|
||||||
r += 'right'
|
|
||||||
r += '">' + time + '</span>\n\t</div>'
|
|
||||||
return (r)
|
|
||||||
|
|
||||||
|
|
||||||
def html_form (body, url, post = True):
|
|
||||||
return ('\n\t<form action="' + url + '"' + (' method="post"' if post else '') + '>' + body + '\n\t</form>')
|
|
||||||
|
|
||||||
|
|
||||||
def html_form_text (id, value = '', textarea = False):
|
|
||||||
if textarea:
|
|
||||||
return ('\n\t<textarea name="' + id + '" rows="8" cols="100">' + value + '</textarea>')
|
|
||||||
return ('\n\t<input type="text" name="' + id + '" value="' + value + '" />')
|
|
||||||
|
|
||||||
|
|
||||||
def html_form_hidden (id, value):
|
|
||||||
return ('\n\t<input type="hidden" name="' + id + '" value="' + value + '" />')
|
|
||||||
|
|
||||||
|
|
||||||
def html_form_submit (label):
|
|
||||||
return ('\n\t<input type="submit" value="' + label + '" />')
|
|
||||||
|
|
||||||
|
|
||||||
def html_footer ():
|
|
||||||
return ('\n</body>\n</html>')
|
|
||||||
|
|
||||||
|
|
||||||
class SMS_Service (Thread):
|
class SMS_Service (Thread):
|
||||||
def __init__ (self):
|
def __init__ (self, sms_list = None):
|
||||||
Thread.__init__ (self)
|
Thread.__init__ (self)
|
||||||
self.sms_list = []
|
if type (sms_list) == list:
|
||||||
|
self.sms_list = sms_list
|
||||||
|
else:
|
||||||
|
self.sms_list = []
|
||||||
|
|
||||||
def run (self):
|
def build_database (self):
|
||||||
limit_fast_get = 1000000
|
limit_fast_get = 1000000
|
||||||
limit_normal_get = 20
|
|
||||||
offset = 0
|
offset = 0
|
||||||
while True:
|
while True:
|
||||||
msgs = self.get_sms (limit = limit_fast_get, offset = offset)
|
msgs = self.get_sms (limit = limit_fast_get, offset = offset)
|
||||||
|
@ -173,20 +29,27 @@ class SMS_Service (Thread):
|
||||||
offset += limit_fast_get
|
offset += limit_fast_get
|
||||||
for msg in self.sms_list:
|
for msg in self.sms_list:
|
||||||
del (msg ['read'])
|
del (msg ['read'])
|
||||||
|
|
||||||
|
def update_database (self):
|
||||||
|
limit_normal_get = 20
|
||||||
|
getting_new = True
|
||||||
|
offset = 0
|
||||||
|
while getting_new:
|
||||||
|
msgs = self.get_sms (limit = limit_normal_get, offset = offset)
|
||||||
|
getting_new = False
|
||||||
|
for msg in msgs:
|
||||||
|
del (msg ['read'])
|
||||||
|
for msg in msgs:
|
||||||
|
if msg not in self.sms_list:
|
||||||
|
getting_new = True
|
||||||
|
self.sms_list.append (msg)
|
||||||
|
offset += limit_normal_get
|
||||||
|
|
||||||
|
def run (self):
|
||||||
|
self.build_database ()
|
||||||
while True:
|
while True:
|
||||||
sleep (5)
|
sleep (5)
|
||||||
getting_new = True
|
self.update_database ()
|
||||||
offset = 0
|
|
||||||
while getting_new:
|
|
||||||
msgs = self.get_sms (limit = limit_normal_get, offset = offset)
|
|
||||||
getting_new = False
|
|
||||||
for msg in msgs:
|
|
||||||
del (msg ['read'])
|
|
||||||
for msg in msgs:
|
|
||||||
if msg not in self.sms_list:
|
|
||||||
getting_new = True
|
|
||||||
self.sms_list.append (msg)
|
|
||||||
offset += limit_normal_get
|
|
||||||
|
|
||||||
def get_sms (self, limit = 10, offset = 0):
|
def get_sms (self, limit = 10, offset = 0):
|
||||||
while True:
|
while True:
|
||||||
|
@ -240,262 +103,3 @@ class SMS_Service (Thread):
|
||||||
if 'sender' in i:
|
if 'sender' in i:
|
||||||
name = i ['sender']
|
name = i ['sender']
|
||||||
return (number, name, sorted (l, key = lambda item: item [0]))
|
return (number, name, sorted (l, key = lambda item: item [0]))
|
||||||
|
|
||||||
|
|
||||||
def contact_list ():
|
|
||||||
p = Popen (
|
|
||||||
[contacts_list_program, ],
|
|
||||||
stdin = DEVNULL,
|
|
||||||
stdout = PIPE
|
|
||||||
)
|
|
||||||
return (read_json (p.communicate () [0]))
|
|
||||||
|
|
||||||
|
|
||||||
class Web_Service (BaseHTTPRequestHandler):
|
|
||||||
def do_GET (self):
|
|
||||||
global locked_client
|
|
||||||
client = self.client_address [0]
|
|
||||||
if locked_client is None:
|
|
||||||
locked_client = client
|
|
||||||
print ('Locking on ' + str (client) + '…')
|
|
||||||
if locked_client == client:
|
|
||||||
global sms
|
|
||||||
if self.path == '/':
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Termux WebSMS') +
|
|
||||||
html_iframe ('/list', height = '100%', width = '30%', name = 'list') + html_iframe ('about:blank', height = '100%', width = '70%', name = 'thread') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path == '/up.svg':
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'image/svg+xml; charset=UTF-8')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">\n' +
|
|
||||||
'\t<g>\n' +
|
|
||||||
'\t\t<path\n' +
|
|
||||||
'\t\t\tstyle="fill:#000000;stroke:#000000;stroke-width:0.264583"\n' +
|
|
||||||
'\t\t\td="M 42,128 86,128 86,64 128,64 64,0 0,64 42,64 Z"\n' +
|
|
||||||
'\t\t/>\n' +
|
|
||||||
'\t</g>\n' +
|
|
||||||
'</svg>'
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path == '/down.svg':
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'image/svg+xml; charset=UTF-8')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">\n' +
|
|
||||||
'\t<g>\n' +
|
|
||||||
'\t\t<path\n' +
|
|
||||||
'\t\t\tstyle="fill:#000000;stroke:#000000;stroke-width:0.264583"\n' +
|
|
||||||
'\t\t\td="M 42,0 86,0 86,64 128,64 64,128 0,64 42,64 Z"\n' +
|
|
||||||
'\t\t/>\n' +
|
|
||||||
'\t</g>\n' +
|
|
||||||
'</svg>'
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path == '/list':
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
|
||||||
self.send_header ('Refresh', '20')
|
|
||||||
self.end_headers ()
|
|
||||||
ls = sms.thread_list ()
|
|
||||||
lst = []
|
|
||||||
for i in sorted (ls, key = lambda item: ls [item] [1], reverse = True):
|
|
||||||
s = ls [i] [0] + ' (' + str (ls [i] [3]) + ')'
|
|
||||||
if len (ls [i]) == 5:
|
|
||||||
s = ls [i] [4] + ' (' + str (ls [i] [3]) + ')'
|
|
||||||
lst.append (html_link ('/?thread=' + str (i), text = s, target = 'thread'))
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Conversations list') +
|
|
||||||
html_title ('Conversation') +
|
|
||||||
html_paragraph (html_link ('/new', text = 'New conversation', target = 'thread')) +
|
|
||||||
html_paragraph (html_link ('/list', text = 'Refresh list')) +
|
|
||||||
html_paragraph ('List of conversations follows:') +
|
|
||||||
html_list (lst) +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path == '/new':
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
|
||||||
self.end_headers ()
|
|
||||||
ls = contact_list ()
|
|
||||||
lst = []
|
|
||||||
for i in sorted (ls, key = lambda item: item ['name']):
|
|
||||||
lst.append (html_link ('/new?num=' + quote (i ['number']), text = i ['name'] + ' (' + i ['number'] + ')'))
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'New conversation') +
|
|
||||||
html_title ('Create a new conversation') +
|
|
||||||
html_paragraph ('Put number here:') +
|
|
||||||
html_form (
|
|
||||||
html_form_text ('num') +
|
|
||||||
html_form_submit ('Validate'),
|
|
||||||
'/new',
|
|
||||||
post = False
|
|
||||||
) +
|
|
||||||
html_paragraph ('List of contacts follows:') +
|
|
||||||
html_list (lst) +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path [0:9] == '/new?num=':
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
|
||||||
self.end_headers ()
|
|
||||||
number = self.path [9:]
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'New conversation') +
|
|
||||||
html_paragraph (html_link ('/new', text = '← Back to new message')) +
|
|
||||||
html_title ('Create a new conversation with ' + unquote (number)) +
|
|
||||||
html_paragraph ('Message:') +
|
|
||||||
html_form (
|
|
||||||
html_form_text ('msg', textarea = True) +
|
|
||||||
html_form_hidden ('number', unquote (number)) +
|
|
||||||
html_form_submit ('Send'),
|
|
||||||
'/send'
|
|
||||||
) +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path [0:9] == '/?thread=':
|
|
||||||
try:
|
|
||||||
id = self.path [9:]
|
|
||||||
number, name, conv = sms.get_thread (id)
|
|
||||||
if number is None:
|
|
||||||
raise (IndexError)
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
self.send_response (404)
|
|
||||||
self.send_header ('Content-type', 'text/html')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Page not found') +
|
|
||||||
html_title ('Not found') +
|
|
||||||
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
return ()
|
|
||||||
if name is None:
|
|
||||||
name = number
|
|
||||||
else:
|
|
||||||
name = name + ' (' + number + ')'
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Conversation with ' + name) +
|
|
||||||
html_title (name) +
|
|
||||||
html_form (
|
|
||||||
html_form_text ('msg', textarea = True) +
|
|
||||||
html_form_hidden ('number', number) +
|
|
||||||
html_form_submit ('Send'),
|
|
||||||
'/send'
|
|
||||||
) +
|
|
||||||
html_iframe ('/messages?thread=' + str (id), height = '80%', width = '100%', name = 'messages') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
elif self.path [0:17] == '/messages?thread=':
|
|
||||||
try:
|
|
||||||
id = self.path [17:]
|
|
||||||
number, name, conv = sms.get_thread (id)
|
|
||||||
if number is None:
|
|
||||||
raise (IndexError)
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
self.send_response (404)
|
|
||||||
self.send_header ('Content-type', 'text/html')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Page not found') +
|
|
||||||
html_title ('Not found') +
|
|
||||||
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
return ()
|
|
||||||
self.send_response (200)
|
|
||||||
self.send_header ('Content-type', 'text/html; charset=UTF-8')
|
|
||||||
self.send_header ('Refresh', '10')
|
|
||||||
self.end_headers ()
|
|
||||||
msgs = ''
|
|
||||||
for i in sorted (conv, key = lambda item: item [0], reverse = True) [0:2000]:
|
|
||||||
msgs += html_message (i [2], not i [1], i [0], '↓' if i [1] else '↑', avatar = 'down.svg' if i [1] else 'up.svg')
|
|
||||||
if name is None:
|
|
||||||
name = number
|
|
||||||
else:
|
|
||||||
name = name + ' (' + number + ')'
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Conversation with ' + name) +
|
|
||||||
html_link (self.path, text = 'Refresh') +
|
|
||||||
msgs +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
else:
|
|
||||||
self.send_response (404)
|
|
||||||
self.send_header ('Content-type', 'text/html')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Page not found') +
|
|
||||||
html_title ('Not found') +
|
|
||||||
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
else:
|
|
||||||
self.send_response (403)
|
|
||||||
self.send_header ('Content-type', 'text/html')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Access denied') +
|
|
||||||
html_title ('Access denied') +
|
|
||||||
html_paragraph ('Access have allready been granted to another host.') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
|
|
||||||
def do_POST (self):
|
|
||||||
global locked_client
|
|
||||||
client = self.client_address [0]
|
|
||||||
if locked_client is None:
|
|
||||||
locked_client = client
|
|
||||||
print ('Locking on ' + str (client) + '…')
|
|
||||||
if locked_client == client:
|
|
||||||
global sms
|
|
||||||
if self.path == '/send':
|
|
||||||
post_data = FieldStorage (fp = self.rfile, headers = self.headers, environ = {'REQUEST_METHOD': 'POST'})
|
|
||||||
sms.send_sms (post_data.getvalue ('number'), post_data.getvalue ('msg'))
|
|
||||||
self.send_response (301)
|
|
||||||
self.send_header ('Location', self.headers.get ('Referer'))
|
|
||||||
self.end_headers ()
|
|
||||||
else:
|
|
||||||
self.send_response (404)
|
|
||||||
self.send_header ('Content-type', 'text/html')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Page not found') +
|
|
||||||
html_title ('Not found') +
|
|
||||||
html_paragraph ('This page does not exist. You cas return to the ' + html_link ('/', text = 'home') + '.') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
else:
|
|
||||||
self.send_response (403)
|
|
||||||
self.send_header ('Content-type', 'text/html')
|
|
||||||
self.end_headers ()
|
|
||||||
self.wfile.write ((
|
|
||||||
html_header (title = 'Access denied') +
|
|
||||||
html_title ('Access denied') +
|
|
||||||
html_paragraph ('Access have allready been granted to another host.') +
|
|
||||||
html_footer ()
|
|
||||||
).encode ('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sms = SMS_Service ()
|
|
||||||
server = HTTPServer (('::', 8080), Web_Service)
|
|
||||||
locked_client = None
|
|
||||||
print ('Started!')
|
|
||||||
try:
|
|
||||||
sms.start ()
|
|
||||||
server.serve_forever ()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print ('Interrupt received, closing process…')
|
|
||||||
server.server_close ()
|
|
||||||
# BUG: the program must kill itself to end or the user must send Ctrl+C several time to close
|
|
||||||
# TODO: find another way to close the program in one action, suicide is never the solution
|
|
||||||
kill (getpid (), 15)
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/data/data/com.termux/files/usr/bin/python3
|
||||||
|
|
||||||
|
from json import loads as json_read, dumps as json_write
|
||||||
|
from sys import argv
|
||||||
|
from os.path import isfile
|
||||||
|
|
||||||
|
from sms import SMS_Service
|
||||||
|
|
||||||
|
output = argv [1]
|
||||||
|
|
||||||
|
if isfile (output):
|
||||||
|
database = json_read (open (output).read ())
|
||||||
|
s = SMS_Service (sms_list = database)
|
||||||
|
s.update_database ()
|
||||||
|
open (output, 'w').write (json_write (s.sorted_list ()))
|
||||||
|
else:
|
||||||
|
s = SMS_Service ()
|
||||||
|
s.build_database ()
|
||||||
|
open (output, 'w').write (json_write (s.sorted_list ()))
|
Loading…
Reference in New Issue