Уведомления

Группа в Telegram: @pythonsu

#1 Май 31, 2010 16:52:54

fanki2
От:
Зарегистрирован: 2010-05-31
Сообщения: 3
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

Есть код, но не запускается. Пишет что ошибка в 66 строчке ? как можно исправить ? или может я неправильно запускаю..

#!usr/bin/env python
#!-*- encoding:UTF-8 -*-

#Fileshares Scanner
#(c)login999
#uasc.org.ua

#Импортирование необходимых модулей
import socket
import re
import threading
import time
import os
from Queue import Queue
from Tkinter import Tk
from Tkinter import Frame
from Tkinter import Label
from Tkinter import Entry
from Tkinter import Text
from Tkinter import Button
from Tkinter import Spinbox
from Tkinter import Scrollbar
from tkMessageBox import showinfo
from tkMessageBox import showerror
from tkFileDialog import asksaveasfilename

#Функция, которая будет работать в отдельном потоке и подгружать в Queue
#количество задач, равное количеству потоков умноженному на 2 (дабы не захламлять Queue)
def Queue_Inserting_Thread(from_, to_, Threads_Number):
while True:
if FINISHED:
return
else:
if queue.qsize()<Threads_Number*2:
for x in xrange((Threads_Number*2)-queue.qsize()):
if from_ == to_+1:
return
else:
queue.put(from_+1)
from_+= 1
else:
time.sleep(0.1)

#Функция -работник, которая будет фактически заниматься парсингом файлообменника
def Parser_Thread(Key, Log, proxy, port):
global FINISHED
while True:
if FINISHED:
return
elif PAUSE:
time.sleep(0.1)
else:
Working_Function = FileShares[Key]
try:
File_Number = queue.get_nowait()
except:
FINISHED = True
return
else:
File_Link, File_Name = Working_Function(File_Number, proxy, port)
if File_Name != "NO_FILE":
if File_Name == "ERROR":
queue.put(File_Number)
else:
try:
Log.insert("end", u"{0}|{1}\n".format(File_Link, File_Name))
except:
os._exit(0)
else:
pass
try:
check_val = Log.get(0.0, 0.1)
except:
os._exit(0)

#Функция, которая отвечает за парсинг файлообменника slil.ru
def Get_From_Slil(number, proxy, port):
file_link = "http://slil.ru/{0}".format(number)
try:
if not proxy:
soket = socket.create_connection(("slil.ru", 80))
else:
soket = socket.create_connection((proxy, int(port)))
request = "GET {0}\r\n".format(file_link)
soket.send(request)
HTTP_EQUIV = ""
while HTTP_EQUIV[-2:] != "\">":
HTTP_EQUIV = HTTP_EQUIV + soket.recv(1)
LINK = re.findall(r'URL\=\/(.*?)\"\>', HTTP_EQUIV)[0].decode("cp1251")
LINK = LINK.split("/")
NAME = LINK[2]
soket.close()
except IndexError:
soket.close()
NAME = "NO_FILE"
except socket.error:
NAME = "ERROR"
return file_link, NAME

#Функция, которая отвечает за парсинг файлообменника webfile.ru
def Get_From_Webfile(number, proxy, port):
file_link = "http://webfile.ru/{0}".format(number)
try:
if not proxy:
soket = socket.create_connection(("webfile.ru", 80))
else:
soket = socket.create_connection((proxy, int(port)))
request = "GET {0}\r\n".format(file_link)
soket.send(request)
PAGE_TITLE = ""
while PAGE_TITLE[-8:] != "</title>":
if PAGE_TITLE[-9:] == "302 Found":
break
else:
PAGE_TITLE = PAGE_TITLE + soket.recv(1)
NAME = re.findall(r'\<title\>(.*?)\<\/title\>', PAGE_TITLE)[0].decode("cp1251").replace(u"WebFile - скачать бесплатно ", "")
soket.close()
except IndexError:
soket.close()
NAME = "NO_FILE"
except socket.error:
NAME = "ERROR"
return file_link, NAME

#Функция, которая отвечает за парсинг файлообменника dump.ru
def Get_From_Dump(number, proxy, port):
file_link = "http://dump.ru/file/{0}".format(number)
try:
if not proxy:
soket = socket.create_connection(("dump.ru", 80))
else:
soket = socket.create_connection((proxy, int(port)))
request = "GET {0}\r\n".format(file_link)
soket.send(request)
PAGE_TITLE = ""
while PAGE_TITLE[-8:] != "</title>":
if PAGE_TITLE[-9:] == "302 Found":
break
else:
PAGE_TITLE = PAGE_TITLE + soket.recv(1)
NAME = re.findall(r'\<title\>(.*?)\<\/title\>', PAGE_TITLE)[0].decode("UTF-8").replace(u"Dump.Ru - ", "")
soket.close()
except IndexError:
soket.close()
NAME = "NO_FILE"
except socket.error:
NAME = "ERROR"
if u"обмен файлами, бесплатный файловый хостинг, регистрация не обязательна" in NAME:
NAME = "NO_FILE"
return file_link, NAME

#Функция, которая отвечает за парсинг файлообменника ifolder.ru
def Get_From_Ifolder(number, proxy, port):
file_link = "http://ifolder.ru/{0}".format(number)
try:
if not proxy:
soket = socket.create_connection(("ifolder.ru", 80))
request = "GET /{0} HTTP/1.1\r\nHost: ifolder.ru\r\n\n".format(number)
else:
soket = socket.create_connection((proxy, int(port)))
request = "GET {0}\r\n".format(file_link)
soket.send(request)
PAGE_CONTENT = ""
Counter = 0
while PAGE_CONTENT[-8:] != "</b><br>":
if Counter == 12000:
break
else:
PAGE_CONTENT = PAGE_CONTENT + soket.recv(1)
Counter = Counter + 1
NAME = re.findall(r'\<b\>(.*?)\<\/b\>\<br\>', PAGE_CONTENT)[0].decode("utf8")
soket.close()
except IndexError:
soket.close()
NAME = "NO_FILE"
except socket.error:
NAME = "ERROR"
return file_link, NAME

#Функция которая будет "присматривать" за Gui и выставлять state кнопок (работает в бесконечном цикле)
def Gui_Watcher(StopButton, PauseButton, StartButton, From_Entry, To_Entry, Threads_Entry, Proxy_Entry, FileShareSpinbox, TimeoutSpinbox):
while True:
if FINISHED:
queue = Queue()
StopButton["state"] = "disabled"
PauseButton["state"] = "disabled"
StartButton["state"] = "disabled"
while threading.active_count()>3:
time.sleep(0.1)
PauseButton["text"] = u"Пауза"
StartButton["state"] = "normal"
From_Entry["state"] = "normal"
To_Entry["state"] = "normal"
Threads_Entry["state"] = "normal"
Proxy_Entry["state"] = "normal"
FileShareSpinbox["state"] = "normal"
TimeoutSpinbox["state"] = "normal"
return
else:
time.sleep(0.1)

#Функция которая создаёт Gui
def Create_Gui():

#Обработчик кнопки Start
def Start_Button():

#Делаем переменные FINISHED и PAUSE доступными для изменения внутри локального пространства имен этой функции
global PAUSE
global FINISHED

#Ну и устанавливаем их в положение по умолчанию - т.е. в False, это-булевые значения
PAUSE = False
FINISHED = False

#Получаем значение начала диапазона
Start = From_Entry.get()

#Получаем значение конца диапазона
End = To_Entry.get()

#Получаем значение количества потоков
Threads_Number = Threads_Entry.get()

#Получаем значение таймаута
TimeOut = float(TimeoutSpinbox.get())

#Получаем значение прокси
Proxy = Proxy_Entry.get()

#Ну и проверка полученных значений
if not Start.isdigit(): #Является ли полученное начало диапазона циферным значением

#В таком случае показываем common окошко ошибки
showerror(u"Ошибка", u"Введите ЦИФЕРНОЕ значение начала диапазона")
return

#Тут по аналогии
elif not End.isdigit():
showerror(u"Ошибка", u"Введите ЦИФЕРНОЕ значение конца диапазона")
return

#Тут по аналогии, есть же дибилы которые укажут неправильные значения диапазона...
elif int(Start)>int(End):
showerror(u"Ошибка", u"Введите ПРАВИЛЬНЫЕ значения диапазона")
return
#Тут по аналогии
elif not Threads_Number.isdigit():
showerror(u"Ошибка", u"Введите ЦИФЕРНОЕ значение количества потоков")
return
elif Proxy !="None" and not Proxy.replace(":", "").replace(".", "").isdigit() or Proxy !="None" and len(Proxy.split(":"))!=2 or Proxy !="None" and len((Proxy.split(":")[0]).split(".")) !=4:
showerror(u"Ошибка", u"Введите Proxy в виде ip:port, либо укажите None для прямого соединения")
return
else:
#Проверка полученного значения прокси
if Proxy == "None" or Proxy == "":
proxy = port = None
else:
proxy, port = Proxy.split(":")
#Устанавливаем таймаут
socket.setdefaulttimeout(TimeOut)

#Получаем строку-ключ из поля выбора файлообменника
FileShareKey = FileShareSpinbox.get()

#Устанавливаем statement виджета для ввода начала диапазона в disabled, другими словами делаем его недоступным для изменения
From_Entry["state"] = "disabled"

#Так же
To_Entry["state"] = "disabled"

#Так же
Threads_Entry["state"] = "disabled"

#Так же
Proxy_Entry["state"] = "disabled"

#Так же
FileShareSpinbox["state"] = "disabled"

#Так же
TimeoutSpinbox["state"] = "disabled"

#Так же
StartButton["state"] = "disabled"

#Тут наобоорот, активируем кнопки Пауза и Стоп
StopButton["state"] = "normal"
PauseButton["state"] = "normal"

#Запускаем отдельный поток который будет наполнять Queue
threading.Thread(target=Queue_Inserting_Thread, args=[int(Start), int(End), int(Threads_Number)]).start()

#Запускаем отдельный поток для кнтроллирования Gui
threading.Thread(target=Gui_Watcher, args=[StopButton, PauseButton, StartButton, From_Entry, To_Entry, Threads_Entry, Proxy_Entry, FileShareSpinbox, TimeoutSpinbox]).start()

#Ну и по указанному количеству делаем запуск потоков-работников
for x in xrange(int(Threads_Number)):
threading.Thread(target=Parser_Thread, args=[FileShareKey, Log, proxy, port]).start()

return

#Обработчик кнопки Stop
def Stop_Button():

#Делаем переменную FINISHED доступной для изменения внутри локального пространства имен этой функции
global FINISHED

#Ну и устанваливаем ее значение в True
FINISHED = True

return

#Обработчик кнопки Pause
def Pause_Button():

#Делаем переменную PAUSE доступной для изменения внутри локального пространства имен этой функции
global PAUSE

if not PAUSE:

#Если не пауза, то делаем ее таковой :), тут короче булевое значение, хз как это описать :)
PAUSE = True

#Изменяем тексткнопки Пауза
PauseButton["text"] = u"Продолжить"

else:

#Тут так же как и в прошлом ветвлениее, по аналогии
PAUSE = False
PauseButton["text"] = u"Пауза"

return

#Обработчик кнопки About
def About_Button():

#Показвыаем окошко Ебаут :)
showinfo(u"About", u"Название: Fileshare Scanner\nВерсия: 1.0\nАвтор: login999\n\
ICQ: 368-816\nПишу скрипты на заказ\nЯзык: Python\n(c)uasc.org.ua")

return

#Обработчик кнопки сохранения лога работы
def Save_Button():

#Получение имени выходного файла через common dialog
output_filename = asksaveasfilename()

#роверка того что получили
if output_filename == "":
pass
else:
#Получение текста из всего виджета (0,0 - это координаты начала виджета, "end" - конца)
log = Log.get(0.0, "end")

#ну и собственно запись этого текста в кодировке cp1251
with open(output_filename, "w") as out:
for line in log:
out.write(line.encode("cp1251"))
return

#Обработчик кнопки очистки лога
def Clear_Button():

#Очистка лога 0,0 - это координаты начала виджета, "end" - конца
Log.delete(0.0, "end")

return

#Создание главного окна
MainWindow = Tk()

#Установка цвета фона главного окна
MainWindow["bg"] = "black"

#Установка ширины рамки (в пикселях) для шлавного окна
MainWindow["bd"] = 5

#Установка заголовка главного окна
MainWindow.title("Fileshare Scanner")

#Запрет на изменение границ окна
MainWindow.resizable(width=False, height=False)

#Создание фрейма для кнопок и других элементов управления, установка цвета по умолчанию на черный
ButtonsFrame = Frame(MainWindow, bg="black")

#Создание фрейма для лога
LogFrame = Frame(MainWindow)

#Создание метки для поля выбора файлообменника
FileShareLabel = Label(MainWindow, text=u"Укажите файлообменник :", font="system 10", width=30, anchor="w", bg="black", fg="green", highlightbackground="blue")

#Создание поля выбора файлообменника (используется не виджет Entry, а виджет Spinbox)
FileShareSpinbox = Spinbox(MainWindow, state="readonly", wrap=True, font="system 10", width=20, fg="blue")

#Заполнение поля выбора файлообменника ключами из ассоциативного массива FileShares (он находится ниже)
FileShareSpinbox["values"] = FileSharesKeys

#Создание метки для поля ввода диапазона
DiapasonLabel = Label(MainWindow, text=u"Укажите диапазон :", font="system 10", width=30, anchor="w", bg="black", fg="green", bd=0)

#Создание поля ввода для указания начала диапазона
From_Entry = Entry(MainWindow, width=10, font="system 10", bg="lightgreen", highlightbackground="lightgreen")

#Вставка значения по умолчанию
From_Entry.insert(0, "0")

#Создание поля ввода для указания конца диапазона
To_Entry = Entry(MainWindow, width=10, font="system 10", bg="lightgreen", highlightbackground="lightgreen")

#Вставка значения по умолчанию
To_Entry.insert(0, "999999999")

#Создание метки для поля ввода количества потоков
ThreadsLabel = Label(MainWindow, text=u"Укажите количество потоков :", font="system 10", width=30, anchor="w", bg="black", fg="green")

#Создание поля ввода для указания количества потоков
Threads_Entry = Entry(MainWindow, width=10, font="system 10", bg="lightblue")

#Вставка значения по умолчанию
Threads_Entry.insert(0, "20")

#Создание метки для поля ввода тайм-аута соединения
TimeoutLabel = Label(MainWindow, text=u"Time-Out соединения :", font="system 10", width=30, anchor="w", bg="black", fg="green")

#Создание поля ввода таймаута соединения (используется не виджет Entry, а виджет Spinbox)
TimeoutSpinbox = Spinbox(MainWindow, state="readonly", wrap=True, font="system 10", width=4, from_=0, to_=100, fg="red")

#Установка значения по умолчанию
for x in xrange(30):
TimeoutSpinbox.invoke("buttonup")

#Создание метки для поля ввода Прокси-сервера
ProxyLabel = Label(MainWindow, text=u"Адрес Proxy-сервера :", font="system 10", width=30, anchor="w", bg="black", fg="green", bd=0)

#Создание поля ввода прокси
Proxy_Entry = Entry(MainWindow, width=20, font="system 10")

#Вставка значения по умолчанию
Proxy_Entry.insert(0, "None")

#Создание кнопки Старт
StartButton = Button(ButtonsFrame, text=u"Старт", width=10, font="system 10", relief="groove", command=Start_Button, cursor="hand1")
#Создание кнопки Пауза
PauseButton = Button(ButtonsFrame, text=u"Пауза", width=11, font="system 10", relief="groove", state="disabled", command=Pause_Button, cursor="hand2")
#Создание кнопки Стоп
StopButton = Button(ButtonsFrame, text=u"Стоп", width=10, font="system 10", relief="groove", state="disabled", command=Stop_Button, cursor="pirate")
#Создание кнопки Ебаут :D Нужно же себя попиарить немного :)
AboutButton = Button(ButtonsFrame, text=u"About", width=10, font="system 10", relief="solid", command=About_Button, cursor="question_arrow")
#Создание кнопки сохранения лога в файл
SaveButton = Button(MainWindow, text=u"Сохранить лог", width=15, command=Save_Button, font="system 8")
#Создание кнопки очистки лога
ClearButton = Button(MainWindow, text=u"Очистить лог", width=15, command=Clear_Button, font="system 8")

#Создание скроллбара для прокрутки лога
LogScrollbar = Scrollbar(LogFrame, orient="vertical")

#Создание самого лога
Log = Text(LogFrame, width=52, font="system 10")

#"Прикручивание скроллбара к логу, и наоборот"
LogScrollbar.config(command = Log.yview)
Log.config(yscrollcommand=LogScrollbar.set)

#Упаковка Фрейма элементов управления с помощью менеджера геометрии grid (сетка)
ButtonsFrame.grid(row=5, column=0, columnspan=3, sticky="w")

#Упаковка Фрейма лога с помощью менеджера геометрии grid (сетка)
LogFrame.grid(row=6, columnspan=3)

#Упаковка Метки для поля указания файлообменника
FileShareLabel.grid(row=0, column=0, sticky="w")

#Упаковка Spinbox для выбора файлообменника
FileShareSpinbox.grid(row=0, column=1, columnspan=2, sticky="w")

#Упаковка Метки для полей указания диапазона
DiapasonLabel.grid(row=1, column=0, sticky="w")

#Упаковка поля ввода начала диапазона
From_Entry.grid(row=1, column=1, sticky="w")

#Упаковка поля ввода конца диапазона
To_Entry.grid(row=1, column=2, sticky="w")

#Упаковка Метки для поля указания количества потоков
ThreadsLabel.grid(row=3, column=0, sticky="w")

#Упаковка поля ввода количества потоков
Threads_Entry.grid(row=3, column=1, columnspan=2, sticky="w")

#Упаковка Метки для Spinbox выбора таймаута
TimeoutLabel.grid(row=4,column=0, sticky="w")

#Упаковка Spinbox для указания таймаута
TimeoutSpinbox.grid(row=4,column=1, sticky="w")

#Упаковка Метки для указания прокси
ProxyLabel.grid(row=2, column=0, sticky="w")

#Упаковка поля ввода для указания прокси
Proxy_Entry.grid(row=2, column=1, columnspan=2, sticky="w")

#Упаковка кнопки Старт
StartButton.grid(row=1, column=0, sticky="w")

#Упаковка кнопки Пауза
PauseButton.grid(row=1, column=1, sticky="w")

#Упаковка кнопки Стоп
StopButton.grid(row=1, column=2, sticky="w")

#Упаковка кнопки Ебаут :D
AboutButton.grid(row=1, column=3, sticky="w")

#Упаковка кнопки Сохранения лога
SaveButton.grid(row=7, column=0, sticky="w")

#Упаковка кнопки Очистки лога
ClearButton.grid(row=7, column=1, columnspan=2, sticky="e")

#Упаковка самого лога
Log.grid(sticky="w")

#Упаковка Скроллбара для лога
LogScrollbar.grid(row=0, column=1, sticky="ns")

#Возврат созданного окна
return MainWindow

#Создание Queue (cr0w, привет, ты бы обошёлся тут "простым итератором",
#вот только с простым итератором ты фиг сделаешь нормальную обработку
#ошибок соединения ,да и любых ошибок в принципе как таковых")
queue = Queue()

#Это -глобальные переменные PAUSE и FINISHED, хотя так делать и некрасиво
#но я решил -абить на это дело :D, эти переменные отвечают за управление работой скрипта
FINISHED = False
PAUSE = False

#Ну вот и сам ассоциативный массив, в котором к каждой строке-ключу
#привязана функция -парсер файлообменника
#Почему так ? Да все очень просто - так расширять скрипт проще, если вдруг захотите
#добавить поддержку еще какого-то файлообменник, то вы тупо пишете функцию для работы с ним
#(берете по аналогии с другой функцие) и добавляете ее в этот массив
#Она сразу же становится доступной из поля выбора файлообменников :)
FileShares = {"slil.ru":Get_From_Slil,
"webfile.ru":Get_From_Webfile,
"ifolder.ru":Get_From_Ifolder,
"dump.ru":Get_From_Dump}
#Краткие моменты моего раннего тупизма :), тогда я еще не знал о такой классной вещи как .keys()
FileSharesKeys = []
for FileShare in FileShares:
FileSharesKeys.append(FileShare)

#Проверка на выполнение скрипта
if __name__ == "__main__":

#Создание главного окна скрипта
MainWindow = Create_Gui()

#Запуск главного цикла работы главного окна
MainWindow.mainloop()



Офлайн

#2 Май 31, 2010 17:30:28

UsCr
От:
Зарегистрирован: 2009-11-04
Сообщения: 216
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

Ну… Блокнотик говорит что 66 строка - это вот:

Log.insert("end", u"{0}|{1}\n".format(File_Link, File_Name))
Может File_Link, File_Name не существуют? Какая ошибка то у вас?

P.S. Тут есть тег code.



Отредактировано (Май 31, 2010 17:31:59)

Офлайн

#3 Май 31, 2010 17:36:49

UsCr
От:
Зарегистрирован: 2009-11-04
Сообщения: 216
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

File_Link, File_Name = Working_Function(File_Number, proxy, port)
А самой Working_Function() я не нашел…



Офлайн

#4 Май 31, 2010 17:38:00

Vader
От:
Зарегистрирован: 2010-01-30
Сообщения: 152
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

Странно, у меня все запускается…
Может саму ошибку покажете?



Офлайн

#5 Май 31, 2010 17:46:11

Vader
От:
Зарегистрирован: 2010-01-30
Сообщения: 152
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

UsCr
А самой Working_Function() я не нашел…
Почему не нашли? Вот же она
Working_Function = FileShares[Key]
где
FileShares = {"slil.ru":Get_From_Slil,
"webfile.ru":Get_From_Webfile,
"ifolder.ru":Get_From_Ifolder,
"dump.ru":Get_From_Dump}
где
def Get_From_Slil(number, proxy, port):
...
и т.п.



Отредактировано (Май 31, 2010 17:46:44)

Офлайн

#6 Май 31, 2010 18:39:57

UsCr
От:
Зарегистрирован: 2009-11-04
Сообщения: 216
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

А ошибку вы так и сохраните в секрете?
Кстати, у меня тоже всё работает.



Отредактировано (Май 31, 2010 18:43:09)

Офлайн

#7 Май 31, 2010 19:36:59

igor.kaist
От:
Зарегистрирован: 2007-11-12
Сообщения: 1879
Репутация: +  3  -
Профиль   Отправить e-mail  

Ошибка

Если топикстартер пытается запустить код на python<2.6 то там насколько я помню нет метода format у строк



Отредактировано (Май 31, 2010 19:37:11)

Офлайн

#8 Май 31, 2010 20:17:37

e1se
От:
Зарегистрирован: 2010-05-04
Сообщения: 3
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

А это не оно?
“str.format(*args, **kwargs)¶
Perform a string formatting operation. The string on which this method is called can contain literal text or replacement fields delimited by braces {}. Each replacement field contains either the numeric index of a positional argument, or the name of a keyword argument. Returns a copy of the string where each replacement field is replaced with the string value of the corresponding argument.”



Офлайн

#9 Июнь 1, 2010 07:05:20

igor.kaist
От:
Зарегистрирован: 2007-11-12
Сообщения: 1879
Репутация: +  3  -
Профиль   Отправить e-mail  

Ошибка

e1se
А это не оно?
"str.format(*args, **kwargs)¶
ага…. python 2.5 на FreeBSD:
AttributeError: 'str' object has no attribute 'format'
в 2.6 format появился, об этом так же и в доках написано.



Отредактировано (Июнь 1, 2010 07:06:11)

Офлайн

#10 Июнь 2, 2010 21:43:44

fanki2
От:
Зарегистрирован: 2010-05-31
Сообщения: 3
Репутация: +  0  -
Профиль   Отправить e-mail  

Ошибка

так..раз у вас запускается, значит неправильно запускаю я)) какое дополнительное ПО нужно скачать и установить, чтобы запустить этот файл ?

В IDLE (Python GUI) выдает ошибку “Invalid syntax” в этой строке:

Log.insert(“end”, u“{0}|{1}\n”.format(File_Link, File_Name)) // после \n выделена красным ковычка "

вот собственно скрин: http://clip2net.com/page/m0/6139116


Python 3.1 вроде у меня установлен.



Отредактировано (Июнь 2, 2010 21:54:44)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version