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 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 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_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): | ||||
| 	def __init__ (self): | ||||
| 	def __init__ (self, sms_list = None): | ||||
| 		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_normal_get = 20 | ||||
| 		offset = 0 | ||||
| 		while True: | ||||
| 			msgs = self.get_sms (limit = limit_fast_get, offset = offset) | ||||
|  | @ -173,20 +29,27 @@ class SMS_Service (Thread): | |||
| 			offset += limit_fast_get | ||||
| 		for msg in self.sms_list: | ||||
| 			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: | ||||
| 			sleep (5) | ||||
| 			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 | ||||
| 			self.update_database () | ||||
| 	 | ||||
| 	def get_sms (self, limit = 10, offset = 0): | ||||
| 		while True: | ||||
|  | @ -239,263 +102,4 @@ class SMS_Service (Thread): | |||
| 					number = i ['number'] | ||||
| 					if 'sender' in i: | ||||
| 						name = i ['sender'] | ||||
| 		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) | ||||
| 		return (number, name, sorted (l, key = lambda item: item [0])) | ||||
|  | @ -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