Compare commits
No commits in common. "master" and "logging" have entirely different histories.
14
README.md
@ -1,4 +1,4 @@
|
|||||||
Meshenger
|
meshenger
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Meshenger is a Forban-inspired messaging software used for a speculative broadcast communication project. The starting point is an electronic messaging system running on a wireless mesh network. The messages propagate through the network when devices that come in contact with each other synchronize their content. It is non-hierarchical, every node receives, relays and broadcasts messages.
|
Meshenger is a Forban-inspired messaging software used for a speculative broadcast communication project. The starting point is an electronic messaging system running on a wireless mesh network. The messages propagate through the network when devices that come in contact with each other synchronize their content. It is non-hierarchical, every node receives, relays and broadcasts messages.
|
||||||
@ -26,18 +26,6 @@ You are going to need to have an internet connection to your router, the easiest
|
|||||||
|
|
||||||
Alternatively if you use OSX you can enable internet sharing (make sure to set your OSX machine as the gateway and DNS server for your router in /etc/config/network)
|
Alternatively if you use OSX you can enable internet sharing (make sure to set your OSX machine as the gateway and DNS server for your router in /etc/config/network)
|
||||||
|
|
||||||
*If after flashing something goes wrong, you can reset the whole router in failsafe mode* see [here](http://wiki.openwrt.org/doc/howto/generic.failsafe).
|
|
||||||
|
|
||||||
### Lazy Install
|
|
||||||
|
|
||||||
Below you will find the manual configuration steps necessary to install Meshenger. We did however create some scripts to help you automate the process.
|
|
||||||
|
|
||||||
Read more [here](https://github.com/rscmbbng/meshenger/blob/master/lazyinstall/README.md)
|
|
||||||
|
|
||||||
To add: stop Luci from running!
|
|
||||||
`$ /etc/init.d/uhttpd disable`
|
|
||||||
Saves a lot of resources!
|
|
||||||
|
|
||||||
### System configuration
|
### System configuration
|
||||||
|
|
||||||
To use your router for Meshenger you're going to need to run the whole filesystem from a USB-Drive.
|
To use your router for Meshenger you're going to need to run the whole filesystem from a USB-Drive.
|
||||||
|
@ -1,46 +1,3 @@
|
|||||||
This is a very primitive script to automate the openwrt setup.
|
This is a very primitive script to automate the openwrt setup.
|
||||||
It's probably best not used at this point.
|
It's probably best not used at this point.
|
||||||
|
|
||||||
However if you choose to use it make sure you have the following things set up:
|
|
||||||
|
|
||||||
- The OpenWRT router has a connection to the internet (by attaching it to another router via ethernet)
|
|
||||||
- You have a properly formatted usb drive (one ext4 partition, one 32mb swap partiton)
|
|
||||||
- Make sure the usb flash drive is inserted in the router
|
|
||||||
|
|
||||||
The lazy install scripts works by moving config files from this folder to replace the ones on the target router's filesystem.
|
|
||||||
Therefore you need to edit the following files to suit your own needs:
|
|
||||||
|
|
||||||
network file:
|
|
||||||
lines 11 and 13, the ip adress you want to use plus the ip adress of the gateway that provides this router with its internet connection.
|
|
||||||
|
|
||||||
wireless file:
|
|
||||||
line 11,the wireless SSID that the router will get
|
|
||||||
|
|
||||||
Once you've set all of this up copy the lazyinstall directory to your open-wrt router:
|
|
||||||
|
|
||||||
scp -r /path/to/meshenger/lazyinstall/ root@target.router.ip.adress:~/
|
|
||||||
|
|
||||||
ssh into the target router:
|
|
||||||
|
|
||||||
ssh root@target.router.ip.address
|
|
||||||
|
|
||||||
navigate to where you copied the lazyinstall directory:
|
|
||||||
cd ~/lazyinstall
|
|
||||||
|
|
||||||
first execute lazyinstall1, it will set up the ext_root on ths usb drive
|
|
||||||
|
|
||||||
./lazyinstall1.sh
|
|
||||||
|
|
||||||
once that's done reboot the router:
|
|
||||||
|
|
||||||
reboot -f
|
|
||||||
|
|
||||||
once it is up again, ssh back in. the router has now booted from the external usb drive. you can verify that by doing:
|
|
||||||
|
|
||||||
df -h
|
|
||||||
|
|
||||||
which should show you that rootfs is the size of your external usb drive
|
|
||||||
|
|
||||||
run lazyinstall2. this will copy all the config files, download python and git and clone the meshenger project. This can take a while.
|
|
||||||
|
|
||||||
After this is done you can reboot and you will have a fully functioning meshenger node!
|
|
||||||
|
@ -17,7 +17,7 @@ mount -t ext4 /dev/sda1 /mnt/sda1
|
|||||||
|
|
||||||
echo 'Cleaning USB drive'
|
echo 'Cleaning USB drive'
|
||||||
|
|
||||||
rm -r /mnt/sda1/*
|
rm -r /mnt/sda1/
|
||||||
|
|
||||||
sleep 4
|
sleep 4
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ echo 'Copying filesystem to USB drive'
|
|||||||
mkdir -p /tmp/cproot
|
mkdir -p /tmp/cproot
|
||||||
mount --bind / /tmp/cproot
|
mount --bind / /tmp/cproot
|
||||||
sleep 1
|
sleep 1
|
||||||
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1/ -xf -
|
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1 -xf -
|
||||||
sleep 1
|
sleep 1
|
||||||
umount /tmp/cproot
|
umount /tmp/cproot
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ sed -i -e "s/option 'interfaces' 'mesh'/option 'interfaces' 'adhoc0'/g" /etc/con
|
|||||||
|
|
||||||
opkg install python git
|
opkg install python git
|
||||||
sleep 1
|
sleep 1
|
||||||
git clone git://github.com/rscmbbng/meshenger /root/meshenger
|
git clone ://github.com/rscmbbng/meshenger /root/meshenger
|
||||||
|
|
||||||
mv uhttpd /etc/config/uhttpd
|
mv uhttpd /etc/config/uhttpd
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ config wifi-iface 'wmesh'
|
|||||||
option bssid '66:66:66:66:66:66'
|
option bssid '66:66:66:66:66:66'
|
||||||
|
|
||||||
config wifi-iface
|
config wifi-iface
|
||||||
option 'device' 'radio0' #use your existing wifi device, look in the list above.
|
option 'device' 'radio0' #use your excisting wifi device, look in the list above.
|
||||||
option 'ssid' 'meshenger_node' #use a unique name for your network?
|
option 'ssid' 'meshenger_node' #use a unique name for your network?
|
||||||
option 'network' 'hotspot'
|
option 'network' 'hotspot'
|
||||||
option 'mode' 'ap'
|
option 'mode' 'ap'
|
||||||
|
125
main.py
@ -2,39 +2,31 @@
|
|||||||
|
|
||||||
import socket, os, time, select, urllib, sys, threading, json, logging, logging.config
|
import socket, os, time, select, urllib, sys, threading, json, logging, logging.config
|
||||||
|
|
||||||
os.chdir(os.path.dirname(__file__)) # change present working directory to the one where this file is
|
|
||||||
|
|
||||||
logging.config.fileConfig('pylog.conf')
|
logging.config.fileConfig('pylog.conf')
|
||||||
logger = logging.getLogger('meshenger'+'.main')
|
logger = logging.getLogger('meshenger'+'.main')
|
||||||
|
|
||||||
class Meshenger:
|
class Meshenger:
|
||||||
devices = {} #the dictionary of all the nodes this this node has seen. each ip-entry has a tuple of 'foreign index update time' and time when last seen
|
devices = {} #the dictionary of all the nodes this this node has seen
|
||||||
devices_old = {}
|
|
||||||
serve_port = "13338"
|
serve_port = "13338"
|
||||||
announce_port = 13337
|
announce_port = 13337
|
||||||
#own_ip = "0.0.0.0"
|
#own_ip = "0.0.0.0"
|
||||||
msg_dir = os.path.relpath('msg/')
|
msg_dir = os.path.relpath('msg/')
|
||||||
exitapp = False #to kill all threads on
|
exitapp = False #to kill all threads on
|
||||||
index_last_update = str(int(time.time()))
|
index_last_update = str(int(time.time()))
|
||||||
node_expiry = 15 #the time to wait before removing a node form the discovered nodelist
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-lan/disable_ipv6")
|
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-lan/disable_ipv6")
|
||||||
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-hotspot/disable_ipv6")
|
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-hotspot/disable_ipv6")
|
||||||
|
|
||||||
|
os.chdir(os.path.dirname(__file__)) # change present working directory to the one where this file is
|
||||||
|
|
||||||
self.own_ip = self.get_ip_adress().strip()
|
self.own_ip = self.get_ip_adress().strip()
|
||||||
# this hash is needed in clientserve, so client can generate color
|
|
||||||
self.own_hash = self.hasj(self.own_ip)
|
|
||||||
|
|
||||||
if not os.path.exists(self.msg_dir):
|
if not os.path.exists(self.msg_dir):
|
||||||
os.mkdir(self.msg_dir)
|
os.mkdir(self.msg_dir)
|
||||||
logger.info('Making message directory')
|
logger.info('Making message directory')
|
||||||
|
|
||||||
self.init_index()
|
|
||||||
self.make_alias()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
d = threading.Thread(target=self.discover)
|
d = threading.Thread(target=self.discover)
|
||||||
d.daemon = True
|
d.daemon = True
|
||||||
@ -52,6 +44,12 @@ class Meshenger:
|
|||||||
c.daemon = True
|
c.daemon = True
|
||||||
c.start()
|
c.start()
|
||||||
|
|
||||||
|
b = threading.Thread(target=self.build_index)
|
||||||
|
b.daemon = True
|
||||||
|
b.start()
|
||||||
|
|
||||||
|
#os.system("python meshenger_clientserve.py")
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
logger.info('exiting discovery thread')
|
logger.info('exiting discovery thread')
|
||||||
d.join()
|
d.join()
|
||||||
@ -60,10 +58,6 @@ class Meshenger:
|
|||||||
n.join()
|
n.join()
|
||||||
c.join()
|
c.join()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
except Exception as e:
|
|
||||||
logger.warning( 'Main __init__ thread exception: %s', repr(e) )
|
|
||||||
except:
|
|
||||||
logger.warning( 'Main __init__ unknown thread exception')
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
logger.debug('Entering main loop')
|
logger.debug('Entering main loop')
|
||||||
@ -73,7 +67,7 @@ class Meshenger:
|
|||||||
|
|
||||||
for device in self.devices.keys():
|
for device in self.devices.keys():
|
||||||
nodehash = self.hasj(device)
|
nodehash = self.hasj(device)
|
||||||
nodepath = os.path.join(os.path.abspath('nodes'), nodehash)
|
nodepath = os.path.join(os.path.abspath('nodes'), nodehash)
|
||||||
nodeupdatepath = os.path.join(nodepath, 'lastupdate')
|
nodeupdatepath = os.path.join(nodepath, 'lastupdate')
|
||||||
|
|
||||||
logger.info('Checking age of foreign node index')
|
logger.info('Checking age of foreign node index')
|
||||||
@ -91,27 +85,15 @@ class Meshenger:
|
|||||||
self.get_index(device, nodepath)
|
self.get_index(device, nodepath)
|
||||||
logger.info('downloading messages')
|
logger.info('downloading messages')
|
||||||
self.get_messages(device, nodepath, nodehash)
|
self.get_messages(device, nodepath, nodehash)
|
||||||
self.build_index()
|
|
||||||
self.node_timestamp(device)
|
self.node_timestamp(device)
|
||||||
|
|
||||||
self.devices_old = dict(self.devices)
|
|
||||||
|
|
||||||
#check to see if a node has been missing for a while, if so remove it from self.devices
|
|
||||||
for device in self.devices_old.keys():
|
|
||||||
update_time = int(self.devices[device][1])
|
|
||||||
time_delta = int(time.time())- update_time
|
|
||||||
if time_delta >= self.node_expiry:
|
|
||||||
logger.info('Node '+device+' missing for '+str(self.node_expiry)+' seconds. Removing from list')
|
|
||||||
del self.devices[device]
|
|
||||||
|
|
||||||
time.sleep(5) #free process or ctrl+c
|
time.sleep(5) #free process or ctrl+c
|
||||||
|
|
||||||
|
|
||||||
def node_timestamp(self, ip):
|
def node_timestamp(self, ip):
|
||||||
nodepath = os.path.abspath(os.path.join('nodes', self.hasj(ip)))
|
nodepath = os.path.abspath(os.path.join('nodes', self.hasj(ip)))
|
||||||
updatepath = os.path.join(nodepath, 'lastupdate')
|
updatepath = os.path.join(nodepath, 'lastupdate')
|
||||||
with open(updatepath, 'wb') as lastupdate:
|
with open(updatepath, 'wb') as lastupdate:
|
||||||
lastupdate.write(self.devices[ip][0])
|
lastupdate.write(self.devices[ip])
|
||||||
#return updatepath
|
#return updatepath
|
||||||
|
|
||||||
|
|
||||||
@ -122,20 +104,17 @@ Announce the node's existance to other nodes
|
|||||||
"""
|
"""
|
||||||
logger.info('Announcing')
|
logger.info('Announcing')
|
||||||
while not self.exitapp:
|
while not self.exitapp:
|
||||||
try:
|
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
|
||||||
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
|
sock.close()
|
||||||
#sock.close()
|
time.sleep(5)
|
||||||
time.sleep(5)
|
|
||||||
except:
|
|
||||||
logger.warning('Failed to announce myself!')
|
|
||||||
|
|
||||||
def discover(self):
|
def discover(self):
|
||||||
"""
|
"""
|
||||||
Discover other devices by listening to the Meshenger announce port
|
Discover other devices by listening to the Meshenger announce port
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info('Discovering')
|
print 'Discovering'
|
||||||
bufferSize = 1024 # whatever you need?
|
bufferSize = 1024 # whatever you need?
|
||||||
|
|
||||||
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
@ -144,19 +123,19 @@ Discover other devices by listening to the Meshenger announce port
|
|||||||
while not self.exitapp:
|
while not self.exitapp:
|
||||||
result = select.select([s],[],[])[0][0].recvfrom(bufferSize)
|
result = select.select([s],[],[])[0][0].recvfrom(bufferSize)
|
||||||
ip = result[1][0]
|
ip = result[1][0]
|
||||||
#logger.debug('%s %s', ip, 'discovered')
|
logger.info('%s %s', ip, "*"*45)
|
||||||
node_path = os.path.join(os.path.abspath('nodes'), self.hasj(ip))
|
node_path = os.path.join(os.path.abspath('nodes'), self.hasj(ip))
|
||||||
announce_time = str(int(time.time()))
|
|
||||||
if not os.path.exists(node_path) and ip != self.own_ip:
|
if not os.path.exists(node_path) and ip != self.own_ip:
|
||||||
#loop for first time
|
#loop for first time
|
||||||
self.ip_to_hash_path(ip) #make a folder /nodes/hash
|
self.ip_to_hash_path(ip) #make a folder /nodes/hash
|
||||||
self.devices[ip] = result[0], announce_time
|
self.devices[ip] = result[0]
|
||||||
#self.node_timestamp(ip) #make a local copy of the timestamp in /nodes/hash/updatetimestamp
|
#self.node_timestamp(ip) #make a local copy of the timestamp in /nodes/hash/updatetimestamp
|
||||||
logger.info('New node %s', ip)
|
logger.info('New node %s', ip)
|
||||||
|
|
||||||
elif os.path.exists(node_path) and ip != self.own_ip:
|
elif os.path.exists(node_path) and ip != self.own_ip:
|
||||||
logger.info('Known node %s', ip)
|
logger.info('Known node %s', ip)
|
||||||
self.devices[ip] = result[0], announce_time
|
self.devices[ip] = result[0]
|
||||||
|
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@ -175,64 +154,45 @@ Initialize the clientserver
|
|||||||
"""
|
"""
|
||||||
logger.info('Serving to client')
|
logger.info('Serving to client')
|
||||||
import meshenger_clientserve
|
import meshenger_clientserve
|
||||||
# set a reference to this object
|
|
||||||
meshenger_clientserve.meshenger = self
|
|
||||||
meshenger_clientserve.main()
|
meshenger_clientserve.main()
|
||||||
# meshenger_clientserve.build_index_callback = self.build_index
|
|
||||||
|
|
||||||
def init_index(self):
|
|
||||||
|
def build_index(self):
|
||||||
"""
|
"""
|
||||||
Initialize the index. Read from disk or create new.
|
Make an index file of all the messages present on the node.
|
||||||
|
Save the time of the last update.
|
||||||
"""
|
"""
|
||||||
logger.info('Checking own index for the first time\n')
|
|
||||||
|
logger.info('Building own index for the first time\n')
|
||||||
|
|
||||||
if not os.path.exists('index'):
|
if not os.path.exists('index'):
|
||||||
with open('index','wb') as index:
|
with open('index','wb') as index:
|
||||||
index.write('')
|
index.write('')
|
||||||
self.previous_index = []
|
previous_index = []
|
||||||
else:
|
else:
|
||||||
self.previous_index = open('index').readlines()
|
previous_index = open('index').readlines()
|
||||||
self.build_index()
|
|
||||||
|
|
||||||
def build_index(self):
|
while not self.exitapp:
|
||||||
"""
|
current_index = os.listdir(self.msg_dir)
|
||||||
Make an index file of all the messages present on the node.
|
if current_index != previous_index:
|
||||||
Save the time of the last update.
|
with open('index', 'wb') as index:
|
||||||
"""
|
for message in os.listdir(self.msg_dir):
|
||||||
logger.debug('build_index')
|
index.write(message)
|
||||||
current_index = os.listdir(self.msg_dir)
|
index.write('\n')
|
||||||
if current_index != self.previous_index:
|
self.index_last_update = str(int(time.time()))
|
||||||
with open('index', 'wb') as index:
|
|
||||||
for message in os.listdir(self.msg_dir):
|
|
||||||
index.write(message)
|
|
||||||
index.write('\n')
|
|
||||||
self.index_last_update = str(int(time.time()))
|
|
||||||
|
|
||||||
logger.info('Index updated: %s', current_index)
|
logger.info('Index updated: %s', current_index)
|
||||||
|
|
||||||
with open('index_last_update', 'wb') as indexupdate:
|
with open('index_last_update', 'wb') as indexupdate:
|
||||||
indexupdate.write(self.index_last_update) ### misschien moet dit index_last_update zijn
|
indexupdate.write(self.index_last_update) ### misschien moet dit index_last_update zijn
|
||||||
|
|
||||||
self.previous_index = current_index
|
|
||||||
|
|
||||||
def make_alias(self):
|
|
||||||
"""
|
|
||||||
See if the node already has an alias (nickname) if not just use the IP-Hash
|
|
||||||
"""
|
|
||||||
if not os.path.exists('alias'):
|
|
||||||
with open('alias','wb') as alias:
|
|
||||||
self.alias = self.own_hash
|
|
||||||
alias.write(self.own_hash)
|
|
||||||
else:
|
|
||||||
self.alias = open('alias','rb').read().replace('\n','') #should we replace the newline?
|
|
||||||
pass
|
|
||||||
logger.debug('Alias is "'+self.alias+'"')
|
|
||||||
|
|
||||||
|
previous_index = current_index
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
def get_index(self,ip, path):
|
def get_index(self,ip, path):
|
||||||
"""
|
"""
|
||||||
Download the indices from other nodes.
|
Download the indices from other nodes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index'))
|
os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index'))
|
||||||
|
|
||||||
@ -285,7 +245,6 @@ Convert a node's ip into a hash and make a directory to store it's files
|
|||||||
"""
|
"""
|
||||||
Hack to adhoc0's inet6 adress
|
Hack to adhoc0's inet6 adress
|
||||||
"""
|
"""
|
||||||
#TODO possibly check if it's an empty file
|
|
||||||
if not os.path.isfile('interfaceip6adress'):
|
if not os.path.isfile('interfaceip6adress'):
|
||||||
os.system('ifconfig -a adhoc0 | grep inet6 > /root/meshenger/interfaceip6adress')
|
os.system('ifconfig -a adhoc0 | grep inet6 > /root/meshenger/interfaceip6adress')
|
||||||
with open('/root/meshenger/interfaceip6adress', 'r') as a:
|
with open('/root/meshenger/interfaceip6adress', 'r') as a:
|
||||||
|
@ -13,34 +13,27 @@ import logging, logging.config
|
|||||||
logging.config.fileConfig('pylog.conf')
|
logging.config.fileConfig('pylog.conf')
|
||||||
logger = logging.getLogger('meshenger'+'.clientserve')
|
logger = logging.getLogger('meshenger'+'.clientserve')
|
||||||
|
|
||||||
|
|
||||||
class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||||
|
|
||||||
messageDir = "msg"
|
messageDir = "msg"
|
||||||
|
|
||||||
|
"""
|
||||||
|
Serve index and messages
|
||||||
|
"""
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""
|
|
||||||
Serve index and messages
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.path == '/index' or self.path.startswith( "/"+self.messageDir ):
|
if self.path == '/':
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
f = os.path.relpath('webapp.html')
|
||||||
|
# f = os.path.join('/root/meshenger/',"webapp.html")
|
||||||
|
with open( f, 'r') as the_file:
|
||||||
|
self.wfile.write(the_file.read())
|
||||||
|
|
||||||
|
elif self.path == '/index' or self.path.startswith( "/"+self.messageDir ):
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||||
elif self.path == '/id':
|
|
||||||
if meshenger and meshenger.own_hash:
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'text-html')
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(meshenger.own_hash)
|
|
||||||
else:
|
|
||||||
self.send_error(404,'Id not yet available')
|
|
||||||
elif self.path == '/alias':
|
|
||||||
if meshenger and meshenger.alias:
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'text-html')
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(meshenger.alias)
|
|
||||||
else:
|
|
||||||
self.send_error(404,'Alias not yet available')
|
|
||||||
|
|
||||||
elif self.path == '/log':
|
elif self.path == '/log':
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text-html')
|
self.send_header('Content-type', 'text-html')
|
||||||
@ -48,33 +41,21 @@ Serve index and messages
|
|||||||
f = os.path.relpath('log/meshenger.log')
|
f = os.path.relpath('log/meshenger.log')
|
||||||
with open( f, 'r') as the_file:
|
with open( f, 'r') as the_file:
|
||||||
self.wfile.write(the_file.read())
|
self.wfile.write(the_file.read())
|
||||||
elif self.path.startswith('/web'):
|
else:
|
||||||
|
|
||||||
f = os.path.relpath(self.path[1:])
|
|
||||||
if not os.path.exists( f ) or os.path.isdir( f ):
|
|
||||||
f = os.path.join('web', 'index.html')
|
|
||||||
|
|
||||||
self.path = '/'+f
|
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
|
||||||
elif self.path == '/old':
|
|
||||||
self.send_response(200) #serve the webapp on every url requested
|
self.send_response(200) #serve the webapp on every url requested
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
f = os.path.relpath( 'webapp.html')
|
f = os.path.join( "webapp.html")
|
||||||
with open( f, 'r') as the_file:
|
with open( f, 'r') as the_file:
|
||||||
self.wfile.write(the_file.read())
|
self.wfile.write(the_file.read())
|
||||||
|
|
||||||
else:
|
|
||||||
self.path = '/' + os.path.join('web', 'index.html')
|
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Allow clients to post messages
|
||||||
|
"""
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
"""
|
|
||||||
Allow clients to post messages
|
|
||||||
"""
|
|
||||||
if self.path == '/send':
|
if self.path == '/send':
|
||||||
logger.info('incoming message from client!')
|
|
||||||
length = int(self.headers['Content-Length'])
|
length = int(self.headers['Content-Length'])
|
||||||
post_data = urlparse.parse_qs(self.rfile.read(length).decode('utf-8'))
|
post_data = urlparse.parse_qs(self.rfile.read(length).decode('utf-8'))
|
||||||
|
|
||||||
@ -85,18 +66,8 @@ Allow clients to post messages
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write('message created')
|
self.wfile.write('message created')
|
||||||
|
|
||||||
#let main rebuild message index
|
|
||||||
try:
|
|
||||||
logger.debug('try to call meshenger.build_index: %s', repr(meshenger))
|
|
||||||
meshenger.build_index()
|
|
||||||
except:
|
|
||||||
logger.error('failed to call meshenger.build_index')
|
|
||||||
pass
|
|
||||||
|
|
||||||
def writeMessage(self, time, message):
|
def writeMessage(self, time, message):
|
||||||
"""
|
|
||||||
Write message to disk
|
|
||||||
"""
|
|
||||||
f = os.path.join( self.messageDir, time)
|
f = os.path.join( self.messageDir, time)
|
||||||
if os.path.isfile( f ):
|
if os.path.isfile( f ):
|
||||||
return
|
return
|
||||||
@ -106,7 +77,6 @@ Write message to disk
|
|||||||
|
|
||||||
class ClientServe():
|
class ClientServe():
|
||||||
def __init__(self, port):
|
def __init__(self, port):
|
||||||
logger.info('ClientServe.__init__')
|
|
||||||
server = HTTPServer( ('', port), ClientServeHandler)
|
server = HTTPServer( ('', port), ClientServeHandler)
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
|
|
||||||
|
@ -28,20 +28,16 @@ class NodeServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
messageDir = "/msg"
|
messageDir = "/msg"
|
||||||
|
|
||||||
|
"""
|
||||||
|
Serve index and messages
|
||||||
|
"""
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""
|
|
||||||
Serve index and messages
|
|
||||||
"""
|
|
||||||
if self.path == '/':
|
if self.path == '/':
|
||||||
self.path = "/index"
|
self.path = "/index"
|
||||||
|
|
||||||
if self.path == '/index' or self.path.startswith( self.messageDir ):
|
if self.path == '/index' or self.path.startswith( self.messageDir ):
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||||
|
|
||||||
if self.path == '/alias' or self.path.startswith( self.messageDir ):
|
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
@ -53,7 +49,6 @@ class HTTPServerV6(BaseHTTPServer.HTTPServer):
|
|||||||
|
|
||||||
class NodeServe():
|
class NodeServe():
|
||||||
def __init__(self, port):
|
def __init__(self, port):
|
||||||
logger.info('NodeServe.__init__')
|
|
||||||
server = HTTPServerV6(('::', port), NodeServeHandler)
|
server = HTTPServerV6(('::', port), NodeServeHandler)
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
|
|
||||||
|
45
web/emoji.js
@ -1,45 +0,0 @@
|
|||||||
function parseEmoticons( inputStr ){
|
|
||||||
|
|
||||||
var emoticons = [
|
|
||||||
{
|
|
||||||
text: ':)',
|
|
||||||
image: '1.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '<iframe',
|
|
||||||
image: 'iframe-open.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '</iframe>',
|
|
||||||
image: 'iframe-close.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '<script',
|
|
||||||
image: 'script-open.gif'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '</script>',
|
|
||||||
image: 'script-close.gif'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '<style',
|
|
||||||
image: 'style-open.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '</style>',
|
|
||||||
image: 'style-close.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text:';)',
|
|
||||||
image: '2.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text:':rock:',
|
|
||||||
image: '3.png'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
for ( var i = 0; i < emoticons.length; i++ ){
|
|
||||||
inputStr = inputStr.split(emoticons[i].text).join('<img class="emo" src="/web/emoticons/'+emoticons[i].image+'">');
|
|
||||||
}
|
|
||||||
return inputStr;
|
|
||||||
}
|
|
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.2 KiB |
@ -1,70 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="cache-control" content="max-age=0" />
|
|
||||||
<meta http-equiv="cache-control" content="no-cache" />
|
|
||||||
<meta http-equiv="expires" content="0" />
|
|
||||||
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
|
|
||||||
<meta http-equiv="pragma" content="no-cache" />
|
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
|
||||||
<link href="/web/style.css" rel="stylesheet" type="text/css">
|
|
||||||
<title>meshenger</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="message-page">
|
|
||||||
<button id="message-back" class="back-button">Back</button><br><br>
|
|
||||||
<form id="message-form" action="" accept-charset="utf-8">
|
|
||||||
<textarea id="name" rows="1" placeholder="Your Name"></textarea>
|
|
||||||
<textarea id="message" rows="2" placeholder="Your Message"></textarea>
|
|
||||||
<br>
|
|
||||||
<input type="submit" class="send-button" value="Send">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="photo-page" class="photo-page">
|
|
||||||
<button id="photo-back" class="back-button">Back</button><br>
|
|
||||||
<input id="fileInput" accept="image/*" capture="camera" type="file">
|
|
||||||
<br>
|
|
||||||
<p>
|
|
||||||
<span id="progress">--</span>
|
|
||||||
<span id="total">--</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!--Squashed source<br>-->
|
|
||||||
<img id="sourceImage" src="" alt="capture" height="200" width="200">
|
|
||||||
<!--Offset done<br>-->
|
|
||||||
<canvas id="canvas1" width="200" height="200"></canvas>
|
|
||||||
<!--Dithered<br>-->
|
|
||||||
<canvas id="canvas2" width="200" height="200"></canvas>
|
|
||||||
<!--Rotated<br>-->
|
|
||||||
<canvas id="canvas3" width="200" height="200"></canvas>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<button id="rot-left" class="rot-left">Rotate left</button>
|
|
||||||
<button id="rot-right" class="rot-right">Rotate right</button>
|
|
||||||
<br><br>
|
|
||||||
<textarea id="photo-name" rows="1" placeholder="Your Name"></textarea>
|
|
||||||
<br>
|
|
||||||
<button id="submit-photo" class="photo-buttons">Send</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="overview-page"><br>
|
|
||||||
<div class="messages">
|
|
||||||
<ul id="inbox"></ul>
|
|
||||||
<ul id="outbox"></ul>
|
|
||||||
</div>
|
|
||||||
<div id="input-footer">
|
|
||||||
<button id="new-photo">Photo</button>
|
|
||||||
<button id="new-message">Message</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/web/main.js"></script>
|
|
||||||
<script src="/web/emoji.js"></script>
|
|
||||||
<script src="/web/photostuff.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
325
web/main.js
@ -1,325 +0,0 @@
|
|||||||
localStorage.clear();
|
|
||||||
|
|
||||||
|
|
||||||
//These need to be obtained from the node
|
|
||||||
var ownId, ownColor, ownAlias;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* INIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function(){
|
|
||||||
|
|
||||||
function update(){
|
|
||||||
if ( !ownId ){
|
|
||||||
getOwnId();
|
|
||||||
}
|
|
||||||
if ( !ownAlias){
|
|
||||||
getOwnAlias();
|
|
||||||
}
|
|
||||||
checkInbox();
|
|
||||||
// also check for outbox items on interval,
|
|
||||||
// necessary in case connection is lost and messages are not yet sent
|
|
||||||
checkOutbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('message-form').onsubmit = onSubmitMessage;
|
|
||||||
|
|
||||||
//update everything to initialize
|
|
||||||
updateInboxView();
|
|
||||||
update();
|
|
||||||
|
|
||||||
//check for new messages every 7 seconds
|
|
||||||
window.setInterval( update, 7000 );
|
|
||||||
|
|
||||||
initState();
|
|
||||||
initPhotoStuff(); //all photostuff is found in photostuff.js
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* STATE CHANGES
|
|
||||||
*/
|
|
||||||
|
|
||||||
function initState(){
|
|
||||||
showOverview();
|
|
||||||
|
|
||||||
document.getElementById('new-photo').onclick = showNewPhoto;
|
|
||||||
document.getElementById('new-message').onclick = showNewMessage;
|
|
||||||
document.getElementById('message-back').onclick = showOverview;
|
|
||||||
document.getElementById('photo-back').onclick = showOverview;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNewPhoto(){
|
|
||||||
document.getElementById('photo-page').style.display = "block";
|
|
||||||
document.getElementById('overview-page').style.display = "none";
|
|
||||||
document.getElementById('message-page').style.display = "none";
|
|
||||||
document.getElementById('fileInput').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNewMessage(){
|
|
||||||
document.getElementById('photo-page').style.display = "none";
|
|
||||||
document.getElementById('overview-page').style.display = "none";
|
|
||||||
document.getElementById('message-page').style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function showOverview(){
|
|
||||||
document.getElementById('photo-page').style.display = "none";
|
|
||||||
document.getElementById('overview-page').style.display = "block";
|
|
||||||
document.getElementById('message-page').style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* OUTBOX STUFF
|
|
||||||
*/
|
|
||||||
function onSubmitMessage(){
|
|
||||||
var msg = document.getElementById('message').value.replace(/\r?\n/g, "<br />");
|
|
||||||
if ( !msg || msg === "" ) { /* prevent sending of empty messages */
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var namm = document.getElementById('name').value;
|
|
||||||
if ( !namm || namm === "" || namm.length > 20) { /* prevent too long usernames */
|
|
||||||
namm = "anonymous";
|
|
||||||
}
|
|
||||||
addOutboxItem( namm, "<p class='text-message'>"+msg+"</p>" );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addOutboxItem( namm, message ){
|
|
||||||
var outStr = localStorage.getItem( 'outbox' ) || '';
|
|
||||||
var newMsgs = {};
|
|
||||||
var ddata = new Date().getTime();
|
|
||||||
var alias = ownAlias;
|
|
||||||
|
|
||||||
var contento = {
|
|
||||||
"time" : ddata,
|
|
||||||
"message" : message,
|
|
||||||
"name" : namm,
|
|
||||||
"node" : "local",
|
|
||||||
"alias": alias,
|
|
||||||
"hops" : "0"
|
|
||||||
};
|
|
||||||
newMsgs.message = contento;
|
|
||||||
|
|
||||||
localStorage.setItem( 'outbox', JSON.stringify(newMsgs) );
|
|
||||||
checkOutbox();
|
|
||||||
document.getElementById('message').value = '';
|
|
||||||
|
|
||||||
showOverview();
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkOutbox() {
|
|
||||||
var outStr = localStorage.getItem( 'outbox' );
|
|
||||||
if ( ! outStr ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var lines = outStr.split( /\n/ );
|
|
||||||
for ( var i in lines ) {
|
|
||||||
if ( lines[i].length === 0 ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var obj = JSON.parse(lines[i]);
|
|
||||||
var ts = obj.message.time;
|
|
||||||
delete obj.message.time;
|
|
||||||
var msg = JSON.stringify(obj.message);
|
|
||||||
sendMessage( ts, msg );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function sendMessage( timestamp, message ) {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
var data = 'time=' + encodeURIComponent( timestamp ) +
|
|
||||||
'&message=' + encodeURIComponent( message );
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function(){
|
|
||||||
if ( xhr.readyState == 4){
|
|
||||||
if ( xhr.status == 200 ) {
|
|
||||||
removeOutboxItem( timestamp );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('POST', 'send', true);
|
|
||||||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
||||||
xhr.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2005 00:00:00 GMT');
|
|
||||||
xhr.send(data);
|
|
||||||
}
|
|
||||||
function removeOutboxItem( timestamp ) {
|
|
||||||
var outStr = localStorage.getItem( 'outbox' ) || '';
|
|
||||||
var lines = outStr.split( /\n/ );
|
|
||||||
for ( var i in lines ) {
|
|
||||||
var obj = JSON.parse(lines[i]);
|
|
||||||
var ts = obj.message.time;
|
|
||||||
if ( ts === timestamp ) {
|
|
||||||
lines.splice( i, 1 );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var newOutStr = lines.join('\n');
|
|
||||||
localStorage.setItem('outbox', newOutStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* INBOX STUFF
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function updateInboxView() {
|
|
||||||
var localStorageArray = [];
|
|
||||||
var contentString = '';
|
|
||||||
|
|
||||||
if (localStorage.length>0) {
|
|
||||||
for (i=0;i<localStorage.length;i++){
|
|
||||||
|
|
||||||
element=localStorage.getItem(localStorage.key(i));
|
|
||||||
|
|
||||||
if ( localStorage.key(i).length < 10 || element === 'outbox' ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
elementj = JSON.parse(element);
|
|
||||||
} catch (e) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorageArray[i] = {
|
|
||||||
time:localStorage.key(i),
|
|
||||||
user:elementj.name,
|
|
||||||
message:elementj.message,
|
|
||||||
node:elementj.node,
|
|
||||||
hops:elementj.hops,
|
|
||||||
alias:elementj.alias
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
orderStorage = localStorageArray.sort(function(a,b) { return b.time - a.time; } );
|
|
||||||
|
|
||||||
for(var i in orderStorage) {
|
|
||||||
if ( i.length === 0 || i === 'outbox' ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var datereadable = getReadableDate( new Date(parseInt(orderStorage[i].time)) );
|
|
||||||
var color = getNodeColor( orderStorage[i].node );
|
|
||||||
contentString += '<li><div class="message-block" style="background-color:'+color+'">'+
|
|
||||||
'<div class="date-sender">On ' + datereadable +
|
|
||||||
' <b>' + parseEmoticons(orderStorage[i].user) +'</b> wrote:</div>' +
|
|
||||||
'<div class="message-text">' + parseEmoticons( orderStorage[i].message ) + '</div>' + //parseEmoticons is found in emoji.js
|
|
||||||
' <div class="nodehops"><div class="node '+orderStorage[i].node+'">from '+orderStorage[i].alias + '</div>' +
|
|
||||||
' <div class="hops '+orderStorage[i].hops+'">via '+orderStorage[i].hops+' nodes</div></div></div></li>';
|
|
||||||
}
|
|
||||||
document.getElementById( 'inbox' ).innerHTML = contentString;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getReadableDate( date ) {
|
|
||||||
var day = date.getDate();
|
|
||||||
if (day < 10) day = '0' + day;
|
|
||||||
var month = date.getMonth()+1;
|
|
||||||
if (month < 10) month = '0' + month;
|
|
||||||
var year = date.getFullYear();
|
|
||||||
var hrs = date.getHours();
|
|
||||||
if (hrs < 10) hrs = '0' + hrs;
|
|
||||||
var min = date.getMinutes();
|
|
||||||
if (min < 10) min = '0' + min;
|
|
||||||
|
|
||||||
return day + '/' + month + '/' + year + ' ' + hrs + ':' + min;
|
|
||||||
}
|
|
||||||
function getNodeColor( nodeId ) {
|
|
||||||
if( nodeId === 'local' ) {
|
|
||||||
return ownColor || '#fff';
|
|
||||||
}
|
|
||||||
return colorLuminance(nodeId.substr(0,6), 0.5);
|
|
||||||
}
|
|
||||||
function colorLuminance(hex, lum) {
|
|
||||||
|
|
||||||
// validate hex string
|
|
||||||
hex = String(hex).replace(/[^0-9a-f]/gi, '');
|
|
||||||
if (hex.length < 6) {
|
|
||||||
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
|
|
||||||
}
|
|
||||||
lum = lum || 0;
|
|
||||||
|
|
||||||
// convert to decimal and change luminosity
|
|
||||||
var rgb = "#", c, i;
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
c = parseInt(hex.substr(i*2,2), 16);
|
|
||||||
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
|
|
||||||
rgb += ("00"+c).substr(c.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMessageDownload( msg, filename ) {
|
|
||||||
if ( localStorage.getItem( filename ) === null ) {
|
|
||||||
localStorage.setItem( filename, msg );
|
|
||||||
}
|
|
||||||
updateInboxView();
|
|
||||||
}
|
|
||||||
function onIndex( index ) {
|
|
||||||
var lines = index.split( /\n/ );
|
|
||||||
var k,i,l,f;
|
|
||||||
for( k in localStorage){
|
|
||||||
l = 1;
|
|
||||||
for ( i in lines ) {
|
|
||||||
f = lines[i];
|
|
||||||
if (f == k){ l = 0; }
|
|
||||||
}
|
|
||||||
if (l == 1){
|
|
||||||
localStorage.removeItem(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateInboxView();
|
|
||||||
|
|
||||||
for ( i in lines ) {
|
|
||||||
var fname = lines[i];
|
|
||||||
if ( localStorage.getItem( fname ) === null ) {
|
|
||||||
//localStorage.setItem( ts, lines[i].substr(lines[i].indexOf(' ')) );
|
|
||||||
downloadMessage( fname );
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
function downloadMessage(filename) {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.onreadystatechange = function(){
|
|
||||||
if (xhr.readyState == 4 && xhr.status == 200){
|
|
||||||
onMessageDownload( xhr.responseText, filename );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.open( "GET", 'msg/'+filename, true);
|
|
||||||
xhr.send();
|
|
||||||
|
|
||||||
}
|
|
||||||
function checkInbox() {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.onreadystatechange = function(){
|
|
||||||
if (xhr.readyState == 4 && xhr.status == 200){
|
|
||||||
onIndex( xhr.responseText );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.open( "GET", 'index', true);
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
function getOwnId() {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.onreadystatechange = function(){
|
|
||||||
if (xhr.readyState == 4 && xhr.status == 200){
|
|
||||||
ownId = xhr.responseText;
|
|
||||||
ownColor = getNodeColor( ownId );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.open( "GET", 'id', true);
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOwnAlias() {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.onreadystatechange = function(){
|
|
||||||
if (xhr.readyState == 4 && xhr.status == 200){
|
|
||||||
ownAlias = xhr.responseText;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.open( "GET", 'alias', true);
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
|||||||
/*
|
|
||||||
* PHOTO STUFF
|
|
||||||
*/
|
|
||||||
var imgDim = 200;
|
|
||||||
var canvas1;
|
|
||||||
var context1;
|
|
||||||
var canvas2;
|
|
||||||
var context2;
|
|
||||||
var imgObj;
|
|
||||||
var fileReader;
|
|
||||||
var rotation = 0;
|
|
||||||
|
|
||||||
var sourceImage = document.getElementById('sourceImage'); // get reference to img
|
|
||||||
var fileInput = document.getElementById('fileInput'); // get reference to file select
|
|
||||||
var progressEl = document.getElementById('progress');
|
|
||||||
var totalEl = document.getElementById('total');
|
|
||||||
|
|
||||||
|
|
||||||
function initPhotoStuff(){
|
|
||||||
canvas1 = document.getElementById('canvas1');
|
|
||||||
context1 = canvas1.getContext('2d');
|
|
||||||
canvas2 = document.getElementById('canvas2');
|
|
||||||
context2 = canvas2.getContext('2d');
|
|
||||||
canvas3 = document.getElementById('canvas3');
|
|
||||||
context3 = canvas3.getContext('2d');
|
|
||||||
initCanvas(context1);
|
|
||||||
initCanvas(context2);
|
|
||||||
initCanvas(context3);
|
|
||||||
|
|
||||||
fileInput.onchange = onFileInputChange;
|
|
||||||
|
|
||||||
document.getElementById('rot-left').onclick = onRotateLeft;
|
|
||||||
document.getElementById('rot-right').onclick = onRotateRight;
|
|
||||||
document.getElementById('submit-photo').onclick = submitImage;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function submitImage(){
|
|
||||||
var image = new Image(); //create new image holder
|
|
||||||
|
|
||||||
var canvas = document.getElementById('canvas3'); // choose canvas element to convert
|
|
||||||
var dataURL = canvas.toDataURL(); // convert cabvas to data url we can handle
|
|
||||||
image.src = dataURL;
|
|
||||||
image.className = 'photo-message';
|
|
||||||
if ( !image.src || image.src === "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAACEUlEQVR4nO3TMQEAIAzAsPk3DQrWF45EQZ/OAVbzOgB+ZhAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBcAEODrNzcVUZ+gAAAABJRU5ErkJggg==" ) { /* prevent sending of empty messages (data url of blank, thus white, canvas)*/
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var namm = document.getElementById('photo-name').value;
|
|
||||||
if( !namm || namm == "" ){
|
|
||||||
namm = "anonymous";
|
|
||||||
}
|
|
||||||
addOutboxItem( parseEmoticons(namm), image.outerHTML );
|
|
||||||
|
|
||||||
showOverview();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initCanvas(context){
|
|
||||||
// Debug filling
|
|
||||||
context.fillStyle ="#dbbd7a";
|
|
||||||
context.fill();
|
|
||||||
context.beginPath();
|
|
||||||
context.rect(0,0, imgDim, imgDim);
|
|
||||||
context.fillStyle = 'white';
|
|
||||||
context.fill();
|
|
||||||
// context.lineWidth = 7;
|
|
||||||
// context.strokeStyle = 'black';
|
|
||||||
// context.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create file reader
|
|
||||||
function onFileInputChange(){
|
|
||||||
var file = fileInput.files[0]; // get selected file (camera capture)
|
|
||||||
fileReader = new FileReader();
|
|
||||||
fileReader.onload = onFileReaderLoad; // add onload listener
|
|
||||||
fileReader.onprogress = onFileReaderProgress; // add progress listener
|
|
||||||
fileReader.readAsDataURL( file ); // get captured image as data URI
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFileReaderProgress(e) {
|
|
||||||
if (e.lengthComputable) {
|
|
||||||
var progress = parseInt( ((e.loaded / e.total) * 100), 10 );
|
|
||||||
console.log(progress);
|
|
||||||
progressEl.innerHTML = e.loaded;
|
|
||||||
totalEl.innerHTML = e.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onFileReaderLoad() {
|
|
||||||
imgObj = new Image();
|
|
||||||
imgObj.src = fileReader.result;
|
|
||||||
imgObj.onload = onImageLoad;
|
|
||||||
|
|
||||||
//for debugging: show image on screen, will be squashed
|
|
||||||
sourceImage.src = fileReader.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onImageLoad() {
|
|
||||||
var w,h;
|
|
||||||
var xOffset = 0;
|
|
||||||
var yOffset = 0;
|
|
||||||
if ( imgObj.width > imgObj.height ) {
|
|
||||||
h = imgDim;
|
|
||||||
w = imgDim * imgObj.width / imgObj.height;
|
|
||||||
xOffset = (imgDim - w) / 2;
|
|
||||||
} else {
|
|
||||||
w = imgDim;
|
|
||||||
h = imgDim * imgObj.height / imgObj.width;
|
|
||||||
yOffset = (imgDim - h) / 2;
|
|
||||||
}
|
|
||||||
context1.drawImage(imgObj, xOffset, yOffset, w, h);
|
|
||||||
var imageData = context1.getImageData( 0, 0, canvas1.width, canvas1.height);
|
|
||||||
context2.putImageData( monochrome(imageData,128,"atkinson"), 0, 0);
|
|
||||||
drawRotated();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRotateRight(){
|
|
||||||
rotation += 90;
|
|
||||||
drawRotated();
|
|
||||||
}
|
|
||||||
function onRotateLeft(){
|
|
||||||
rotation -= 90;
|
|
||||||
drawRotated();
|
|
||||||
}
|
|
||||||
function drawRotated(){
|
|
||||||
|
|
||||||
context3.clearRect(0,0,canvas3.width,canvas3.height);
|
|
||||||
context3.save();
|
|
||||||
context3.translate(canvas3.width/2,canvas3.height/2);
|
|
||||||
context3.rotate(rotation * Math.PI / 180);
|
|
||||||
context3.translate(-canvas3.width/2,-canvas3.height/2);
|
|
||||||
context3.drawImage( canvas2, 0, 0 );
|
|
||||||
context3.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var bayerThresholdMap = [
|
|
||||||
[ 15, 135, 45, 165 ],
|
|
||||||
[ 195, 75, 225, 105 ],
|
|
||||||
[ 60, 180, 30, 150 ],
|
|
||||||
[ 240, 120, 210, 90 ]
|
|
||||||
];
|
|
||||||
|
|
||||||
var lumR = [];
|
|
||||||
var lumG = [];
|
|
||||||
var lumB = [];
|
|
||||||
for (var i=0; i<256; i++) {
|
|
||||||
lumR[i] = i*0.299;
|
|
||||||
lumG[i] = i*0.587;
|
|
||||||
lumB[i] = i*0.114;
|
|
||||||
}
|
|
||||||
|
|
||||||
function monochrome(imageData, threshold, type){
|
|
||||||
|
|
||||||
var imageDataLength = imageData.data.length;
|
|
||||||
|
|
||||||
// Greyscale luminance (sets r pixels to luminance of rgb)
|
|
||||||
for (var i = 0; i <= imageDataLength; i += 4) {
|
|
||||||
imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i+1]] + lumB[imageData.data[i+2]]);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
var w = imageData.width;
|
|
||||||
var newPixel, err;
|
|
||||||
|
|
||||||
for (var currentPixel = 0; currentPixel <= imageDataLength; currentPixel+=4) {
|
|
||||||
|
|
||||||
if (type === "none") {
|
|
||||||
// No dithering
|
|
||||||
imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255;
|
|
||||||
} else if (type === "bayer") {
|
|
||||||
// 4x4 Bayer ordered dithering algorithm
|
|
||||||
var x = currentPixel/4 % w;
|
|
||||||
var y = Math.floor(currentPixel/4 / w);
|
|
||||||
var map = Math.floor( (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2 );
|
|
||||||
imageData.data[currentPixel] = (map < threshold) ? 0 : 255;
|
|
||||||
} else if (type === "floydsteinberg") {
|
|
||||||
// Floyd–Steinberg dithering algorithm
|
|
||||||
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
|
|
||||||
err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
|
|
||||||
imageData.data[currentPixel] = newPixel;
|
|
||||||
|
|
||||||
imageData.data[currentPixel + 4 ] += err*7;
|
|
||||||
imageData.data[currentPixel + 4*w - 4 ] += err*3;
|
|
||||||
imageData.data[currentPixel + 4*w ] += err*5;
|
|
||||||
imageData.data[currentPixel + 4*w + 4 ] += err*1;
|
|
||||||
} else {
|
|
||||||
// Bill Atkinson's dithering algorithm
|
|
||||||
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
|
|
||||||
err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
|
|
||||||
imageData.data[currentPixel] = newPixel;
|
|
||||||
|
|
||||||
imageData.data[currentPixel + 4 ] += err;
|
|
||||||
imageData.data[currentPixel + 8 ] += err;
|
|
||||||
imageData.data[currentPixel + 4*w - 4 ] += err;
|
|
||||||
imageData.data[currentPixel + 4*w ] += err;
|
|
||||||
imageData.data[currentPixel + 4*w + 4 ] += err;
|
|
||||||
imageData.data[currentPixel + 8*w ] += err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set g and b pixels equal to r
|
|
||||||
imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
|
|
||||||
}
|
|
||||||
// Alpha: make white pixels transparent!
|
|
||||||
var newColor = {r:0,g:0,b:0, a:0};
|
|
||||||
|
|
||||||
for ( i = 0, n = imageData.data.length; i <n; i += 4) {
|
|
||||||
var r = imageData.data[i],
|
|
||||||
g = imageData.data[i+1],
|
|
||||||
b = imageData.data[i+2];
|
|
||||||
|
|
||||||
// If its white, or close to white then change it
|
|
||||||
if(r >=200 && g >= 200 && b >= 200){
|
|
||||||
// Change the white to whatever.
|
|
||||||
imageData.data[i] = newColor.r;
|
|
||||||
imageData.data[i+1] = newColor.g;
|
|
||||||
imageData.data[i+2] = newColor.b;
|
|
||||||
imageData.data[i+3] = newColor.a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageData;
|
|
||||||
}
|
|
523
web/style.css
@ -1,523 +0,0 @@
|
|||||||
body, html{
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
font-size:20px;
|
|
||||||
text-shadow: 1px 1px #ddd;
|
|
||||||
font-family: times, 'times new roman', helvetica,serif;
|
|
||||||
line-height:1.5em;
|
|
||||||
color:#444;
|
|
||||||
background:#fff;
|
|
||||||
padding:20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* resize images on mobile */
|
|
||||||
@media all and (max-width: 768px) {
|
|
||||||
|
|
||||||
.photo-message{ /* rounded corner imgs...*/
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
overflow:hidden;
|
|
||||||
line-height: 0;
|
|
||||||
-webkit-border-top-left-radius:123px;
|
|
||||||
-webkit-border-top-right-radius:123px;
|
|
||||||
-webkit-border-bottom-right-radius:123px;
|
|
||||||
-webkit-border-bottom-left-radius:123px;
|
|
||||||
-moz-border-radius-bottomright:123px;
|
|
||||||
-moz-border-radius-bottomleft:123px;
|
|
||||||
-moz-border-top-left-radius:123px;
|
|
||||||
-moz-border-top-right-radius:123px;
|
|
||||||
border-top-left-radius:123px;
|
|
||||||
border-top-right-radius:123px;
|
|
||||||
border-bottom-right-radius:123px;
|
|
||||||
border-bottom-left-radius:123px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.photo-message{
|
|
||||||
width:100%;
|
|
||||||
line-height: 0;
|
|
||||||
vertical-align:middle; /* otherwise produces bottom border the size of the body 'line-height' */
|
|
||||||
/* disable anti aliasing */
|
|
||||||
image-rendering: optimizeSpeed;
|
|
||||||
image-rendering: -moz-crisp-edges;
|
|
||||||
image-rendering: -o-crisp-edges;
|
|
||||||
image-rendering: -webkit-optimize-contrast;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
image-rendering: optimize-contrast;
|
|
||||||
-ms-interpolation-mode: nearest-neighbor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nodehops{
|
|
||||||
position: absolute;
|
|
||||||
float: right;
|
|
||||||
right:20px;
|
|
||||||
text-align: right;
|
|
||||||
padding: 0px;
|
|
||||||
margin-top:-50px;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-border-top-left-radius:123px;
|
|
||||||
-webkit-border-top-right-radius:123px;
|
|
||||||
-webkit-border-bottom-right-radius:123px;
|
|
||||||
-webkit-border-bottom-left-radius:123px;
|
|
||||||
-moz-border-radius-bottomright:123px;
|
|
||||||
-moz-border-radius-bottomleft:123px;
|
|
||||||
-moz-border-top-left-radius:123px;
|
|
||||||
-moz-border-top-right-radius:123px;
|
|
||||||
border-top-left-radius:123px;
|
|
||||||
border-top-right-radius:123px;
|
|
||||||
border-bottom-right-radius:123px;
|
|
||||||
border-bottom-left-radius:123px;
|
|
||||||
background:#fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* resize images on desktop */
|
|
||||||
@media all and (min-width: 768px) {
|
|
||||||
img.photo-message{
|
|
||||||
width:50%;
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
/* disable anti aliasing */
|
|
||||||
image-rendering: optimizeSpeed;
|
|
||||||
image-rendering: -moz-crisp-edges;
|
|
||||||
image-rendering: -o-crisp-edges;
|
|
||||||
image-rendering: -webkit-optimize-contrast;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
image-rendering: optimize-contrast;
|
|
||||||
-ms-interpolation-mode: nearest-neighbor;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
img.photo-message{
|
|
||||||
/* disable anti aliasing */
|
|
||||||
image-rendering: optimizeSpeed;
|
|
||||||
image-rendering: -moz-crisp-edges;
|
|
||||||
image-rendering: -o-crisp-edges;
|
|
||||||
image-rendering: -webkit-optimize-contrast;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
image-rendering: optimize-contrast;
|
|
||||||
-ms-interpolation-mode: nearest-neighbor;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-message{ /* rounded corner imgs on desktop...*/
|
|
||||||
padding:30px;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
overflow:hidden;
|
|
||||||
-webkit-border-top-left-radius:123px;
|
|
||||||
-webkit-border-top-right-radius:123px;
|
|
||||||
-webkit-border-bottom-right-radius:123px;
|
|
||||||
-webkit-border-bottom-left-radius:123px;
|
|
||||||
-moz-border-radius-bottomright:123px;
|
|
||||||
-moz-border-radius-bottomleft:123px;
|
|
||||||
-moz-border-top-left-radius:123px;
|
|
||||||
-moz-border-top-right-radius:123px;
|
|
||||||
border-top-left-radius:123px;
|
|
||||||
border-top-right-radius:123px;
|
|
||||||
border-bottom-right-radius:123px;
|
|
||||||
border-bottom-left-radius:123px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nodehops{
|
|
||||||
position: absolute;
|
|
||||||
float: right;
|
|
||||||
right:20px;
|
|
||||||
text-align: right;
|
|
||||||
padding: 4px;
|
|
||||||
margin-top:-60px;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-border-top-left-radius:123px;
|
|
||||||
-webkit-border-top-right-radius:123px;
|
|
||||||
-webkit-border-bottom-right-radius:123px;
|
|
||||||
-webkit-border-bottom-left-radius:123px;
|
|
||||||
-moz-border-radius-bottomright:123px;
|
|
||||||
-moz-border-radius-bottomleft:123px;
|
|
||||||
-moz-border-top-left-radius:123px;
|
|
||||||
-moz-border-top-right-radius:123px;
|
|
||||||
border-top-left-radius:123px;
|
|
||||||
border-top-right-radius:123px;
|
|
||||||
border-bottom-right-radius:123px;
|
|
||||||
border-bottom-left-radius:123px;
|
|
||||||
background:#fff;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
img.emo {
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-sender .emo {
|
|
||||||
width:22px;
|
|
||||||
height:22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.text-message{
|
|
||||||
padding: 30px;
|
|
||||||
padding-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#input-footer{
|
|
||||||
position: fixed;
|
|
||||||
top:0px;
|
|
||||||
width:100%;
|
|
||||||
margin:10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#new-photo{
|
|
||||||
display:inline;
|
|
||||||
float:left;
|
|
||||||
width:30%;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
#new-message{
|
|
||||||
margin-right: 60px;
|
|
||||||
display:inline;
|
|
||||||
width:30%;
|
|
||||||
float:right;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-button{
|
|
||||||
display:inline;
|
|
||||||
float:left;
|
|
||||||
width:100%;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.rot-left{
|
|
||||||
display:inline;
|
|
||||||
float:left;
|
|
||||||
width:50%;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rot-right{
|
|
||||||
display:inline;
|
|
||||||
width:50%;
|
|
||||||
float:right;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.photo-buttons {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
width:100%;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* hide calculation canvases */
|
|
||||||
#sourceImage{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#canvas1{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#canvas2{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#progress{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#total{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#message-form textarea{
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
outline:none;
|
|
||||||
width: 98%;
|
|
||||||
margin: 0 auto;
|
|
||||||
line-height: 40px;
|
|
||||||
border:none;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#photo-name{ /* text field */
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
outline:none;
|
|
||||||
width: 98%;
|
|
||||||
margin: 0 auto;
|
|
||||||
line-height: 40px;
|
|
||||||
border:none;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#message-form .send-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
outline: none;
|
|
||||||
border:0;
|
|
||||||
-webkit-border-radius: 13px;
|
|
||||||
-moz-border-radius: 13px;
|
|
||||||
border-radius: 13px;
|
|
||||||
height:40px;
|
|
||||||
width:100%;
|
|
||||||
display: block;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight:bold;
|
|
||||||
text-shadow: 0 0 23px #888;
|
|
||||||
background-color:#fff;
|
|
||||||
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
select{
|
|
||||||
width:100%;
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin: 1.5em 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
font-size: 20px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
h2{
|
|
||||||
margin: 0 0 20px;
|
|
||||||
font-size: 40px;
|
|
||||||
text-shadow: 0px 0px 23px #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr{
|
|
||||||
height:10px;
|
|
||||||
border: 10;
|
|
||||||
width:87%;
|
|
||||||
background-image: url(css/bg1.gif);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages ul{
|
|
||||||
padding:0px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.messages li{
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.messages .message-block {
|
|
||||||
margin-top:30px;
|
|
||||||
word-wrap:break-word;
|
|
||||||
overflow: visible;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-border-top-left-radius:123px;
|
|
||||||
-webkit-border-top-right-radius:123px;
|
|
||||||
-webkit-border-bottom-right-radius:123px;
|
|
||||||
-webkit-border-bottom-left-radius:123px;
|
|
||||||
-moz-border-radius-bottomright:123px;
|
|
||||||
-moz-border-radius-bottomleft:123px;
|
|
||||||
-moz-border-top-left-radius:123px;
|
|
||||||
-moz-border-top-right-radius:123px;
|
|
||||||
border-top-left-radius:123px;
|
|
||||||
border-top-right-radius:123px;
|
|
||||||
border-bottom-right-radius:123px;
|
|
||||||
border-bottom-left-radius:123px;
|
|
||||||
background:#fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.photoBox {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
line-height: 40px;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
background-color:#fff;
|
|
||||||
z-index:1002;
|
|
||||||
overflow: visible;
|
|
||||||
background:#fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.node, .hops {
|
|
||||||
font-size:10px;
|
|
||||||
display: inline-block;
|
|
||||||
word-wrap:break-word;
|
|
||||||
overflow: visible;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.message-text {
|
|
||||||
font-size: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-sender {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 4px;
|
|
||||||
word-wrap:break-word;
|
|
||||||
overflow: visible;
|
|
||||||
-moz-box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-box-shadow:0 0 23px #bbb;
|
|
||||||
box-shadow:0 0 23px #bbb;
|
|
||||||
-webkit-border-top-left-radius:123px;
|
|
||||||
-webkit-border-top-right-radius:123px;
|
|
||||||
-webkit-border-bottom-right-radius:123px;
|
|
||||||
-webkit-border-bottom-left-radius:123px;
|
|
||||||
-moz-border-radius-bottomright:123px;
|
|
||||||
-moz-border-radius-bottomleft:123px;
|
|
||||||
-moz-border-top-left-radius:123px;
|
|
||||||
-moz-border-top-right-radius:123px;
|
|
||||||
border-top-left-radius:123px;
|
|
||||||
border-top-right-radius:123px;
|
|
||||||
border-bottom-right-radius:123px;
|
|
||||||
border-bottom-left-radius:123px;
|
|
||||||
background:#fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
#outbox{
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
#header{
|
|
||||||
width:100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
@ -355,7 +355,7 @@ updateOutboxView();
|
|||||||
window.setInterval( function(){
|
window.setInterval( function(){
|
||||||
checkInbox();
|
checkInbox();
|
||||||
checkOutbox();
|
checkOutbox();
|
||||||
}, 7000 );
|
}, 3000 );
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|