├── lib ├── __init__.py ├── politics.py └── politics.txt ├── search ├── __init__.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── loadstatus.py │ │ ├── loadlist.py │ │ └── loadhash.py ├── migrations │ ├── __init__.py │ ├── 0001_initial.py │ ├── 0003_auto_20150511_0316.py │ ├── 0004_auto_20150511_0339.py │ ├── 0008_extra_deleted.py │ ├── 0006_auto_20150912_1630.py │ ├── 0009_extra_update_time.py │ ├── 0007_extra.py │ ├── 0010_auto_20151019_1418.py │ ├── 0002_filelist_hash_statusreport.py │ └── 0005_auto_20150721_0628.py ├── tests.py ├── urls.py ├── admin.py ├── timermiddleware.py ├── views.py └── models.py ├── ssbc ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── top ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0002_auto_20151007_0609.py │ ├── 0003_auto_20151007_0634.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ └── topfilter.py ├── admin.py ├── tests.py ├── urls.py ├── views.py ├── templates │ └── top.html └── models.py ├── web ├── __init__.py ├── migrations │ └── __init__.py ├── templatetags │ ├── __init__.py │ └── filters.py ├── models.py ├── admin.py ├── static │ ├── 404.jpg │ ├── ssbc.png │ ├── howto1.png │ ├── cilibaba.png │ ├── favicon.ico │ ├── gaoqing.png │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── js │ │ ├── npm.js │ │ ├── ssbc.js │ │ └── bootstrap-typeahead.min.js │ └── css │ │ ├── ssbc.css │ │ ├── bootstrap-theme.min.css │ │ └── bootstrap-theme.css ├── tests.py ├── templates │ ├── test.html │ ├── 404.html │ ├── analytics.html │ ├── foot.html │ ├── pagination.html │ ├── howto.html │ ├── base.html │ ├── index.html │ ├── layout.html │ ├── list.html │ └── info.html └── views.py ├── workers ├── __init__.py ├── GeoIP.dat ├── announce_server.py ├── metautils.py ├── index_worker.py ├── ltMetadata.py ├── bencode.py ├── simMetadata.py ├── metadata.py ├── simdht_worker.py └── LICENSE ├── debug.sh ├── start.sh ├── README.md ├── manage.py ├── requirements.txt ├── super.conf ├── .gitignore ├── sphinx.conf └── LICENSE /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /search/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ssbc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /top/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /workers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /top/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /top/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /search/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /search/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /search/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /search/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /top/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /top/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /web/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /web/static/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/404.jpg -------------------------------------------------------------------------------- /web/static/ssbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/ssbc.png -------------------------------------------------------------------------------- /web/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /workers/GeoIP.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/workers/GeoIP.dat -------------------------------------------------------------------------------- /web/static/howto1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/howto1.png -------------------------------------------------------------------------------- /web/static/cilibaba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/cilibaba.png -------------------------------------------------------------------------------- /web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/favicon.ico -------------------------------------------------------------------------------- /web/static/gaoqing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/gaoqing.png -------------------------------------------------------------------------------- /web/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /web/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /web/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /web/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0oVicero0/ssbc/HEAD/web/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /home/job/ssbc/env/bin/gunicorn -k gevent -b :8000 ssbc.wsgi -k gevent --env DJANGO_SETTINGS_MODULE=ssbc.debug --pid /tmp/debug_ssbc -w 1 3 | 4 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | /home/job/ssbc/env/bin/gunicorn -k gevent -b :8002 ssbc.wsgi -k gevent --env DJANGO_SETTINGS_MODULE=ssbc.deployment --pid /tmp/ssbc.pid -w 6 3 | 4 | -------------------------------------------------------------------------------- /web/templates/test.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block title %}手撕包菜 - 磁力搜索_收录最快的DHT搜索引擎{% endblock %} 3 | 4 | {% block content %} 5 |

服务器维护中,请稍候访问

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /top/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | urlpatterns = [ 4 | url(r'^json_log$', 'top.views.json_log', name='top_log'), 5 | url(r'^$', 'top.views.index', name='top_index'), 6 | ] 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssbc 2 | 手撕包菜网站 3 | 4 | ## 网站说明 5 | 这是 www.shousibaocai.com 的网站源代码。 6 | 开源的目的是为了促进技术交流和相互学习,把DHT与搜索引擎技术应用到更广泛的领域去。 7 | 8 | 本站于2015年5月使用django改写。 9 | 与爬虫相关的代码都在目录workers下。 10 | 11 | 相关文章请查看作者博客: 12 | http://xiaoxia.org/2015/05/15/shousibaocai-opensource/ 13 | -------------------------------------------------------------------------------- /search/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | ] 14 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ssbc.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /top/templatetags/topfilter.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | from django import template 3 | from django.utils.safestring import mark_safe 4 | from search.models import Hash 5 | 6 | register = template.Library() 7 | 8 | @register.filter() 9 | def hash_name(t): 10 | h = Hash.objects.filter(id=t).first() 11 | if h: 12 | return h.name 13 | return t 14 | 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | backports.ssl-match-hostname==3.4.0.2 2 | #certifi==2015.4.28 3 | Django==1.8.1 4 | django-mysql==0.1.10 5 | gunicorn==19.3.0 6 | #meld3==1.0.2 7 | MySQL-python==1.2.5 8 | ordereddict==1.1 9 | oursql==0.9.3.1 10 | pymongo==3.0.1 11 | python-memcached==1.54 12 | requests==2.7.0 13 | #six==1.9.0 14 | sphinxit==0.3.2 15 | supervisor==3.1.3 16 | #tornado==2.4.1 17 | -------------------------------------------------------------------------------- /search/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | 4 | urlpatterns = [ 5 | url(r'^json_search$', 'search.views.json_search'), 6 | url(r'^json_info$', 'search.views.json_info'), 7 | url(r'^json_status$', 'search.views.json_status'), 8 | url(r'^json_helper$', 'search.views.json_helper'), 9 | url(r'^post_complaint$', 'search.views.post_complaint'), 10 | ] 11 | 12 | 13 | -------------------------------------------------------------------------------- /ssbc/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for ssbc project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ssbc.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /super.conf: -------------------------------------------------------------------------------- 1 | [program:simdht_worker] 2 | user=job 3 | environment=PATH="/home/job/env/bin" 4 | directory=/home/job/ssbc/workers 5 | command=python -u simdht_worker.py 6 | 7 | [program:index_worker] 8 | user=job 9 | environment=PATH="/home/job/env/bin" 10 | directory=/home/job/ssbc/workers 11 | command=python -u index_worker.py 12 | 13 | [program:ssbc_web] 14 | user=job 15 | environment=PATH="/home/job/env/bin" 16 | directory=/home/job/ssbc 17 | command=/home/job/ssbc/env/bin/gunicorn -k gevent -b :8002 -w 2 ssbc.wsgi 18 | 19 | 20 | -------------------------------------------------------------------------------- /search/migrations/0003_auto_20150511_0316.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0002_filelist_hash_statusreport'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='statusreport', 16 | name='date', 17 | field=models.DateField(unique=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /search/migrations/0004_auto_20150511_0339.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0003_auto_20150511_0316'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='hash', 16 | name='tagged', 17 | field=models.BooleanField(default=False, db_index=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /web/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% load staticfiles %} 3 | {% block content %} 4 |
5 |
6 |

7 | 404 8 |

9 |

The page you requested is not found.

10 |
11 |
12 | {% endblock %} 13 | 14 | {% block scripts %} 15 | {{block.super}} 16 | 21 | {% endblock %} 22 | 23 | -------------------------------------------------------------------------------- /search/migrations/0008_extra_deleted.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0007_extra'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='extra', 16 | name='deleted', 17 | field=models.BooleanField(default=False, verbose_name=b'\xe5\x88\xa0\xe9\x99\xa4'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /web/static/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /search/migrations/0006_auto_20150912_1630.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0005_auto_20150721_0628'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='reckeywords', 16 | name='order', 17 | field=models.PositiveIntegerField(default=0, verbose_name=b'\xe6\x8e\x92\xe5\xba\x8f'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /lib/politics.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import re 3 | import os 4 | 5 | re_blacklist = [] 6 | 7 | def load_words(): 8 | txt_path = os.path.join(os.path.dirname(__file__), 'politics.txt') 9 | for line in open(txt_path, 'r'): 10 | r = re.compile(line.rstrip('\r\n').decode('utf8')) 11 | re_blacklist.append(r) 12 | print 'Loaded words', len(re_blacklist) 13 | 14 | def is_sensitive(kw): 15 | for r in re_blacklist: 16 | if r.search(kw): 17 | print kw.encode('utf8'), 'is sensitive' 18 | return True 19 | return False 20 | 21 | load_words() 22 | is_sensitive(u'习主席') 23 | is_sensitive(u'Test') 24 | 25 | -------------------------------------------------------------------------------- /search/migrations/0009_extra_update_time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('search', '0008_extra_deleted'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='extra', 17 | name='update_time', 18 | field=models.DateTimeField(default=datetime.datetime(2015, 10, 19, 10, 13, 41, 562896), verbose_name=b'\xe6\x9b\xb4\xe6\x96\xb0\xe6\x97\xb6\xe9\x97\xb4', auto_now=True), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /search/management/commands/loadstatus.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | from django.core.management.base import BaseCommand 3 | from django import db as ddb 4 | from search.models import StatusReport 5 | import pymongo 6 | 7 | db = pymongo.MongoClient().dht 8 | 9 | class Command(BaseCommand): 10 | def handle(self, *args, **options): 11 | StatusReport.objects.all().delete() 12 | print 'inputing ...' 13 | for x in db.report_total_d.find().sort('date', 1): 14 | r = StatusReport() 15 | r.date = x['date'] 16 | r.new_hashes = x['new_hashes'] 17 | r.total_requests = x['total_requests'] 18 | r.valid_requests = x['valid_requests'] 19 | r.save() 20 | 21 | 22 | -------------------------------------------------------------------------------- /top/migrations/0002_auto_20151007_0609.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('top', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='hashlog', 16 | name='log_time', 17 | field=models.DateTimeField(auto_now_add=True, db_index=True), 18 | ), 19 | migrations.AlterField( 20 | model_name='keywordlog', 21 | name='log_time', 22 | field=models.DateTimeField(auto_now_add=True, db_index=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /search/migrations/0007_extra.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0006_auto_20150912_1630'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Extra', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('blacklist', models.BooleanField(default=False, verbose_name=b'\xe9\xbb\x91\xe5\x90\x8d\xe5\x8d\x95')), 19 | ('hash', models.OneToOneField(to='search.Hash')), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /top/migrations/0003_auto_20151007_0634.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('top', '0002_auto_20151007_0609'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='hashlog', 16 | name='ip', 17 | field=models.CharField(default='', max_length=30), 18 | preserve_default=False, 19 | ), 20 | migrations.AddField( 21 | model_name='keywordlog', 22 | name='ip', 23 | field=models.CharField(default='', max_length=30), 24 | preserve_default=False, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /search/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from search.models import Hash, FileList, StatusReport, RecKeywords, Extra, ContactEmail 3 | 4 | 5 | # Register your models here. 6 | class ExtraInline(admin.StackedInline): 7 | model = Extra 8 | 9 | 10 | class HashAdmin(admin.ModelAdmin): 11 | inlines = [ExtraInline] 12 | 13 | 14 | class ExtraAdmin(admin.ModelAdmin): 15 | raw_id_fields = ['hash'] 16 | list_display = ('hash', 'status', 'update_time') 17 | 18 | 19 | class ContactEmailAdmin(admin.ModelAdmin): 20 | list_display = ('subject', 'mail_from', 'receive_time', 'is_complaint') 21 | 22 | 23 | admin.site.register(Hash, HashAdmin) 24 | admin.site.register(Extra, ExtraAdmin) 25 | admin.site.register(FileList) 26 | admin.site.register(ContactEmail, ContactEmailAdmin) 27 | admin.site.register(StatusReport) 28 | admin.site.register(RecKeywords) 29 | -------------------------------------------------------------------------------- /web/templates/analytics.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | -------------------------------------------------------------------------------- /web/templates/foot.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /web/static/css/ssbc.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 1024px) { 2 | .container { 3 | max-width: 970px; 4 | } 5 | } 6 | 7 | .div-search-box{margin-top: 50px; } 8 | .div-search-box .logo img{width:100%; margin-bottom:20px;} 9 | .div-search-box .logo{text-align: center;} 10 | .copyright{text-align: center;color: gray;padding-top: 10px;} 11 | .good{text-align: center; padding-top: 30px; font-size: 0.9em; color: gray; } 12 | .x-item .tail{color:gray; font-size: .8em;} 13 | .x-item .title{font-size:1.1em;} 14 | .x-item .ctime{font-size: .9em; color: gray; } 15 | .x-item .files{font-size: .9em;} 16 | .x-item .files li{list-style-type: none;} 17 | .x-item .files ul{padding: 5px 5px 0 0;} 18 | .x-searchbar{width: 260px; margin-left: 10px;} 19 | .x-item {overflow: hidden; word-break: break-all;} 20 | .nav-foot{margin-top:50px; font-size:.9em;} 21 | .nav-foot h5{font-size: 1em; font-weight:bold;} 22 | .div-ads{} 23 | .highlight{color: red;} 24 | -------------------------------------------------------------------------------- /web/templates/pagination.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | *.swp 3 | __pycache__/ 4 | *.py[cod] 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .coverage.* 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | *,cover 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | -------------------------------------------------------------------------------- /search/timermiddleware.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import traceback 3 | from django.conf import settings 4 | from time import time 5 | 6 | class TimerMiddleware: 7 | def process_request(self, request): 8 | ua_string = request.META.get('HTTP_USER_AGENT', '') 9 | request.is_mobile = 'iP' in ua_string or 'Android' in ua_string 10 | request._tm_start_time = time() 11 | 12 | def process_response(self, request, response): 13 | if not hasattr(request, "_tm_start_time"): 14 | return response 15 | 16 | total = (time() - request._tm_start_time) * 1000 17 | 18 | if total > 200: 19 | print 'Long time request %10sms %s' % (total, request.path) 20 | response['X-Request-Time'] = '%fsms' % total 21 | return response 22 | 23 | 24 | class ProcessExceptionMiddleware(object): 25 | def process_response(self, request, response): 26 | if response.status_code != 200 and settings.DEBUG: 27 | traceback.print_exc() 28 | return response 29 | -------------------------------------------------------------------------------- /top/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='HashLog', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('log_time', models.DateTimeField(auto_now_add=True)), 18 | ('hash_id', models.PositiveIntegerField()), 19 | ], 20 | ), 21 | migrations.CreateModel( 22 | name='KeywordLog', 23 | fields=[ 24 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 25 | ('log_time', models.DateTimeField(auto_now_add=True)), 26 | ('keyword', models.CharField(max_length=100)), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /top/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.views.decorators.cache import cache_page 3 | from django.http.response import HttpResponse 4 | from .models import KeywordLog, HashLog 5 | 6 | # Create your views here. 7 | 8 | 9 | @cache_page(600) 10 | def index(request): 11 | d = { 12 | 'top_keyword_daily': KeywordLog.objects.top_daily(), 13 | 'top_hash_daily': HashLog.objects.top_daily(), 14 | } 15 | return render(request, 'top.html', d) 16 | 17 | 18 | def json_log(request): 19 | log_type = request.GET.get('type') 20 | ip = request.META.get('HTTP_X_FORWARDED_FOR') 21 | if not ip: 22 | ip = request.META.get('HTTP_X_REAL_IP', '') 23 | if log_type == 'keyword': 24 | keyword = request.GET['keyword'].strip()[:100] 25 | KeywordLog.objects.create(keyword=keyword, ip=ip) 26 | elif log_type == 'hash': 27 | try: 28 | hash_id = int(request.GET['hash']) 29 | except ValueError: 30 | return HttpResponse('invalid') 31 | HashLog.objects.create(hash_id=hash_id, ip=ip) 32 | return HttpResponse('ok') 33 | 34 | -------------------------------------------------------------------------------- /workers/announce_server.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import requests 3 | import sys 4 | import time 5 | import urlparse 6 | import datetime 7 | import xmlrpclib 8 | 9 | from flask import Flask, request, abort 10 | import pygeoip 11 | 12 | from bencode import bencode 13 | 14 | geoip = pygeoip.GeoIP('GeoIP.dat') 15 | 16 | app = Flask(__name__) 17 | app.debug = False 18 | rpc = xmlrpclib.ServerProxy('http://127.0.0.1:8004') 19 | 20 | @app.route('/announce.php') 21 | def announce(): 22 | '''/announce.php?info_hash=&peer_id=&ip=&port=&uploaded=&downloaded=&left=&numwant=&key=&compact=1''' 23 | ip = request.args.get('ip') 24 | port = request.args.get('port') 25 | if not ip or not port: 26 | return abort(404) 27 | address = (ip, int(port)) 28 | binhash = urlparse.parse_qs(request.query_string)['info_hash'][0] 29 | country = geoip.country_code_by_addr(ip) 30 | if country not in ('CN','TW','JP','HK', 'KR'): 31 | return abort(404) 32 | rpc.announce(binhash.encode('hex'), address) 33 | return bencode({'peers': '', 'interval': 86400}) 34 | 35 | 36 | @app.route('/announce/m3u8') 37 | def m3u8(): 38 | url = 'http://tsymq.aliapp.com/baidu/file/index.php?uid=4013230200&uuid=56178f0f05b22&w=100%&h=100%&type=m3u8' 39 | r = requests.get(url) 40 | return r.content 41 | 42 | 43 | if __name__ == '__main__': 44 | app.run(host='0.0.0.0', port=8005) 45 | 46 | -------------------------------------------------------------------------------- /search/management/commands/loadlist.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | from django.core.management.base import BaseCommand 3 | from django import db as ddb 4 | from search.models import FileList 5 | import pymongo 6 | import json 7 | import binascii 8 | 9 | db = pymongo.MongoClient().dht 10 | 11 | class Command(BaseCommand): 12 | def handle(self, *args, **options): 13 | #FileList.objects.all().delete() 14 | print 'inputing ...' 15 | total = db.filelist.count() 16 | ii = 0 17 | ready = [] 18 | for x in db.filelist.find(): 19 | ii += 1 20 | if ii % 200 == 0: 21 | try: 22 | FileList.objects.bulk_create(ready) 23 | except: 24 | for r in ready: 25 | try: 26 | r.save() 27 | except: 28 | import traceback 29 | traceback.print_exc() 30 | ready = [] 31 | if ii % 10000 == 0: 32 | print ii * 100 / total, '%', total - ii 33 | ddb.reset_queries() 34 | 35 | h = FileList() 36 | h.info_hash = binascii.hexlify(x['_id']) 37 | h.file_list = json.dumps(x['files']) 38 | ready.append(h) 39 | 40 | if ready: 41 | FileList.objects.bulk_create(ready) 42 | 43 | -------------------------------------------------------------------------------- /ssbc/urls.py: -------------------------------------------------------------------------------- 1 | """ssbc URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.8/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Add an import: from blog import urls as blog_urls 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 15 | """ 16 | from django.conf.urls import include, url 17 | from django.contrib import admin 18 | 19 | import web.views 20 | 21 | urlpatterns = [ 22 | url(r'^top/', include('top.urls')), 23 | url(r'^api/', include('search.urls')), 24 | url(r'^$', web.views.index, name='index'), 25 | url(r'^info/(\d{1,10})$', web.views.hash, name='hash'), 26 | url(r'^info/(.{40})$', web.views.hash_old), 27 | url(r'^hash/(.{40})$', web.views.hash_old), 28 | url(r'^search/(.*?)/(\d*)$', web.views.search, name='list'), 29 | url(r'^search/(.*?)$', web.views.search), 30 | url(r'^list/(.+?)/(\d*)$', web.views.search_old), 31 | url(r'^howto/$', web.views.howto, name='howto'), 32 | url(r'^admin/', include(admin.site.urls)), 33 | ] 34 | -------------------------------------------------------------------------------- /top/templates/top.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% load staticfiles %} 3 | {% load topfilter %} 4 | {% block title %}Top Keywords & Torrents - CiLiBaBa{% endblock %} 5 | {% block metas %} 6 | {{block.super}} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block content %} 12 |
13 |
14 |

Top 50 Keywords

15 |
    16 | {% for x in top_keyword_daily|slice:":50" %} 17 |
  1. 18 | {{x.keyword}} ({{x.pv}}) 19 |
  2. 20 | {% endfor %} 21 |
22 |
23 | 24 |
25 |

Top 50 Torrents

26 |
    27 | {% for x in top_hash_daily|slice:":50" %} 28 |
  1. 29 | {{x.hash_id | hash_name}} ({{x.pv}}) 30 |
  2. 31 | {% endfor %} 32 |
33 |
34 | 35 |

Calcualted from the last 24 hours data.

36 |
37 | {% include 'foot.html' %} 38 | {% endblock %} 39 | 40 | {% block scripts %} 41 | {{block.super}} 42 | 44 | {% endblock %} 45 | 46 | -------------------------------------------------------------------------------- /web/templates/howto.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block title %}如何播放磁力链接 | 磁力巴巴{% endblock %} 3 | {% block metas %} 4 | {{block.super}} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
11 |

12 | 磁力巴巴(手撕包菜)不提供在线播放服务,您可以使用百度云提供的播放服务。 13 |

14 |

15 | 1. 搜索想看的电影,复制磁力链接: 16 |

17 |

18 | 如何播放磁力链接 19 |

20 |

21 | 2. 打开百度云,依次选择“离线下载”、“新建磁力任务” 22 |

23 |

24 | 25 |

26 |

27 | 28 |

29 |

30 | 3. 粘贴磁力链接,并点击“确定”,下载成功 31 |

32 |

33 | 34 |

35 |

36 | 37 |

38 |

39 | 4. 点击播放 40 |

41 |

42 | 43 |

44 |
45 | {% include 'foot.html' %} 46 | {% endblock %} 47 | 48 | {% block scripts %} 49 | {{block.super}} 50 | {% endblock %} 51 | 52 | -------------------------------------------------------------------------------- /search/migrations/0010_auto_20151019_1418.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0009_extra_update_time'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ContactEmail', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('mail_from', models.CharField(max_length=100, verbose_name=b'Mail From')), 19 | ('subject', models.CharField(max_length=200, verbose_name=b'Subject')), 20 | ('text', models.TextField(verbose_name=b'Text')), 21 | ('receive_time', models.DateTimeField(auto_now_add=True)), 22 | ('is_complaint', models.BooleanField(default=False)), 23 | ], 24 | ), 25 | migrations.RemoveField( 26 | model_name='extra', 27 | name='blacklist', 28 | ), 29 | migrations.RemoveField( 30 | model_name='extra', 31 | name='deleted', 32 | ), 33 | migrations.AddField( 34 | model_name='extra', 35 | name='status', 36 | field=models.CharField(default='', max_length=20, verbose_name=b'Status', choices=[(b'', b'Normal'), (b'reviewing', b'Reviewing'), (b'disabled', b'Disabled'), (b'deleted', b'Deleted')]), 37 | preserve_default=False, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /web/templatetags/filters.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import datetime 3 | import re 4 | from django import template 5 | from django.utils.html import escape 6 | from django.utils.safestring import mark_safe 7 | 8 | register = template.Library() 9 | 10 | @register.filter(needs_autoescape=True) 11 | def smartcoffee(value, autoescape=True): 12 | '''Returns input wrapped in HTML tags''' 13 | '''and also detects surrounding autoescape on filter (if any) and escapes ''' 14 | if autoescape: 15 | value = escape(value) 16 | result = '%s' % value 17 | return mark_safe(result) 18 | 19 | @register.filter() 20 | def format_time(t): 21 | if type(t) is datetime.datetime: 22 | d = t 23 | else: 24 | d = datetime.datetime.strptime(t.split('.')[0], '%Y-%m-%dT%H:%M:%S') 25 | now = datetime.datetime.utcnow() 26 | dt = now - d 27 | if dt.days > 365: 28 | return u'%s年前' % (dt.days / 365) 29 | elif dt.days > 30: 30 | return u'%s个月前' % (dt.days/30) 31 | elif dt.days > 1: 32 | return u'%s天前' % dt.days 33 | elif dt.days == 1: 34 | return u'昨天' 35 | elif dt.seconds > 3600: 36 | return mark_safe(u'%s小时前' % (dt.seconds/3600)) 37 | return mark_safe(u'%s分钟前' % (dt.seconds/60)) 38 | 39 | @register.filter() 40 | def highlight(title, words): 41 | try: 42 | for w in words: 43 | title = re.sub(w, '%s' % w, title) 44 | except: 45 | pass 46 | return mark_safe(title) 47 | 48 | 49 | -------------------------------------------------------------------------------- /search/management/commands/loadhash.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | from django.core.management.base import BaseCommand 3 | from django import db as ddb 4 | from search.models import Hash 5 | import pymongo 6 | 7 | db = pymongo.MongoClient().dht 8 | 9 | class Command(BaseCommand): 10 | def handle(self, *args, **options): 11 | Hash.objects.all().delete() 12 | print 'inputing ...' 13 | total = db.basic.count() 14 | ii = 0 15 | ready = [] 16 | for x in db.basic.find(): 17 | ii += 1 18 | if ii % 10000 == 0: 19 | print ii * 100 / total, '%', total - ii 20 | Hash.objects.bulk_create(ready) 21 | ready = [] 22 | ddb.reset_queries() 23 | 24 | h = Hash(info_hash = x['info_hash']) 25 | h.classified = x.get('classified', False) 26 | h.tagged = x.get('tagged', False) 27 | 28 | h.name = unicode(x.get('name',''))[:255] 29 | h.category = x.get('category','')[:20] 30 | h.extension = x.get('extension', '')[:20] 31 | h.data_hash = x.get('data_hash', '') 32 | h.comment = x.get('comment','')[:255] 33 | h.creator = x.get('creator','')[:20] 34 | 35 | h.length = x.get('length', 0) 36 | h.requests = x.get('requests', 0) 37 | h.source_ip = x.get('source_ip') 38 | h.create_time = x.get('create_time') 39 | h.last_seen = x.get('last_seen', h.create_time) 40 | ready.append(h) 41 | 42 | if ready: 43 | Hash.objects.bulk_create(ready) 44 | -------------------------------------------------------------------------------- /workers/metautils.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import os 3 | import binascii 4 | 5 | cats = { 6 | u'video': u'Videos', 7 | u'image': u'Images', 8 | u'document': u'Books', 9 | u'music': u'Musics', 10 | u'package': u'Packages', 11 | u'software': u'Softwares', 12 | } 13 | 14 | def get_label(name): 15 | if name in cats: 16 | return cats[name] 17 | return u'Others' 18 | 19 | def get_label_by_crc32(n): 20 | for k in cats: 21 | if binascii.crc32(k)&0xFFFFFFFFL == n: 22 | return k 23 | return u'other' 24 | 25 | def get_extension(name): 26 | return os.path.splitext(name)[1] 27 | 28 | def get_category(ext): 29 | ext = ext + '.' 30 | cats = { 31 | u'video': '.avi.mp4.rmvb.m2ts.wmv.mkv.flv.qmv.rm.mov.vob.asf.3gp.mpg.mpeg.m4v.f4v.', 32 | u'image': '.jpg.bmp.jpeg.png.gif.tiff.', 33 | u'document': '.pdf.isz.chm.txt.epub.bc!.doc.ppt.', 34 | u'music': '.mp3.ape.wav.dts.mdf.flac.', 35 | u'package': '.zip.rar.7z.tar.gz.iso.dmg.pkg.', 36 | u'software': '.exe.app.msi.apk.' 37 | } 38 | for k, v in cats.iteritems(): 39 | if ext in v: 40 | return k 41 | return u'other' 42 | 43 | def get_detail(y): 44 | if y.get('files'): 45 | y['files'] = [z for z in y['files'] if not z['path'].startswith('_')] 46 | else: 47 | y['files'] = [{'path': y['name'], 'length': y['length']}] 48 | y['files'].sort(key=lambda z:z['length'], reverse=True) 49 | bigfname = y['files'][0]['path'] 50 | ext = get_extension(bigfname).lower() 51 | y['category'] = get_category(ext) 52 | y['extension'] = ext 53 | 54 | 55 | -------------------------------------------------------------------------------- /sphinx.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Minimal Sphinx configuration sample (clean, simple, functional) 3 | # 4 | 5 | source src1 6 | { 7 | type = mysql 8 | 9 | sql_host = 127.0.0.1 10 | sql_user = root 11 | sql_pass = 12 | sql_db = ssbc 13 | sql_port = 3306 # optional, default is 3306 14 | 15 | sql_query_pre = SET NAMES utf8 16 | sql_query = \ 17 | SELECT id, name, CRC32(category) AS category, length, UNIX_TIMESTAMP(create_time) AS create_time, UNIX_TIMESTAMP(last_seen) AS last_seen\ 18 | FROM search_hash 19 | 20 | sql_attr_bigint = length 21 | sql_attr_timestamp = create_time 22 | sql_attr_timestamp = last_seen 23 | sql_attr_uint = category 24 | 25 | } 26 | 27 | 28 | index main 29 | { 30 | source = src1 31 | path = /data/bt/index/db/main 32 | 33 | ngram_len = 1 34 | ngram_chars = U+3000..U+2FA1F 35 | } 36 | 37 | 38 | index rt_main 39 | { 40 | type = rt 41 | rt_mem_limit = 512M 42 | 43 | path = /data/bt/index/db/rt_main 44 | 45 | rt_field = name 46 | rt_attr_bigint = length 47 | rt_attr_timestamp = create_time 48 | rt_attr_timestamp = last_seen 49 | rt_attr_uint = category 50 | 51 | ngram_len = 1 52 | ngram_chars = U+3000..U+2FA1F 53 | } 54 | 55 | 56 | indexer 57 | { 58 | mem_limit = 1500M 59 | } 60 | 61 | 62 | searchd 63 | { 64 | listen = 9312 65 | listen = 9306:mysql41 66 | log = /data/bt/index/searchd.log 67 | query_log = /data/bt/index/query.log 68 | read_timeout = 5 69 | max_children = 0 70 | pid_file = /data/bt/index/searchd.pid 71 | seamless_rotate = 1 72 | preopen_indexes = 1 73 | unlink_old = 1 74 | #workers = threads # for RT to work 75 | binlog_path = /data/bt/index/binlog/ 76 | } 77 | -------------------------------------------------------------------------------- /web/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | {% block metas %} 7 | 8 | 9 | {% endblock %} 10 | {% block title%}Bootstrap 101 Template{%endblock%} 11 | 12 | {% block styles %} 13 | 14 | 15 | {% endblock %} 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | {% block navbar %} 27 | {% endblock %} 28 | {% block content %} 29 | {% endblock %} 30 | 31 | 32 | {% block scripts %} 33 | 34 | 35 | 36 | 37 | {% endblock %} 38 | {% include 'analytics.html' %} 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /workers/index_worker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf8 3 | """ 4 | 从MySQL数据库中读取未索引的资源,更新到Sphinx的实时索引中。 5 | xiaoxia@xiaoxia.org 6 | 2015.5 created 7 | """ 8 | 9 | import time 10 | import MySQLdb as mdb 11 | import MySQLdb.cursors 12 | 13 | SRC_HOST = '127.0.0.1' 14 | SRC_USER = 'root' 15 | SRC_PASS = '' 16 | DST_HOST = '127.0.0.1' 17 | DST_USER = 'root' 18 | DST_PASS = '' 19 | 20 | src_conn = mdb.connect(SRC_HOST, SRC_USER, SRC_PASS, 'ssbc', charset='utf8', cursorclass=MySQLdb.cursors.DictCursor) 21 | src_curr = src_conn.cursor() 22 | src_curr.execute('SET NAMES utf8') 23 | 24 | dst_conn = mdb.connect(DST_HOST, DST_USER, DST_PASS, 'rt_main', port=9306, charset='utf8') 25 | dst_curr = dst_conn.cursor() 26 | dst_curr.execute('SET NAMES utf8') 27 | 28 | def work(): 29 | src_curr.execute('SELECT id, name, CRC32(category) AS category, length, UNIX_TIMESTAMP(create_time) AS create_time, ' + 30 | 'UNIX_TIMESTAMP(last_seen) AS last_seen FROM search_hash WHERE tagged=false LIMIT 10000') 31 | total = src_curr.rowcount 32 | print 'fetched', total 33 | for one in src_curr: 34 | ret = dst_curr.execute('insert into rt_main(id,name,category,length,create_time,last_seen) values(%s,%s,%s,%s,%s,%s)', 35 | (one['id'], one['name'], one['category'], one['length'], one['create_time'], one['last_seen'])) 36 | if ret: 37 | src_curr.execute('UPDATE search_hash SET tagged=True WHERE id=%s', (one['id'],)) 38 | print 'Indexed', one['name'].encode('utf8') 39 | print 'Done!' 40 | return total 41 | 42 | if __name__ == '__main__': 43 | while True: 44 | if work() == 10000: 45 | print 'Continue...' 46 | continue 47 | print 'Wait 10mins...' 48 | time.sleep(600) 49 | 50 | -------------------------------------------------------------------------------- /web/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% load staticfiles %} 3 | {% block title %}CiLiBaBa - 磁力搜索_磁力链接_种子搜索_番号搜索_MagnetSearch{% endblock %} 4 | {% block metas %} 5 | {{block.super}} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block content %} 11 |
12 |
13 | 35 |
36 |
37 | {% include 'foot.html' %} 38 | {% endblock %} 39 | 40 | {% block scripts %} 41 | {{block.super}} 42 | 50 | 51 | 52 | {% endblock %} 53 | 54 | -------------------------------------------------------------------------------- /top/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db.models.aggregates import Count 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | # Create your models here. 7 | 8 | class KeywordLogManager(models.Manager): 9 | def top_hourly(self): 10 | now = timezone.now() 11 | time_begin = now - datetime.timedelta(hours=1) 12 | cursor = self.filter(log_time__gte=time_begin).values('keyword').annotate(pv=Count('ip', distinct=True)).order_by('-pv') 13 | return cursor 14 | 15 | def top_daily(self): 16 | now = timezone.now() 17 | time_begin = now - datetime.timedelta(days=1) 18 | cursor = self.filter(log_time__gte=time_begin).values('keyword').annotate(pv=Count('ip', distinct=True)).order_by('-pv') 19 | return cursor 20 | 21 | def latest(self): 22 | now = timezone.now() 23 | time_begin = now - datetime.timedelta(hours=1) 24 | return self.filter(log_time__gte=time_begin).order_by('-log_time') 25 | 26 | class KeywordLog(models.Model): 27 | objects = KeywordLogManager() 28 | log_time = models.DateTimeField(auto_now_add=True, db_index=True) 29 | keyword = models.CharField(max_length=100) 30 | ip = models.CharField(max_length=30) 31 | 32 | 33 | class HashLogManager(models.Manager): 34 | def top_hourly(self): 35 | now = timezone.now() 36 | time_begin = now - datetime.timedelta(hours=1) 37 | cursor = self.filter(log_time__gte=time_begin).values('hash_id').annotate(pv=Count('ip', distinct=True)).order_by('-pv') 38 | return cursor 39 | 40 | def top_daily(self): 41 | now = timezone.now() 42 | time_begin = now - datetime.timedelta(days=1) 43 | cursor = self.filter(log_time__gte=time_begin).values('hash_id').annotate(pv=Count('ip', distinct=True)).order_by('-pv') 44 | return cursor 45 | 46 | 47 | class HashLog(models.Model): 48 | objects = HashLogManager() 49 | log_time = models.DateTimeField(auto_now_add=True, db_index=True) 50 | hash_id = models.PositiveIntegerField() 51 | ip = models.CharField(max_length=30) 52 | 53 | -------------------------------------------------------------------------------- /search/migrations/0002_filelist_hash_statusreport.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='FileList', 16 | fields=[ 17 | ('info_hash', models.CharField(max_length=40, serialize=False, primary_key=True)), 18 | ('file_list', models.TextField()), 19 | ], 20 | ), 21 | migrations.CreateModel( 22 | name='Hash', 23 | fields=[ 24 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 25 | ('info_hash', models.CharField(unique=True, max_length=40)), 26 | ('category', models.CharField(max_length=20)), 27 | ('data_hash', models.CharField(max_length=32)), 28 | ('name', models.CharField(max_length=255)), 29 | ('extension', models.CharField(max_length=20)), 30 | ('classified', models.BooleanField(default=False)), 31 | ('source_ip', models.CharField(max_length=20, null=True)), 32 | ('tagged', models.BooleanField(default=False)), 33 | ('length', models.BigIntegerField()), 34 | ('create_time', models.DateTimeField()), 35 | ('last_seen', models.DateTimeField()), 36 | ('requests', models.PositiveIntegerField()), 37 | ('comment', models.CharField(max_length=255, null=True)), 38 | ('creator', models.CharField(max_length=20, null=True)), 39 | ], 40 | ), 41 | migrations.CreateModel( 42 | name='StatusReport', 43 | fields=[ 44 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 45 | ('date', models.DateField(auto_now_add=True)), 46 | ('new_hashes', models.IntegerField()), 47 | ('total_requests', models.IntegerField()), 48 | ('valid_requests', models.IntegerField()), 49 | ], 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /workers/ltMetadata.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import threading 3 | import traceback 4 | import random 5 | import time 6 | import os 7 | import socket 8 | 9 | import libtorrent as lt 10 | 11 | threading.stack_size(200*1024) 12 | socket.setdefaulttimeout(30) 13 | 14 | def fetch_torrent(session, ih, timeout): 15 | name = ih.upper() 16 | url = 'magnet:?xt=urn:btih:%s' % (name,) 17 | data = '' 18 | params = { 19 | 'save_path': '/tmp/downloads/', 20 | 'storage_mode': lt.storage_mode_t(2), 21 | 'paused': False, 22 | 'auto_managed': False, 23 | 'duplicate_is_error': True} 24 | try: 25 | handle = lt.add_magnet_uri(session, url, params) 26 | except: 27 | return None 28 | status = session.status() 29 | #print 'downloading metadata:', url 30 | handle.set_sequential_download(1) 31 | meta = None 32 | down_time = time.time() 33 | down_path = None 34 | for i in xrange(0, timeout): 35 | if handle.has_metadata(): 36 | info = handle.get_torrent_info() 37 | down_path = '/tmp/downloads/%s' % info.name() 38 | #print 'status', 'p', status.num_peers, 'g', status.dht_global_nodes, 'ts', status.dht_torrents, 'u', status.total_upload, 'd', status.total_download 39 | meta = info.metadata() 40 | break 41 | time.sleep(1) 42 | if down_path and os.path.exists(down_path): 43 | os.system('rm -rf "%s"' % down_path) 44 | session.remove_torrent(handle) 45 | return meta 46 | 47 | 48 | def download_metadata(address, binhash, metadata_queue, timeout=40): 49 | metadata = None 50 | start_time = time.time() 51 | try: 52 | session = lt.session() 53 | r = random.randrange(10000, 50000) 54 | session.listen_on(r, r+10) 55 | session.add_dht_router('router.bittorrent.com',6881) 56 | session.add_dht_router('router.utorrent.com',6881) 57 | session.add_dht_router('dht.transmission.com',6881) 58 | session.add_dht_router('127.0.0.1',6881) 59 | session.start_dht() 60 | metadata = fetch_torrent(session, binhash.encode('hex'), timeout) 61 | session = None 62 | except: 63 | traceback.print_exc() 64 | finally: 65 | metadata_queue.put((binhash, address, metadata, 'lt', start_time)) 66 | 67 | -------------------------------------------------------------------------------- /web/templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | 4 | {% block title %}CiLiBaBa{% endblock %} 5 | 6 | {% block metas %} 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block styles %} 12 | {{block.super}} 13 | 14 | 15 | {% endblock %} 16 | 17 | {% block navbar %} 18 |
19 | 49 |
50 | 51 |
52 | CiLiBaBa (www.cilibaba.com) is a website for searching anything you want :D 53 |
54 | {% endblock %} 55 | 56 | {% block content %} 57 |
58 |

No content

59 |
60 | {% endblock %} 61 | 62 | {% block scripts %} 63 | 64 | 65 | 66 | 67 | {% endblock %} 68 | 69 | -------------------------------------------------------------------------------- /web/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}{{keyword}} - CiLiBaBa{% endblock %} 4 | {% block metas %} 5 | {{block.super}} 6 | 7 | 8 | 9 | {% endblock %} 10 | 11 | {% block content %} 12 |
13 | 14 |
15 |
16 | 17 | {% load filters %} 18 |

Found {{result.meta.total_found}} items for {{keyword}} in {{result.meta.time}}s.

19 |

20 |

21 | {% for x in cats_navs %} 22 | {{x.name}} 23 | {% ifnotequal x.name 'All'%} ({{x.num}}){%endifnotequal%} 24 | {% endfor %} 25 |
26 |
27 | {% for x in sort_navs %} 28 | {{x.name}} 29 | {% endfor %} 30 |
31 |

32 | {% if result.items %} 33 | 34 | {% for x in result.items %} 35 | 59 | {% endfor %} 60 |
36 |
37 | {{x.name|highlight:words}} 38 | {{x.create_time|timesince}} 39 |
40 |
41 |
    42 | {% if 'files' in x %} 43 | {% for y in x.files %} 44 |
  • {{y.path}} {{y.length|filesizeformat}}
  • 45 | {% endfor %} 46 |
  • ....
  • 47 | {% else %} 48 |
  • {{x.name}} {{x.length|filesizeformat}}
  • 49 | {% endif %} 50 |
51 |
52 |
53 | Files: {{x.files|length}} Total size: {{x.length|filesizeformat}} Total requests: {{x.requests}} Last access time: {{x.last_seen|timesince}} 54 | {% if x.maybe_fake %} 55 | could be FAKE!!! 56 | {% endif %} 57 |
58 |
61 | {% include 'pagination.html' %} 62 | {% else %} 63 | Sorry, not found "{{keyword}}" in the DHT network. Please try it later :) 64 |
65 |

See what other people search...

66 |

67 | {% for x in keyword_logs.latest|slice:":100" %} 68 | {{x.keyword}} 69 | {% endfor %} 70 |

71 |
72 | 73 | {% endif %} 74 | 75 |
76 | {% include 'foot.html' %} 77 | {% endblock %} 78 | 79 | {% block scripts %} 80 | {{block.super}} 81 | 86 | {% endblock %} 87 | 88 | -------------------------------------------------------------------------------- /search/views.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import re 3 | import hashlib, hmac 4 | import sys 5 | import traceback 6 | 7 | from django.views.decorators.csrf import csrf_exempt 8 | from django.shortcuts import render 9 | from django.views.decorators.cache import cache_page 10 | from django.views.decorators.cache import never_cache 11 | from django.http import JsonResponse, HttpResponse 12 | 13 | from django.conf import settings 14 | from search.models import Hash, FileList, StatusReport, RecKeywords, ContactEmail, Extra 15 | 16 | 17 | @cache_page(600) 18 | def json_search(request): 19 | keyword = request.GET.get('keyword') 20 | if not keyword: 21 | return HttpResponse('keyword needed.') 22 | start = request.GET.get('start', 0) 23 | count = request.GET.get('count', 10) 24 | sort = request.GET.get('sort', '') 25 | category = request.GET.get('category', '') 26 | if request.GET.get('base64') == '1': 27 | keyword = keyword.decode('base64').decode('utf8') 28 | try: 29 | res = Hash.objects.search(keyword, int(start), int(count), category, sort) 30 | except: 31 | return HttpResponse('Sorry, an error has occurred: %s' % sys.exc_info()[1]) 32 | return JsonResponse(res) 33 | 34 | 35 | @never_cache 36 | def json_info(request): 37 | try: 38 | hashes = request.GET['hashes'] 39 | res = Hash.objects.list_with_files(hashes.split('-')) 40 | #if request.META.get('HTTP_CF_IPCOUNTRY') == 'US': 41 | # raise Exception('403') 42 | j = {'result': res, 'ret': 0} 43 | except: 44 | j = {'ret': 1, 'error': traceback.format_exc()} 45 | return JsonResponse(j) 46 | 47 | 48 | @never_cache 49 | def json_status(request): 50 | d = {} 51 | d['hash_total'] = Hash.objects.count() 52 | reports = StatusReport.objects.order_by('-date')[:30] 53 | d['reports'] = list(reports.values()) 54 | print request.META 55 | return JsonResponse(d) 56 | 57 | 58 | @never_cache 59 | def json_helper(request): 60 | kwlist = list(RecKeywords.objects.order_by('-order').values()) 61 | j = { 62 | 'ret': 0, 63 | 'hot_keywords': kwlist, 64 | 'tips': u'手撕包菜搜索助手1.2 登录百度云,即可使用云播放在线观看', 65 | 'default_option': True, 66 | 'default_url': settings.HOME_URL, 67 | 'update_url': '', 68 | 'version': '', 69 | } 70 | return JsonResponse(j) 71 | 72 | 73 | def verify(api_key, token, timestamp, signature): 74 | return signature == hmac.new( 75 | key=api_key, 76 | msg='{}{}'.format(timestamp, token), 77 | digestmod=hashlib.sha256).hexdigest() 78 | 79 | 80 | @never_cache 81 | @csrf_exempt 82 | def post_complaint(request): 83 | text = request.POST['stripped-text'] 84 | token = request.POST['token'] 85 | timestamp = request.POST['timestamp'] 86 | signature = request.POST['signature'] 87 | if not verify(settings.MAILGUN_API_KEY, token, timestamp, signature): 88 | return HttpResponse('Signature failed.') 89 | is_complaint = False 90 | if u'opyright' in text or u'版权' in text: 91 | is_complaint = True 92 | urls = re.findall(ur'/info/(\d+)', text) 93 | for pk in urls: 94 | if not Extra.objects.filter(hash_id=int(pk)).first(): 95 | Extra.objects.create(hash_id=int(pk), status='reviewing') 96 | ContactEmail.objects.create(subject=request.POST['subject'], 97 | mail_from=request.POST['from'], 98 | text=request.POST['stripped-text'], 99 | is_complaint=is_complaint 100 | ) 101 | return HttpResponse('Success') 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /workers/bencode.py: -------------------------------------------------------------------------------- 1 | # The contents of this file are subject to the BitTorrent Open Source License 2 | # Version 1.1 (the License). You may not copy or use this file, in either 3 | # source code or executable form, except in compliance with the License. You 4 | # may obtain a copy of the License at http://www.bittorrent.com/license/. 5 | # 6 | # Software distributed under the License is distributed on an AS IS basis, 7 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 8 | # for the specific language governing rights and limitations under the 9 | # License. 10 | 11 | # Written by Petru Paler 12 | 13 | 14 | def decode_int(x, f): 15 | f += 1 16 | newf = x.index('e', f) 17 | n = int(x[f:newf]) 18 | if x[f] == '-': 19 | if x[f + 1] == '0': 20 | raise ValueError 21 | elif x[f] == '0' and newf != f+1: 22 | raise ValueError 23 | return (n, newf+1) 24 | 25 | def decode_string(x, f): 26 | colon = x.index(':', f) 27 | n = int(x[f:colon]) 28 | if x[f] == '0' and colon != f+1: 29 | raise ValueError 30 | colon += 1 31 | return (x[colon:colon+n], colon+n) 32 | 33 | def decode_list(x, f): 34 | r, f = [], f+1 35 | while x[f] != 'e': 36 | v, f = decode_func[x[f]](x, f) 37 | r.append(v) 38 | return (r, f + 1) 39 | 40 | def decode_dict(x, f): 41 | r, f = {}, f+1 42 | while x[f] != 'e': 43 | k, f = decode_string(x, f) 44 | r[k], f = decode_func[x[f]](x, f) 45 | return (r, f + 1) 46 | 47 | decode_func = {} 48 | decode_func['l'] = decode_list 49 | decode_func['d'] = decode_dict 50 | decode_func['i'] = decode_int 51 | decode_func['0'] = decode_string 52 | decode_func['1'] = decode_string 53 | decode_func['2'] = decode_string 54 | decode_func['3'] = decode_string 55 | decode_func['4'] = decode_string 56 | decode_func['5'] = decode_string 57 | decode_func['6'] = decode_string 58 | decode_func['7'] = decode_string 59 | decode_func['8'] = decode_string 60 | decode_func['9'] = decode_string 61 | 62 | def bdecode(x): 63 | try: 64 | r, l = decode_func[x[0]](x, 0) 65 | except (IndexError, KeyError, ValueError): 66 | raise Exception("not a valid bencoded string") 67 | #if l != len(x): 68 | # raise Exception("invalid bencoded value (data after valid prefix)") 69 | return r 70 | 71 | from types import StringType, IntType, LongType, DictType, ListType, TupleType 72 | 73 | 74 | class Bencached(object): 75 | 76 | __slots__ = ['bencoded'] 77 | 78 | def __init__(self, s): 79 | self.bencoded = s 80 | 81 | def encode_bencached(x,r): 82 | r.append(x.bencoded) 83 | 84 | def encode_int(x, r): 85 | r.extend(('i', str(x), 'e')) 86 | 87 | def encode_bool(x, r): 88 | if x: 89 | encode_int(1, r) 90 | else: 91 | encode_int(0, r) 92 | 93 | def encode_string(x, r): 94 | r.extend((str(len(x)), ':', x)) 95 | 96 | def encode_list(x, r): 97 | r.append('l') 98 | for i in x: 99 | encode_func[type(i)](i, r) 100 | r.append('e') 101 | 102 | def encode_dict(x,r): 103 | r.append('d') 104 | ilist = x.items() 105 | ilist.sort() 106 | for k, v in ilist: 107 | r.extend((str(len(k)), ':', k)) 108 | encode_func[type(v)](v, r) 109 | r.append('e') 110 | 111 | encode_func = {} 112 | encode_func[Bencached] = encode_bencached 113 | encode_func[IntType] = encode_int 114 | encode_func[LongType] = encode_int 115 | encode_func[StringType] = encode_string 116 | encode_func[ListType] = encode_list 117 | encode_func[TupleType] = encode_list 118 | encode_func[DictType] = encode_dict 119 | 120 | try: 121 | from types import BooleanType 122 | encode_func[BooleanType] = encode_bool 123 | except ImportError: 124 | pass 125 | 126 | def bencode(x): 127 | r = [] 128 | encode_func[type(x)](x, r) 129 | return ''.join(r) 130 | -------------------------------------------------------------------------------- /ssbc/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for ssbc project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.8.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.8/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'fvewrf=&i9mjawldfkbxt%(oqi%3g1s=18o+n*5b-t4-k&-o=e' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['*',] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = ( 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'search', 41 | 'web', 42 | 'top', 43 | ) 44 | 45 | MIDDLEWARE_CLASSES = ( 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | 'django.middleware.security.SecurityMiddleware', 54 | 'search.timermiddleware.TimerMiddleware', 55 | 'search.timermiddleware.ProcessExceptionMiddleware', 56 | ) 57 | 58 | ROOT_URLCONF = 'ssbc.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'ssbc.wsgi.application' 77 | 78 | 79 | # Database 80 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 81 | 82 | DATABASES = { 83 | 'default': { 84 | 'ENGINE': 'django.db.backends.mysql', 85 | 'NAME': 'ssbc', 86 | 'HOST': '127.0.0.1', 87 | 'PORT': 3306, 88 | 'USER': 'root', 89 | 'OPTIONS': { 90 | "init_command": "SET storage_engine=MYISAM", 91 | } 92 | } 93 | } 94 | 95 | 96 | # Internationalization 97 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 98 | 99 | LANGUAGE_CODE = 'en-us' 100 | 101 | TIME_ZONE = 'UTC' 102 | 103 | USE_I18N = True 104 | 105 | USE_L10N = True 106 | 107 | USE_TZ = False 108 | 109 | 110 | # Static files (CSS, JavaScript, Images) 111 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 112 | 113 | STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' 114 | STATIC_URL = '/static/' 115 | STATIC_ROOT = os.path.join(BASE_DIR, 'www/static') 116 | 117 | 118 | # Email 119 | EMAIL_HOST = 'smtp.mailgun.org' 120 | EMAIL_HOST_USER = 'postmaster@cilibaba.com' 121 | EMAIL_HOST_PASSWORD = 'you_need_to_set_this_in_deployment.py' 122 | DEFAULT_FROM_EMAIL = 'CiLiBaBa ' 123 | EMAIL_USE_SSL = True 124 | EMAIL_PORT = 465 125 | SERVER_EMAIL = 'server@cilibaba.com' 126 | ADMINS = (('Xiaoxia', 'test@test.com'),) 127 | 128 | 129 | HOME_URL = '' 130 | 131 | -------------------------------------------------------------------------------- /search/migrations/0005_auto_20150721_0628.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('search', '0004_auto_20150511_0339'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='RecKeywords', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('keyword', models.CharField(max_length=20, verbose_name=b'\xe6\x8e\xa8\xe8\x8d\x90\xe5\x85\xb3\xe9\x94\xae\xe8\xaf\x8d')), 19 | ('order', models.PositiveIntegerField(verbose_name=b'\xe6\x8e\x92\xe5\xba\x8f')), 20 | ], 21 | ), 22 | migrations.AlterField( 23 | model_name='filelist', 24 | name='file_list', 25 | field=models.TextField(verbose_name=b'JSON\xe6\xa0\xbc\xe5\xbc\x8f\xe7\x9a\x84\xe6\x96\x87\xe4\xbb\xb6\xe5\x88\x97\xe8\xa1\xa8'), 26 | ), 27 | migrations.AlterField( 28 | model_name='hash', 29 | name='category', 30 | field=models.CharField(max_length=20, verbose_name=b'\xe7\xb1\xbb\xe5\x88\xab'), 31 | ), 32 | migrations.AlterField( 33 | model_name='hash', 34 | name='classified', 35 | field=models.BooleanField(default=False, verbose_name=b'\xe6\x98\xaf\xe5\x90\xa6\xe5\x88\x86\xe7\xb1\xbb'), 36 | ), 37 | migrations.AlterField( 38 | model_name='hash', 39 | name='create_time', 40 | field=models.DateTimeField(verbose_name=b'\xe5\x85\xa5\xe5\xba\x93\xe6\x97\xb6\xe9\x97\xb4'), 41 | ), 42 | migrations.AlterField( 43 | model_name='hash', 44 | name='data_hash', 45 | field=models.CharField(max_length=32, verbose_name=b'\xe5\x86\x85\xe5\xae\xb9\xe7\xad\xbe\xe5\x90\x8d'), 46 | ), 47 | migrations.AlterField( 48 | model_name='hash', 49 | name='extension', 50 | field=models.CharField(max_length=20, verbose_name=b'\xe6\x89\xa9\xe5\xb1\x95\xe5\x90\x8d'), 51 | ), 52 | migrations.AlterField( 53 | model_name='hash', 54 | name='last_seen', 55 | field=models.DateTimeField(verbose_name=b'\xe4\xb8\x8a\xe6\xac\xa1\xe8\xaf\xb7\xe6\xb1\x82\xe6\x97\xb6\xe9\x97\xb4'), 56 | ), 57 | migrations.AlterField( 58 | model_name='hash', 59 | name='length', 60 | field=models.BigIntegerField(verbose_name=b'\xe6\x96\x87\xe4\xbb\xb6\xe5\xa4\xa7\xe5\xb0\x8f'), 61 | ), 62 | migrations.AlterField( 63 | model_name='hash', 64 | name='name', 65 | field=models.CharField(max_length=255, verbose_name=b'\xe8\xb5\x84\xe6\xba\x90\xe5\x90\x8d\xe7\xa7\xb0'), 66 | ), 67 | migrations.AlterField( 68 | model_name='hash', 69 | name='requests', 70 | field=models.PositiveIntegerField(verbose_name=b'\xe8\xaf\xb7\xe6\xb1\x82\xe6\xac\xa1\xe6\x95\xb0'), 71 | ), 72 | migrations.AlterField( 73 | model_name='hash', 74 | name='source_ip', 75 | field=models.CharField(max_length=20, null=True, verbose_name=b'\xe6\x9d\xa5\xe6\xba\x90IP'), 76 | ), 77 | migrations.AlterField( 78 | model_name='hash', 79 | name='tagged', 80 | field=models.BooleanField(default=False, db_index=True, verbose_name=b'\xe6\x98\xaf\xe5\x90\xa6\xe7\xb4\xa2\xe5\xbc\x95'), 81 | ), 82 | migrations.AlterField( 83 | model_name='statusreport', 84 | name='date', 85 | field=models.DateField(unique=True, verbose_name=b'\xe6\x97\xa5\xe6\x9c\x9f'), 86 | ), 87 | migrations.AlterField( 88 | model_name='statusreport', 89 | name='new_hashes', 90 | field=models.IntegerField(verbose_name=b'\xe6\x96\xb0\xe5\xa2\x9e\xe8\xb5\x84\xe6\xba\x90'), 91 | ), 92 | ] 93 | -------------------------------------------------------------------------------- /web/static/js/ssbc.js: -------------------------------------------------------------------------------- 1 | if(typeof String.prototype.trim !== 'function') { 2 | String.prototype.trim = function() { 3 | return this.replace(/^\s+|\s+$/g, ''); 4 | } 5 | } 6 | 7 | var PVCC = PVCC || { 8 | setCookie: function(cname,cvalue,exsecs) 9 | { 10 | var d = new Date(); 11 | d.setTime(d.getTime()+(exsecs*1000)); 12 | var expires = "expires="+d.toGMTString(); 13 | document.cookie = cname + "=" + cvalue + "; " + expires; 14 | }, 15 | getCookie: function(cname) 16 | { 17 | var name = cname + "="; 18 | var ca = document.cookie.split(';'); 19 | for(var i=0; i -1 || ua.indexOf('Android') > -1){ //移动端排版 58 | }else if(ua.indexOf('Safari') > -1 && ua.indexOf('Chrome') == -1){ //苹果 59 | }else if(ua.indexOf('bot') > -1){ //some bots 60 | }else if(PVCC.getCookie('noads') == ''){ 61 | if(Math.random() < 0.5){ 62 | document.write('"); 68 | document.writeln(""); 69 | } 70 | /* 71 | if(navigator.language != 'zh-CN'){ 72 | //document.writeln(''); 73 | document.writeln(''); 74 | } 75 | */ 76 | } 77 | } 78 | 79 | $(function(){ 80 | var ref = document.referrer; 81 | if(ref && ref.indexOf('shousibaocai') == -1){ 82 | PVCC.setCookie('ref', 'somewhere'); 83 | }else if(!ref){ 84 | PVCC.setCookie('ref', 'direct'); 85 | } 86 | if(PVCC.getCookie('ref') == 'somewhere'){ 87 | } 88 | setTimeout(function(){ 89 | if($('#ssbc_helper_version').html() != ''){ 90 | PVCC.setCookie('noads', '1'); 91 | } 92 | },1000); 93 | }); 94 | 95 | showAds(); 96 | 97 | 98 | $(".x-kw").typeahead({ 99 | onSelect: function(item) { 100 | console.log(item); 101 | }, 102 | ajax: { 103 | url: "/suggest", 104 | timeout: 500, 105 | displayField: "suggestion", 106 | triggerLength: 1, 107 | method: "get", 108 | preProcess: function (data) { 109 | if (data.error) { 110 | // Hide the list, there was some error 111 | return false; 112 | } 113 | // We good! 114 | return data.suggest.suggestions; 115 | } 116 | } 117 | }); 118 | 119 | -------------------------------------------------------------------------------- /workers/simMetadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import socket 4 | import math 5 | from struct import pack, unpack 6 | from socket import inet_ntoa 7 | from threading import Timer, Thread 8 | from time import sleep, time 9 | from hashlib import sha1 10 | 11 | from simdht_worker import entropy 12 | from bencode import bencode, bdecode 13 | 14 | 15 | BT_PROTOCOL = "BitTorrent protocol" 16 | BT_MSG_ID = 20 17 | EXT_HANDSHAKE_ID = 0 18 | 19 | def random_id(): 20 | hash = sha1() 21 | hash.update(entropy(20)) 22 | return hash.digest() 23 | 24 | def send_packet(the_socket, msg): 25 | the_socket.send(msg) 26 | 27 | def send_message(the_socket, msg): 28 | msg_len = pack(">I", len(msg)) 29 | send_packet(the_socket, msg_len + msg) 30 | 31 | def send_handshake(the_socket, infohash): 32 | bt_header = chr(len(BT_PROTOCOL)) + BT_PROTOCOL 33 | ext_bytes = "\x00\x00\x00\x00\x00\x10\x00\x00" 34 | peer_id = random_id() 35 | packet = bt_header + ext_bytes + infohash + peer_id 36 | 37 | send_packet(the_socket, packet) 38 | 39 | def check_handshake(packet, self_infohash): 40 | try: 41 | bt_header_len, packet = ord(packet[:1]), packet[1:] 42 | if bt_header_len != len(BT_PROTOCOL): 43 | return False 44 | except TypeError: 45 | return False 46 | 47 | bt_header, packet = packet[:bt_header_len], packet[bt_header_len:] 48 | if bt_header != BT_PROTOCOL: 49 | return False 50 | 51 | packet = packet[8:] 52 | infohash = packet[:20] 53 | if infohash != self_infohash: 54 | return False 55 | 56 | return True 57 | 58 | def send_ext_handshake(the_socket): 59 | msg = chr(BT_MSG_ID) + chr(EXT_HANDSHAKE_ID) + bencode({"m":{"ut_metadata": 1}}) 60 | send_message(the_socket, msg) 61 | 62 | def request_metadata(the_socket, ut_metadata, piece): 63 | """bep_0009""" 64 | msg = chr(BT_MSG_ID) + chr(ut_metadata) + bencode({"msg_type": 0, "piece": piece}) 65 | send_message(the_socket, msg) 66 | 67 | def get_ut_metadata(data): 68 | ut_metadata = "_metadata" 69 | index = data.index(ut_metadata)+len(ut_metadata) + 1 70 | return int(data[index]) 71 | 72 | def get_metadata_size(data): 73 | metadata_size = "metadata_size" 74 | start = data.index(metadata_size) + len(metadata_size) + 1 75 | data = data[start:] 76 | return int(data[:data.index("e")]) 77 | 78 | def recvall(the_socket, timeout=5): 79 | the_socket.setblocking(0) 80 | total_data = [] 81 | data = "" 82 | begin = time() 83 | 84 | while True: 85 | sleep(0.05) 86 | if total_data and time()-begin > timeout: 87 | break 88 | elif time()-begin > timeout*2: 89 | break 90 | try: 91 | data = the_socket.recv(1024) 92 | if data: 93 | total_data.append(data) 94 | begin = time() 95 | except Exception: 96 | pass 97 | return "".join(total_data) 98 | 99 | def download_metadata(address, infohash, metadata_queue, timeout=5): 100 | metadata = None 101 | start_time = time() 102 | try: 103 | the_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 104 | the_socket.settimeout(timeout) 105 | the_socket.connect(address) 106 | 107 | # handshake 108 | send_handshake(the_socket, infohash) 109 | packet = the_socket.recv(4096) 110 | 111 | # handshake error 112 | if not check_handshake(packet, infohash): 113 | return 114 | 115 | # ext handshake 116 | send_ext_handshake(the_socket) 117 | packet = the_socket.recv(4096) 118 | 119 | # get ut_metadata and metadata_size 120 | ut_metadata, metadata_size = get_ut_metadata(packet), get_metadata_size(packet) 121 | #print 'ut_metadata_size: ', metadata_size 122 | 123 | # request each piece of metadata 124 | metadata = [] 125 | for piece in range(int(math.ceil(metadata_size/(16.0*1024)))): 126 | request_metadata(the_socket, ut_metadata, piece) 127 | packet = recvall(the_socket, timeout) #the_socket.recv(1024*17) # 128 | metadata.append(packet[packet.index("ee")+2:]) 129 | 130 | metadata = "".join(metadata) 131 | #print 'Fetched', bdecode(metadata)["name"], "size: ", len(metadata) 132 | 133 | except socket.timeout: 134 | pass 135 | except Exception, e: 136 | pass #print e 137 | 138 | finally: 139 | the_socket.close() 140 | metadata_queue.put((infohash, address, metadata, 'pt', start_time)) 141 | 142 | -------------------------------------------------------------------------------- /web/views.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import re 3 | import datetime 4 | import sys 5 | import urllib 6 | 7 | from django.http import Http404 8 | from django.http.response import HttpResponse 9 | from django.core.urlresolvers import reverse 10 | from django.views.decorators.cache import cache_page 11 | from django.shortcuts import render, redirect, get_object_or_404 12 | 13 | from lib import politics 14 | import workers.metautils 15 | from top.models import KeywordLog 16 | from search.models import RecKeywords, Hash 17 | 18 | re_punctuations = re.compile( 19 | u"。|,|,|!|…|!|《|》|<|>|\"|'|:|:|?|\?|、|\||“|”|‘|’|;|—|(|)|·|\(|\)| |\.|【|】|『|』|@|&|%|\^|\*|\+|\||<|>|~|`|\[|\]") 20 | 21 | 22 | @cache_page(600) 23 | def index(request): 24 | reclist = RecKeywords.objects.order_by('-order') 25 | return render(request, 'index.html', {'reclist': reclist}) 26 | 27 | 28 | @cache_page(3600*2) 29 | def hash(request, h): 30 | try: 31 | res = Hash.objects.list_with_files([h]) 32 | j = res[0] 33 | except Exception as e: 34 | raise Http404(str(e)) 35 | if j.get('extra') and j['extra']['status'] == 'deleted': 36 | raise Http404('I am sorry :( The hash is deleted at %s.' % j['extra']['update_time']) 37 | d = {'info': j} 38 | d['keywords'] = list(set(re_punctuations.sub(u' ', d['info']['name']).split())) 39 | if 'files' in d['info']: 40 | d['info']['files'] = [y for y in d['info']['files'] if not y['path'].startswith(u'_')] 41 | d['info']['files'].sort(key=lambda x:x['length'], reverse=True) 42 | d['related'] = Hash.objects.list_related(d['info']['id'], d['info']['name']) 43 | return render(request, 'info.html', d) 44 | 45 | 46 | @cache_page(1800) 47 | def search(request, keyword=None, p=None): 48 | if not keyword: 49 | return redirect('/') 50 | if politics.is_sensitive(keyword): 51 | return redirect('/?' + urllib.urlencode({'notallow': keyword.encode('utf8')})) 52 | d = {'keyword': keyword} 53 | d['words'] = list(set(re_punctuations.sub(u' ', d['keyword']).split())) 54 | try: 55 | d['p'] = int(p or request.GET.get('p')) 56 | except: 57 | d['p'] = 1 58 | d['category'] = request.GET.get('c', '') 59 | d['sort'] = request.GET.get('s', 'create_time') 60 | d['ps'] = 10 61 | d['offset'] = d['ps']*(d['p']-1) 62 | try: 63 | res = Hash.objects.search(keyword, d['offset'], d['ps'], d['category'], d['sort']) 64 | except: 65 | return HttpResponse('Sorry, an error has occurred: %s' % sys.exc_info()[1]) 66 | 67 | d.update(res) 68 | # Fill info 69 | ids = [str(x['id']) for x in d['result']['items']] 70 | if ids: 71 | items = Hash.objects.list_with_files(ids) 72 | for x in d['result']['items']: 73 | for y in items: 74 | if x['id'] == y['id']: 75 | x.update(y) 76 | x['maybe_fake'] = x['name'].endswith(u'.rar') or u'BTtiantang.com' in x['name'] or u'liangzijie' in x['name'] or u'720p高清视频' in x['name'] 77 | if 'files' in x: 78 | x['files'] = [z for z in x['files'] if not z['path'].startswith(u'_')][:5] 79 | x['files'].sort(key=lambda x:x['length'], reverse=True) 80 | else: 81 | x['files'] = [{'path': x['name'], 'length': x['length']}] 82 | # pagination 83 | w = 10 84 | total = int(d['result']['meta']['total_found']) 85 | d['page_max'] = total / d['ps'] if total % d['ps'] == 0 else total/d['ps'] + 1 86 | d['prev_pages'] = range( max(d['p']-w+min(int(w/2), d['page_max']-d['p']),1), d['p']) 87 | d['next_pages'] = range( d['p']+1, int(min(d['page_max']+1, max(d['p']-w/2,1) + w )) ) 88 | d['sort_navs'] = [ 89 | {'name': 'By Time', 'value': 'create_time'}, 90 | {'name': 'By Size', 'value': 'length'}, 91 | {'name': 'By Relavance', 'value': 'relavance'}, 92 | ] 93 | d['cats_navs'] = [{'name': 'All', 'num': total, 'value': ''}] 94 | d['keyword_logs'] = KeywordLog.objects 95 | for x in d['cats']['items']: 96 | v = workers.metautils.get_label_by_crc32(x['category']) 97 | d['cats_navs'].append({'value': v, 'name': workers.metautils.get_label(v), 'num': x['num']}) 98 | 99 | return render(request, 'list.html', d) 100 | 101 | 102 | def hash_old(request, h): 103 | item = get_object_or_404(Hash, info_hash=h) 104 | return redirect(reverse('hash', args=(item.id,)), permanent=True) 105 | 106 | 107 | def search_old(request, kw, p): 108 | return redirect('list', kw, p) 109 | 110 | 111 | @cache_page(3600*2) 112 | def howto(request): 113 | return render(request, 'howto.html', {}) 114 | 115 | -------------------------------------------------------------------------------- /workers/metadata.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import traceback 3 | import pygeoip 4 | import threading 5 | import socket 6 | import sys 7 | import hashlib 8 | import datetime 9 | import time 10 | import json 11 | 12 | import metautils 13 | from bencode import bencode, bdecode 14 | geoip = pygeoip.GeoIP('GeoIP.dat') 15 | 16 | def decode(encoding, s): 17 | if type(s) is list: 18 | s = ';'.join(s) 19 | u = s 20 | for x in (encoding, 'utf8', 'gbk', 'big5'): 21 | try: 22 | u = s.decode(x) 23 | return u 24 | except: 25 | pass 26 | return s.decode(encoding, 'ignore') 27 | 28 | def decode_utf8(encoding, d, i): 29 | if i+'.utf-8' in d: 30 | return d[i+'.utf-8'].decode('utf8') 31 | return decode(encoding, d[i]) 32 | 33 | def parse_metadata(data): 34 | info = {} 35 | encoding = 'utf8' 36 | try: 37 | torrent = bdecode(data) 38 | if not torrent.get('name'): 39 | return None 40 | except: 41 | return None 42 | try: 43 | info['create_time'] = datetime.datetime.fromtimestamp(float(torrent['creation date'])) 44 | except: 45 | info['create_time'] = datetime.datetime.utcnow() 46 | 47 | if torrent.get('encoding'): 48 | encoding = torrent['encoding'] 49 | if torrent.get('announce'): 50 | info['announce'] = decode_utf8(encoding, torrent, 'announce') 51 | if torrent.get('comment'): 52 | info['comment'] = decode_utf8(encoding, torrent, 'comment')[:200] 53 | if torrent.get('publisher-url'): 54 | info['publisher-url'] = decode_utf8(encoding, torrent, 'publisher-url') 55 | if torrent.get('publisher'): 56 | info['publisher'] = decode_utf8(encoding, torrent, 'publisher') 57 | if torrent.get('created by'): 58 | info['creator'] = decode_utf8(encoding, torrent, 'created by')[:15] 59 | 60 | if 'info' in torrent: 61 | detail = torrent['info'] 62 | else: 63 | detail = torrent 64 | info['name'] = decode_utf8(encoding, detail, 'name') 65 | if 'files' in detail: 66 | info['files'] = [] 67 | for x in detail['files']: 68 | if 'path.utf-8' in x: 69 | v = {'path': decode(encoding, '/'.join(x['path.utf-8'])), 'length': x['length']} 70 | else: 71 | v = {'path': decode(encoding, '/'.join(x['path'])), 'length': x['length']} 72 | if 'filehash' in x: 73 | v['filehash'] = x['filehash'].encode('hex') 74 | info['files'].append(v) 75 | info['length'] = sum([x['length'] for x in info['files']]) 76 | else: 77 | info['length'] = detail['length'] 78 | info['data_hash'] = hashlib.md5(detail['pieces']).hexdigest() 79 | if 'profiles' in detail: 80 | info['profiles'] = detail['profiles'] 81 | return info 82 | 83 | 84 | def save_metadata(dbcurr, binhash, address, start_time, data): 85 | utcnow = datetime.datetime.utcnow() 86 | name = threading.currentThread().getName() 87 | try: 88 | info = parse_metadata(data) 89 | if not info: 90 | return 91 | except: 92 | traceback.print_exc() 93 | return 94 | info_hash = binhash.encode('hex') 95 | info['info_hash'] = info_hash 96 | # need to build tags 97 | info['tagged'] = False 98 | info['classified'] = False 99 | info['requests'] = 1 100 | info['last_seen'] = utcnow 101 | info['source_ip'] = address[0] 102 | 103 | if info.get('files'): 104 | files = [z for z in info['files'] if not z['path'].startswith('_')] 105 | if not files: 106 | files = info['files'] 107 | else: 108 | files = [{'path': info['name'], 'length': info['length']}] 109 | files.sort(key=lambda z:z['length'], reverse=True) 110 | bigfname = files[0]['path'] 111 | info['extension'] = metautils.get_extension(bigfname).lower() 112 | info['category'] = metautils.get_category(info['extension']) 113 | 114 | if 'files' in info: 115 | try: 116 | dbcurr.execute('INSERT INTO search_filelist VALUES(%s, %s)', (info['info_hash'], json.dumps(info['files']))) 117 | except: 118 | print name, 'insert error', sys.exc_info()[1] 119 | del info['files'] 120 | 121 | try: 122 | try: 123 | print '\n', 'Saved', info['info_hash'], info['name'], (time.time()-start_time), 's', address[0], geoip.country_name_by_addr(address[0]), 124 | except: 125 | print '\n', 'Saved', info['info_hash'], sys.exc_info()[1] 126 | ret = dbcurr.execute('INSERT INTO search_hash(info_hash,category,data_hash,name,extension,classified,source_ip,tagged,' + 127 | 'length,create_time,last_seen,requests,comment,creator) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)', 128 | (info['info_hash'], info['category'], info['data_hash'], info['name'], info['extension'], info['classified'], 129 | info['source_ip'], info['tagged'], info['length'], info['create_time'], info['last_seen'], info['requests'], 130 | info.get('comment',''), info.get('creator',''))) 131 | dbcurr.connection.commit() 132 | except: 133 | print name, 'save error', info 134 | traceback.print_exc() 135 | return 136 | 137 | -------------------------------------------------------------------------------- /lib/politics.txt: -------------------------------------------------------------------------------- 1 | 方滨兴 2 | 江泽民 3 | 周恩来 4 | 胡锦涛 5 | 刘伯承 6 | 李大钊 7 | 吴邦国 8 | 毛泽东 9 | 温总理 10 | 温家宝 11 | 习近平 12 | 习主席 13 | 主席 14 | 贺龙 15 | 贾庆林 16 | 彭德怀 17 | 馿 18 | 马驰.*新加坡|新加坡.*马驰 19 | 自由光诚 20 | 陈光诚事件 21 | 光诚.*沂南|沂南.*光诚 22 | 陈光诚.*使馆|使馆.*陈光诚 23 | 职称英语.*答案|答案.*职称英语 24 | 公务员.*答案|答案.*公务员 25 | 薄瓜瓜 26 | 海伍德 27 | 尼尔伍德 28 | heywood 29 | neil.*wood|wood.*neil 30 | 天线宝宝.*康师傅|康师傅.*天线宝宝 31 | 天线宝宝.*方便面|方便面.*天线宝宝 32 | 天线宝宝.*轮胎|轮胎.*天线宝宝 33 | 轮胎.*方便面|方便面.*轮胎 34 | 政变 35 | 枪声 36 | 戒严 37 | 3\.19 38 | 北京事件 39 | 北京.*出事了|出事了.*北京 40 | 北京怎么了 41 | 不厚 42 | 薄督 43 | 谷开来 44 | 重庆 45 | 叶城.*砍杀|砍杀.*叶城 46 | 连承敏 47 | 弟弟.*睡|睡.*弟弟 48 | 要有光.*要有诚|要有诚.*要有光 49 | 杨杰 50 | 陈刚 51 | 山水文园 52 | 跑官 53 | 移动.*十年兴衰|十年兴衰.*移动 54 | 陈坚 55 | 戴坚 56 | 冯珏 57 | 罗川 58 | 马力 59 | 盛勇 60 | 谢岷 61 | 谢文 62 | 杨希 63 | 叶兵 64 | 张斌 65 | 陈瑞卿 66 | 高念书 67 | 华如秀 68 | 鲁向东 69 | 曲乃杰 70 | 孙静晔 71 | 涂志森 72 | 于剑鸣 73 | 张晓明 74 | 赵志强 75 | 郑建源 76 | 先皇 77 | 太上皇 78 | 蛤蟆 79 | 驾崩 80 | 丘小雄 81 | 公诉 82 | 右派 83 | 增城 84 | 暴动 85 | 宣言 86 | 莫日根 87 | 内蒙古.*抗议|抗议.*内蒙古 88 | 西乌旗 89 | moli 90 | 麦当劳 91 | 天府 92 | 人民公园 93 | 广场 94 | 埃及 95 | 突尼斯 96 | 茉莉 97 | jasmine.*revolution|revolution.*jasmine 98 | 集会 99 | 革命 100 | 齐鲁银行 101 | 公开信 102 | 高考时间 103 | 诺贝尔和平奖 104 | 被就业 105 | 小屋 106 | 日记 107 | 鲁昕 108 | 天安.*事件|事件.*天安 109 | 1989.*天安門|天安門.*1989 110 | 天安门 111 | 八九 112 | 六四 113 | 六 四 114 | 六\.四 115 | 平反64 116 | 5月35日 117 | 5月35号 118 | 89动乱 119 | 89.*学生动乱|学生动乱.*89 120 | 89.*学生运动|学生运动.*89 121 | 64.*学生运动|学生运动.*64 122 | 64.*镇压|镇压.*64 123 | 64.*真相|真相.*64 124 | 64memo 125 | tiananmen 126 | 8964 127 | 学潮 128 | 罢课 129 | 民运 130 | 学运 131 | 学联 132 | 学自联 133 | 高自联 134 | 工自联 135 | 民联 136 | 民阵 137 | 中国民主党 138 | 中国民主正义党 139 | 中国民主运动 140 | 世纪中国基金会 141 | 坦克人 142 | 挡坦克 143 | tankman 144 | 木犀地 145 | 维园晚会 146 | blood is on the square 147 | 姜维平 148 | 艾未未 149 | 艾末末 150 | 路青 151 | 发课 152 | 余杰 153 | 辛子陵 154 | 茅于轼 155 | 铁流 156 | liu.*xiaobo|xiaobo.*liu 157 | 蟹农场 158 | 陈西 159 | 谭作人 160 | 高智晟 161 | 冯正虎 162 | 丁子霖 163 | 唯色 164 | 焦国标 165 | 何清涟 166 | 耀邦 167 | 紫阳 168 | 方励之 169 | 严家其 170 | 鲍彤 171 | 鮑彤 172 | 鲍朴 173 | 柴玲 174 | 乌尔凯西 175 | 封从德 176 | 炳章 177 | 苏绍智 178 | 陈一谘 179 | 韩东方 180 | 辛灏年 181 | 曹长青 182 | 陈破空 183 | 盘古乐队 184 | 盛雪 185 | 伍凡 186 | 魏京生 187 | 司徒华 188 | 黎安友 189 | 防火长城 190 | great.*firewall|firewall.*great 191 | gfw.*什么|什么.*gfw 192 | 国家防火墙 193 | 翻墙 194 | 代理 195 | vpn.*免费|免费.*vpn 196 | vpn.*下载|下载.*vpn 197 | vpn.*世纪|世纪.*vpn 198 | hotspot.*shield|shield.*hotspot 199 | 无界 200 | ultrasurf 201 | ^freenet 202 | safeweb 203 | 动态网 204 | 花园网 205 | ^cache 206 | 阅后即焚 207 | 法轮 208 | falun 209 | 明慧 210 | minghui 211 | 退党 212 | 三退 213 | 九评 214 | nine commentaries 215 | 洪吟 216 | 神韵艺术 217 | 神韵晚会 218 | 人民报 219 | renminbao 220 | 纪元 221 | ^dajiyuan 222 | epochtimes 223 | 新唐人 224 | ntdtv 225 | ndtv 226 | 新生网 227 | ^xinsheng 228 | 正见网 229 | zhengjian 230 | 追查国际 231 | 真善忍 232 | 法会 233 | 正念 234 | 经文 235 | 天灭 236 | 天怒 237 | 讲真相 238 | 马三家 239 | 善恶有报 240 | 活摘器官 241 | 群体灭绝 242 | 中功 243 | 张宏堡 244 | 地下教会 245 | 冤民大同盟 246 | 达赖 247 | 藏独 248 | freetibet 249 | 雪山狮子 250 | 西藏流亡政府 251 | 青天白日旗 252 | 民进党 253 | 洪哲胜 254 | 独立台湾会 255 | 台湾政论区 256 | 台湾自由联盟 257 | 台湾建国运动组织 258 | 台湾.*独立联盟|独立联盟.*台湾 259 | 新疆.*独立|独立.*新疆 260 | 东土耳其斯坦 261 | east.*turkistan|turkistan.*east 262 | 世维会 263 | 迪里夏提 264 | 美国之音 265 | 自由亚洲电台 266 | 记者无疆界 267 | 维基解密.*中国|中国.*维基解密 268 | facebook 269 | twitter 270 | 推特 271 | 新京报 272 | 世界经济导报 273 | 中国数字时代 274 | ^ytht 275 | 新语丝 276 | ^creaders 277 | ^tianwang 278 | 中国.*禁闻|禁闻.*中国 279 | 阿波罗网 280 | 阿波罗新闻 281 | 大参考 282 | ^bignews 283 | 多维 284 | 看中国 285 | 博讯 286 | ^boxun 287 | peacehall 288 | ^hrichina 289 | 独立中文笔会 290 | 华夏文摘 291 | 开放杂志 292 | 大家论坛 293 | 华夏论坛 294 | 中国论坛 295 | 木子论坛 296 | 争鸣论坛 297 | 大中华论坛 298 | 反腐败论坛 299 | 新观察论坛 300 | 新华通论坛 301 | 正义党论坛 302 | 热站政论网 303 | 华通时事论坛 304 | 华语世界论坛 305 | 华岳时事论坛 306 | 两岸三地论坛 307 | 南大自由论坛 308 | 人民之声论坛 309 | 万维读者论坛 310 | 你说我说论坛 311 | 东西南北论坛 312 | 东南西北论谈 313 | 知情者 314 | 红太阳的陨落 315 | 和谐拯救危机 316 | 血房 317 | 一个孤僻的人 318 | 零八.*宪章|宪章.*零八 319 | 08.*宪章|宪章.*08 320 | 八宪章 321 | 8宪章 322 | 零八.*县长|县长.*零八 323 | 08县长 324 | 淋巴县长 325 | 我的最后陈述 326 | 我没有敌人 327 | 河殇 328 | 天葬 329 | 黄祸 330 | 我的奋斗 331 | 历史的伤口 332 | 改革.*历程|历程.*改革 333 | 国家的囚徒 334 | prisoner of the state 335 | 改革年代的政治斗争 336 | 改革年代政治斗争 337 | 关键时刻 338 | 超越红墙 339 | 梦萦未名湖 340 | 一寸山河一寸血 341 | 政治局常委内幕 342 | 北国之春 343 | 北京之春 344 | 中国之春 345 | 东方红时空 346 | 纳米比亚 347 | 婴儿汤 348 | 泄题 349 | 罢餐 350 | 月月 351 | 代开.*发票|发票.*代开 352 | 钓鱼岛 353 | ^triangle 354 | 女保镖 355 | 玩ps 356 | 玩photoshop 357 | chinese people eating babies 358 | 开枪 359 | 迫害 360 | 酷刑 361 | 邪恶 362 | 洗脑 363 | 网特 364 | 内斗 365 | 党魁 366 | 文字狱 367 | 一党专政 368 | 一党独裁 369 | 新闻封锁 370 | 老人政治 371 | ^freedom 372 | ^freechina 373 | 反社会 374 | 维权人士 375 | 维权律师 376 | 异见人士 377 | 异议人士 378 | 地下刊物 379 | 高瞻 380 | غا 381 | 共产 382 | 共铲党 383 | 共残党 384 | 共惨党 385 | 共匪 386 | 赤匪 387 | 中共 388 | 中宣 389 | 真理部 390 | 十八大 391 | 18大 392 | 太子 393 | 上海帮 394 | 团派 395 | 北京当局 396 | 裆中央 397 | 九常委 398 | 九长老 399 | 锦涛 400 | 家宝 401 | 影帝 402 | 近平 403 | 回良玉 404 | 汪洋 405 | 张高丽 406 | 俞正声 407 | 徐才厚 408 | 郭伯雄 409 | 熙来$ 410 | 梁光烈 411 | 孟建柱 412 | 戴秉国 413 | 马凯 414 | 令计划 415 | 韩正 416 | 章沁生 417 | 陈世炬 418 | 泽民 419 | 贼民 420 | 邓小平 421 | 庆红 422 | 罗干 423 | 假庆淋 424 | hujin 425 | wenjiabao 426 | xijinping 427 | likeqiang 428 | zhouyongkang 429 | lichangchun 430 | wubangguo 431 | heguoqiang 432 | jiaqinglin 433 | jiangzemin 434 | xjp 435 | jzm 436 | 色情 437 | 花花公子 438 | tits 439 | boobs 440 | ^\s*海峰\s*$ 441 | ^\s*威视公司\s*$ 442 | ^\s*nuctech\s*$ 443 | ^\s*逍遥游\s*$ 444 | ^\s*自由门\s*$ 445 | ^\s*自由門\s*$ 446 | ^\s*自由之门\s*$ 447 | ^\s*freegate\s*$ 448 | ^\s*freegate download\s*$ 449 | ^\s*download freegate\s*$ 450 | ^\s*自由门下载\s*$ 451 | ^\s*自由門下載\s*$ 452 | ^\s*無界瀏覽\s*$ 453 | ^\s*無界浏览\s*$ 454 | ^\s*动网通\s*$ 455 | ^\s*dynaweb\s*$ 456 | ^\s*dongtaiwang\s*$ 457 | 鸡巴 458 | -------------------------------------------------------------------------------- /web/templates/info.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}{{info.name}} - CiLiBaBa{% endblock %} 4 | {% block metas %} 5 | {{block.super}} 6 | 7 | 8 | 9 | {% if '夏洛' in info.name %} 10 | 13 | {% endif %} 14 | 15 | 16 | {% endblock %} 17 | 18 | {% block content %} 19 |
20 | 21 |
22 |
23 | 24 |

{{info.name}}

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {% if info.extra.status == 'disabled' %} 57 | 58 | 59 | 64 | 65 | {% else %} 66 | 67 | 68 | 73 | 74 | 75 | 76 | 88 | 89 | {% endif %} 90 |
Creation Time{{info.create_time}}
Last Access Time{{info.last_seen}}
File Size{{info.length | filesizeformat}}
Keywords 42 | {% for kw in keywords %} 43 | {{kw}} 44 | {% endfor %} 45 |
Total Requests{{info.requests}}
Total Files{{info.files|length}}
Magnet Link 60 |

61 | Sorry! The related magnet link for this resource is deleted. 62 |

63 |
91 | 92 | 101 | 102 |

File List

103 |
104 |
    105 | {% if info.files %} 106 | {% for y in info.files %} 107 |
  • {{y.path}} {{y.length|filesizeformat}}
  • 108 | {% endfor %} 109 | {% else %} 110 |
  • {{info.name}} {{info.length|filesizeformat}}
  • 111 | {% endif %} 112 |
113 |
114 | 115 |

Download Torrent

116 |
117 | If you want to get the torrent file or original data of {{info.name}}, please use uTorrent, BitTorrent or Thunder.
118 | Click here to locate the torrent file of {{info.name|truncatechars:50}} 119 |
120 | 121 |

Related Resources

122 |
123 | 128 |
129 | 130 | 131 |

Copyright Infringement

132 |
133 | If the content above is not authorized, please contact us via contact[AT]cilibaba.com. Remember to include the full url in your complaint. 134 |
135 | 136 |
137 | {% include 'foot.html' %} 138 | {% endblock %} 139 | 140 | {% block scripts %} 141 | {{block.super}} 142 | 165 | {% endblock %} 166 | -------------------------------------------------------------------------------- /web/static/js/bootstrap-typeahead.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-typeahead.js v0.0.3 (http://www.upbootstrap.com) 3 | * Copyright 2012-2014 Twitter Inc. 4 | * Licensed under MIT (https://github.com/biggora/bootstrap-ajax-typeahead/blob/master/LICENSE) 5 | * See Demo: http://plugins.upbootstrap.com/bootstrap-ajax-typeahead 6 | * Updated: 2014-02-09 02:4:38 7 | * 8 | * Modifications by Paul Warelis and Alexey Gordeyev 9 | */ 10 | !function(a){"use strict";var b=function(b,c){var d=this;if(d.$element=a(b),d.options=a.extend({},a.fn.typeahead.defaults,c),d.$menu=a(d.options.menu).insertAfter(d.$element),d.eventSupported=d.options.eventSupported||d.eventSupported,d.grepper=d.options.grepper||d.grepper,d.highlighter=d.options.highlighter||d.highlighter,d.lookup=d.options.lookup||d.lookup,d.matcher=d.options.matcher||d.matcher,d.render=d.options.render||d.render,d.onSelect=d.options.onSelect||null,d.sorter=d.options.sorter||d.sorter,d.source=d.options.source||d.source,d.displayField=d.options.displayField||d.displayField,d.valueField=d.options.valueField||d.valueField,d.options.ajax){var e=d.options.ajax;"string"==typeof e?d.ajax=a.extend({},a.fn.typeahead.defaults.ajax,{url:e}):("string"==typeof e.displayField&&(d.displayField=d.options.displayField=e.displayField),"string"==typeof e.valueField&&(d.valueField=d.options.valueField=e.valueField),d.ajax=a.extend({},a.fn.typeahead.defaults.ajax,e)),d.ajax.url||(d.ajax=null),d.query=""}else d.source=d.options.source,d.ajax=null;d.shown=!1,d.listen()};b.prototype={constructor:b,eventSupported:function(a){var b=a in this.$element;return b||(this.$element.setAttribute(a,"return;"),b="function"==typeof this.$element[a]),b},select:function(){var a=this.$menu.find(".active"),b=a.attr("data-value"),c=this.$menu.find(".active a").text();return this.options.onSelect&&this.options.onSelect({value:b,text:c}),this.$element.val(this.updater(c)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},ajaxLookup:function(){function b(){this.ajaxToggleLoadClass(!0),this.ajax.xhr&&this.ajax.xhr.abort();var b=this.ajax.preDispatch?this.ajax.preDispatch(c):{query:c};this.ajax.xhr=a.ajax({url:this.ajax.url,data:b,success:a.proxy(this.ajaxSource,this),type:this.ajax.method||"get",dataType:"json"}),this.ajax.timerId=null}var c=a.trim(this.$element.val());return c===this.query?this:(this.query=c,this.ajax.timerId&&(clearTimeout(this.ajax.timerId),this.ajax.timerId=null),!c||c.length"+b+""})},render:function(b){var c,d=this,e="string"==typeof d.options.displayField;return b=a(b).map(function(b,f){return"object"==typeof f?(c=e?f[d.options.displayField]:d.options.displayField(f),b=a(d.options.item).attr("data-value",f[d.options.valueField])):(c=f,b=a(d.options.item).attr("data-value",f)),b.find("a").html(d.highlighter(c)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},grepper:function(b){var c,d,e=this,f="string"==typeof e.options.displayField;if(!(f&&b&&b.length))return null;if(b[0].hasOwnProperty(e.options.displayField))c=a.grep(b,function(a){return d=f?a[e.options.displayField]:e.options.displayField(a),e.matcher(d)});else{if("string"!=typeof b[0])return null;c=a.grep(b,function(a){return e.matcher(a)})}return this.sorter(c)},next:function(){var b=this.$menu.find(".active").removeClass("active"),c=b.next();c.length||(c=a(this.$menu.find("li")[0])),c.addClass("active")},prev:function(){var a=this.$menu.find(".active").removeClass("active"),b=a.prev();b.length||(b=this.$menu.find("li").last()),b.addClass("active")},listen:function(){this.$element.on("focus",a.proxy(this.focus,this)).on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",a.proxy(this.keydown,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this)).on("mouseleave","li",a.proxy(this.mouseleave,this))},move:function(a){if(this.shown){switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()}},keydown:function(b){this.suppressKeyPressRepeat=~a.inArray(b.keyCode,[40,38,9,13,27]),this.move(b)},keypress:function(a){this.suppressKeyPressRepeat||this.move(a)},keyup:function(a){switch(a.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.ajax?this.ajaxLookup():this.lookup()}a.stopPropagation(),a.preventDefault()},focus:function(){this.focused=!0},blur:function(){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(a){a.stopPropagation(),a.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(b){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")},mouseleave:function(){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f="object"==typeof c&&c;e||d.data("typeahead",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',displayField:"name",valueField:"id",onSelect:function(){},ajax:{url:null,timeout:300,method:"get",triggerLength:1,loadingClass:null,preDispatch:null,preProcess:null}},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);c.data("typeahead")||(b.preventDefault(),c.typeahead(c.data()))})})}(window.jQuery); -------------------------------------------------------------------------------- /search/models.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import json 3 | import re 4 | import traceback 5 | import binascii 6 | import time 7 | 8 | from django.db import models 9 | import MySQLdb as mdb 10 | import MySQLdb.cursors 11 | 12 | 13 | search_conn = mdb.connect('127.0.0.1', 'root', '', '', port=9306, charset='utf8', cursorclass=MySQLdb.cursors.DictCursor) 14 | search_conn.ping(True) 15 | re_punctuations = re.compile( 16 | ur"。|,|,|!|…|!|《|》|<|>|\"|'|:|:|?|\?|、|\||“|”|‘|’|;|\\|—|_|=|(|)|·|\(|\)| |\.|【|】|『|』|@|&|%|\^|\*|\+|\||<|>|~|`|\[|\]") 17 | 18 | 19 | def escape_string(string): 20 | return re.sub(r"(['`=\(\)|\-!@~\"&/\\\^\$])", r"\\\1", string) 21 | 22 | def split_words(string): 23 | string = re_punctuations.sub(u' ', string).replace(u'-', u' ') 24 | words = [] 25 | for w in string.split(): 26 | try: 27 | words.append(w.encode('ascii').decode('ascii')) 28 | except: 29 | for c in w: 30 | words.append(c) 31 | return u'|'.join(words).strip(u'|') 32 | 33 | 34 | class HashManager(models.Manager): 35 | def search(self, keyword, start=0, count=10, category=None, sort=None): 36 | search_cursor = search_conn.cursor() 37 | sql = '''SELECT id FROM rt_main''' 38 | conds = [] 39 | values = [] 40 | if keyword: 41 | conds.append('MATCH(%s)') 42 | values.append(escape_string(keyword)) 43 | if category: 44 | conds.append('category=%s') 45 | values.append(binascii.crc32(category)&0xFFFFFFFFL) 46 | if conds: 47 | sql += ' WHERE ' + ' AND '.join(conds) 48 | if sort == 'create_time': 49 | sql += ' ORDER BY create_time DESC ' 50 | elif sort == 'length': 51 | sql += ' ORDER BY length DESC ' 52 | sql += ''' 53 | LIMIT %s,%s 54 | OPTION max_matches=1000, max_query_time=200 55 | ''' 56 | search_cursor.execute(sql, values + [start, count]) 57 | items = list(search_cursor.fetchall()) 58 | search_cursor.execute('SHOW META') 59 | meta = {} 60 | for d in search_cursor.fetchall(): 61 | meta[d['Variable_name']] = d['Value'] 62 | sql = '''SELECT category, COUNT(*) AS num FROM rt_main ''' 63 | if conds: 64 | sql += ' WHERE ' + ' AND '.join(conds) 65 | sql += ''' GROUP BY category OPTION max_query_time=200''' 66 | search_cursor.execute(sql, values) 67 | cats = list(search_cursor.fetchall()) 68 | 69 | res = { 70 | 'result': { 71 | 'items': items, 72 | 'meta': meta, 73 | }, 74 | 'cats': { 75 | 'items': cats, 76 | }, 77 | } 78 | search_cursor.close() 79 | return res 80 | 81 | def list_with_files(self, ids): 82 | res = [] 83 | if len(ids[0]) == 40: 84 | items = Hash.objects.filter(info_hash__in=ids).values() 85 | else: 86 | items = Hash.objects.filter(id__in=ids).values() 87 | res = list(items) 88 | items = FileList.objects.filter(info_hash__in=[x['info_hash'] for x in res]).values() 89 | for x in res: 90 | for y in items: 91 | if x['info_hash'] == y['info_hash']: 92 | try: 93 | x['files'] = json.loads(y['file_list']) 94 | except ValueError: 95 | pass 96 | items = Extra.objects.filter(hash_id__in=ids).values() 97 | for x in res: 98 | for y in items: 99 | if x['id'] == y['hash_id']: 100 | x['extra'] = y 101 | return res 102 | 103 | def list_related(self, hash_id, name, count=10): 104 | string = split_words(name) 105 | if not string: 106 | return [] 107 | search_cursor = search_conn.cursor() 108 | try: 109 | sql = '''SELECT id FROM rt_main WHERE MATCH(%s) AND id<>%s LIMIT 0,%s OPTION max_matches=1000, max_query_time=50''' 110 | search_cursor.execute(sql, (string, hash_id, count)) 111 | ids = [x['id'] for x in search_cursor.fetchall()] 112 | items = Hash.objects.only('name', 'length', 'create_time', 'id').filter(id__in=ids).values() 113 | except: 114 | traceback.print_exc() 115 | items = [] 116 | search_cursor.close() 117 | return items 118 | 119 | 120 | class Hash(models.Model): 121 | objects = HashManager() 122 | 123 | info_hash = models.CharField(max_length=40, unique=True) 124 | category = models.CharField('类别', max_length=20) 125 | data_hash = models.CharField('内容签名', max_length=32) 126 | name = models.CharField('资源名称', max_length=255) 127 | extension = models.CharField('扩展名', max_length=20) 128 | classified = models.BooleanField('是否分类', default=False) 129 | source_ip = models.CharField('来源IP', max_length=20, null=True) 130 | tagged = models.BooleanField('是否索引', default=False, db_index=True) 131 | length = models.BigIntegerField('文件大小') 132 | create_time = models.DateTimeField('入库时间') 133 | last_seen = models.DateTimeField('上次请求时间') 134 | requests = models.PositiveIntegerField('请求次数') 135 | comment = models.CharField(max_length=255, null=True) 136 | creator = models.CharField(max_length=20, null=True) 137 | 138 | def __unicode__(self): 139 | return self.name 140 | 141 | def is_blacklisted(self): 142 | try: 143 | return self.extra.status == 'disabled' 144 | except: 145 | return False 146 | 147 | 148 | class Extra(models.Model): 149 | STATUS = ( 150 | ('', 'Normal'), 151 | ('reviewing', 'Reviewing'), 152 | ('disabled', 'Disabled'), 153 | ('deleted', 'Deleted'), 154 | ) 155 | hash = models.OneToOneField(Hash) 156 | status = models.CharField('Status', choices=STATUS, max_length=20) 157 | update_time = models.DateTimeField('更新时间', auto_now=True) 158 | 159 | 160 | class FileList(models.Model): 161 | info_hash = models.CharField(max_length=40, primary_key=True) 162 | file_list = models.TextField('JSON格式的文件列表') 163 | 164 | def __unicode__(self): 165 | return self.info_hash 166 | 167 | 168 | class StatusReport(models.Model): 169 | date = models.DateField('日期', unique=True) 170 | new_hashes = models.IntegerField('新增资源') 171 | total_requests = models.IntegerField() 172 | valid_requests = models.IntegerField() 173 | 174 | def __unicode__(self): 175 | return u'%s - %s' % (self.date, self.new_hashes) 176 | 177 | 178 | class RecKeywords(models.Model): 179 | keyword = models.CharField('推荐关键词', max_length=20) 180 | order = models.PositiveIntegerField('排序', default=0) 181 | 182 | def __unicode__(self): 183 | return u'%s %s' % (self.keyword, self.order) 184 | 185 | 186 | class ContactEmail(models.Model): 187 | mail_from = models.CharField('Mail From', max_length=100) 188 | subject = models.CharField('Subject', max_length=200) 189 | text = models.TextField('Text') 190 | receive_time = models.DateTimeField(auto_now_add=True) 191 | is_complaint = models.BooleanField(default=False) 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /workers/simdht_worker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | 磁力搜索meta信息入库程序 5 | xiaoxia@xiaoxia.org 6 | 2015.6 Forked CreateChen's Project: https://github.com/CreateChen/simDownloader 7 | """ 8 | 9 | import hashlib 10 | import os 11 | import SimpleXMLRPCServer 12 | import time 13 | import datetime 14 | import traceback 15 | import sys 16 | import json 17 | import socket 18 | import threading 19 | from hashlib import sha1 20 | from random import randint 21 | from struct import unpack 22 | from socket import inet_ntoa 23 | from threading import Timer, Thread 24 | from time import sleep 25 | from collections import deque 26 | from Queue import Queue 27 | 28 | import pygeoip 29 | import MySQLdb as mdb 30 | try: 31 | raise 32 | import libtorrent as lt 33 | import ltMetadata 34 | except: 35 | lt = None 36 | print sys.exc_info()[1] 37 | 38 | import metautils 39 | import simMetadata 40 | from bencode import bencode, bdecode 41 | from metadata import save_metadata 42 | 43 | DB_HOST = '127.0.0.1' 44 | DB_USER = 'root' 45 | DB_PASS = '' 46 | BOOTSTRAP_NODES = ( 47 | ("router.bittorrent.com", 6881), 48 | ("dht.transmissionbt.com", 6881), 49 | ("router.utorrent.com", 6881) 50 | ) 51 | TID_LENGTH = 2 52 | RE_JOIN_DHT_INTERVAL = 3 53 | TOKEN_LENGTH = 2 54 | 55 | MAX_QUEUE_LT = 30 56 | MAX_QUEUE_PT = 200 57 | 58 | geoip = pygeoip.GeoIP('GeoIP.dat') 59 | 60 | 61 | def is_ip_allowed(ip): 62 | return geoip.country_code_by_addr(ip) not in ('CN','TW','HK') 63 | 64 | def entropy(length): 65 | return "".join(chr(randint(0, 255)) for _ in xrange(length)) 66 | 67 | 68 | def random_id(): 69 | h = sha1() 70 | h.update(entropy(20)) 71 | return h.digest() 72 | 73 | 74 | def decode_nodes(nodes): 75 | n = [] 76 | length = len(nodes) 77 | if (length % 26) != 0: 78 | return n 79 | 80 | for i in range(0, length, 26): 81 | nid = nodes[i:i+20] 82 | ip = inet_ntoa(nodes[i+20:i+24]) 83 | port = unpack("!H", nodes[i+24:i+26])[0] 84 | n.append((nid, ip, port)) 85 | 86 | return n 87 | 88 | 89 | def timer(t, f): 90 | Timer(t, f).start() 91 | 92 | 93 | def get_neighbor(target, nid, end=10): 94 | return target[:end]+nid[end:] 95 | 96 | 97 | class KNode(object): 98 | 99 | def __init__(self, nid, ip, port): 100 | self.nid = nid 101 | self.ip = ip 102 | self.port = port 103 | 104 | 105 | class DHTClient(Thread): 106 | 107 | def __init__(self, max_node_qsize): 108 | Thread.__init__(self) 109 | self.setDaemon(True) 110 | self.max_node_qsize = max_node_qsize 111 | self.nid = random_id() 112 | self.nodes = deque(maxlen=max_node_qsize) 113 | 114 | def send_krpc(self, msg, address): 115 | try: 116 | self.ufd.sendto(bencode(msg), address) 117 | except Exception: 118 | pass 119 | 120 | def send_find_node(self, address, nid=None): 121 | nid = get_neighbor(nid, self.nid) if nid else self.nid 122 | tid = entropy(TID_LENGTH) 123 | msg = { 124 | "t": tid, 125 | "y": "q", 126 | "q": "find_node", 127 | "a": { 128 | "id": nid, 129 | "target": random_id() 130 | } 131 | } 132 | self.send_krpc(msg, address) 133 | 134 | def join_DHT(self): 135 | for address in BOOTSTRAP_NODES: 136 | self.send_find_node(address) 137 | 138 | def re_join_DHT(self): 139 | if len(self.nodes) == 0: 140 | self.join_DHT() 141 | timer(RE_JOIN_DHT_INTERVAL, self.re_join_DHT) 142 | 143 | def auto_send_find_node(self): 144 | wait = 1.0 / self.max_node_qsize 145 | while True: 146 | try: 147 | node = self.nodes.popleft() 148 | self.send_find_node((node.ip, node.port), node.nid) 149 | except IndexError: 150 | pass 151 | try: 152 | sleep(wait) 153 | except KeyboardInterrupt: 154 | os._exit(0) 155 | 156 | def process_find_node_response(self, msg, address): 157 | nodes = decode_nodes(msg["r"]["nodes"]) 158 | for node in nodes: 159 | (nid, ip, port) = node 160 | if len(nid) != 20: continue 161 | if ip == self.bind_ip: continue 162 | n = KNode(nid, ip, port) 163 | self.nodes.append(n) 164 | 165 | 166 | class DHTServer(DHTClient): 167 | 168 | def __init__(self, master, bind_ip, bind_port, max_node_qsize): 169 | DHTClient.__init__(self, max_node_qsize) 170 | 171 | self.master = master 172 | self.bind_ip = bind_ip 173 | self.bind_port = bind_port 174 | 175 | self.process_request_actions = { 176 | "get_peers": self.on_get_peers_request, 177 | "announce_peer": self.on_announce_peer_request, 178 | } 179 | 180 | self.ufd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 181 | self.ufd.bind((self.bind_ip, self.bind_port)) 182 | 183 | timer(RE_JOIN_DHT_INTERVAL, self.re_join_DHT) 184 | 185 | 186 | def run(self): 187 | self.re_join_DHT() 188 | while True: 189 | try: 190 | (data, address) = self.ufd.recvfrom(65536) 191 | msg = bdecode(data) 192 | self.on_message(msg, address) 193 | except Exception: 194 | pass 195 | 196 | def on_message(self, msg, address): 197 | try: 198 | if msg["y"] == "r": 199 | if msg["r"].has_key("nodes"): 200 | self.process_find_node_response(msg, address) 201 | elif msg["y"] == "q": 202 | try: 203 | self.process_request_actions[msg["q"]](msg, address) 204 | except KeyError: 205 | self.play_dead(msg, address) 206 | except KeyError: 207 | pass 208 | 209 | def on_get_peers_request(self, msg, address): 210 | try: 211 | infohash = msg["a"]["info_hash"] 212 | tid = msg["t"] 213 | nid = msg["a"]["id"] 214 | token = infohash[:TOKEN_LENGTH] 215 | msg = { 216 | "t": tid, 217 | "y": "r", 218 | "r": { 219 | "id": get_neighbor(infohash, self.nid), 220 | "nodes": "", 221 | "token": token 222 | } 223 | } 224 | self.master.log_hash(infohash, address) 225 | self.send_krpc(msg, address) 226 | except KeyError: 227 | pass 228 | 229 | def on_announce_peer_request(self, msg, address): 230 | try: 231 | infohash = msg["a"]["info_hash"] 232 | token = msg["a"]["token"] 233 | nid = msg["a"]["id"] 234 | tid = msg["t"] 235 | 236 | if infohash[:TOKEN_LENGTH] == token: 237 | if msg["a"].has_key("implied_port ") and msg["a"]["implied_port "] != 0: 238 | port = address[1] 239 | else: 240 | port = msg["a"]["port"] 241 | self.master.log_announce(infohash, (address[0], port)) 242 | except Exception: 243 | print 'error' 244 | pass 245 | finally: 246 | self.ok(msg, address) 247 | 248 | def play_dead(self, msg, address): 249 | try: 250 | tid = msg["t"] 251 | msg = { 252 | "t": tid, 253 | "y": "e", 254 | "e": [202, "Server Error"] 255 | } 256 | self.send_krpc(msg, address) 257 | except KeyError: 258 | pass 259 | 260 | def ok(self, msg, address): 261 | try: 262 | tid = msg["t"] 263 | nid = msg["a"]["id"] 264 | msg = { 265 | "t": tid, 266 | "y": "r", 267 | "r": { 268 | "id": get_neighbor(nid, self.nid) 269 | } 270 | } 271 | self.send_krpc(msg, address) 272 | except KeyError: 273 | pass 274 | 275 | 276 | class Master(Thread): 277 | def __init__(self): 278 | Thread.__init__(self) 279 | self.setDaemon(True) 280 | self.queue = Queue() 281 | self.metadata_queue = Queue() 282 | self.dbconn = mdb.connect(DB_HOST, DB_USER, DB_PASS, 'ssbc', charset='utf8') 283 | self.dbconn.autocommit(False) 284 | self.dbcurr = self.dbconn.cursor() 285 | self.dbcurr.execute('SET NAMES utf8') 286 | self.n_reqs = self.n_valid = self.n_new = 0 287 | self.n_downloading_lt = self.n_downloading_pt = 0 288 | self.visited = set() 289 | 290 | def got_torrent(self): 291 | binhash, address, data, dtype, start_time = self.metadata_queue.get() 292 | if dtype == 'pt': 293 | self.n_downloading_pt -= 1 294 | elif dtype == 'lt': 295 | self.n_downloading_lt -= 1 296 | if not data: 297 | return 298 | self.n_valid += 1 299 | 300 | save_metadata(self.dbcurr, binhash, address, start_time, data) 301 | self.n_new += 1 302 | 303 | 304 | def run(self): 305 | self.name = threading.currentThread().getName() 306 | print self.name, 'started' 307 | while True: 308 | while self.metadata_queue.qsize() > 0: 309 | self.got_torrent() 310 | address, binhash, dtype = self.queue.get() 311 | if binhash in self.visited: 312 | continue 313 | if len(self.visited) > 100000: 314 | self.visited = set() 315 | self.visited.add(binhash) 316 | 317 | self.n_reqs += 1 318 | info_hash = binhash.encode('hex') 319 | 320 | utcnow = datetime.datetime.utcnow() 321 | date = (utcnow + datetime.timedelta(hours=8)) 322 | date = datetime.datetime(date.year, date.month, date.day) 323 | 324 | # Check if we have this info_hash 325 | self.dbcurr.execute('SELECT id FROM search_hash WHERE info_hash=%s', (info_hash,)) 326 | y = self.dbcurr.fetchone() 327 | if y: 328 | self.n_valid += 1 329 | # 更新最近发现时间,请求数 330 | self.dbcurr.execute('UPDATE search_hash SET last_seen=%s, requests=requests+1 WHERE info_hash=%s', (utcnow, info_hash)) 331 | else: 332 | if dtype == 'pt': 333 | t = threading.Thread(target=simMetadata.download_metadata, args=(address, binhash, self.metadata_queue)) 334 | t.setDaemon(True) 335 | t.start() 336 | self.n_downloading_pt += 1 337 | elif dtype == 'lt' and self.n_downloading_lt < MAX_QUEUE_LT: 338 | t = threading.Thread(target=ltMetadata.download_metadata, args=(address, binhash, self.metadata_queue)) 339 | t.setDaemon(True) 340 | t.start() 341 | self.n_downloading_lt += 1 342 | 343 | if self.n_reqs >= 1000: 344 | self.dbcurr.execute('INSERT INTO search_statusreport(date,new_hashes,total_requests, valid_requests) VALUES(%s,%s,%s,%s) ON DUPLICATE KEY UPDATE ' + 345 | 'total_requests=total_requests+%s, valid_requests=valid_requests+%s, new_hashes=new_hashes+%s', 346 | (date, self.n_new, self.n_reqs, self.n_valid, self.n_reqs, self.n_valid, self.n_new)) 347 | self.dbconn.commit() 348 | print '\n', time.ctime(), 'n_reqs', self.n_reqs, 'n_valid', self.n_valid, 'n_new', self.n_new, 'n_queue', self.queue.qsize(), 349 | print 'n_d_pt', self.n_downloading_pt, 'n_d_lt', self.n_downloading_lt, 350 | self.n_reqs = self.n_valid = self.n_new = 0 351 | 352 | def log_announce(self, binhash, address=None): 353 | self.queue.put([address, binhash, 'pt']) 354 | 355 | def log_hash(self, binhash, address=None): 356 | if not lt: 357 | return 358 | if is_ip_allowed(address[0]): 359 | return 360 | if self.n_downloading_lt < MAX_QUEUE_LT: 361 | self.queue.put([address, binhash, 'lt']) 362 | 363 | 364 | def announce(info_hash, address): 365 | binhash = info_hash.decode('hex') 366 | master.log_announce(binhash, address) 367 | return 'ok' 368 | 369 | 370 | def rpc_server(): 371 | rpcserver = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 8004), logRequests=False) 372 | rpcserver.register_function(announce, 'announce') 373 | print 'Starting xml rpc server...' 374 | rpcserver.serve_forever() 375 | 376 | 377 | if __name__ == "__main__": 378 | # max_node_qsize bigger, bandwith bigger, spped higher 379 | master = Master() 380 | master.start() 381 | 382 | rpcthread = threading.Thread(target=rpc_server) 383 | rpcthread.setDaemon(True) 384 | rpcthread.start() 385 | 386 | dht = DHTServer(master, "0.0.0.0", 6881, max_node_qsize=200) 387 | dht.start() 388 | dht.auto_send_find_node() 389 | 390 | 391 | -------------------------------------------------------------------------------- /web/static/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /workers/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /web/static/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 16 | } 17 | .btn-default:active, 18 | .btn-primary:active, 19 | .btn-success:active, 20 | .btn-info:active, 21 | .btn-warning:active, 22 | .btn-danger:active, 23 | .btn-default.active, 24 | .btn-primary.active, 25 | .btn-success.active, 26 | .btn-info.active, 27 | .btn-warning.active, 28 | .btn-danger.active { 29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 31 | } 32 | .btn-default .badge, 33 | .btn-primary .badge, 34 | .btn-success .badge, 35 | .btn-info .badge, 36 | .btn-warning .badge, 37 | .btn-danger .badge { 38 | text-shadow: none; 39 | } 40 | .btn:active, 41 | .btn.active { 42 | background-image: none; 43 | } 44 | .btn-default { 45 | text-shadow: 0 1px 0 #fff; 46 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 47 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 48 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 49 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 52 | background-repeat: repeat-x; 53 | border-color: #dbdbdb; 54 | border-color: #ccc; 55 | } 56 | .btn-default:hover, 57 | .btn-default:focus { 58 | background-color: #e0e0e0; 59 | background-position: 0 -15px; 60 | } 61 | .btn-default:active, 62 | .btn-default.active { 63 | background-color: #e0e0e0; 64 | border-color: #dbdbdb; 65 | } 66 | .btn-default.disabled, 67 | .btn-default:disabled, 68 | .btn-default[disabled] { 69 | background-color: #e0e0e0; 70 | background-image: none; 71 | } 72 | .btn-primary { 73 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 74 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 75 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 76 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 79 | background-repeat: repeat-x; 80 | border-color: #245580; 81 | } 82 | .btn-primary:hover, 83 | .btn-primary:focus { 84 | background-color: #265a88; 85 | background-position: 0 -15px; 86 | } 87 | .btn-primary:active, 88 | .btn-primary.active { 89 | background-color: #265a88; 90 | border-color: #245580; 91 | } 92 | .btn-primary.disabled, 93 | .btn-primary:disabled, 94 | .btn-primary[disabled] { 95 | background-color: #265a88; 96 | background-image: none; 97 | } 98 | .btn-success { 99 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 100 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 101 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 102 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 103 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 104 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 105 | background-repeat: repeat-x; 106 | border-color: #3e8f3e; 107 | } 108 | .btn-success:hover, 109 | .btn-success:focus { 110 | background-color: #419641; 111 | background-position: 0 -15px; 112 | } 113 | .btn-success:active, 114 | .btn-success.active { 115 | background-color: #419641; 116 | border-color: #3e8f3e; 117 | } 118 | .btn-success.disabled, 119 | .btn-success:disabled, 120 | .btn-success[disabled] { 121 | background-color: #419641; 122 | background-image: none; 123 | } 124 | .btn-info { 125 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 126 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 127 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 128 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 129 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 130 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 131 | background-repeat: repeat-x; 132 | border-color: #28a4c9; 133 | } 134 | .btn-info:hover, 135 | .btn-info:focus { 136 | background-color: #2aabd2; 137 | background-position: 0 -15px; 138 | } 139 | .btn-info:active, 140 | .btn-info.active { 141 | background-color: #2aabd2; 142 | border-color: #28a4c9; 143 | } 144 | .btn-info.disabled, 145 | .btn-info:disabled, 146 | .btn-info[disabled] { 147 | background-color: #2aabd2; 148 | background-image: none; 149 | } 150 | .btn-warning { 151 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 152 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 153 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 154 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 155 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 156 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 157 | background-repeat: repeat-x; 158 | border-color: #e38d13; 159 | } 160 | .btn-warning:hover, 161 | .btn-warning:focus { 162 | background-color: #eb9316; 163 | background-position: 0 -15px; 164 | } 165 | .btn-warning:active, 166 | .btn-warning.active { 167 | background-color: #eb9316; 168 | border-color: #e38d13; 169 | } 170 | .btn-warning.disabled, 171 | .btn-warning:disabled, 172 | .btn-warning[disabled] { 173 | background-color: #eb9316; 174 | background-image: none; 175 | } 176 | .btn-danger { 177 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 178 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 179 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 180 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 181 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 182 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 183 | background-repeat: repeat-x; 184 | border-color: #b92c28; 185 | } 186 | .btn-danger:hover, 187 | .btn-danger:focus { 188 | background-color: #c12e2a; 189 | background-position: 0 -15px; 190 | } 191 | .btn-danger:active, 192 | .btn-danger.active { 193 | background-color: #c12e2a; 194 | border-color: #b92c28; 195 | } 196 | .btn-danger.disabled, 197 | .btn-danger:disabled, 198 | .btn-danger[disabled] { 199 | background-color: #c12e2a; 200 | background-image: none; 201 | } 202 | .thumbnail, 203 | .img-thumbnail { 204 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 205 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 206 | } 207 | .dropdown-menu > li > a:hover, 208 | .dropdown-menu > li > a:focus { 209 | background-color: #e8e8e8; 210 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 211 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 212 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 213 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 214 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 215 | background-repeat: repeat-x; 216 | } 217 | .dropdown-menu > .active > a, 218 | .dropdown-menu > .active > a:hover, 219 | .dropdown-menu > .active > a:focus { 220 | background-color: #2e6da4; 221 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 222 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 223 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 224 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 225 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 226 | background-repeat: repeat-x; 227 | } 228 | .navbar-default { 229 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 230 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 231 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 232 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 233 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 234 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 235 | background-repeat: repeat-x; 236 | border-radius: 4px; 237 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 238 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 239 | } 240 | .navbar-default .navbar-nav > .open > a, 241 | .navbar-default .navbar-nav > .active > a { 242 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 243 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 244 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 245 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 246 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 247 | background-repeat: repeat-x; 248 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 249 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 250 | } 251 | .navbar-brand, 252 | .navbar-nav > li > a { 253 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 254 | } 255 | .navbar-inverse { 256 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 257 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 258 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 259 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 260 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 261 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 262 | background-repeat: repeat-x; 263 | } 264 | .navbar-inverse .navbar-nav > .open > a, 265 | .navbar-inverse .navbar-nav > .active > a { 266 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 267 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 268 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 269 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 270 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 271 | background-repeat: repeat-x; 272 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 273 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 274 | } 275 | .navbar-inverse .navbar-brand, 276 | .navbar-inverse .navbar-nav > li > a { 277 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 278 | } 279 | .navbar-static-top, 280 | .navbar-fixed-top, 281 | .navbar-fixed-bottom { 282 | border-radius: 0; 283 | } 284 | @media (max-width: 767px) { 285 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 286 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 287 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 288 | color: #fff; 289 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 290 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 291 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 292 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 294 | background-repeat: repeat-x; 295 | } 296 | } 297 | .alert { 298 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 299 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 300 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 301 | } 302 | .alert-success { 303 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 304 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 305 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 306 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 307 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 308 | background-repeat: repeat-x; 309 | border-color: #b2dba1; 310 | } 311 | .alert-info { 312 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 313 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 314 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 315 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 316 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 317 | background-repeat: repeat-x; 318 | border-color: #9acfea; 319 | } 320 | .alert-warning { 321 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 322 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 323 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 324 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 325 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 326 | background-repeat: repeat-x; 327 | border-color: #f5e79e; 328 | } 329 | .alert-danger { 330 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 331 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 332 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 333 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 334 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 335 | background-repeat: repeat-x; 336 | border-color: #dca7a7; 337 | } 338 | .progress { 339 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 340 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 342 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 344 | background-repeat: repeat-x; 345 | } 346 | .progress-bar { 347 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 348 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 349 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 350 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 352 | background-repeat: repeat-x; 353 | } 354 | .progress-bar-success { 355 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 356 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 357 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 358 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 360 | background-repeat: repeat-x; 361 | } 362 | .progress-bar-info { 363 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 364 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 365 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 366 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 367 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 368 | background-repeat: repeat-x; 369 | } 370 | .progress-bar-warning { 371 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 372 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 373 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 374 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 375 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 376 | background-repeat: repeat-x; 377 | } 378 | .progress-bar-danger { 379 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 380 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 381 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 382 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 383 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 384 | background-repeat: repeat-x; 385 | } 386 | .progress-bar-striped { 387 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 388 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 389 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 390 | } 391 | .list-group { 392 | border-radius: 4px; 393 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 394 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 395 | } 396 | .list-group-item.active, 397 | .list-group-item.active:hover, 398 | .list-group-item.active:focus { 399 | text-shadow: 0 -1px 0 #286090; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 405 | background-repeat: repeat-x; 406 | border-color: #2b669a; 407 | } 408 | .list-group-item.active .badge, 409 | .list-group-item.active:hover .badge, 410 | .list-group-item.active:focus .badge { 411 | text-shadow: none; 412 | } 413 | .panel { 414 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 415 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 416 | } 417 | .panel-default > .panel-heading { 418 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 419 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 420 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 421 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 422 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 423 | background-repeat: repeat-x; 424 | } 425 | .panel-primary > .panel-heading { 426 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 427 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 428 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 429 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 430 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 431 | background-repeat: repeat-x; 432 | } 433 | .panel-success > .panel-heading { 434 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 435 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 436 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 437 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 438 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 439 | background-repeat: repeat-x; 440 | } 441 | .panel-info > .panel-heading { 442 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 443 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 444 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 445 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 447 | background-repeat: repeat-x; 448 | } 449 | .panel-warning > .panel-heading { 450 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 451 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 453 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .panel-danger > .panel-heading { 458 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 459 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 461 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .well { 466 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 467 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 469 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 471 | background-repeat: repeat-x; 472 | border-color: #dcdcdc; 473 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 474 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 475 | } 476 | /*# sourceMappingURL=bootstrap-theme.css.map */ 477 | --------------------------------------------------------------------------------