You are not logged in.

#26 2008-03-19 23:08:39

Reasons
Member
From: Washington
Registered: 2007-11-04
Posts: 572

Re: Openbox Weather Pipe Menu

It's not inaccurate. I just did a bit of looking and it is updated less. In that case it's not as big of a deal.

Offline

#27 2008-03-19 23:08:56

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

Reasons wrote:

Just because this is slightly related, I got the gmail menu to work, but every time it opens, it gets put in /tmp/.gmail-cache and then won't work again until that is deleted. Can that be fixed so it doesn't have to? It's nice to have because it makes loading less.

hmmmm first of all, are these your real gmail user/password ? If so, CHANGE THEM NOW!

Offline

#28 2008-03-19 23:09:33

Reasons
Member
From: Washington
Registered: 2007-11-04
Posts: 572

Re: Openbox Weather Pipe Menu

noalwin wrote:
Reasons wrote:

Just because this is slightly related, I got the gmail menu to work, but every time it opens, it gets put in /tmp/.gmail-cache and then won't work again until that is deleted. Can that be fixed so it doesn't have to? It's nice to have because it makes loading less.

hmmmm first of all, are these your real gmail user/password ? If so, CHANGE THEM NOW!

That is my spam box. You can use it if you want.

Offline

#29 2008-03-19 23:30:16

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

Reasons wrote:

Just because this is slightly related, I got the gmail menu to work, but every time it opens, it gets put in /tmp/.gmail-cache and then won't work again until that is deleted. Can that be fixed so it doesn't have to? It's nice to have because it makes loading less.

I haven't tried but I guess that commenting

    state = libgmail.GmailSessionState(account = ga).save(filename)

should prevent the file creation

I think that you can also change

    if not os.path.isfile(filename):
        ga = libgmail.GmailAccount(name, pw)

        try:
            ga.login()
        except libgmail.GmailLoginFailure:
        print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            print "<openbox_pipe_menu>"
        print "  <item label=\"login failed.\">"
            print "    <action name=\"Execute\"><execute>" + browser + " " + login + "</execute></action>"
            print "  </item>"
        print "</openbox_pipe_menu>"
        raise SystemExit

    else:
        ga = libgmail.GmailAccount(
            state = libgmail.GmailSessionState(filename = filename))

with

    ga = libgmail.GmailAccount(name, pw)

    try:
        ga.login()
    except libgmail.GmailLoginFailure:
        print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            print "<openbox_pipe_menu>"
        print "  <item label=\"login failed.\">"
            print "    <action name=\"Execute\"><execute>" + browser + " " + login + "</execute></action>"
            print "  </item>"
        print "</openbox_pipe_menu>"
        raise SystemExit

to avoid the file reading

But both solutions will avoid the cache use, so it will be slower. If you use conky you can think on configure it to check gmail.

Offline

#30 2008-03-19 23:31:51

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

Reasons wrote:
noalwin wrote:
Reasons wrote:

Just because this is slightly related, I got the gmail menu to work, but every time it opens, it gets put in /tmp/.gmail-cache and then won't work again until that is deleted. Can that be fixed so it doesn't have to? It's nice to have because it makes loading less.

hmmmm first of all, are these your real gmail user/password ? If so, CHANGE THEM NOW!

That is my spam box. You can use it if you want.

well, keep in mind that all users subscribed to this thread have your password

Offline

#31 2008-03-19 23:36:54

Reasons
Member
From: Washington
Registered: 2007-11-04
Posts: 572

Re: Openbox Weather Pipe Menu

noalwin wrote:
Reasons wrote:
noalwin wrote:

hmmmm first of all, are these your real gmail user/password ? If so, CHANGE THEM NOW!

That is my spam box. You can use it if you want.

well, keep in mind that all users subscribed to this thread have your password

I don't have a problem with that. I don't log in to it. It's one of the accounts I use when you fill out some survey and get a prize. One in every hundred come and you get a lot of spam.

Offline

#32 2008-03-20 00:14:28

Reasons
Member
From: Washington
Registered: 2007-11-04
Posts: 572

Re: Openbox Weather Pipe Menu

noalwin wrote:
Reasons wrote:

Just because this is slightly related, I got the gmail menu to work, but every time it opens, it gets put in /tmp/.gmail-cache and then won't work again until that is deleted. Can that be fixed so it doesn't have to? It's nice to have because it makes loading less.

I haven't tried but I guess that commenting

    state = libgmail.GmailSessionState(account = ga).save(filename)

should prevent the file creation

I think that you can also change

    if not os.path.isfile(filename):
        ga = libgmail.GmailAccount(name, pw)

        try:
            ga.login()
        except libgmail.GmailLoginFailure:
        print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            print "<openbox_pipe_menu>"
        print "  <item label=\"login failed.\">"
            print "    <action name=\"Execute\"><execute>" + browser + " " + login + "</execute></action>"
            print "  </item>"
        print "</openbox_pipe_menu>"
        raise SystemExit

    else:
        ga = libgmail.GmailAccount(
            state = libgmail.GmailSessionState(filename = filename))

with

    ga = libgmail.GmailAccount(name, pw)

    try:
        ga.login()
    except libgmail.GmailLoginFailure:
        print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            print "<openbox_pipe_menu>"
        print "  <item label=\"login failed.\">"
            print "    <action name=\"Execute\"><execute>" + browser + " " + login + "</execute></action>"
            print "  </item>"
        print "</openbox_pipe_menu>"
        raise SystemExit

to avoid the file reading

But both solutions will avoid the cache use, so it will be slower. If you use conky you can think on configure it to check gmail.

I had conky do it, but I'm trying to move away from that. Anyways, sadly none of this worked. It might help if I gave the error it spits out the second+ time I run it.

Traceback (most recent call last):
  File "/home/shawn/.config/openbox/scripts/gmail.py", line 56, in <module>
    msgtotals = ga.getUnreadMsgCount()
  File "/home/shawn/.config/openbox/scripts/libgmail.py", line 547, in getUnreadMsgCount
    q = "is:" + U_AS_SUBSET_UNREAD)
  File "/home/shawn/.config/openbox/scripts/libgmail.py", line 428, in _parseSearchResult
    return self._parsePage(_buildURL(**params))
  File "/home/shawn/.config/openbox/scripts/libgmail.py", line 401, in _parsePage
    items = _parsePage(self._retrievePage(urlOrRequest))
  File "/home/shawn/.config/openbox/scripts/libgmail.py", line 369, in _retrievePage
    if self.opener is None:
AttributeError: GmailAccount instance has no attribute 'opener'

Offline

#33 2008-03-20 00:30:28

Reasons
Member
From: Washington
Registered: 2007-11-04
Posts: 572

Re: Openbox Weather Pipe Menu

I got it by taking out the last line which was

    state = libgmail.GmailSessionState(account = ga).save(filename)

but why would that not work with reading the cache?

Offline

#34 2008-03-20 01:18:59

.:B:.
Forum Fellow
Registered: 2006-11-26
Posts: 5,819

Re: Openbox Weather Pipe Menu

Okay. I got help on the IRC channel (props to Storlek!) with the parsing of the degree symbol °.

This is how the script looks now (I paste it completely so you can see the things in context, I already localised it).

#!/usr/bin/python
# -*- coding: utf8 -*-

import sys, urllib, codecs ; sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
from string import maketrans
#from xml.sax import make_parser, handler
from xml.sax import handler, parseString
class ElementProcesser(handler.ContentHandler):
    
    def startElement(self, name, attrs):
        
        if name == "city":
            print "<separator label='Sint-Niklaas' />"
        elif name == "current_conditions":
            print "<separator label='Huidige toestand' />"
        elif name == "condition":
            print "<item label='Weer: " + attrs["data"] + "' />"
        elif name == "humidity":
            print "<item label='" + attrs["data"] + "' />"
        elif name == "wind_condition":
            print "<item label='" + attrs["data"] + "' />"
        elif name == "day_of_week":
            print "<separator label='" + self.getDayOfWeek(attrs["data"]) + "' />"
            
        #Celsius
        elif name == "temp_c":
            print "<item label='Temperatuur " + attrs["data"] + u"° C' />"
        elif name == "low":
            print "<item label='Minimum " + attrs["data"] + u"° C' />"
        elif name == "high":
            print "<item label='Maximum " + attrs["data"] + u"° C' />"
        
        #Fahrenheit
        #elif name == "temp_c":
        #    print "<item label='Temperatuur " + attrs["data"] + " F' />"
        #elif name == "low":
        #    print "<item label='Minimum " + attrs["data"] + " F' />"
        #elif name == "high":
        #    print "<item label='Maximum " + attrs["data"] + " F' />"
        
        
    def endElement(self, name):
        
        if name == "current_conditions":
            print "<separator label='Voorspelling' />"
        
    
    def startDocument(self):
        print '<openbox_pipe_menu>'
    
    def endDocument(self):
        print '</openbox_pipe_menu>'
    
    def getDayOfWeek(self,day):
        
        #English
        if day == "Mon":
            return "Monday"
        elif day == "Tue":
            return "Tuesday"
        elif day == "Wed":
            return "Wednesday"
        elif day == "Thu":
            return "Thursday"
        elif day == "Sat":
            return "Saturday"
        elif day == "Sun":
            return "Sunday"
        
        else:
            return day

# You should use your local version of google to have the messages in your language and metric system
f = urllib.urlopen("http://www.google.nl/ig/api?weather="+sys.argv[1])
xml = f.read()
f.close()

#Avoid problems with non english characters
trans=maketrans("\xe1\xe9\xed\xf3\xfa","aeiou")
xml = xml.translate(trans)

#parser.parse("http://www.google.nl/ig/api?weather="+sys.argv[1])
parseString(xml,ElementProcesser())

What needs to be done:
1. To make sure the script gets parsed as UTF-8, define it so by adding this line right under the shebang:

# -*- coding: utf8 -*-

2. You need to import the 'codecs' python module (it seems you can perfectly use one line to load multiple modules, so I put them together); immediately after that you need to wrap all the output in a codec translator [sic - thanks to Storlek for the crashcourse].

import sys, urllib, codecs ; sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

Additionally, you need to tell python the strings with a degree symbol in it are unicode strings (so it will stop throwing errors at you). You do this by putting a u in front of those, so here goes:

u"° C' />"
u"° F' />"

You need to do this for every string you want to be parsed as unicode.

Last edited by B (2008-03-20 01:19:40)


Got Leenucks? :: Arch: Power in simplicity :: Get Counted! Registered Linux User #392717 :: Blog thingy

Offline

#35 2008-03-20 21:44:20

ep2011
Member
Registered: 2008-02-28
Posts: 66

Re: Openbox Weather Pipe Menu

This worked great smile I used the original script from the first post, edited a little spelling, and used the changes to display "°" from Bs post. Thanks for the scripts big_smile

by the way, the spelling mistakes were "Minimun" instead of "Minimum" and "Maximun" instead of "Maximum"

edit: the gmail script corrections work as well. I use conky but  it is nice to not have to go to my desktop to see how many emails I have.

Last edited by ep2011 (2008-03-20 22:25:24)

Offline

#36 2008-03-26 10:11:08

serrghi
Member
From: Bergen, NO
Registered: 2008-02-12
Posts: 100
Website

Re: Openbox Weather Pipe Menu

Anyone have a Norwegian translation of this script? when i try to use google.no the script crashes sad
I also cant print out norwegian letters "æøå" even if i have the utf8 support on.

Last edited by serrghi (2008-03-26 11:49:52)


Unyttig.INFO - Your source to not so useless information
My github - Various configs, dotfiles and nifty scripts

Offline

#37 2008-03-26 11:46:32

nucleuswizard
Member
From: Serbia
Registered: 2007-10-07
Posts: 79

Re: Openbox Weather Pipe Menu

How to change this script so I could write Cyrillic letters.I tried to change utf-8 to cp1250 but then script crashes. sad sad

Offline

#38 2008-03-26 12:32:22

.:B:.
Forum Fellow
Registered: 2006-11-26
Posts: 5,819

Re: Openbox Weather Pipe Menu

serrghi wrote:

Anyone have a Norwegian translation of this script? when i try to use google.no the script crashes sad
I also cant print out norwegian letters "æøå" even if i have the utf8 support on.

You cannot just put it in utf-8 and expect all to go well, python + UTF-8 seems to be a little shaky still. You need to make sure the strings printed containing UTF-8 characters are marked and parsed as UTF-8. Same goes for cyrillic probably.

ep2011 wrote:

This worked great smile I used the original script from the first post, edited a little spelling, and used the changes to display "°" from Bs post. Thanks for the scripts big_smile

by the way, the spelling mistakes were "Minimun" instead of "Minimum" and "Maximun" instead of "Maximum"

edit: the gmail script corrections work as well. I use conky but  it is nice to not have to go to my desktop to see how many emails I have.

Those were changed in my 'version' too wink. I guess I should have said that roll. Then again for most people it's easier to work with English stuff than with Dutch tongue.


Got Leenucks? :: Arch: Power in simplicity :: Get Counted! Registered Linux User #392717 :: Blog thingy

Offline

#39 2008-06-17 13:09:12

chm0d
Member
Registered: 2008-04-05
Posts: 19

Re: Openbox Weather Pipe Menu

How would I go about piping this to a fluxbox menu?

Chm0d

Offline

#40 2008-08-17 21:57:51

moore.bryan
Member
Registered: 2008-08-17
Posts: 4

Re: Openbox Weather Pipe Menu

looks like an awesome script, but i just can't seem to get it to work for me; i keep getting a dialog box:

Invalid output from pipe-menu "~/.config/openbox/scripts/gweather.py Philadelphia"

any suggestions?
====
EDIT:
nevermind... i got it to work somehow; if only i knew how!
wink

Last edited by moore.bryan (2008-08-17 22:01:42)

Offline

#41 2009-02-13 00:30:06

ninjaprawn
Member
From: Manchester, UK
Registered: 2008-01-26
Posts: 455

Re: Openbox Weather Pipe Menu

hi,

awesome idea! i cant figure out the error im getting from the script tho!!

[danny@ArchLinux ~]$ python ~/weather.py Philadelphia
<openbox_pipe_menu>
Traceback (most recent call last):
  File "/home/danny/weather.py", line 83, in <module>
    parseString(xml,ElementProcesser())
  File "/usr/lib/python2.6/xml/sax/__init__.py", line 49, in parseString
    parser.parse(inpsrc)
  File "/usr/lib/python2.6/xml/sax/expatreader.py", line 107, in parse
    xmlreader.IncrementalParser.parse(self, source)
  File "/usr/lib/python2.6/xml/sax/xmlreader.py", line 123, in parse
    self.feed(buffer)
  File "/usr/lib/python2.6/xml/sax/expatreader.py", line 211, in feed
    self._err_handler.fatalError(exc)
  File "/usr/lib/python2.6/xml/sax/handler.py", line 38, in fatalError
    raise exception
xml.sax._exceptions.SAXParseException: <unknown>:20:2: mismatched tag
[danny@ArchLinux ~]$

my python knowledge is poor at best!


- Samsung Series 3, Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz - 6Gb DDR3 ram - 700Gb HDD
On board intel Graphics & Sound

Offline

#42 2009-02-13 15:58:36

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

As far I can see, google seems to block the query, I'll rewrite it using the Yahoo service

Offline

#43 2009-02-13 18:09:42

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

I have modified the script to use the yahoo weather information instead google's.

yweathergi2.png
Brrr, Antarctica is a cold place wink

You can use execute it with: "yweather.py AYXX0001 Celsius" where AYXX0001 is your location code in yahoo weather and Celsius are the units (you can use also Fahrenheit).

It doesn't give the translated information. If it is also a problem for you, say it, I think that I could make a workarround.

I added a cache file in order to speed up the execution and to not query everytime to yahoo.

#!/usr/bin/python

import urllib
from xml.etree.cElementTree import parse
from datetime import datetime, timedelta
import os
from os.path import join
from sys import argv
try:
    import cPickle as pickle
except ImportError:
    import pickle

#Usage: yweather.py AYXX0001 Celsius

if len(argv) != 3:
    raise Exception('Usage: yweather.py zip_code units. zip_code is your city code in Yahoo Weather, units can be Celsius or Fahrenheit.')
else:
    zip_code = argv[1]
    if argv[2] == 'Fahrenheit' or argv[2] == 'fahrenheit':
        units = 'f'
    else:
        units = 'c'



CACHE_HOURS = 6

#http://weather.yahooapis.com/forecastrss
WEATHER_URL = 'http://xml.weather.yahoo.com/forecastrss?p=%s&u=%s'
WEATHER_NS = 'http://xml.weather.yahoo.com/ns/rss/1.0'

def weather_for_zip(zip_code, units):
    url = WEATHER_URL % (zip_code, units)
    rss = parse(urllib.urlopen(url)).getroot()
    forecasts = []
    for element in rss.findall('channel/item/{%s}forecast' % WEATHER_NS):
        forecasts.append(dict(element.items()))
    ycondition = rss.find('channel/item/{%s}condition' % WEATHER_NS)
    return {
        'current_condition': dict(ycondition.items()),
        'forecasts': forecasts,
        'title': rss.findtext('channel/title'),
        'pubDate': rss.findtext('channel/item/pubDate'), #rss.findtext('channel/lastBuildDate'),
        'location': dict(rss.find('channel/{%s}location' % WEATHER_NS).items()),
        'wind': dict(rss.find('channel/{%s}wind' % WEATHER_NS).items()),
        'atmosphere': dict(rss.find('channel/{%s}atmosphere' % WEATHER_NS).items()),
        'astronomy': dict(rss.find('channel/{%s}astronomy' % WEATHER_NS).items()),
        'units': dict(rss.find('channel/{%s}units' % WEATHER_NS).items())
    }

def print_openbox_pipe_menu(weather):
    print '<openbox_pipe_menu>'
    print '<separator label="%s %s" />' % (weather['location']['city'],weather['pubDate'])
    print '<separator label="Current conditions" />'
    print '<item label="Weather: %s" />' % weather['current_condition']['text']
    print '<item label="Temperature: %s %s" />' % ( weather['current_condition']['temp'],
                                          weather['units']['temperature'] )
    print '<item label="Humidity: %s%%" />' % weather['atmosphere']['humidity']
    print '<item label="Visibility: %s %s" />' % ( weather['atmosphere']['visibility'],
                                          weather['units']['distance'] )
    
    #pressure: steady (0), rising (1), or falling (2)
    if weather['atmosphere']['rising'] == 0:
        pressure_state = 'steady'
    elif weather['atmosphere']['rising'] == 1:
        pressure_state = 'rising'
    else:
        pressure_state = 'falling'
    print '<item label="Pressure: %s %s (%s)" />' % ( weather['atmosphere']['pressure'],
                                          weather['units']['pressure'], pressure_state )
    print '<item label="Wind chill: %s %s" />' % ( weather['wind']['chill'],
                                          weather['units']['temperature'] )
    print '<item label="Wind direction: %s degrees" />' % weather['wind']['direction']
    print '<item label="Wind speed: %s %s" />' % ( weather['wind']['speed'],
                                          weather['units']['speed'] )
    print '<item label="Sunrise: %s" />' % weather['astronomy']['sunrise']
    print '<item label="Sunset: %s" />' % weather['astronomy']['sunset']
    for forecast in weather['forecasts']:
        print '<separator label="Forecast: %s" />' % forecast['day']
        print '<item label="Weather: %s" />' % forecast['text']
        print '<item label="Min temperature: %s %s" />' % ( forecast['low'],
                                                weather['units']['temperature'] )
        print '<item label="Max temperature: %s %s" />' % ( forecast['high'],
                                                weather['units']['temperature'] )
    print '</openbox_pipe_menu>'

cache_file = join(os.getenv("HOME"), '.yweather.cache')

try:
    f = open(cache_file,'rb')
    cache = pickle.load(f)
    f.close()
except IOError:
    cache = None

if cache == None or (zip_code, units) not in cache or (
        cache[(zip_code, units)]['date'] + timedelta(hours=CACHE_HOURS) < datetime.utcnow()):
    # The cache is outdated
    weather = weather_for_zip(zip_code, units)
    if cache == None:
        cache = dict()
    cache[(zip_code, units)] = {'date': datetime.utcnow(), 'weather': weather}
    
    #Save the data in the cache
    try:
        f = open(cache_file, 'wb')
        cache = pickle.dump(cache, f, -1)
        f.close()
    except IOError:
        raise
else:
    weather = cache[(zip_code, units)]['weather']


print_openbox_pipe_menu(weather)

Use it adding to your openbox menu

  <menu id="pipe-weather" label="Weather" execute="python ~/.config/openbox/scripts/yweather.py AYXX0001 Celsius" />

Last edited by noalwin (2009-02-13 23:22:12)

Offline

#44 2009-02-13 19:48:12

ninjaprawn
Member
From: Manchester, UK
Registered: 2008-01-26
Posts: 455

Re: Openbox Weather Pipe Menu

brilliant! thanks for that! if you get a minute, i would apprecite it if it looked like the google one, but i will have a lil go my self! i need to lern more about python!


- Samsung Series 3, Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz - 6Gb DDR3 ram - 700Gb HDD
On board intel Graphics & Sound

Offline

#45 2009-02-13 23:16:18

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

ninjaprawn wrote:

brilliant! thanks for that! if you get a minute, i would apprecite it if it looked like the google one, but i will have a lil go my self! i need to lern more about python!

Ok, updated, now it shows more information too.

Delete the cache file ( ~/.yweather.cache ) before execute the new version

Last edited by noalwin (2009-02-13 23:23:15)

Offline

#46 2009-02-14 00:09:17

ninjaprawn
Member
From: Manchester, UK
Registered: 2008-01-26
Posts: 455

Re: Openbox Weather Pipe Menu

thanks for that! quick question, what does

CACHE_HOURS = 6

do?


- Samsung Series 3, Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz - 6Gb DDR3 ram - 700Gb HDD
On board intel Graphics & Sound

Offline

#47 2009-02-14 11:45:07

noalwin
Member
From: Spain
Registered: 2007-06-08
Posts: 115

Re: Openbox Weather Pipe Menu

It means that if the cache information is older than 6 hours, the info is outdated and we get it again.

I think that the yahoo api suggest to use 1 hour instead, but I think that the weather doesn't change (except the temperature) so fast, and I don't want to wait while it gets the info from yahoo.

Feel free to change it

Offline

#48 2009-02-14 15:30:21

tyr0
Member
Registered: 2007-06-02
Posts: 152

Re: Openbox Weather Pipe Menu

Great Work! Thank you. Looks really good in the ob-menu.

Offline

#49 2009-02-23 07:11:02

securitybreach
Member
From: In front of my computers
Registered: 2007-11-18
Posts: 409
Website

Re: Openbox Weather Pipe Menu

Wow that is awesome. Thanks alot.


Thanks


"Every normal man must be tempted at times to spit upon his hands, hoist the black flag, and begin slitting throats." -- H.L. Mencken
Website      Configs
Forum Admin: Bruno's All Things Linux   
securitybreach<a>archlinux.us

Offline

#50 2009-02-23 16:36:44

patogen
Member
Registered: 2008-05-11
Posts: 86

Re: Openbox Weather Pipe Menu

The scripts works fine for me, but now in the openbox menu. Executing the script with:

./yweather.pl SWXX0040 Celsius

Works fine, but adding the line to menu.xml nothing happens when I try to enter it. Anyone else experiencing this?

Edit: Hmm, I named it to yweather.pl but in menu.xml I had it as yweather.py, this obviously does not work. Naming it correctly works...

Last edited by patogen (2009-02-26 22:48:27)


We met up with the aliens and guess what? They have no word for fluffy!

Offline

Board footer

Powered by FluxBB