wttr.in/bin/srv.py
2015-12-27 00:22:13 +02:00

261 lines
7.4 KiB
Python

import sys
import logging
import os
import re
import requests
import socket
import subprocess
import time
import traceback
import geoip2.database
from geopy.geocoders import Nominatim
import jinja2
import gevent
from gevent.wsgi import WSGIServer
from gevent.queue import Queue
from gevent.monkey import patch_all
from gevent.subprocess import Popen, PIPE, STDOUT
patch_all()
from flask import Flask, request, render_template, send_from_directory
app = Flask(__name__)
MYDIR = os.path.abspath(os.path.dirname( os.path.dirname('__file__') ))
GEOLITE = os.path.join( MYDIR, "GeoLite2-City.mmdb" )
WEGO = "/home/igor/go/bin/wego"
CACHEDIR = os.path.join( MYDIR, "cache" )
IP2LCACHE = os.path.join( MYDIR, "cache/ip2l" )
ALIASES = os.path.join( MYDIR, "share/aliases" )
ANSI2HTML = os.path.join( MYDIR, "share/ansi2html.sh" )
HELP_FILE = os.path.join( MYDIR, 'share/help.txt' )
LOG_FILE = os.path.join( MYDIR, 'log/main.log' )
TEMPLATES = os.path.join( MYDIR, 'share/templates' )
STATIC = os.path.join( MYDIR, 'share/static' )
NOT_FOUND_LOCATION = "NOT_FOUND"
DEFAULT_LOCATION = "Oymyakon"
NOT_FOUND_MESSAGE = """
We were unable to find your location,
so we have brought you to Oymyakon,
one of the coldest permanently inhabited locales on the planet.
"""
logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG)
reader = geoip2.database.Reader(GEOLITE)
geolocator = Nominatim()
my_loader = jinja2.ChoiceLoader([
app.jinja_loader,
jinja2.FileSystemLoader(TEMPLATES),
])
app.jinja_loader = my_loader
class Limits:
def __init__( self ):
self.intervals = [ 'min', 'hour', 'day' ]
self.divisor = {
'min': 60,
'hour': 3600,
'day': 86400,
}
self.counter = {
'min': {},
'hour': {},
'day': {},
}
self.limit = {
'min': 10,
'hour': 20,
'day': 100,
}
self.last_update = {
'min': 0,
'hour': 0,
'day': 0,
}
self.clear_counters()
def check_ip( self, ip ):
self.clear_counters()
for interval in self.intervals:
if ip not in self.counter[interval]:
self.counter[interval][ip] = 0
self.counter[interval][ip] += 1
if self.limit[interval] <= self.counter[interval][ip]:
log("Too many queries: %s in %s for %s" % (self.limit[interval], interval, ip) )
raise RuntimeError("Not so fast! Number of queries per %s is limited to %s" % (interval, self.limit[interval]))
print self.counter
def clear_counters( self ):
t = int( time.time() )
for interval in self.intervals:
if t / self.divisor[interval] != self.last_update[interval]:
self.counter[interval] = {}
self.last_update[interval] = t / self.divisor[interval]
limits = Limits()
def error( text ):
print text
raise RuntimeError(text)
def log( text ):
print text
logging.info( text )
def is_ip( ip ):
if re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip) is None:
return False
try:
socket.inet_aton(ip)
return True
except socket.error:
return False
def save_weather_data( location, filename ):
if location == NOT_FOUND_LOCATION:
location_not_found = True
location = DEFAULT_LOCATION
else:
location_not_found = False
p = Popen( [ WEGO, '--city=%s' % location ], stdout=PIPE, stderr=PIPE )
stdout, stderr = p.communicate()
if p.returncode != 0:
error( stdout + stderr )
dirname = os.path.dirname( filename )
if not os.path.exists( dirname ):
os.makedirs( dirname )
if location_not_found:
stdout += NOT_FOUND_MESSAGE
open( filename, 'w' ).write( stdout )
p = Popen( [ "bash", ANSI2HTML, "--palette=solarized", "--bg=dark" ], stdin=PIPE, stdout=PIPE, stderr=PIPE )
stdout, stderr = p.communicate( stdout )
if p.returncode != 0:
error( stdout + stderr )
open( filename+'.html', 'w' ).write( stdout )
def get_filename( location ):
location = location.replace('/', '_')
timestamp = time.strftime( "%Y%m%d%H", time.localtime() )
return "%s/%s/%s" % ( CACHEDIR, location, timestamp )
def get_wetter(location, ip, html=False):
filename = get_filename( location )
if not os.path.exists( filename ):
limits.check_ip( ip )
save_weather_data( location, filename )
if html:
filename += '.html'
return open(filename).read()
def ip2location( ip ):
cached = os.path.join( IP2LCACHE, ip )
if os.path.exists( cached ):
return open( cached, 'r' ).read()
try:
t = requests.get( 'http://api.ip2location.com/?ip=%s&key=demo&package=WS10' % ip ).text
if ';' in t:
location = t.split(';')[3]
if not os.path.exists( IP2LCACHE ):
os.makedirs( IP2LCACHE )
open( cached, 'w' ).write( location )
return location
except:
pass
def get_location( ip_addr ):
response = reader.city( ip_addr )
city = response.city.name
if city is None and response.location:
coord = "%s, %s" % (response.location.latitude, response.location.longitude)
location = geolocator.reverse(coord, language='en')
city = location.raw.get('address', {}).get('city')
if city is None:
print ip_addr
city = ip2location( ip_addr )
return city or NOT_FOUND_LOCATION
def load_aliases( aliases_filename ):
aliases_db = {}
with open( aliases_filename, 'r' ) as f:
for line in f.readlines():
from_, to_ = line.split(':', 1)
aliases_db[ from_.strip().lower() ] = to_.strip()
return aliases_db
location_alias = load_aliases( ALIASES )
def location_canonical_name( location ):
if location.lower() in location_alias:
return location_alias[location.lower()]
return location
def show_help():
return open(HELP_FILE, 'r').read()
@app.route('/files/<path:path>')
def send_static(path):
print path
print STATIC
return send_from_directory(STATIC, path)
@app.route("/")
@app.route("/<string:location>")
def wttr(location = None):
user_agent = request.headers.get('User-Agent').lower()
html_output = True
if 'curl' in user_agent or 'wget' in user_agent:
html_output = False
if location == ':help':
help_ = show_help()
if html_output:
return render_template( 'index.html', body=help_ )
else:
return help_
orig_location = location
if request.headers.getlist("X-Forwarded-For"):
ip = request.headers.getlist("X-Forwarded-For")[0]
if ip.startswith('::ffff:'):
ip = ip[7:]
else:
ip = request.remote_addr
try:
if location is None:
location = get_location( ip )
if is_ip( location ):
location = get_location( location )
if location.startswith('@'):
location = get_location( socket.gethostbyname( location[1:] ) )
location = location_canonical_name( location )
log("%s %s %s %s" % (ip, user_agent, orig_location, location))
return get_wetter( location, ip, html=html_output )
except Exception, e:
logging.error("Exception has occured", exc_info=1)
return str(e).rstrip()+"\n"
server = WSGIServer(("", 8002), app)
server.serve_forever()