Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
816c4f5f6b | |||
d974b08694 | |||
253990eaae | |||
1e63cc4f4d | |||
2ff471f4a7 | |||
|
c51c092c87 | ||
1c8760c8c3 | |||
ae99ee803f | |||
d5c5ccc4ba | |||
8bcfbb8070 | |||
19aeec6264 | |||
bc432d3dbc | |||
611734331f | |||
3658d3cf9e | |||
59f6370bb2 | |||
20e3fcacf5 | |||
61c33d2993 | |||
a8535433b5 | |||
|
1efcae7b48 | ||
|
801b87590b | ||
|
7ec5f54422 | ||
|
c7c2170ee9 | ||
|
ce1b342a5e | ||
4e07207c5b | |||
b68bae4f42 | |||
b2a0b343b2 | |||
|
79ffd9d369 | ||
2ead3f5aaa | |||
|
0128c61fea | ||
|
1a590a5a41 | ||
481ceba7b4 | |||
|
0a38eee079 | ||
|
b7fb1d4636 | ||
a3caf7e407 | |||
|
5de5ed9f1a | ||
|
a90c35451f | ||
aba8149226 | |||
d6f081f8b0 | |||
5e0fd9c935 | |||
3c71c68d70 | |||
8e0b6f8532 | |||
736d45d465 | |||
8f135c9cf1 | |||
c74673dbe3 | |||
07503b5285 | |||
3d1652eaa8 | |||
|
0873ef597b | ||
|
90b5ee0273 | ||
|
a37b519ddf | ||
|
3b07132282 | ||
|
d19179cbf8 | ||
|
df281b3fa9 | ||
|
842d656bd9 | ||
|
e91905d63b | ||
|
0de4a6b878 | ||
|
e336ba209e | ||
|
1dcfbee6b3 | ||
|
70cbbcdcf0 |
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,6 +26,18 @@ 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,3 +1,46 @@
|
|||||||
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 ://github.com/rscmbbng/meshenger /root/meshenger
|
git clone git://github.com/rscmbbng/meshenger /root/meshenger
|
||||||
|
|
||||||
mv uhttpd /etc/config/uhttpd
|
mv uhttpd /etc/config/uhttpd
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ 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 excisting wifi device, look in the list above.
|
option 'device' 'radio0' #use your existing 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'
|
||||||
option 'encryption' 'none'
|
option 'encryption' 'none'
|
||||||
option 'isolate' '1'
|
option 'isolate' '1'
|
||||||
|
125
main.py
@ -2,31 +2,39 @@
|
|||||||
|
|
||||||
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
|
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_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
|
||||||
@ -44,12 +52,6 @@ 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()
|
||||||
@ -58,6 +60,10 @@ 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')
|
||||||
@ -67,7 +73,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')
|
||||||
@ -85,15 +91,27 @@ 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])
|
lastupdate.write(self.devices[ip][0])
|
||||||
#return updatepath
|
#return updatepath
|
||||||
|
|
||||||
|
|
||||||
@ -104,17 +122,20 @@ Announce the node's existance to other nodes
|
|||||||
"""
|
"""
|
||||||
logger.info('Announcing')
|
logger.info('Announcing')
|
||||||
while not self.exitapp:
|
while not self.exitapp:
|
||||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
try:
|
||||||
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
|
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
sock.close()
|
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
|
||||||
time.sleep(5)
|
#sock.close()
|
||||||
|
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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print 'Discovering'
|
logger.info('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)
|
||||||
@ -123,19 +144,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.info('%s %s', ip, "*"*45)
|
#logger.debug('%s %s', ip, 'discovered')
|
||||||
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]
|
self.devices[ip] = result[0], announce_time
|
||||||
#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]
|
self.devices[ip] = result[0], announce_time
|
||||||
|
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@ -154,45 +175,64 @@ 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):
|
|
||||||
"""
|
"""
|
||||||
Make an index file of all the messages present on the node.
|
Initialize the index. Read from disk or create new.
|
||||||
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('')
|
||||||
previous_index = []
|
self.previous_index = []
|
||||||
else:
|
else:
|
||||||
previous_index = open('index').readlines()
|
self.previous_index = open('index').readlines()
|
||||||
|
self.build_index()
|
||||||
|
|
||||||
while not self.exitapp:
|
def build_index(self):
|
||||||
current_index = os.listdir(self.msg_dir)
|
"""
|
||||||
if current_index != previous_index:
|
Make an index file of all the messages present on the node.
|
||||||
with open('index', 'wb') as index:
|
Save the time of the last update.
|
||||||
for message in os.listdir(self.msg_dir):
|
"""
|
||||||
index.write(message)
|
logger.debug('build_index')
|
||||||
index.write('\n')
|
current_index = os.listdir(self.msg_dir)
|
||||||
self.index_last_update = str(int(time.time()))
|
if current_index != self.previous_index:
|
||||||
|
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'))
|
||||||
|
|
||||||
@ -245,6 +285,7 @@ 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,27 +13,34 @@ 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 == '/':
|
if self.path == '/index' or self.path.startswith( "/"+self.messageDir ):
|
||||||
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')
|
||||||
@ -41,21 +48,33 @@ class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|||||||
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())
|
||||||
else:
|
elif self.path.startswith('/web'):
|
||||||
|
|
||||||
|
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.join( "webapp.html")
|
f = os.path.relpath( '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'))
|
||||||
|
|
||||||
@ -66,8 +85,18 @@ class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write('message created')
|
self.wfile.write('message created')
|
||||||
|
|
||||||
def writeMessage(self, time, message):
|
#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):
|
||||||
|
"""
|
||||||
|
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
|
||||||
@ -77,6 +106,7 @@ class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|||||||
|
|
||||||
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,16 +28,20 @@ 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')
|
||||||
@ -49,6 +53,7 @@ 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()
|
||||||
|
|
||||||
|
BIN
web/.index.html.swo
Normal file
45
web/emoji.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
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;
|
||||||
|
}
|
BIN
web/emoticons/1.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
web/emoticons/2.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
web/emoticons/3.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
web/emoticons/iframe-close.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
web/emoticons/iframe-open.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
web/emoticons/script-close.gif
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
web/emoticons/script-open.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
web/emoticons/style-close.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
web/emoticons/style-open.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
70
web/index.html
Executable file
@ -0,0 +1,70 @@
|
|||||||
|
<!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
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
225
web/photostuff.js
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* 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
Executable file
@ -0,0 +1,523 @@
|
|||||||
|
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();
|
||||||
}, 3000 );
|
}, 7000 );
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|