├── .idea ├── inspectionProfiles │ └── profiles_settings.xml ├── vcs.xml └── workspace.xml ├── README.md ├── crawl ├── __init__.py ├── crawl.py └── spiders │ ├── __init__.py │ ├── btv.py │ ├── cabletv.py │ ├── cctv.py │ ├── chuanliu.py │ ├── g4tv.py │ ├── gdtv.py │ ├── gxntv.py │ ├── hks.py │ ├── icable.py │ ├── mod.py │ ├── mytvsuper.py │ ├── nowtv.py │ ├── sdtv.py │ ├── tbc.py │ ├── tvb.py │ ├── tvmao.py │ ├── tvsou.py │ ├── viu.py │ └── zhongshu.py ├── db.sqlite3 ├── epg ├── .idea │ ├── epg.iml │ ├── inspectionProfiles │ │ └── profiles_settings.xml │ ├── modules.xml │ ├── vcs.xml │ └── workspace.xml ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── img ├── alipay.jpg ├── back.png ├── channel1.png ├── channel2.png ├── crawl.png ├── main_page.png └── wechat.png ├── main.py ├── manage.py ├── static ├── admin │ ├── css │ │ ├── autocomplete.css │ │ ├── base.css │ │ ├── changelists.css │ │ ├── dashboard.css │ │ ├── fonts.css │ │ ├── forms.css │ │ ├── login.css │ │ ├── nav_sidebar.css │ │ ├── responsive.css │ │ ├── responsive_rtl.css │ │ ├── rtl.css │ │ ├── vendor │ │ │ └── select2 │ │ │ │ ├── LICENSE-SELECT2.md │ │ │ │ ├── select2.css │ │ │ │ └── select2.min.css │ │ └── widgets.css │ ├── fonts │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── Roboto-Bold-webfont.woff │ │ ├── Roboto-Light-webfont.woff │ │ └── Roboto-Regular-webfont.woff │ ├── img │ │ ├── LICENSE │ │ ├── README.txt │ │ ├── calendar-icons.svg │ │ ├── gis │ │ │ ├── move_vertex_off.svg │ │ │ └── move_vertex_on.svg │ │ ├── icon-addlink.svg │ │ ├── icon-alert.svg │ │ ├── icon-calendar.svg │ │ ├── icon-changelink.svg │ │ ├── icon-clock.svg │ │ ├── icon-deletelink.svg │ │ ├── icon-no.svg │ │ ├── icon-unknown-alt.svg │ │ ├── icon-unknown.svg │ │ ├── icon-viewlink.svg │ │ ├── icon-yes.svg │ │ ├── inline-delete.svg │ │ ├── search.svg │ │ ├── selector-icons.svg │ │ ├── sorting-icons.svg │ │ ├── tooltag-add.svg │ │ └── tooltag-arrowright.svg │ └── js │ │ ├── SelectBox.js │ │ ├── SelectFilter2.js │ │ ├── actions.js │ │ ├── admin │ │ ├── DateTimeShortcuts.js │ │ └── RelatedObjectLookups.js │ │ ├── autocomplete.js │ │ ├── calendar.js │ │ ├── cancel.js │ │ ├── change_form.js │ │ ├── collapse.js │ │ ├── core.js │ │ ├── inlines.js │ │ ├── jquery.init.js │ │ ├── nav_sidebar.js │ │ ├── popup_response.js │ │ ├── prepopulate.js │ │ ├── prepopulate_init.js │ │ ├── urlify.js │ │ └── vendor │ │ ├── jquery │ │ ├── LICENSE.txt │ │ ├── jquery.js │ │ └── jquery.min.js │ │ ├── select2 │ │ ├── LICENSE.md │ │ ├── i18n │ │ │ ├── af.js │ │ │ ├── ar.js │ │ │ ├── az.js │ │ │ ├── bg.js │ │ │ ├── bn.js │ │ │ ├── bs.js │ │ │ ├── ca.js │ │ │ ├── cs.js │ │ │ ├── da.js │ │ │ ├── de.js │ │ │ ├── dsb.js │ │ │ ├── el.js │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ ├── et.js │ │ │ ├── eu.js │ │ │ ├── fa.js │ │ │ ├── fi.js │ │ │ ├── fr.js │ │ │ ├── gl.js │ │ │ ├── he.js │ │ │ ├── hi.js │ │ │ ├── hr.js │ │ │ ├── hsb.js │ │ │ ├── hu.js │ │ │ ├── hy.js │ │ │ ├── id.js │ │ │ ├── is.js │ │ │ ├── it.js │ │ │ ├── ja.js │ │ │ ├── ka.js │ │ │ ├── km.js │ │ │ ├── ko.js │ │ │ ├── lt.js │ │ │ ├── lv.js │ │ │ ├── mk.js │ │ │ ├── ms.js │ │ │ ├── nb.js │ │ │ ├── ne.js │ │ │ ├── nl.js │ │ │ ├── pl.js │ │ │ ├── ps.js │ │ │ ├── pt-BR.js │ │ │ ├── pt.js │ │ │ ├── ro.js │ │ │ ├── ru.js │ │ │ ├── sk.js │ │ │ ├── sl.js │ │ │ ├── sq.js │ │ │ ├── sr-Cyrl.js │ │ │ ├── sr.js │ │ │ ├── sv.js │ │ │ ├── th.js │ │ │ ├── tk.js │ │ │ ├── tr.js │ │ │ ├── uk.js │ │ │ ├── vi.js │ │ │ ├── zh-CN.js │ │ │ └── zh-TW.js │ │ ├── select2.full.js │ │ └── select2.full.min.js │ │ └── xregexp │ │ ├── LICENSE.txt │ │ ├── xregexp.js │ │ └── xregexp.min.js ├── css │ └── bootstrap │ │ └── 4.3.1 │ │ └── bootstrap.min.css └── js │ ├── bootstrap │ └── 4.3.1 │ │ └── bootstrap.min.js │ ├── ipv6-s1.svg │ ├── jquery.lazyload.min.js │ ├── jquery │ └── 2.1.1 │ │ └── jquery-2.1.1.min.js │ └── popper │ └── 1.14.7 │ └── popper.min.js ├── utils ├── __init__.py ├── aboutdb.py ├── crawl_channel_lists.py ├── general.py └── zhtools │ ├── Mandarin.dat │ ├── __init__.py │ ├── chconv.py │ ├── langconv.py │ ├── test_langconv.py │ ├── xpinyin.py │ └── zh_wiki.py └── web ├── __init__.py ├── admin.py ├── apps.py ├── migrations └── __init__.py ├── models.py ├── templates ├── index.html ├── single_channel_epgs.html └── test.html ├── urls.py └── views.py /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 老张的EPG 2 | * 基于`python3`及`django4`的节目表数据抓取及发布系统 3 | * 本人并非专业,很多内容只是为实现功能,可能会有很多BUG,见谅。 4 | * 不保证后续会更新。 5 | * DEMO地址:[老张的EPG](http://epg.51zmt.top:8000/) 6 | 7 | ## 主要功能 8 | - 从网上抓取各来源的节目表信息并生成[xmltv](http://wiki.xmltv.org/) 格式文件,用于[perfect player](http://niklabs.com/) 等APP直接载入的节目表信息。 9 | - 后台配置频道获取列表及抓取日志。 10 | - 抓取失败时自动更换来源。 11 | - 各数来源提供节目表的频道获取 12 | - 提供向外发布的接口 13 | - 使用nginx+uwsgi+MYSQL、普通办公电脑经长期测试,一天DIYP接口访问量可千万以上。 14 | 15 | ## 节目表来源 16 | - 电视猫 17 | - 搜视 18 | - 央视 19 | - 中数 20 | - 台湾宽频 21 | - 中华电信 22 | - 香港有线宽频caletv 23 | - 台湾四季电视 24 | - 香港有线宽频i-cable 25 | - 香港NOWTV 26 | - 香港无线电视 27 | - 北京卫视 28 | - 广东卫视 29 | - 香港卫视 30 | - viutv 31 | - 川流TV 32 | - myTVSUPER 33 | ## 需求 34 | - requests 35 | - django 36 | - BeautifulSoup 37 | ## 使用方法 38 | 默认使用[sqlite3](https://www.sqlite.org/) 数据库 39 | ### 下载源码 40 | ```git clone http://github.com/supzhang/``` 41 | ### 抓取数据 42 | ```python 43 | python main.py #抓取数据并存入数据库,可设置为定时任务 44 | python main.py -channel #抓取所有来源的频道 45 | python main.py -n CCTV1 #单独测试某一频道 46 | ``` 47 | 另:抓取的频道会加入Channel_list表,需要自己手动整理进Channel表中才可以抓取 48 | ![抓取](./img/crawl.png) 49 | ### 启动后台及接口 50 | #### 启动后台 51 | ```python 52 | python manage.py runserver 0.0.0.0:80 53 | ``` 54 | #### 访问 55 | 浏览器访问[http://127.0.0.1](http://127.0.0.1)查看已有数据抓取情况。 56 | ![主页](./img/main_page.png) 57 | 浏览器访问[http://127.0.0.1/admin](http://127.0.0.1/admin) 打开后台(用户名密码:`admin/admin`) 58 | - 后台首页 59 | ![后台](./img/back.png) 60 | - 频道列表 61 | ![频道列表](./img/channel1.png) 62 | - 修改频道 63 | ![频道修改](./img/channel2.png) 64 | - DIYP接口`http://127.0.0.1/api/diyp/` 需要提供参数`?ch=CCTV1&date=20230309` 65 | ### 程序配置 66 | `util/general` 中有大部分配置 67 | `crawl_info`:需要采集的节目天数、生成xml的天数、是否需要换源等 68 | `dirs`:生成测试文件目录 69 | `chuanliu_Authorization`:如果使用川流TV来源,需要提供此信息 70 | ### 其他配置 71 | #### 更改数据库 72 | `epg/settings`在此文件中修改配置如下: 73 | ```python 74 | DATABASES = { 75 | 'default': { 76 | 'ENGINE': 'django.db.backends.mysql', 77 | 'NAME': '数据库名称', 78 | 'USER': '数据库密码', 79 | 'PASSWORD': '数据库密码', 80 | 'HOST': '127.0.0.1', 81 | 'PORT': '3306', 82 | }, 83 | } 84 | ``` 85 | #### 增加抓取来源 86 | `crawl/spider`在此文件夹中复制当前存在的采集程序,对其进行修改,主要设置两个方法get_epgs_xxx,get_channels_xxx 87 | `crawl/spider/__init__.py` 中导入上面设置的方法,并参照其他来源加入:epg_funcs,epg_source,func_args,__all__ 88 | #### 增加其他频道 89 | 在后台“频道列表”中增加,“频道来源网站ID:”字段使用`<来源名:id>`格式设置。 90 | **** 91 | ## 捐赠 92 | 如果您觉得本项目对您有所帮助,请您多多支持,您的支持是我最大的动力,多谢。 93 | - 支付宝 94 | ![支付宝](./img/alipay.jpg) 95 | - 微信 96 | ![微信](./img/wechat.png) 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /crawl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/crawl/__init__.py -------------------------------------------------------------------------------- /crawl/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | #package spider 2 | #__init__.py 3 | 4 | from crawl.spiders.cctv import get_epgs_cctv,get_channels_cctv 5 | from crawl.spiders.tbc import get_epgs_tbc,get_channels_tbc 6 | from crawl.spiders.tvmao import get_epgs_tvmao2,get_channels_tvmao 7 | from crawl.spiders.zhongshu import get_epgs_zhongshu,get_channels_zhongshu 8 | from crawl.spiders.cabletv import get_epgs_cabletv,get_channels_cabletv 9 | from crawl.spiders.g4tv import get_epgs_4gtv,get_channels_4gtv 10 | from crawl.spiders.mod import get_epgs_mod,get_channels_mod 11 | from crawl.spiders.tvb import get_epgs_tvb,get_channels_tvb 12 | from crawl.spiders.nowtv import get_epgs_nowtv,get_channels_nowtv 13 | from crawl.spiders.gdtv import get_epgs_gdtv,get_channels_gdtv 14 | from crawl.spiders.icable import get_epgs_icable,get_channels_icable 15 | from crawl.spiders.btv import get_epgs_btv,get_channels_btv 16 | from crawl.spiders.tvsou import get_epgs_tvsou,get_channels_tvsou 17 | from crawl.spiders.hks import get_epgs_hks,get_channels_hks 18 | from crawl.spiders.viu import get_epgs_viu,get_channels_viu 19 | from crawl.spiders.chuanliu import get_channels_chuanliu,get_epgs_chuanliu 20 | from crawl.spiders.mytvsuper import get_epgs_mytvsuper,get_channels_mytvsuper 21 | from crawl.spiders.gxntv import get_epgs_gxntv,get_channels_gxntv 22 | from utils.general import chuanliu_Authorization 23 | from crawl.spiders.sdtv import get_epgs_sdtv,get_channels_sdtv 24 | epg_funcs = { 25 | 'tvmao':get_epgs_tvmao2, 26 | 'tbc':get_epgs_tbc, 27 | 'cctv':get_epgs_cctv, 28 | 'zhongshu':get_epgs_zhongshu, 29 | 'cabletv':get_epgs_cabletv, 30 | 'tvsou':get_epgs_tvsou, 31 | '4gtv':get_epgs_4gtv, 32 | 'mod':get_epgs_mod, 33 | 'tvb':get_epgs_tvb, 34 | 'nowtv':get_epgs_nowtv, 35 | 'icable':get_epgs_icable, 36 | 'gdtv':get_epgs_gdtv, 37 | 'btv':get_epgs_btv, 38 | 'hks':get_epgs_hks, 39 | 'viu':get_epgs_viu, 40 | 'chuanliu':get_epgs_chuanliu, 41 | 'mytvsuper':get_epgs_mytvsuper, 42 | 'gxntv':get_epgs_gxntv, 43 | 'sdtv':get_epgs_sdtv, 44 | } #所有EPG的接口 45 | epg_source = { 46 | 'tvmao':get_channels_tvmao, 47 | 'tbc':get_channels_tbc, 48 | 'cctv':get_channels_cctv, 49 | 'zhongshu':get_channels_zhongshu, 50 | 'cabletv':get_channels_cabletv, 51 | 'tvsou':get_channels_tvsou, 52 | '4gtv':get_channels_4gtv, 53 | 'mod':get_channels_mod, 54 | 'tvb':get_channels_tvb, 55 | 'nowtv':get_channels_nowtv, 56 | 'icable':get_channels_icable, 57 | 'gdtv':get_channels_gdtv, 58 | 'btv':get_channels_btv, 59 | 'hks':get_channels_hks, 60 | 'viu':get_channels_viu, 61 | 'chuanliu':get_channels_chuanliu, 62 | 'mytvsuper':get_channels_mytvsuper, 63 | 'gxntv':get_channels_gxntv, 64 | 'sdtv':get_channels_sdtv, 65 | } 66 | func_args = { 67 | 'tvmao':0, 68 | 'tbc':0, 69 | 'cctv':0, 70 | 'zhongshu':0, 71 | 'cabletv':0, 72 | 'tvsou':0, 73 | '4gtv':0, 74 | 'mod':0, 75 | 'tvb':0, 76 | 'nowtv':0, 77 | 'icable':0, 78 | 'gdtv':0, 79 | 'btv':0, 80 | 'hks':0, 81 | 'viu':0, 82 | 'chuanliu':chuanliu_Authorization, 83 | 'mytvsuper':0, 84 | 'gxntv':0, 85 | 'sdtv':0, 86 | } 87 | def epg_func(channel,id,dt,func_arg=0,source = 0): 88 | if source: 89 | source1 = source 90 | else: 91 | source1 = channel.source 92 | func_arg = func_args[source1] #if func_arg else func_arg 93 | return epg_funcs[source1](channel,id,dt,func_arg) 94 | 95 | __all__ = ['get_epgs_4gtv', 96 | 'get_epgs_btv', 97 | 'get_epgs_cabletv', 98 | 'get_epgs_cctv', 99 | 'get_epgs_gdtv', 100 | 'get_epgs_icable', 101 | 'get_epgs_mod', 102 | 'get_epgs_nowtv', 103 | 'get_epgs_tbc', 104 | 'get_epgs_tvb', 105 | 'get_epgs_tvmao2', 106 | 'get_epgs_zhongshu', 107 | 'get_epgs_tvsou', 108 | 'get_epgs_hks', 109 | 'get_epgs_viu', 110 | 'get_epgs_chuanliu', 111 | 'get_epgs_mytvsuper', 112 | 'get_epgs_gxntv', 113 | 'get_epgs_sdtv', 114 | 'epg_funcs', 115 | 'func_args', 116 | 'epg_func', 117 | 118 | ] 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /crawl/spiders/btv.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 北京电视台官方来源 10 个频道 3 | #2022-11-03官方更改接口 4 | #https://dynamic.rbc.cn/bvradio_app/service/LIVE?functionName=getCurrentChannel&channelId=135&curdate=2022-11-01 5 | # http://jiemudan.brtv.org.cn/index.html?channel=TvCh1602660467213184 地址 6 | # http://www.brtv.org.cn/mobileinf/rest/cctv/videolivelist/dayWeb?json={'id':'TvCh1602660467213184','day':'2021-12-15'} 接口 7 | from bs4 import BeautifulSoup as bs 8 | import requests, datetime,os 9 | from utils.general import headers 10 | 11 | 12 | def get_epgs_btv(channel, channel_id, dt, func_arg): 13 | epgs = [] 14 | msg = '' 15 | success = 1 16 | need_date = dt.strftime('%Y-%m-%d') 17 | url = "https://dynamic.rbc.cn/bvradio_app/service/LIVE?functionName=getCurrentChannel&channelId=%s&curdate=%s"%(channel_id, need_date) 18 | 19 | try: 20 | res = requests.get(url, headers=headers,timeout=5) 21 | res.encoding = 'utf-8' 22 | res_j = res.json()['channel']['programes'] 23 | old_dt = datetime.datetime(1999, 12, 31, 12, 12) 24 | n = 0 #计数 节目表的第几个节目 25 | max_n = len(res_j) 26 | for epga in res_j: 27 | n += 1 28 | starttime = epga['startTime'] 29 | endtime = epga['endTime'] 30 | title = epga['name'] 31 | starttime = datetime.datetime.strptime(need_date + starttime, '%Y-%m-%d%H:%M') 32 | if n == max_n and endtime[:2] == '00': 33 | endtime = datetime.datetime.strptime(need_date + endtime, '%Y-%m-%d%H:%M') 34 | endtime = endtime + datetime.timedelta(days=1) 35 | else: 36 | endtime = datetime.datetime.strptime(need_date + endtime, '%Y-%m-%d%H:%M') 37 | epg = {'channel_id': channel.id, 38 | 'starttime': starttime, 39 | 'endtime': endtime, 40 | 'title': title, 41 | 'desc': '', 42 | 'program_date': dt, 43 | } 44 | epgs.append(epg) 45 | except Exception as e: 46 | success = 0 47 | spidername = os.path.basename(__file__).split('.')[0] 48 | msg = 'spider-%s- %s' % (spidername,e) 49 | ret = { 50 | 'success': success, 51 | 'epgs': epgs, 52 | 'msg': msg, 53 | 'last_program_date': dt, 54 | 'ban':0, 55 | } 56 | return ret 57 | 58 | 59 | def get_channels_btv(): 60 | channels = [] 61 | url= 'https://www.brtv.org.cn/gbdsb.shtml' 62 | res = requests.get(url, headers=headers) 63 | res.encoding = 'utf-8' 64 | soup = bs(res.text, 'html.parser') 65 | lis = soup.select('div.conWrapper > div.templateBox > ul > li') 66 | for li in lis: 67 | name = li.div.text.replace('\n','').strip() 68 | id = li.attrs['channelid'] 69 | channel = { 70 | 'name': name, 71 | 'id': [id], 72 | 'url': 'https://www.brtv.org.cn/gbdsb.shtml',#url, 73 | 'source': 'btv', 74 | 'logo': '', 75 | 'desc': '', 76 | 'sort': '北京', 77 | } 78 | channels.append(channel) 79 | infos = soup.select('div.introductionWrapper') 80 | n = 0 81 | for info in infos: 82 | desc = info.text.strip().replace(' ','').replace('\t','').replace('\r','').replace('\n\n','\n').replace('\n\n','\n') 83 | if '青年频道' in desc: 84 | continue 85 | channels[n]['desc'] = desc 86 | n += 1 87 | return channels 88 | -------------------------------------------------------------------------------- /crawl/spiders/cctv.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # CCTV官方 3 | import requests, re, datetime,json,os 4 | from utils.general import headers 5 | from bs4 import BeautifulSoup as bs 6 | 7 | def get_epgs_cctv(channel, channel_id, dt, func_arg): 8 | epgs = [] 9 | msg = '' 10 | success = 1 11 | need_date = dt.strftime('%Y%m%d') 12 | url = 'http://api.cntv.cn/epg/getEpgInfoByChannelNew?c=%s&serviceId=tvcctv&d=%s&t=jsonp&cb=set'%(channel_id,need_date) 13 | try: 14 | res = requests.get(url, headers=headers,timeout=5) 15 | res.encoding = 'utf-8' 16 | programs = json.loads(re.search('set\((.*)\)', res.text).group(1)) 17 | prog_lists = programs['data'][channel_id]['list'] 18 | for prog_list in prog_lists: # 19 | title = prog_list['title'] #节目名称 20 | starttime = datetime.datetime.fromtimestamp(prog_list['startTime']) #开始时间 21 | endtime=datetime.datetime.fromtimestamp(prog_list['endTime']) #结束时间 22 | epg = {'channel_id': channel.id, 23 | 'starttime': starttime, 24 | 'endtime': endtime, 25 | 'title': title, 26 | 'desc': '', 27 | 'program_date': dt, 28 | } 29 | epgs.append(epg) 30 | epglen = len(epgs) 31 | except Exception as e: 32 | success = 0 33 | spidername = os.path.basename(__file__).split('.')[0] 34 | msg = 'spider-%s- %s' % (spidername,e) 35 | ret = { 36 | 'success': success, 37 | 'epgs': epgs, 38 | 'msg': msg, 39 | 'last_program_date': dt, 40 | 'ban':0, 41 | } 42 | return ret 43 | 44 | def get_channels_cctv(): 45 | channels = [] 46 | host = 'https://tv.cctv.com' 47 | url = '%s/epg/index.shtml' % host 48 | res = requests.get(url, headers=headers) 49 | res.encoding = 'utf-8' 50 | soup = bs(res.text, 'html.parser') 51 | lis = soup.select('div.channel_con > div > ul > li') 52 | need_date = datetime.datetime.now().strftime('%Y%m%d') 53 | for li in lis: 54 | id = li.select('img')[0].attrs['title'].strip() 55 | logo = 'http://' + li.select('img')[0].attrs['src'].strip() 56 | url_info = 'http://api.cntv.cn/epg/getEpgInfoByChannelNew?c=%s&serviceId=tvcctv&d=%s&t=jsonp&cb=set' % (id, need_date) 57 | res = requests.get(url_info,headers = headers,timeout = 5) 58 | res.encoding = 'utf-8' 59 | research = re.search('"channelName":"(.+?)".+?"lvUrl":"(.+?)"',res.text) 60 | name = research.group(1) 61 | url = research.group(2) 62 | channel = { 63 | 'name': name, 64 | 'id': [id], 65 | 'url': url, 66 | 'source': 'cctv', 67 | 'logo': logo, 68 | 'desc': '', 69 | 'sort':'央视', 70 | } 71 | channels.append(channel) 72 | return channels 73 | -------------------------------------------------------------------------------- /crawl/spiders/chuanliu.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | ''' 3 | 2023-02-20四川移动:川流TV APP获取 4 | 需要 UA、cookies、Authorization、X-Device-Id(抓包获取,除Authorization其他非必须),记不得是哪个链接了,自己试下就知道了 5 | ''' 6 | import requests, datetime,os 7 | from utils.general import chuanliu_Authorization 8 | 9 | headers = { 10 | # 'Cookie': '', 11 | 'Authorization': chuanliu_Authorization, 12 | # 'X-Device-Id': '', 13 | # 'User-Agent': '', 14 | } 15 | 16 | def get_epgs_chuanliu(channel, channel_id, dt, func_arg): 17 | epgs = [] 18 | msg = '' 19 | success = 1 20 | if len(chuanliu_Authorization) < 10: 21 | return { 22 | 'success': 0, 23 | 'epgs': [], 24 | 'msg': '未提供川流TV的Authorization', 25 | 'last_program_date': dt, 26 | 'ban': 0, 27 | } 28 | need_date = dt.strftime('%Y-%m-%d') 29 | url = 'http://epg.iqy.sc96655.com/v1/getPrograms?channel=%s&begin_time=%s 00:00:00&end_time=%s 23:59:59&partner=2'%(channel_id,need_date,need_date) 30 | try: 31 | res = requests.get(url, headers=headers,timeout=5) 32 | res.encoding = 'utf-8' 33 | ret_data = res.json()['ret_data'] 34 | n = 0 #计数 节目表的第几个节目 35 | for j in ret_data: 36 | n += 1 37 | title = j['name'] 38 | channel_name = j['bd_name'] 39 | desc = j['desc'] 40 | starttime = j['begin_time'] #2023-02-18 15:42:00' 41 | endtime = j['end_time'] 42 | starttime = datetime.datetime.strptime(starttime, '%Y-%m-%d %H:%M:%S') 43 | endtime = datetime.datetime.strptime(endtime, '%Y-%m-%d %H:%M:%S') 44 | epg = {'channel_id': channel.id, 45 | 'starttime': starttime, 46 | 'endtime': endtime, 47 | 'title': title, 48 | 'desc': desc, 49 | 'program_date': dt, 50 | } 51 | epgs.append(epg) 52 | except Exception as e: 53 | success = 0 54 | spidername = os.path.basename(__file__).split('.')[0] 55 | msg = 'spider-%s- %s' % (spidername,e) 56 | ret = { 57 | 'success': success, 58 | 'epgs': epgs, 59 | 'msg': msg, 60 | 'last_program_date': dt, 61 | 'ban':0, 62 | } 63 | return ret 64 | ''' 65 | 获取所有频道信息 66 | ''' 67 | def get_channels_chuanliu(): 68 | channels = [] 69 | sorts = { 70 | '1008':'教育频道', 71 | '1010': '4K超高清', 72 | '1002':'央视', 73 | '1003':'卫视', 74 | '1004':'本地频道', 75 | '1014':'试播频道', 76 | '1012':'休闲生活', 77 | '1013':'8K超高清', 78 | } 79 | url = 'http://epg.iqy.sc96655.com/v1/getChannels?partner=2&terminal=&definition=&citycode=&adcode=&charge_type=&channel_type=' 80 | res = requests.get(url, headers=headers) 81 | res.encoding = 'utf-8' 82 | j = res.json() 83 | ret_data = j['ret_data'] 84 | for c in ret_data: 85 | name = c['bd_name'] 86 | name2 = c['name'] 87 | name = name if len(name)>0 else name2 88 | id = c['id'] 89 | sort_type = c['channelType'] 90 | sort = sorts[sort_type] if sort_type in sorts else '其他' 91 | channel = { 92 | 'name': name, 93 | 'id': [id], 94 | 'url': '',#url, 95 | 'source': 'chuanliu', 96 | 'logo': '', 97 | 'desc': '', 98 | 'sort': sort, 99 | } 100 | channels.append(channel) 101 | return channels 102 | def get_sorts_type(): 103 | url = 'http://epg.iqy.sc96655.com/v1/getChannelType?partner=2&terminal=' 104 | res = requests.get(url ,headers =headers) 105 | ret_data = res.json()['ret_data'] 106 | n = 0 107 | for j in ret_data: 108 | if n == 0: 109 | n+=1 110 | continue 111 | key = j['pramKey'] 112 | sortname=j['pramValue'] 113 | print(key,sortname) -------------------------------------------------------------------------------- /crawl/spiders/g4tv.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import requests,datetime,os 3 | from utils.general import headers,cht_to_chs 4 | def get_epgs_4gtv(channel, channel_id, dt, func_arg): 5 | epgs = [] 6 | msg = '' 7 | success = 1 8 | url = 'https://www.4gtv.tv/ProgList/%s.txt' % (channel_id) 9 | try: 10 | res = requests.get(url, headers=headers,timeout=8) 11 | res.encoding = 'utf-8' 12 | res_json = res.json() 13 | for j in res_json: 14 | title = j['title'] 15 | start_date = j['sdate'] 16 | start_time = j['stime'] 17 | end_date = j['edate'] 18 | end_time = j['etime'] 19 | starttime = datetime.datetime.strptime('%s%s'%(start_date,start_time),'%Y-%m-%d%H:%M:%S') 20 | endtime = datetime.datetime.strptime('%s%s'%(end_date,end_time),'%Y-%m-%d%H:%M:%S') 21 | if starttime.date() < dt: # 对于已经采集过的日期,跳过 22 | continue 23 | epg = {'channel_id': channel.id, 24 | 'starttime': starttime, 25 | 'endtime': endtime, 26 | 'title': title, 27 | 'desc': '', 28 | 'program_date': starttime.date(), 29 | } 30 | epgs.append(epg) 31 | epglen = len(epgs) 32 | except Exception as e: 33 | success = 0 34 | spidername = os.path.basename(__file__).split('.')[0] 35 | msg = 'spider-%s- %s' % (spidername,e) 36 | ret = { 37 | 'success': success, 38 | 'epgs': epgs, 39 | 'msg': msg, 40 | 'last_program_date': starttime.date() if 'starttime' in dir() else dt, 41 | 'ban':0, 42 | 43 | } 44 | return ret 45 | 46 | def get_channels_4gtv(): 47 | url = 'http://api2.4gtv.tv/Channel/GetAllChannel/pc/L' 48 | headers.update({ 49 | 'accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 50 | 'Accept-Encoding': 'gzip, deflate, br', 51 | 'Accept-Language': 'zh-CN,zh;q=0.9,en-CA;q=0.8,en;q=0.7,zh-TW;q=0.6', 52 | 'sec-ch-ua':'" Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"', 53 | 'sec-ch-ua-mobile': '?0', 54 | 'sec-ch-ua-platform': "Windows", 55 | 'Sec-Fetch-Dest': 'document', 56 | 'Sec-Fetch-Mode': 'navigate', 57 | 'Sec-Fetch-User': '?1', 58 | 'Upgrade-Insecure-Requests': '1', 59 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36', 60 | }) 61 | res = requests.get(url,headers = headers) 62 | res.encoding = 'utf-8' 63 | js = res.json()['Data'] 64 | channels = [] 65 | for j in js: 66 | id = str(j['fnID']) 67 | type = j['fsTYPE'] 68 | name = cht_to_chs(j['fsNAME']) 69 | gtvid = j['fs4GTV_ID'] 70 | logo = j['fsLOGO_MOBILE'] 71 | desc = j['fsDESCRIPTION'] if 'fsDESCRIPTION' in j else '' 72 | all = [name,gtvid,logo] 73 | 74 | channel = { 75 | 'name': name, 76 | 'id': [gtvid], 77 | 'url': 'https://www.4gtv.tv/channel', 78 | 'source': '4gtv', 79 | 'logo': logo, 80 | 'desc': desc, 81 | 'sort':'台湾', 82 | } 83 | channels.append(channel) 84 | return channels 85 | -------------------------------------------------------------------------------- /crawl/spiders/gdtv.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | #广东广播电视台-官网来源,2021-12-16添加到数据库,只有广东地区频道共10多个 3 | import requests,datetime,os 4 | from bs4 import BeautifulSoup as bs 5 | from utils.general import headers 6 | def get_epgs_gdtv(channel, channel_id, dt, func_arg): 7 | epgs = [] 8 | msg = '' 9 | success = 1 10 | try: 11 | url = 'http://epg.gdtv.cn/f/%s/%s.xml'%(channel_id,dt.strftime('%Y-%m-%d')) 12 | res = requests.get(url,headers = headers,timeout=8) 13 | res.encoding = 'utf-8' 14 | soup = bs(res.text, 'html.parser') 15 | epgs_contents = soup.select('content') 16 | epgs = [] 17 | for epga in epgs_contents: 18 | starttime = datetime.datetime.fromtimestamp(int(epga.attrs['time1'])) 19 | endtime = datetime.datetime.fromtimestamp(int(epga.attrs['time2'])) 20 | title = epga.get_text().strip() 21 | epg = {'channel_id': channel.id, 22 | 'starttime': starttime, 23 | 'endtime': endtime, 24 | 'title': title, 25 | 'desc': '', 26 | 'program_date': dt, 27 | } 28 | epgs.append(epg) 29 | except Exception as e: 30 | success = 0 31 | spidername = os.path.basename(__file__).split('.')[0] 32 | msg = 'spider-%s- %s' % (spidername,e) 33 | ret = { 34 | 'success': success, 35 | 'epgs': epgs, 36 | 'msg': msg, 37 | 'last_program_date': dt, 38 | 'ban':0, 39 | } 40 | return ret 41 | 42 | def get_channels_gdtv(): 43 | url = 'http://epg.gdtv.cn/f/1.xml' 44 | res = requests.get(url,headers = headers) 45 | res.encoding = 'utf-8' 46 | soup = bs(res.text,'html.parser') 47 | contents = soup.select('channel') 48 | channels = [] 49 | for content in contents: 50 | id = content.attrs['id'] 51 | name = content.ctitle.text 52 | cdate = content.cdate.text 53 | channel = { 54 | 'name': name, 55 | 'id': [id], 56 | 'url': 'http://epg.gdtv.cn/', 57 | 'source': 'gdtv', 58 | 'logo': '', 59 | 'desc': '', 60 | 'sort':'广东', 61 | 'newestdate':cdate 62 | } 63 | channels.append(channel) 64 | return channels 65 | 66 | -------------------------------------------------------------------------------- /crawl/spiders/gxntv.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | #广西网络广播电视台-官网来源,2023-06-05添加到数据库,广西卫视及地方6个频道 3 | 4 | #POST请求数据 5 | # channelName: 广西卫视 6 | # dateStr: 2023-06-05 7 | # programName: 8 | # deptId: 0a509685ba1a11e884e55cf3fc49331c 9 | # platformId: bd7d620a502d43c09b35469b3cd8c211 10 | #deptid及PLATFORMID为固定值,来源 https://www.gxtv.cn/static/httpRequestOfParams.js webDeptId = "0a509685ba1a11e884e55cf3fc49331c" webType = "bd7d620a502d43c09b35469b3cd8c211"; 11 | 12 | import requests,datetime,os 13 | from bs4 import BeautifulSoup as bs 14 | from utils.general import headers 15 | def get_epgs_gxntv(channel, channel_id, dt, func_arg): 16 | epgs = [] 17 | msg = '' 18 | success = 1 19 | dt_str = dt.strftime('%Y-%m-%d') 20 | data = { 21 | 'channelName':channel_id , 22 | 'dateStr': dt_str, 23 | 'programName': '', 24 | 'deptId': '0a509685ba1a11e884e55cf3fc49331c', 25 | 'platformId': 'bd7d620a502d43c09b35469b3cd8c211' 26 | } 27 | try: 28 | url = 'https://api2019.gxtv.cn/memberApi/programList/selectListByChannelId' 29 | res = requests.post(url,headers = headers,timeout=8,data= data) 30 | res.encoding = 'utf-8' 31 | res_json = res.json() 32 | epgs_contents = res_json['data'] 33 | epgs = [] 34 | for epga in epgs_contents: 35 | starttime_str = epga['programTime'] 36 | time_delay = epga['programmeLength'] 37 | starttime = datetime.datetime.strptime(starttime_str,'%Y-%m-%d %H:%M:%S') 38 | endtime = starttime+datetime.timedelta(seconds = time_delay) 39 | title = epga['programName'].strip() 40 | epg = {'channel_id': channel.id, 41 | 'starttime': starttime, 42 | 'endtime': endtime, 43 | 'title': title, 44 | 'desc': '', 45 | 'program_date': dt, 46 | } 47 | epgs.append(epg) 48 | except Exception as e: 49 | success = 0 50 | spidername = os.path.basename(__file__).split('.')[0] 51 | msg = 'spider-%s- %s' % (spidername,e) 52 | ret = { 53 | 'success': success, 54 | 'epgs': epgs, 55 | 'msg': msg, 56 | 'last_program_date': dt, 57 | 'ban':0, 58 | } 59 | return ret 60 | 61 | def get_channels_gxntv(): 62 | url = 'https://program.gxtv.cn/' 63 | res = requests.get(url,headers = headers) 64 | res.encoding = 'utf-8' 65 | soup = bs(res.text,'html.parser') 66 | contents = soup.select('#TV_tab > ul > li') 67 | channels = [] 68 | for content in contents: 69 | id = content.attrs['id'] 70 | name = content.text 71 | channel = { 72 | 'name': name, 73 | 'id': [id], 74 | 'url': 'https://program.gxtv.cn/', 75 | 'source': 'gxntv', 76 | 'logo': '', 77 | 'desc': '', 78 | 'sort':'广西', 79 | 80 | } 81 | channels.append(channel) 82 | return channels 83 | -------------------------------------------------------------------------------- /crawl/spiders/hks.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 香港卫视官方来源 1 个频道 3 | from bs4 import BeautifulSoup as bs 4 | import requests, datetime,os 5 | from utils.general import headers 6 | def get_epgs_hks(channel, channel_id, dt, func_arg): 7 | epgs = [] 8 | msg = '' 9 | success = 1 10 | url = 'http://www.hkstv.tv/index/live.html' 11 | 12 | try: 13 | res = requests.get(url, headers=headers,timeout=5) 14 | res.encoding = 'utf-8' 15 | soup = bs(res.text, 'html.parser') 16 | lis = soup.select('div.living-list ul > li') 17 | for li in lis: 18 | title = [text for text in li.a.stripped_strings][1] 19 | starttime = datetime.datetime.fromtimestamp(int(li.a.attrs['id'])) 20 | if starttime.date() < dt: 21 | continue 22 | epg = {'channel_id': channel.id, 23 | 'starttime': starttime, 24 | 'endtime': None, 25 | 'title': title, 26 | 'desc': '', 27 | 'program_date': starttime.date() if starttime in locals() else dt, 28 | 29 | } 30 | epgs.append(epg) 31 | except Exception as e: 32 | success = 0 33 | spidername = os.path.basename(__file__).split('.')[0] 34 | msg = 'spider-%s- %s' % (spidername,e) 35 | 36 | ret = { 37 | 'success': success, 38 | 'epgs': epgs, 39 | 'msg': msg, 40 | 'last_program_date': starttime.date(), 41 | 'ban':0, 42 | } 43 | return ret 44 | 45 | 46 | def get_channels_hks(): 47 | channels = [] 48 | 49 | channel = { 50 | 'name': '香港卫视', 51 | 'id': ['hks'], 52 | 'url': 'http://www.hkstv.tv/index/live.html',#url, 53 | 'source': 'hks', 54 | 'logo': 'http://www.hkstv.tv/templates/site_shared/assets/img/blogo.png', 55 | 'desc': '香港衛視國際傳媒集團有限公司是一家集衛星電視、網路電視、影視投資、文化產業等為一體的國際傳媒集團。' 56 | '香港衛視集團於2008年在香港成立,擁有6個衛星頻道和1個網絡電視臺。香港衛視於2010年10月正式開播以來,' 57 | '以24小時開路不加密的方式,通過亞太5號、亞太6號、亞太7號三顆衛星雙波段播出,已經覆蓋亞太、北美、歐洲、中東、非洲等150多個國家和地區。' 58 | '香港衛視堅持“立足香港、延伸兩岸、融入世界”的戰略發展定位,堅持“中國元素,國際表達”、“傳播中華文化,傾聽世界聲音”和“溫暖、善意、積極”的傳播理念,堅持把 “講好中國故事、' 59 | '傳播好中國聲音、傳遞中國正能量”作為立台之本,大力推動“中國文化”、“中國變化”、“中國形象”走出去,著力建設有影響力的對外宣傳平臺,打造中西優秀文化融合傳播的窗口,' 60 | '媒體傳播力和品牌影響力不斷提升,在服務特區政府、助力改善香港政治輿論環境,宣傳中國、傳播好中國聲音等方面,發揮了積極、獨特的作用。', 61 | 'sort': '香港', 62 | } 63 | channels.append(channel) 64 | return channels 65 | 66 | 67 | ''' 68 | 69 | 70 | ''' 71 | -------------------------------------------------------------------------------- /crawl/spiders/icable.py: -------------------------------------------------------------------------------- 1 | #-*-coding:utf-8-*- 2 | import requests,os,datetime 3 | from utils.general import cht_to_chs,headers 4 | def get_epgs_icable(channel, channel_id, dt, func_arg): 5 | epgs = [] 6 | msg = '' 7 | success = 1 8 | url = 'http://epg.i-cable.com/ci/channel/epg/%s/%s?api=api&locale=chi'%(channel_id,dt.strftime('%Y%m%d')) 9 | try: 10 | res = requests.get(url, headers=headers,timeout = 8) 11 | res.encoding = 'utf-8' 12 | js = res.json() 13 | epg_list = js['epgs'] 14 | for g in epg_list: 15 | title = cht_to_chs(g['programme_name_chi']) #可以选择英文名 16 | ampm = g['session_mark'] 17 | t= g['time'] 18 | starttime = datetime.datetime.strptime(dt.strftime('%Y%m%d') + t, '%Y%m%d%H:%M') 19 | if ampm.upper() == 'PM': 20 | starttime = starttime + datetime.timedelta(hours=12) 21 | elif ampm.upper() == 'NM': 22 | starttime = starttime + datetime.timedelta(days = 1) 23 | epg = {'channel_id': channel.id, 24 | 'starttime': starttime, 25 | 'endtime': None, 26 | 'title': title, 27 | 'desc': '', 28 | 'program_date': starttime.date(), 29 | } 30 | epgs.append(epg) 31 | except Exception as e: 32 | success = 0 33 | spidername = os.path.basename(__file__).split('.')[0] 34 | msg = 'spider-%s- %s' % (spidername, e) 35 | 36 | ret = { 37 | 'success': success, 38 | 'epgs': epgs, 39 | 'msg': msg, 40 | 'last_program_date': dt, 41 | 'ban':0, 42 | } 43 | return ret 44 | 45 | def get_channels_icable(): 46 | url = 'http://epg.i-cable.com/ci/home/?api=api&locale=chi' 47 | res = requests.get(url) 48 | res_json = res.json() 49 | channels_json = res_json['channels'] 50 | channels = [] 51 | for c in channels_json: 52 | name = c['channel_name'] 53 | id = c['channel_id'] 54 | url = 'http://epg.i-cable.com/ci/channel/epg/%s'%c['channel_no'] 55 | logo = 'http://epg.i-cable.com/new/images/epgMobileIcon/%s.png'%c['channel_no'] 56 | name_eng = c['channel_name_en'] 57 | catelog = c['cate_id'] 58 | 59 | channel = { 60 | 'name': name, 61 | 'id': [id], 62 | 'url': url, 63 | 'source': 'icable', 64 | 'logo': logo, 65 | 'desc': '', 66 | 'sort':'海外', 67 | 'name_eng':name_eng, 68 | 'catelog':catelog, 69 | } 70 | channels.append(channel) 71 | return channels 72 | 73 | -------------------------------------------------------------------------------- /crawl/spiders/mod.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import requests, re, datetime,os 3 | from utils.general import headers 4 | from bs4 import BeautifulSoup as bs 5 | def get_epgs_mod(channel, channel_id, dt, func_arg): 6 | epgs = [] 7 | msg = '' 8 | success = 1 9 | days = (dt - datetime.datetime.now().date()).days 10 | url = 'http://mod.cht.com.tw/tv/channel.php?id=%s&d=%s' % ( 11 | channel_id, days) # d=0表示当天,至少能获取最近七天数据 http://mod.cht.com.tw/tv/channel.php?id=6&d=2 12 | try: 13 | res = requests.get(url, headers=headers,timeout=8) 14 | res.encoding = 'utf-8' 15 | soup = bs(res.text, 'html.parser') 16 | old_dt = datetime.datetime(1999, 12, 31, 12, 12) 17 | lis = soup.select('ul.striped-time-table > li') 18 | for li in lis[1:]: 19 | title = li.select('h4')[0].text.replace('\t', '').replace('\r', '').replace('\n', '').strip() 20 | timestr = li.select('time.time')[0].text.strip() 21 | starttime = datetime.datetime(dt.year, dt.month, dt.day, int(timestr[:2]), int(timestr[-2:])) 22 | if starttime < old_dt: #第一条记录可能为上一天的,剔除。 23 | epgs.pop(0) 24 | old_dt = datetime.datetime(2999, 12, 31, 12, 12) 25 | epg = {'channel_id': channel.id, 26 | 'starttime': starttime, 27 | 'endtime': None, 28 | 'title': title, 29 | 'desc': '', 30 | 'program_date': dt, 31 | } 32 | epgs.append(epg) 33 | except Exception as e: 34 | success = 0 35 | spidername = os.path.basename(__file__).split('.')[0] 36 | msg = 'spider-%s- %s' % (spidername,e) 37 | ret = { 38 | 'success': success, 39 | 'epgs': epgs, 40 | 'msg': msg, 41 | 'last_program_date': dt, 42 | 'ban':0, 43 | } 44 | return ret 45 | def get_channels_mod(): 46 | # http://mod.cht.com.tw/tv/channel.php?id=006 采集节目表地址 47 | url = 'http://mod.cht.com.tw/bepg2/' 48 | res = requests.get(url,timeout = 10) 49 | res.encoding = 'utf-8' 50 | soup = bs(res.text, 'html.parser') 51 | divs = soup.select('div.rowat') 52 | divs2 = soup.select('div.rowat_gray') 53 | divs += divs2 54 | channels = [] 55 | for div in divs: 56 | try: 57 | urlid = div.select('div > a')[0].attrs['href'] 58 | name = div.select('div.channel_info')[0].text 59 | id = name[:3].strip() 60 | img = 'http://mod.cht.com.tw' + re.sub('\?rand=\d*', '', div.select('img')[0].attrs['src']).strip() 61 | channel = { 62 | 'name': name, 63 | 'id': [id], 64 | 'url': '%s%s'%('http://mod.cht.com.tw/',urlid), 65 | 'source': 'mod', 66 | 'logo': img, 67 | 'desc': '', 68 | 'sort':'海外', 69 | } 70 | channels.append(channel) 71 | except Exception as e: 72 | print(div) 73 | return channels 74 | -------------------------------------------------------------------------------- /crawl/spiders/mytvsuper.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import requests,datetime,os 3 | from utils.general import headers 4 | def get_epgs_mytvsuper(channel, channel_id, dt, func_arg):#channel_id,dt ,每次获取当天开始共7天数据 5 | epgs = [] 6 | msg = '' 7 | success = 1 8 | start_date_str = dt.strftime('%Y%m%d') 9 | end_date = dt + datetime.timedelta(days=6) 10 | end_date_str = end_date.strftime('%Y%m%d') 11 | url = 'https://content-api.mytvsuper.com/v1/epg?network_code=%s&from=%s&to=%s&platform=web '%(channel_id,start_date_str,end_date_str) 12 | try: 13 | res = requests.get(url,timeout = 8,headers = headers) 14 | res.encoding = 'utf-8' 15 | res_j = res.json() 16 | items = res_j[0]['item'] 17 | for item in items: 18 | epg_list = item['epg'] 19 | firtst_line_date = 1 20 | for li in epg_list: 21 | starttime = datetime.datetime.strptime(li['start_datetime'],'%Y-%m-%d %H:%M:%S') 22 | title = li['programme_title_tc'] 23 | title_en = li['programme_title_en'] 24 | desc=li['episode_synopsis_tc'] 25 | desc_en = li['episode_synopsis_en'] 26 | url = 'https://www.mytvsuper.com/tc/programme/%s'%li['programme_path'] 27 | program_date = starttime.date() if 'starttime' in locals() else dt 28 | if firtst_line_date: 29 | last_program_date = starttime 30 | first_line_date = 0 31 | #print(title,starttime,title_en) 32 | epg = {'channel_id': channel.id, 33 | 'starttime': starttime, 34 | 'endtime': None, 35 | 'title': '%s-%s'%(title,title_en), 36 | 'title_en':title_en, 37 | 'desc': desc, 38 | 'desc_en':desc_en, 39 | 'program_date': program_date, 40 | } 41 | epgs.append(epg) 42 | except Exception as e: 43 | success = 0 44 | spidername = os.path.basename(__file__).split('.')[0] 45 | msg = 'spider-%s- %s' % (spidername, e) 46 | ret = { 47 | 'success': success, 48 | 'epgs': epgs, 49 | 'msg': msg, 50 | 'last_program_date': last_program_date if 'last_program_date' in locals() else dt, 51 | 'ban': 0, 52 | } 53 | return ret 54 | def get_channels_mytvsuper(): 55 | url = 'https://content-api.mytvsuper.com/v1/channel/list?platform=web' 56 | res = requests.get(url,headers = headers) 57 | res_channels = res.json()['channels'] 58 | channels = [] 59 | for li in res_channels: 60 | name = li['name_tc'] 61 | name_en = li['name_en'] 62 | cn = li['channel_no'] 63 | href = 'https://www.mytvsuper.com/tc/epg/%s/'%cn 64 | logo = li['path'] if 'path' in li else '' 65 | id = li['network_code'] 66 | desc = '' 67 | channel = { 68 | 'name': name, 69 | 'name_en':name_en, 70 | 'id': [id], 71 | 'url': href, 72 | 'source': 'mytvsuper', 73 | 'logo': logo, 74 | 'desc': desc, 75 | 'sort':'香港', 76 | } 77 | channels.append(channel) 78 | return channels -------------------------------------------------------------------------------- /crawl/spiders/nowtv.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import requests,re,datetime,json,os 3 | from utils.general import headers 4 | requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' 5 | headers = { 6 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36', 7 | 'X-Requested-With':'XMLHttpRequest', 8 | 'Content-Type': 'application/x-www-form-urlencoded', 9 | 'Referer': 'http://nowtv.now.com/epg/', 10 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 11 | 'Accept': 'application/json, text/javascript, */*', 12 | 'Pragma': 'no-cache', 13 | 'Accept-Encoding': 'gzip, deflate', 14 | 'Proxy-Connection': 'keep-alive', 15 | } 16 | 17 | def get_epgs_nowtv(channel, channel_id, dt, func_arg): 18 | epgs = [] 19 | msg = '' 20 | success = 1 21 | url = 'http://nowtv.now.com/gw-epg/epg/zh_tw/%s/prf0/resp-genre/ch_%s.json' % (dt.strftime("%Y%m%d"),channel_id[:3]) #d=0表示当天,至少能获取最近七天数据 22 | try: 23 | res = requests.get(url, headers=headers,timeout = 8) 24 | res.encoding = 'utf-8' 25 | j = res.json() 26 | chs = j['data']['chProgram'] #很多频道的这一天的节目表 27 | for ch in chs: 28 | if "ids" == ch or ch != channel_id[4:]: #一个记录,记录显示的所有ID;只有ID和需要获取的ID相同才处理。 29 | continue 30 | nowtvid = '%s-%s'%(channel_id[:3],ch.strip()) #拼出nowtv的id 31 | for channelepg in chs[ch]: 32 | starttime = datetime.datetime.fromtimestamp(channelepg['start'] / 1000) 33 | endtime = datetime.datetime.fromtimestamp(channelepg['end'] / 1000) 34 | title = channelepg['name'] 35 | desc = channelepg['synopsis'] 36 | 37 | epg = {'channel_id': channel.id, 38 | 'starttime': starttime, 39 | 'endtime': endtime, 40 | 'title': title, 41 | 'desc': desc, 42 | 'program_date': starttime.date(), 43 | } 44 | epgs.append(epg) 45 | except Exception as e: 46 | success = 0 47 | spidername = os.path.basename(__file__).split('.')[0] 48 | msg = 'spider-%s- %s' % (spidername,e) 49 | ret = { 50 | 'success': success, 51 | 'epgs': epgs, 52 | 'msg': msg, 53 | 'last_program_date': dt, 54 | 'ban':0, 55 | } 56 | return ret 57 | # 获取所有最新频道列表 58 | def get_channels_nowtv(): 59 | url = 'http://nowtv.now.com/gw-epg/epg/channelMapping.zh-TW.js' 60 | res = requests.get(url,headers = headers,timeout=10) 61 | res.encoding = 'utf-8' 62 | reinfo = re.search('.+?var ChannelMapping=(.*)var GenreToChanne',res.text,re.DOTALL) 63 | cs = reinfo.group(1)[:-2] 64 | cs = json.loads(cs) 65 | channels = [] 66 | for c in cs: 67 | if 'name' not in cs[c]: 68 | continue 69 | id1 = cs[c]['genreKeys'][0] 70 | id2 = c 71 | channel_id = '%s-%s'%(id1,id2) 72 | print(cs[c]) 73 | name = cs[c]['name'] 74 | channel = { 75 | 'name': name, 76 | 'id': [channel_id], 77 | 'url': url, 78 | 'source': 'nowtv', 79 | 'logo': '', 80 | 'desc': '', 81 | 'sort':'海外', 82 | } 83 | channels.append(channel) 84 | return channels -------------------------------------------------------------------------------- /crawl/spiders/sdtv.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 齐鲁网-官网来源,2023-08-07添加到数据库,只有山东地区12个频道 3 | import requests, datetime, os, re, time, json 4 | from utils.general import headers 5 | 6 | 7 | def get_epgs_sdtv(channel, channel_id, dt, func_arg): 8 | epgs = [] 9 | msg = '' 10 | success = 1 11 | t = time.time() 12 | try: 13 | url = 'http://module.iqilu.com/media/apis/main/getprograms?jsonpcallback=jQuery37004547755401387288_%s&channelID=%s&date=%s&_=%s' % ( 14 | t, channel_id, dt, t) 15 | res = requests.get(url, timeout=8) 16 | res.encoding = 'utf-8' 17 | re_j = re.search('.+?\((.+?)\)', res.text, re.DOTALL).group(1) 18 | re_json = json.loads(re_j) 19 | contents = re_json['value']['list'] 20 | time_delta_days = (dt - datetime.datetime.now().date()).days 21 | for content in contents: 22 | starttime = datetime.datetime.fromtimestamp(int(content['begintime'])) + datetime.timedelta(days = time_delta_days) 23 | endtime = datetime.datetime.fromtimestamp(int(content['endtime'])) + datetime.timedelta(days = time_delta_days) 24 | title = content['name'] 25 | epg = {'channel_id': channel_id, 26 | 'starttime': starttime, 27 | 'endtime': endtime, 28 | 'title': title, 29 | 'desc': '', 30 | 'program_date': dt, 31 | } 32 | epgs.append(epg) 33 | except Exception as e: 34 | success = 0 35 | spidername = os.path.basename(__file__).split('.')[0] 36 | msg = 'spider-%s- %s' % (spidername, e) 37 | ret = { 38 | 'success': success, 39 | 'epgs': epgs, 40 | 'msg': msg, 41 | 'last_program_date': dt, 42 | 'ban': 0, 43 | } 44 | return ret 45 | 46 | 47 | def get_channels_sdtv(): 48 | url = 'http://v.iqilu.com/live/qlpd/' 49 | res = requests.get(url) 50 | res.encoding = 'utf-8' 51 | re_l = re.search('var channels = (.+?);', res.text, re.DOTALL).group(1) 52 | contents = re.findall( 53 | '\"\w+\"\:{.+?\"id\"\:(\d+)\,\"live\"\:\"(\w+)\"\,\"m3u8\"\:\"(.+?)\"\,\"catname\"\:\"(\w+)\".+?}', re_l, 54 | re.DOTALL) 55 | channels = [] 56 | for content in contents: 57 | id = content[0] 58 | name = content[3] 59 | curl = 'http://v.iqilu.com/live/%s/' % content[1] 60 | channel = { 61 | 'name': name, 62 | 'id': [id], 63 | 'url': curl, 64 | 'source': 'sdtv', 65 | 'logo': '', 66 | 'desc': '', 67 | 'sort': '山东' if name != '山东卫视' else '卫视' 68 | 69 | } 70 | channels.append(channel) 71 | return channels 72 | -------------------------------------------------------------------------------- /crawl/spiders/tbc.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import requests,datetime,time,re,os 3 | from utils.general import headers 4 | from bs4 import BeautifulSoup as bs 5 | def get_epgs_tbc(channel, channel_id, dt, func_arg): 6 | epgs = [] 7 | msg = '' 8 | success = 1 9 | channel_id = channel_id.replace('tbc','') 10 | url = 'https://www.tbc.net.tw/EPG/Channel?channelId=%s'%channel_id 11 | try: 12 | res = requests.get(url, timeout=30) 13 | res.encoding = 'utf-8' 14 | soup = bs(res.text, 'html.parser') 15 | uls = soup.select('ul.list_program2') 16 | for ul in uls: 17 | n1 = 0 # 记录当天的节目数 18 | lis = ul.select('li') 19 | for li in lis: 20 | title = li.p.text 21 | desc = li.attrs['desc'] 22 | date_ = li.attrs['date'] 23 | time_delay = li.attrs['time'].strip() 24 | time_delay_re = re.search('(\d+:\d+)~(\d+:\d+)', time_delay) 25 | if time_delay_re: # 有节目信息则解析 26 | start_str, end_str = time_delay_re.group(1), time_delay_re.group(2) # 将开始与结束时间的文本分开 27 | starttime = datetime.datetime.strptime(date_ + start_str,'%Y/%m/%d%H:%M') 28 | endtime = datetime.datetime.strptime(date_ + end_str,'%Y/%m/%d%H:%M') 29 | if starttime > endtime: 30 | endtime = endtime + datetime.timedelta(days=1) 31 | if starttime.date() < dt: 32 | continue 33 | epg = {'channel_id': channel.id, 34 | 'starttime': starttime, 35 | 'endtime': endtime, 36 | 'title': title, 37 | 'desc': desc, 38 | 'program_date': starttime.date(), 39 | } 40 | epgs.append(epg) 41 | except Exception as e: 42 | success = 0 43 | spidername = os.path.basename(__file__).split('.')[0] 44 | msg = 'spider-%s- %s' % (spidername,e) 45 | ret = { 46 | 'success': success, 47 | 'epgs': epgs, 48 | 'msg': msg, 49 | 'last_program_date': starttime.date() if 'starttime' in dir() else dt, 50 | 'ban': 0, 51 | } 52 | return ret 53 | today_int = int(time.strftime('%Y%m%d',time.localtime())) #今天的日期数字 20190325 一次抓取多天数据,不能重复爬取 54 | 55 | 56 | #下载TBC所有频道ID及名称 57 | def get_channels_tbc(): 58 | channels = [] 59 | cookies = { 60 | 'ASP.NET_SessionId':'v111fiox1mzc0wpc0d4iue5c' 61 | } 62 | url = 'https://www.tbc.net.tw/EPG' 63 | res = requests.get(url,headers = headers,cookies = cookies,timeout = 6) 64 | res.encoding = 'utf-8' 65 | soup = bs(res.text,'html.parser') 66 | lis = soup.select('ul.list_tv > li') 67 | for li in lis: 68 | name = li['title'] 69 | id = li['id'] 70 | img = li.select('img')[0]['src'] 71 | url = li.a['href'] 72 | channel = { 73 | 'name': name, 74 | 'id': [id], 75 | 'url': url, 76 | 'source': 'tbc', 77 | 'logo': img, 78 | 'desc': '', 79 | 'sort':'海外', 80 | } 81 | channels.append(channel) 82 | return channels 83 | 84 | 85 | -------------------------------------------------------------------------------- /crawl/spiders/tvb.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import requests,datetime,os,re,json 3 | from utils.general import headers 4 | from bs4 import BeautifulSoup as bs 5 | 6 | ''' 7 | TVB来源 8 | 2023-2-15更新为新的接口,旧html接口失效,更改获取频道ID方式 9 | ''' 10 | 11 | def get_epgs_tvb(channel, channel_id, dt, func_arg):#channel_id,dt 12 | epgs = [] 13 | msg = '' 14 | success = 1 15 | dt_str = dt.strftime('%Y%m%d') 16 | url = 'https://programme.tvb.com/api/schedule?input_date=%s&network_code=%s'%(dt_str,channel_id) 17 | try: 18 | res = requests.get(url,timeout = 8,headers = headers) 19 | res.encoding = 'utf-8' 20 | res_j = res.json() 21 | epg_list = res_j['data']['list'][0]['schedules'] 22 | for li in epg_list: 23 | starttime = datetime.datetime.fromtimestamp(int(li['event_time'])) 24 | title = li['programme_title'] 25 | title_en = li['en_programme_title'] 26 | desc=li['synopsis'] 27 | desc_en = li['en_synopsis'] 28 | url = li['mytv_super_url'] 29 | #print(title,starttime,title_en) 30 | epg = {'channel_id': channel.id, 31 | 'starttime': starttime, 32 | 'endtime': None, 33 | 'title': '%s-%s'%(title,title_en), 34 | 'title_en':title_en, 35 | 'desc': desc, 36 | 'desc_en':desc_en, 37 | 'program_date': starttime.date() if 'starttime' in locals() else dt, 38 | } 39 | 40 | epgs.append(epg) 41 | except Exception as e: 42 | success = 0 43 | spidername = os.path.basename(__file__).split('.')[0] 44 | msg = 'spider-%s- %s' % (spidername, e) 45 | #print(li) 46 | 47 | ret = { 48 | 'success': success, 49 | 'epgs': epgs, 50 | 'msg': msg, 51 | 'last_program_date': dt, 52 | 'ban': 0, 53 | } 54 | return ret 55 | def get_epgs_tvb_old(channel, channel_id, dt, func_arg):#只需要channel_id即可 ,同时获取15天,包含前面时间的节目 56 | epgs = [] 57 | msg = '' 58 | success = 1 59 | url = 'http://programme.tvb.com/%s/week/'%channel_id 60 | try: 61 | res = requests.get(url,timeout = 8,headers = headers) 62 | res.encoding = 'utf-8' 63 | soup = bs(res.text.replace('/>','>'),'html.parser') 64 | lis = soup.select('div.channel > ul > li') 65 | for li in lis: 66 | if 'time' not in li.attrs: 67 | continue 68 | starttime = datetime.datetime.fromtimestamp(int(li.attrs['time'])) 69 | em_time = li.select('em')[0].text 70 | title = li.text.strip().replace(em_time,'') 71 | if starttime.date() < dt: #已经采集过的数据忽略 72 | continue 73 | epg = {'channel_id': channel.id, 74 | 'starttime': starttime, 75 | 'endtime': None, 76 | 'title': title, 77 | 'desc': '', 78 | 'program_date': starttime.date() if 'starttime' in locals() else dt, 79 | } 80 | epgs.append(epg) 81 | except Exception as e: 82 | success = 0 83 | spidername = os.path.basename(__file__).split('.')[0] 84 | msg = 'spider-%s- %s' % (spidername, e) 85 | ret = { 86 | 'success': success, 87 | 'epgs': epgs, 88 | 'msg': msg, 89 | 'last_program_date': starttime.date() if 'starttime' in locals() else dt, 90 | 'ban': 0, 91 | } 92 | return ret 93 | 94 | def get_channels_tvb(): 95 | url = 'https://programme.tvb.com/assets/index.85ba94a8.js' 96 | res = requests.get(url) 97 | res_re = re.search('const e\=(.+?),n\=', res.text) 98 | channels_str = res_re.group(1) 99 | channels_str = channels_str.encode('raw_unicode_escape').decode('unicode_escape').strip().replace('null','0') 100 | channels_str = re.sub('([{,])(?!")(\w+)(\:)', lambda m: m.group(1) + '"' + m.group(2) + '"' + m.group(3), 101 | channels_str) 102 | channels_j = json.loads(channels_str) 103 | channels = [] 104 | for li in channels_j: 105 | name = li['name'] 106 | name_en = li['nameEn'] 107 | href = li['liveUrl'] if 'liveUrl' in li else '' 108 | id = li['code'] 109 | desc = li['description'] 110 | channel = { 111 | 'name': name, 112 | 'name_en':name_en, 113 | 'id': [id], 114 | 'url': href, 115 | 'source': 'tvb', 116 | 'logo': '', 117 | 'desc': desc, 118 | 'sort':'香港', 119 | } 120 | channels.append(channel) 121 | return channels -------------------------------------------------------------------------------- /crawl/spiders/tvsou.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import requests,datetime 3 | from utils.general import headers 4 | from bs4 import BeautifulSoup as bs 5 | def get_desc_tvsou(url): 6 | return '' 7 | try: 8 | res = requests.get(url, headers=headers,timeout = 5) 9 | res.encoding = 'utf-8' 10 | soup = bs(res.text, 'html.parser') 11 | s = soup.select('div.prog_content_txt')[0].text 12 | desc = s.replace('\n', '').replace(' ', '').replace('\t','').replace('剧情简介:','').replace(' ','') 13 | except Exception as e: 14 | desc = '' 15 | return desc 16 | def get_epgs_tvsou(channel, channel_id_, dt, func_arg): 17 | epgs = [] 18 | msg = '' 19 | success = 1 20 | desc = '' 21 | need_weekday = dt.weekday() + 1 # 需要获取周几的节目可以获取下周数据 .下周节目,另一个周一即为w1 ,需要再次确认 22 | if "#" in channel_id_: 23 | channel_id,sort_class = channel_id_.split('#') 24 | url = 'https://www.tvsou.com/epg/%s/w%s' % (channel_id, need_weekday) 25 | else: 26 | url = 'https://www.tvsou.com/epg/%s/w%s' % (channel_id_, need_weekday) 27 | 28 | #url = 'https://www.tvsou.com/epg/%s_w%s' % (channel_id_, need_weekday) 29 | try: 30 | res = requests.get(url, headers=headers,timeout=5) 31 | res.encoding = 'utf-8' 32 | soup = bs(res.text, 'html.parser') 33 | rows = soup.select('table.layui-table')[0].select('tr') 34 | except Exception as e: 35 | msg = 'spider-tvsou-连接失败,%s' % e 36 | success = 0 37 | ret = { 38 | 'success': success, 39 | 'epgs': epgs, 40 | 'msg': msg, 41 | 'last_program_date': dt, 42 | 'ban': 0, 43 | } 44 | return ret 45 | program_urls = {} 46 | for row in rows: 47 | try: 48 | if row.select('td > a'): 49 | starttime_str = row.select('td > a')[0].text 50 | title = row.select('td > a')[1].text 51 | else: 52 | starttime_str = row.select('td')[0].text 53 | title = row.select('td')[1].text 54 | starttime = datetime.datetime.combine(dt,datetime.time(int(starttime_str[:2]),int(starttime_str[-2:]))) 55 | program_url = None 56 | if row.select('td > a') and 'href' in row.select('td > a')[0].attrs: 57 | program_url = 'https:' + row.select('td > a')[0].attrs['href'] 58 | program_url = program_url.replace(' ','') 59 | if program_url in program_urls: #同一频道,同一天的节目同一个描述只抓取一次 60 | desc = program_urls[program_url] 61 | else: 62 | if len(program_url) > 17: 63 | desc = get_desc_tvsou(program_url) 64 | program_urls.update({program_url:desc}) 65 | else: 66 | program_url ,desc= '','' 67 | else: 68 | program_url ,desc= '','' 69 | epg = {'channel_id': channel.id, 70 | 'starttime': starttime, 71 | 'endtime': None, 72 | 'title': title, 73 | 'desc': desc, 74 | 'program_date': dt, 75 | } 76 | epgs.append(epg) 77 | except Exception as e: 78 | msg = 'spider-tvsou-FOR:%s' % e 79 | continue 80 | 81 | ret = { 82 | 'success': success, 83 | 'epgs': epgs, 84 | 'msg': msg, 85 | 'last_program_date': dt, 86 | 'ban': 0, 87 | } 88 | return ret 89 | 90 | def get_channels_tvsou(): 91 | channels = [] 92 | sorts = [] 93 | host = 'https://www.tvsou.com' 94 | url = '%s/%s'%(host,'epg/difang/') 95 | res = requests.get(url,headers = headers) 96 | res.encoding = 'utf-8' 97 | soup = bs(res.text,'html.parser') 98 | div_sorts = soup.select('div.pd_list > div.pd_tit') 99 | div_channels = soup.select('div.pd_list > div.pd_con > ul') 100 | n = 0 101 | #print(div_sorts) 102 | for div_channel in div_channels: 103 | sort_name = div_sorts[n].text.strip() 104 | lis = div_channel.select('li') 105 | n += 1 106 | for li in lis: 107 | name = li.a.text.strip() 108 | url = host + li.a['href'] 109 | id = li.a['href'].replace('epg','').replace('/','') 110 | if len(id) < 5: 111 | continue 112 | channel = { 113 | 'name': name, 114 | 'id': [id], 115 | 'url': url, 116 | 'source': 'tvsou', 117 | 'logo': '', 118 | 'desc': '', 119 | 'sort':sort_name, 120 | } 121 | channels.append(channel) 122 | print('共有:%s 个分类,%s 个频道'%(n,len(channels))) 123 | return channels 124 | -------------------------------------------------------------------------------- /crawl/spiders/viu.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | #仅提供viu频道节目信息https://viu.tv/epg/99 3 | # https://api.viu.tv/production/epgs/99 4 | import requests,datetime,time,re,os 5 | from utils.general import headers 6 | from bs4 import BeautifulSoup as bs 7 | def get_epgs_viu(channel, channel_id, dt, func_arg): 8 | epgs = [] 9 | msg = '' 10 | success = 1 11 | url = 'https://api.viu.tv/production/epgs/99' 12 | try: 13 | res = requests.get(url,headers = headers, timeout=6) 14 | res.encoding = 'utf-8' 15 | js = res.json()['epgs'] 16 | for j in js: 17 | title = j['program_title'].strip() 18 | desc1 = j['episode_title'] if 'episode_title' in j else '' 19 | desc2 = j['short_synopsis'] if 'short_synopsis' in j else '' 20 | desc = '--'.join([desc1,desc2]) if len(desc2)>0 else desc1 21 | desc = '' 22 | starttime = datetime.datetime.fromtimestamp(j['start']/1000) 23 | endtime = datetime.datetime.fromtimestamp(j['end']/1000) 24 | if starttime.date() < dt: 25 | continue 26 | epg = {'channel_id': channel.id, 27 | 'starttime': starttime, 28 | 'endtime': endtime, 29 | 'title': title, 30 | 'desc': desc, 31 | 'program_date': starttime.date(), 32 | } 33 | epgs.append(epg) 34 | except Exception as e: 35 | success = 0 36 | spidername = os.path.basename(__file__).split('.')[0] 37 | msg = 'spider-%s- %s' % (spidername,e) 38 | ret = { 39 | 'success': success, 40 | 'epgs': epgs, 41 | 'msg': msg, 42 | 'last_program_date': starttime.date() if 'starttime' in dir() else dt, 43 | 'ban': 0, 44 | } 45 | return ret 46 | 47 | 48 | #下载所有频道ID及名称 49 | def get_channels_viu(): 50 | channel = { 51 | 'name': 'ViuTV', 52 | 'id': ['viu'], 53 | 'url': 'https://viu.tv/epg/99', 54 | 'source': 'viu', 55 | 'logo':'https://viu.tv/assets/img/logo.png' , 56 | 'desc': '', 57 | 'sort':'海外', 58 | } 59 | channels = [channel] 60 | return channels 61 | 62 | 63 | -------------------------------------------------------------------------------- /crawl/spiders/zhongshu.py: -------------------------------------------------------------------------------- 1 | import requests,re,datetime 2 | from utils.general import headers 3 | from bs4 import BeautifulSoup as bs 4 | #中数传媒官网抓取数据,一次抓取多天数据,但不能重复抓取 5 | #从前一天最后一个节目,到今天最后的节目 6 | def get_epgs_zhongshu(channel, channel_id, dt, func_arg): 7 | epgs = [] 8 | msg = '' 9 | success = 1 10 | host = 'http://epg.tv.cn' 11 | w = 0 #本周节目 12 | #如果这一天不是本周节目,则获取下周的节目 13 | if dt.weekday() < datetime.datetime.now().weekday(): 14 | w = 1 15 | url = '%s/epg/%s/live/index.php?week=%s' % (host,channel_id,w) 16 | try: 17 | res = requests.get(url, headers=headers,timeout=5) 18 | rs = re.findall('epgs\[\d+\]=new Array\(\"(\d+)\",\"(\d+)\",\"(\d+:\d+)\", \"(.+?)\",.+?\)',res.text) #0 月 1日 2时间 3节目 19 | except Exception as e: 20 | msg = 'spider-zhongshu-request-%s'%e 21 | ret = { 22 | 'success': success, 23 | 'epgs': epgs, 24 | 'msg': msg, 25 | 'last_program_date': dt, 26 | 'ban':0, 27 | } 28 | return ret 29 | for r in rs: 30 | try: 31 | starttime = datetime.datetime.strptime('%s%02d%02d%s' % (dt.year, int(r[0]), int(r[1]), r[2]),'%Y%m%d%H:%M') 32 | title = r[3] # 节目名称 33 | if starttime.date() < dt: 34 | continue 35 | epg = {'channel_id': channel.id, 36 | 'starttime': starttime, 37 | 'endtime': None, 38 | 'title': title, 39 | 'desc': '', 40 | 'program_date': starttime.date(), 41 | } 42 | except Exception as e: 43 | msg = 'spider-zhongshu-获取具体信息出错-%s'%e 44 | starttime = datetime.datetime.combine(dt,datetime.time(1,0,0)) 45 | continue 46 | epgs.append(epg) 47 | ret = { 48 | 'success': success, 49 | 'epgs': epgs, 50 | 'msg': msg, 51 | 'last_program_date': starttime.date(), 52 | 'ban': 0, 53 | } 54 | return ret 55 | 56 | def get_channels_zhongshu(): 57 | channels = [] 58 | host = 'http://epg.tv.cn/' 59 | res = requests.get(host, headers=headers) 60 | res.encoding = 'utf-8' 61 | soup = bs(res.text, 'html.parser') 62 | uls = soup.select('div.epgleft > ul') 63 | sorts = [] 64 | spans = soup.select('div.epgleft > ul#channel > div > span') 65 | sort_name_change = { 66 | '央视高清付费频道':'央视', 67 | '4K超高清频道':'央视', 68 | '央视频道':'央视', 69 | '卫视频道':'卫视', 70 | '其他':'北京', 71 | '其他付费频道':'数字付费', 72 | } 73 | for span in spans: 74 | sort = span.text.strip() 75 | sorts.append(sort) 76 | uls = soup.select('div.epgleft > ul#channel > li > ul') 77 | x = 0 78 | for ul in uls: 79 | lis = ul.select('li') 80 | for li in lis: 81 | name = li.a.text.strip() 82 | id = li.a['href'].strip() 83 | url = '%s%s' % (host, id) 84 | channel = { 85 | 'name': name, 86 | 'id': [id], 87 | 'url': url, 88 | 'source': 'zhongshu', 89 | 'logo': '', 90 | 'desc': '', 91 | 'sort':sort_name_change[sorts[x]], 92 | } 93 | channels.append(channel) 94 | x += 1 95 | return channels -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/db.sqlite3 -------------------------------------------------------------------------------- /epg/.idea/epg.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | -------------------------------------------------------------------------------- /epg/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /epg/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /epg/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /epg/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 49 | 50 | 51 | 52 | 53 | 54 | 1678410593773 55 | 60 | 61 | 62 | 63 | 65 | -------------------------------------------------------------------------------- /epg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/epg/__init__.py -------------------------------------------------------------------------------- /epg/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for epg project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'epg.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /epg/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for epg project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.12. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import os 15 | 16 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 17 | BASE_DIR = Path(__file__).resolve().parent.parent 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'django-insecure-h+$18j4w!n3*9pk@wi3nb(oi&w=&*$c$dzw^7cgp5b4&14im&7' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = ['*'] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | #'django.contrib.sites', 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | 'web', 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | ] 52 | ROOT_URLCONF = 'epg.urls' 53 | 54 | TEMPLATES = [ 55 | { 56 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 57 | 'DIRS': [], 58 | 'APP_DIRS': True, 59 | 'OPTIONS': { 60 | 'context_processors': [ 61 | 'django.template.context_processors.debug', 62 | 'django.template.context_processors.request', 63 | 'django.contrib.auth.context_processors.auth', 64 | 'django.contrib.messages.context_processors.messages', 65 | ], 66 | }, 67 | }, 68 | ] 69 | ## 显示SQL语句配置 70 | WSGI_APPLICATION = 'epg.wsgi.application' 71 | 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 75 | 76 | DATABASES = { 77 | 'default': { 78 | 'ENGINE': 'django.db.backends.sqlite3', 79 | 'NAME': BASE_DIR / 'db.sqlite3', 80 | }, 81 | } 82 | 83 | 84 | # Password validation 85 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 86 | 87 | AUTH_PASSWORD_VALIDATORS = [ 88 | { 89 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 90 | }, 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 99 | }, 100 | ] 101 | 102 | 103 | # Internationalization 104 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 105 | 106 | LANGUAGE_CODE = 'zh-hans' 107 | 108 | TIME_ZONE = 'Asia/Shanghai' 109 | 110 | USE_I18N = True 111 | 112 | USE_L10N = True 113 | 114 | USE_TZ = True 115 | 116 | STATIC_URL = '/static/' 117 | STATICFILES_DIRS =[os.path.join(BASE_DIR,'static')] 118 | 119 | # Default primary key field type 120 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 121 | 122 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 123 | -------------------------------------------------------------------------------- /epg/urls.py: -------------------------------------------------------------------------------- 1 | """epg URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/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: path('', 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: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | from django.urls import include 19 | import web.views 20 | 21 | urlpatterns = [ 22 | path('',web.views.index), 23 | path('download/',web.views.download), 24 | path('api/', include('web.urls')), 25 | path('admin/', admin.site.urls), 26 | path('test/',web.views.d), 27 | ] 28 | -------------------------------------------------------------------------------- /epg/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for epg 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/3.2/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', 'epg.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /img/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/alipay.jpg -------------------------------------------------------------------------------- /img/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/back.png -------------------------------------------------------------------------------- /img/channel1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/channel1.png -------------------------------------------------------------------------------- /img/channel2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/channel2.png -------------------------------------------------------------------------------- /img/crawl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/crawl.png -------------------------------------------------------------------------------- /img/main_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/main_page.png -------------------------------------------------------------------------------- /img/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/img/wechat.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import sys 3 | from crawl.crawl import main as start_crawl 4 | from utils.crawl_channel_lists import crawl 5 | if '-log' in sys.argv: 6 | ''' 7 | 统计日志 8 | ''' 9 | pass 10 | if '-channel' in sys.argv: 11 | ''' 12 | 获取各来源的频道列表 13 | ''' 14 | crawl() 15 | else: 16 | ''' 17 | 启动抓取程序 18 | ''' 19 | start_crawl() 20 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'epg.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | 19 | execute_from_command_line(sys.argv) 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | -------------------------------------------------------------------------------- /static/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | } 27 | -------------------------------------------------------------------------------- /static/admin/css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('../fonts/Roboto-Bold-webfont.woff'); 4 | font-weight: 700; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Roboto'; 10 | src: url('../fonts/Roboto-Regular-webfont.woff'); 11 | font-weight: 400; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Roboto'; 17 | src: url('../fonts/Roboto-Light-webfont.woff'); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /static/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | .login { 4 | background: var(--darkened-bg); 5 | height: auto; 6 | } 7 | 8 | .login #header { 9 | height: auto; 10 | padding: 15px 16px; 11 | justify-content: center; 12 | } 13 | 14 | .login #header h1 { 15 | font-size: 18px; 16 | margin: 0; 17 | } 18 | 19 | .login #header h1 a { 20 | color: var(--header-link-color); 21 | } 22 | 23 | .login #content { 24 | padding: 20px 20px 0; 25 | } 26 | 27 | .login #container { 28 | background: var(--body-bg); 29 | border: 1px solid var(--hairline-color); 30 | border-radius: 4px; 31 | overflow: hidden; 32 | width: 28em; 33 | min-width: 300px; 34 | margin: 100px auto; 35 | height: auto; 36 | } 37 | 38 | .login .form-row { 39 | padding: 4px 0; 40 | } 41 | 42 | .login .form-row label { 43 | display: block; 44 | line-height: 2em; 45 | } 46 | 47 | .login .form-row #id_username, .login .form-row #id_password { 48 | padding: 8px; 49 | width: 100%; 50 | box-sizing: border-box; 51 | } 52 | 53 | .login .submit-row { 54 | padding: 1em 0 0 0; 55 | margin: 0; 56 | text-align: center; 57 | } 58 | 59 | .login .password-reset-link { 60 | text-align: center; 61 | } 62 | -------------------------------------------------------------------------------- /static/admin/css/nav_sidebar.css: -------------------------------------------------------------------------------- 1 | .sticky { 2 | position: sticky; 3 | top: 0; 4 | max-height: 100vh; 5 | } 6 | 7 | .toggle-nav-sidebar { 8 | z-index: 20; 9 | left: 0; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | flex: 0 0 23px; 14 | width: 23px; 15 | border: 0; 16 | border-right: 1px solid var(--hairline-color); 17 | background-color: var(--body-bg); 18 | cursor: pointer; 19 | font-size: 20px; 20 | color: var(--link-fg); 21 | padding: 0; 22 | } 23 | 24 | [dir="rtl"] .toggle-nav-sidebar { 25 | border-left: 1px solid var(--hairline-color); 26 | border-right: 0; 27 | } 28 | 29 | .toggle-nav-sidebar:hover, 30 | .toggle-nav-sidebar:focus { 31 | background-color: var(--darkened-bg); 32 | } 33 | 34 | #nav-sidebar { 35 | z-index: 15; 36 | flex: 0 0 275px; 37 | left: -276px; 38 | margin-left: -276px; 39 | border-top: 1px solid transparent; 40 | border-right: 1px solid var(--hairline-color); 41 | background-color: var(--body-bg); 42 | overflow: auto; 43 | } 44 | 45 | [dir="rtl"] #nav-sidebar { 46 | border-left: 1px solid var(--hairline-color); 47 | border-right: 0; 48 | left: 0; 49 | margin-left: 0; 50 | right: -276px; 51 | margin-right: -276px; 52 | } 53 | 54 | .toggle-nav-sidebar::before { 55 | content: '\00BB'; 56 | } 57 | 58 | .main.shifted .toggle-nav-sidebar::before { 59 | content: '\00AB'; 60 | } 61 | 62 | .main.shifted > #nav-sidebar { 63 | margin-left: 0; 64 | } 65 | 66 | [dir="rtl"] .main.shifted > #nav-sidebar { 67 | margin-right: 0; 68 | } 69 | 70 | #nav-sidebar .module th { 71 | width: 100%; 72 | overflow-wrap: anywhere; 73 | } 74 | 75 | #nav-sidebar .module th, 76 | #nav-sidebar .module caption { 77 | padding-left: 16px; 78 | } 79 | 80 | #nav-sidebar .module td { 81 | white-space: nowrap; 82 | } 83 | 84 | [dir="rtl"] #nav-sidebar .module th, 85 | [dir="rtl"] #nav-sidebar .module caption { 86 | padding-left: 8px; 87 | padding-right: 16px; 88 | } 89 | 90 | #nav-sidebar .current-app .section:link, 91 | #nav-sidebar .current-app .section:visited { 92 | color: var(--header-color); 93 | font-weight: bold; 94 | } 95 | 96 | #nav-sidebar .current-model { 97 | background: var(--selected-row); 98 | } 99 | 100 | .main > #nav-sidebar + .content { 101 | max-width: calc(100% - 23px); 102 | } 103 | 104 | .main.shifted > #nav-sidebar + .content { 105 | max-width: calc(100% - 299px); 106 | } 107 | 108 | @media (max-width: 767px) { 109 | #nav-sidebar, #toggle-nav-sidebar { 110 | display: none; 111 | } 112 | 113 | .main > #nav-sidebar + .content, 114 | .main.shifted > #nav-sidebar + .content { 115 | max-width: 100%; 116 | } 117 | } 118 | 119 | #nav-filter { 120 | width: 100%; 121 | box-sizing: border-box; 122 | padding: 2px 5px; 123 | margin: 5px 0; 124 | border: 1px solid var(--border-color); 125 | background-color: var(--darkened-bg); 126 | color: var(--body-fg); 127 | } 128 | 129 | #nav-filter:focus { 130 | border-color: var(--body-quiet-color); 131 | } 132 | 133 | #nav-filter.no-results { 134 | background: var(--message-error-bg); 135 | } 136 | 137 | #nav-sidebar table { 138 | width: 100%; 139 | } 140 | -------------------------------------------------------------------------------- /static/admin/css/responsive_rtl.css: -------------------------------------------------------------------------------- 1 | /* TABLETS */ 2 | 3 | @media (max-width: 1024px) { 4 | [dir="rtl"] .colMS { 5 | margin-right: 0; 6 | } 7 | 8 | [dir="rtl"] #user-tools { 9 | text-align: right; 10 | } 11 | 12 | [dir="rtl"] #changelist .actions label { 13 | padding-left: 10px; 14 | padding-right: 0; 15 | } 16 | 17 | [dir="rtl"] #changelist .actions select { 18 | margin-left: 0; 19 | margin-right: 15px; 20 | } 21 | 22 | [dir="rtl"] .change-list .filtered .results, 23 | [dir="rtl"] .change-list .filtered .paginator, 24 | [dir="rtl"] .filtered #toolbar, 25 | [dir="rtl"] .filtered div.xfull, 26 | [dir="rtl"] .filtered .actions, 27 | [dir="rtl"] #changelist-filter { 28 | margin-left: 0; 29 | } 30 | 31 | [dir="rtl"] .inline-group ul.tools a.add, 32 | [dir="rtl"] .inline-group div.add-row a, 33 | [dir="rtl"] .inline-group .tabular tr.add-row td a { 34 | padding: 8px 26px 8px 10px; 35 | background-position: calc(100% - 8px) 9px; 36 | } 37 | 38 | [dir="rtl"] .related-widget-wrapper-link + .selector { 39 | margin-right: 0; 40 | margin-left: 15px; 41 | } 42 | 43 | [dir="rtl"] .selector .selector-filter label { 44 | margin-right: 0; 45 | margin-left: 8px; 46 | } 47 | 48 | [dir="rtl"] .object-tools li { 49 | float: right; 50 | } 51 | 52 | [dir="rtl"] .object-tools li + li { 53 | margin-left: 0; 54 | margin-right: 15px; 55 | } 56 | 57 | [dir="rtl"] .dashboard .module table td a { 58 | padding-left: 0; 59 | padding-right: 16px; 60 | } 61 | } 62 | 63 | /* MOBILE */ 64 | 65 | @media (max-width: 767px) { 66 | [dir="rtl"] .aligned .related-lookup, 67 | [dir="rtl"] .aligned .datetimeshortcuts { 68 | margin-left: 0; 69 | margin-right: 15px; 70 | } 71 | 72 | [dir="rtl"] .aligned ul { 73 | margin-right: 0; 74 | } 75 | 76 | [dir="rtl"] #changelist-filter { 77 | margin-left: 0; 78 | margin-right: 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /static/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | /* GLOBAL */ 2 | 3 | th { 4 | text-align: right; 5 | } 6 | 7 | .module h2, .module caption { 8 | text-align: right; 9 | } 10 | 11 | .module ul, .module ol { 12 | margin-left: 0; 13 | margin-right: 1.5em; 14 | } 15 | 16 | .viewlink, .addlink, .changelink { 17 | padding-left: 0; 18 | padding-right: 16px; 19 | background-position: 100% 1px; 20 | } 21 | 22 | .deletelink { 23 | padding-left: 0; 24 | padding-right: 16px; 25 | background-position: 100% 1px; 26 | } 27 | 28 | .object-tools { 29 | float: left; 30 | } 31 | 32 | thead th:first-child, 33 | tfoot td:first-child { 34 | border-left: none; 35 | } 36 | 37 | /* LAYOUT */ 38 | 39 | #user-tools { 40 | right: auto; 41 | left: 0; 42 | text-align: left; 43 | } 44 | 45 | div.breadcrumbs { 46 | text-align: right; 47 | } 48 | 49 | #content-main { 50 | float: right; 51 | } 52 | 53 | #content-related { 54 | float: left; 55 | margin-left: -300px; 56 | margin-right: auto; 57 | } 58 | 59 | .colMS { 60 | margin-left: 300px; 61 | margin-right: 0; 62 | } 63 | 64 | /* SORTABLE TABLES */ 65 | 66 | table thead th.sorted .sortoptions { 67 | float: left; 68 | } 69 | 70 | thead th.sorted .text { 71 | padding-right: 0; 72 | padding-left: 42px; 73 | } 74 | 75 | /* dashboard styles */ 76 | 77 | .dashboard .module table td a { 78 | padding-left: .6em; 79 | padding-right: 16px; 80 | } 81 | 82 | /* changelists styles */ 83 | 84 | .change-list .filtered table { 85 | border-left: none; 86 | border-right: 0px none; 87 | } 88 | 89 | #changelist-filter { 90 | border-left: none; 91 | border-right: none; 92 | margin-left: 0; 93 | margin-right: 30px; 94 | } 95 | 96 | #changelist-filter li.selected { 97 | border-left: none; 98 | padding-left: 10px; 99 | margin-left: 0; 100 | border-right: 5px solid var(--hairline-color); 101 | padding-right: 10px; 102 | margin-right: -15px; 103 | } 104 | 105 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 106 | border-right: none; 107 | border-left: none; 108 | } 109 | 110 | /* FORMS */ 111 | 112 | .aligned label { 113 | padding: 0 0 3px 1em; 114 | float: right; 115 | } 116 | 117 | .submit-row { 118 | text-align: left 119 | } 120 | 121 | .submit-row p.deletelink-box { 122 | float: right; 123 | } 124 | 125 | .submit-row input.default { 126 | margin-left: 0; 127 | } 128 | 129 | .vDateField, .vTimeField { 130 | margin-left: 2px; 131 | } 132 | 133 | .aligned .form-row input { 134 | margin-left: 5px; 135 | } 136 | 137 | form .aligned p.help, form .aligned div.help { 138 | clear: right; 139 | } 140 | 141 | form .aligned ul { 142 | margin-right: 163px; 143 | margin-left: 0; 144 | } 145 | 146 | form ul.inline li { 147 | float: right; 148 | padding-right: 0; 149 | padding-left: 7px; 150 | } 151 | 152 | input[type=submit].default, .submit-row input.default { 153 | float: left; 154 | } 155 | 156 | fieldset .fieldBox { 157 | float: right; 158 | margin-left: 20px; 159 | margin-right: 0; 160 | } 161 | 162 | .errorlist li { 163 | background-position: 100% 12px; 164 | padding: 0; 165 | } 166 | 167 | .errornote { 168 | background-position: 100% 12px; 169 | padding: 10px 12px; 170 | } 171 | 172 | /* WIDGETS */ 173 | 174 | .calendarnav-previous { 175 | top: 0; 176 | left: auto; 177 | right: 10px; 178 | } 179 | 180 | .calendarnav-next { 181 | top: 0; 182 | right: auto; 183 | left: 10px; 184 | } 185 | 186 | .calendar caption, .calendarbox h2 { 187 | text-align: center; 188 | } 189 | 190 | .selector { 191 | float: right; 192 | } 193 | 194 | .selector .selector-filter { 195 | text-align: right; 196 | } 197 | 198 | .inline-deletelink { 199 | float: left; 200 | } 201 | 202 | form .form-row p.datetime { 203 | overflow: hidden; 204 | } 205 | 206 | .related-widget-wrapper { 207 | float: right; 208 | } 209 | 210 | /* MISC */ 211 | 212 | .inline-related h2, .inline-group h2 { 213 | text-align: right 214 | } 215 | 216 | .inline-related h3 span.delete { 217 | padding-right: 20px; 218 | padding-left: inherit; 219 | left: 10px; 220 | right: inherit; 221 | float:left; 222 | } 223 | 224 | .inline-related h3 span.delete label { 225 | margin-left: inherit; 226 | margin-right: 2px; 227 | } 228 | -------------------------------------------------------------------------------- /static/admin/css/vendor/select2/LICENSE-SELECT2.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/admin/fonts/README.txt: -------------------------------------------------------------------------------- 1 | Roboto webfont source: https://www.google.com/fonts/specimen/Roboto 2 | WOFF files extracted using https://github.com/majodev/google-webfonts-helper 3 | Weights used in this project: Light (300), Regular (400), Bold (700) 4 | -------------------------------------------------------------------------------- /static/admin/fonts/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/static/admin/fonts/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /static/admin/fonts/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/static/admin/fonts/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /static/admin/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/static/admin/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /static/admin/img/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Code Charm Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /static/admin/img/README.txt: -------------------------------------------------------------------------------- 1 | All icons are taken from Font Awesome (http://fontawesome.io/) project. 2 | The Font Awesome font is licensed under the SIL OFL 1.1: 3 | - https://scripts.sil.org/OFL 4 | 5 | SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG 6 | Font-Awesome-SVG-PNG is licensed under the MIT license (see file license 7 | in current folder). 8 | -------------------------------------------------------------------------------- /static/admin/img/calendar-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/admin/img/gis/move_vertex_on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/admin/img/icon-addlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/admin/img/icon-changelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/admin/img/icon-deletelink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-no.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-unknown-alt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-unknown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-viewlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/icon-yes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/inline-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/selector-icons.svg: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /static/admin/img/sorting-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /static/admin/img/tooltag-add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/img/tooltag-arrowright.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const SelectBox = { 4 | cache: {}, 5 | init: function(id) { 6 | const box = document.getElementById(id); 7 | SelectBox.cache[id] = []; 8 | const cache = SelectBox.cache[id]; 9 | for (const node of box.options) { 10 | cache.push({value: node.value, text: node.text, displayed: 1}); 11 | } 12 | }, 13 | redisplay: function(id) { 14 | // Repopulate HTML select box from cache 15 | const box = document.getElementById(id); 16 | const scroll_value_from_top = box.scrollTop; 17 | box.innerHTML = ''; 18 | for (const node of SelectBox.cache[id]) { 19 | if (node.displayed) { 20 | const new_option = new Option(node.text, node.value, false, false); 21 | // Shows a tooltip when hovering over the option 22 | new_option.title = node.text; 23 | box.appendChild(new_option); 24 | } 25 | } 26 | box.scrollTop = scroll_value_from_top; 27 | }, 28 | filter: function(id, text) { 29 | // Redisplay the HTML select box, displaying only the choices containing ALL 30 | // the words in text. (It's an AND search.) 31 | const tokens = text.toLowerCase().split(/\s+/); 32 | for (const node of SelectBox.cache[id]) { 33 | node.displayed = 1; 34 | const node_text = node.text.toLowerCase(); 35 | for (const token of tokens) { 36 | if (!node_text.includes(token)) { 37 | node.displayed = 0; 38 | break; // Once the first token isn't found we're done 39 | } 40 | } 41 | } 42 | SelectBox.redisplay(id); 43 | }, 44 | delete_from_cache: function(id, value) { 45 | let delete_index = null; 46 | const cache = SelectBox.cache[id]; 47 | for (const [i, node] of cache.entries()) { 48 | if (node.value === value) { 49 | delete_index = i; 50 | break; 51 | } 52 | } 53 | cache.splice(delete_index, 1); 54 | }, 55 | add_to_cache: function(id, option) { 56 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 57 | }, 58 | cache_contains: function(id, value) { 59 | // Check if an item is contained in the cache 60 | for (const node of SelectBox.cache[id]) { 61 | if (node.value === value) { 62 | return true; 63 | } 64 | } 65 | return false; 66 | }, 67 | move: function(from, to) { 68 | const from_box = document.getElementById(from); 69 | for (const option of from_box.options) { 70 | const option_value = option.value; 71 | if (option.selected && SelectBox.cache_contains(from, option_value)) { 72 | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); 73 | SelectBox.delete_from_cache(from, option_value); 74 | } 75 | } 76 | SelectBox.redisplay(from); 77 | SelectBox.redisplay(to); 78 | }, 79 | move_all: function(from, to) { 80 | const from_box = document.getElementById(from); 81 | for (const option of from_box.options) { 82 | const option_value = option.value; 83 | if (SelectBox.cache_contains(from, option_value)) { 84 | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); 85 | SelectBox.delete_from_cache(from, option_value); 86 | } 87 | } 88 | SelectBox.redisplay(from); 89 | SelectBox.redisplay(to); 90 | }, 91 | sort: function(id) { 92 | SelectBox.cache[id].sort(function(a, b) { 93 | a = a.text.toLowerCase(); 94 | b = b.text.toLowerCase(); 95 | if (a > b) { 96 | return 1; 97 | } 98 | if (a < b) { 99 | return -1; 100 | } 101 | return 0; 102 | } ); 103 | }, 104 | select_all: function(id) { 105 | const box = document.getElementById(id); 106 | for (const option of box.options) { 107 | option.selected = true; 108 | } 109 | } 110 | }; 111 | window.SelectBox = SelectBox; 112 | } 113 | -------------------------------------------------------------------------------- /static/admin/js/autocomplete.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const $ = django.jQuery; 4 | 5 | $.fn.djangoAdminSelect2 = function() { 6 | $.each(this, function(i, element) { 7 | $(element).select2({ 8 | ajax: { 9 | data: (params) => { 10 | return { 11 | term: params.term, 12 | page: params.page, 13 | app_label: element.dataset.appLabel, 14 | model_name: element.dataset.modelName, 15 | field_name: element.dataset.fieldName 16 | }; 17 | } 18 | } 19 | }); 20 | }); 21 | return this; 22 | }; 23 | 24 | $(function() { 25 | // Initialize all autocomplete widgets except the one in the template 26 | // form used when a new formset is added. 27 | $('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2(); 28 | }); 29 | 30 | $(document).on('formset:added', (function() { 31 | return function(event, $newFormset) { 32 | return $newFormset.find('.admin-autocomplete').djangoAdminSelect2(); 33 | }; 34 | })(this)); 35 | } 36 | -------------------------------------------------------------------------------- /static/admin/js/cancel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | // Call function fn when the DOM is loaded and ready. If it is already 4 | // loaded, call the function now. 5 | // http://youmightnotneedjquery.com/#ready 6 | function ready(fn) { 7 | if (document.readyState !== 'loading') { 8 | fn(); 9 | } else { 10 | document.addEventListener('DOMContentLoaded', fn); 11 | } 12 | } 13 | 14 | ready(function() { 15 | function handleClick(event) { 16 | event.preventDefault(); 17 | const params = new URLSearchParams(window.location.search); 18 | if (params.has('_popup')) { 19 | window.close(); // Close the popup. 20 | } else { 21 | window.history.back(); // Otherwise, go back. 22 | } 23 | } 24 | 25 | document.querySelectorAll('.cancel-link').forEach(function(el) { 26 | el.addEventListener('click', handleClick); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /static/admin/js/change_form.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const inputTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA']; 4 | const modelName = document.getElementById('django-admin-form-add-constants').dataset.modelName; 5 | if (modelName) { 6 | const form = document.getElementById(modelName + '_form'); 7 | for (const element of form.elements) { 8 | // HTMLElement.offsetParent returns null when the element is not 9 | // rendered. 10 | if (inputTags.includes(element.tagName) && !element.disabled && element.offsetParent) { 11 | element.focus(); 12 | break; 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /static/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | /*global gettext*/ 2 | 'use strict'; 3 | { 4 | window.addEventListener('load', function() { 5 | // Add anchor tag for Show/Hide link 6 | const fieldsets = document.querySelectorAll('fieldset.collapse'); 7 | for (const [i, elem] of fieldsets.entries()) { 8 | // Don't hide if fields in this fieldset have errors 9 | if (elem.querySelectorAll('div.errors, ul.errorlist').length === 0) { 10 | elem.classList.add('collapsed'); 11 | const h2 = elem.querySelector('h2'); 12 | const link = document.createElement('a'); 13 | link.id = 'fieldsetcollapser' + i; 14 | link.className = 'collapse-toggle'; 15 | link.href = '#'; 16 | link.textContent = gettext('Show'); 17 | h2.appendChild(document.createTextNode(' (')); 18 | h2.appendChild(link); 19 | h2.appendChild(document.createTextNode(')')); 20 | } 21 | } 22 | // Add toggle to hide/show anchor tag 23 | const toggleFunc = function(ev) { 24 | if (ev.target.matches('.collapse-toggle')) { 25 | ev.preventDefault(); 26 | ev.stopPropagation(); 27 | const fieldset = ev.target.closest('fieldset'); 28 | if (fieldset.classList.contains('collapsed')) { 29 | // Show 30 | ev.target.textContent = gettext('Hide'); 31 | fieldset.classList.remove('collapsed'); 32 | } else { 33 | // Hide 34 | ev.target.textContent = gettext('Show'); 35 | fieldset.classList.add('collapsed'); 36 | } 37 | } 38 | }; 39 | document.querySelectorAll('fieldset.module').forEach(function(el) { 40 | el.addEventListener('click', toggleFunc); 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /static/admin/js/jquery.init.js: -------------------------------------------------------------------------------- 1 | /*global jQuery:false*/ 2 | 'use strict'; 3 | /* Puts the included jQuery into our own namespace using noConflict and passing 4 | * it 'true'. This ensures that the included jQuery doesn't pollute the global 5 | * namespace (i.e. this preserves pre-existing values for both window.$ and 6 | * window.jQuery). 7 | */ 8 | window.django = {jQuery: jQuery.noConflict(true)}; 9 | -------------------------------------------------------------------------------- /static/admin/js/nav_sidebar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const toggleNavSidebar = document.getElementById('toggle-nav-sidebar'); 4 | if (toggleNavSidebar !== null) { 5 | const navLinks = document.querySelectorAll('#nav-sidebar a'); 6 | function disableNavLinkTabbing() { 7 | for (const navLink of navLinks) { 8 | navLink.tabIndex = -1; 9 | } 10 | } 11 | function enableNavLinkTabbing() { 12 | for (const navLink of navLinks) { 13 | navLink.tabIndex = 0; 14 | } 15 | } 16 | 17 | const main = document.getElementById('main'); 18 | let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen'); 19 | if (navSidebarIsOpen === null) { 20 | navSidebarIsOpen = 'true'; 21 | } 22 | if (navSidebarIsOpen === 'false') { 23 | disableNavLinkTabbing(); 24 | } 25 | main.classList.toggle('shifted', navSidebarIsOpen === 'true'); 26 | 27 | toggleNavSidebar.addEventListener('click', function() { 28 | if (navSidebarIsOpen === 'true') { 29 | navSidebarIsOpen = 'false'; 30 | disableNavLinkTabbing(); 31 | } else { 32 | navSidebarIsOpen = 'true'; 33 | enableNavLinkTabbing(); 34 | } 35 | localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen); 36 | main.classList.toggle('shifted'); 37 | }); 38 | } 39 | 40 | function initSidebarQuickFilter() { 41 | const options = []; 42 | const navSidebar = document.getElementById('nav-sidebar'); 43 | if (!navSidebar) { 44 | return; 45 | } 46 | navSidebar.querySelectorAll('th[scope=row] a').forEach((container) => { 47 | options.push({title: container.innerHTML, node: container}); 48 | }); 49 | 50 | function checkValue(event) { 51 | let filterValue = event.target.value; 52 | if (filterValue) { 53 | filterValue = filterValue.toLowerCase(); 54 | } 55 | if (event.key === 'Escape') { 56 | filterValue = ''; 57 | event.target.value = ''; // clear input 58 | } 59 | let matches = false; 60 | for (const o of options) { 61 | let displayValue = ''; 62 | if (filterValue) { 63 | if (o.title.toLowerCase().indexOf(filterValue) === -1) { 64 | displayValue = 'none'; 65 | } else { 66 | matches = true; 67 | } 68 | } 69 | // show/hide parent 70 | o.node.parentNode.parentNode.style.display = displayValue; 71 | } 72 | if (!filterValue || matches) { 73 | event.target.classList.remove('no-results'); 74 | } else { 75 | event.target.classList.add('no-results'); 76 | } 77 | sessionStorage.setItem('django.admin.navSidebarFilterValue', filterValue); 78 | } 79 | 80 | const nav = document.getElementById('nav-filter'); 81 | nav.addEventListener('change', checkValue, false); 82 | nav.addEventListener('input', checkValue, false); 83 | nav.addEventListener('keyup', checkValue, false); 84 | 85 | const storedValue = sessionStorage.getItem('django.admin.navSidebarFilterValue'); 86 | if (storedValue) { 87 | nav.value = storedValue; 88 | checkValue({target: nav, key: ''}); 89 | } 90 | } 91 | window.initSidebarQuickFilter = initSidebarQuickFilter; 92 | initSidebarQuickFilter(); 93 | } 94 | -------------------------------------------------------------------------------- /static/admin/js/popup_response.js: -------------------------------------------------------------------------------- 1 | /*global opener */ 2 | 'use strict'; 3 | { 4 | const initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse); 5 | switch(initData.action) { 6 | case 'change': 7 | opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value); 8 | break; 9 | case 'delete': 10 | opener.dismissDeleteRelatedObjectPopup(window, initData.value); 11 | break; 12 | default: 13 | opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj); 14 | break; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate.js: -------------------------------------------------------------------------------- 1 | /*global URLify*/ 2 | 'use strict'; 3 | { 4 | const $ = django.jQuery; 5 | $.fn.prepopulate = function(dependencies, maxLength, allowUnicode) { 6 | /* 7 | Depends on urlify.js 8 | Populates a selected field with the values of the dependent fields, 9 | URLifies and shortens the string. 10 | dependencies - array of dependent fields ids 11 | maxLength - maximum length of the URLify'd string 12 | allowUnicode - Unicode support of the URLify'd string 13 | */ 14 | return this.each(function() { 15 | const prepopulatedField = $(this); 16 | 17 | const populate = function() { 18 | // Bail if the field's value has been changed by the user 19 | if (prepopulatedField.data('_changed')) { 20 | return; 21 | } 22 | 23 | const values = []; 24 | $.each(dependencies, function(i, field) { 25 | field = $(field); 26 | if (field.val().length > 0) { 27 | values.push(field.val()); 28 | } 29 | }); 30 | prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode)); 31 | }; 32 | 33 | prepopulatedField.data('_changed', false); 34 | prepopulatedField.on('change', function() { 35 | prepopulatedField.data('_changed', true); 36 | }); 37 | 38 | if (!prepopulatedField.val()) { 39 | $(dependencies.join(',')).on('keyup change focus', populate); 40 | } 41 | }); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /static/admin/js/prepopulate_init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const $ = django.jQuery; 4 | const fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields'); 5 | $.each(fields, function(index, field) { 6 | $('.empty-form .form-row .field-' + field.name + ', .empty-form.form-row .field-' + field.name).addClass('prepopulated_field'); 7 | $(field.id).data('dependency_list', field.dependency_list).prepopulate( 8 | field.dependency_ids, field.maxLength, field.allowUnicode 9 | ); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /static/admin/js/vendor/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/af.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/af",[],function(){return{errorLoading:function(){return"Die resultate kon nie gelaai word nie."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Verwyders asseblief "+n+" character";return 1!=n&&(r+="s"),r},inputTooShort:function(e){return"Voer asseblief "+(e.minimum-e.input.length)+" of meer karakters"},loadingMore:function(){return"Meer resultate word gelaai…"},maximumSelected:function(e){var n="Kies asseblief net "+e.maximum+" item";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"Geen resultate gevind"},searching:function(){return"Besig…"},removeAllItems:function(){return"Verwyder alle items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ar.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ar",[],function(){return{errorLoading:function(){return"لا يمكن تحميل النتائج"},inputTooLong:function(n){return"الرجاء حذف "+(n.input.length-n.maximum)+" عناصر"},inputTooShort:function(n){return"الرجاء إضافة "+(n.minimum-n.input.length)+" عناصر"},loadingMore:function(){return"جاري تحميل نتائج إضافية..."},maximumSelected:function(n){return"تستطيع إختيار "+n.maximum+" بنود فقط"},noResults:function(){return"لم يتم العثور على أي نتائج"},searching:function(){return"جاري البحث…"},removeAllItems:function(){return"قم بإزالة كل العناصر"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/az.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/az",[],function(){return{inputTooLong:function(n){return n.input.length-n.maximum+" simvol silin"},inputTooShort:function(n){return n.minimum-n.input.length+" simvol daxil edin"},loadingMore:function(){return"Daha çox nəticə yüklənir…"},maximumSelected:function(n){return"Sadəcə "+n.maximum+" element seçə bilərsiniz"},noResults:function(){return"Nəticə tapılmadı"},searching:function(){return"Axtarılır…"},removeAllItems:function(){return"Bütün elementləri sil"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/bg.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/bg",[],function(){return{inputTooLong:function(n){var e=n.input.length-n.maximum,u="Моля въведете с "+e+" по-малко символ";return e>1&&(u+="a"),u},inputTooShort:function(n){var e=n.minimum-n.input.length,u="Моля въведете още "+e+" символ";return e>1&&(u+="a"),u},loadingMore:function(){return"Зареждат се още…"},maximumSelected:function(n){var e="Можете да направите до "+n.maximum+" ";return n.maximum>1?e+="избора":e+="избор",e},noResults:function(){return"Няма намерени съвпадения"},searching:function(){return"Търсене…"},removeAllItems:function(){return"Премахнете всички елементи"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/bn.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/bn",[],function(){return{errorLoading:function(){return"ফলাফলগুলি লোড করা যায়নি।"},inputTooLong:function(n){var e=n.input.length-n.maximum,u="অনুগ্রহ করে "+e+" টি অক্ষর মুছে দিন।";return 1!=e&&(u="অনুগ্রহ করে "+e+" টি অক্ষর মুছে দিন।"),u},inputTooShort:function(n){return n.minimum-n.input.length+" টি অক্ষর অথবা অধিক অক্ষর লিখুন।"},loadingMore:function(){return"আরো ফলাফল লোড হচ্ছে ..."},maximumSelected:function(n){var e=n.maximum+" টি আইটেম নির্বাচন করতে পারবেন।";return 1!=n.maximum&&(e=n.maximum+" টি আইটেম নির্বাচন করতে পারবেন।"),e},noResults:function(){return"কোন ফলাফল পাওয়া যায়নি।"},searching:function(){return"অনুসন্ধান করা হচ্ছে ..."}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/bs.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/bs",[],function(){function e(e,n,r,t){return e%10==1&&e%100!=11?n:e%10>=2&&e%10<=4&&(e%100<12||e%100>14)?r:t}return{errorLoading:function(){return"Preuzimanje nije uspijelo."},inputTooLong:function(n){var r=n.input.length-n.maximum,t="Obrišite "+r+" simbol";return t+=e(r,"","a","a")},inputTooShort:function(n){var r=n.minimum-n.input.length,t="Ukucajte bar još "+r+" simbol";return t+=e(r,"","a","a")},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(n){var r="Možete izabrati samo "+n.maximum+" stavk";return r+=e(n.maximum,"u","e","i")},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Uklonite sve stavke"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ca.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/ca",[],function(){return{errorLoading:function(){return"La càrrega ha fallat"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Si us plau, elimina "+n+" car";return r+=1==n?"àcter":"àcters"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Si us plau, introdueix "+n+" car";return r+=1==n?"àcter":"àcters"},loadingMore:function(){return"Carregant més resultats…"},maximumSelected:function(e){var n="Només es pot seleccionar "+e.maximum+" element";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No s'han trobat resultats"},searching:function(){return"Cercant…"},removeAllItems:function(){return"Treu tots els elements"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/cs.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/cs",[],function(){function e(e,n){switch(e){case 2:return n?"dva":"dvě";case 3:return"tři";case 4:return"čtyři"}return""}return{errorLoading:function(){return"Výsledky nemohly být načteny."},inputTooLong:function(n){var t=n.input.length-n.maximum;return 1==t?"Prosím, zadejte o jeden znak méně.":t<=4?"Prosím, zadejte o "+e(t,!0)+" znaky méně.":"Prosím, zadejte o "+t+" znaků méně."},inputTooShort:function(n){var t=n.minimum-n.input.length;return 1==t?"Prosím, zadejte ještě jeden znak.":t<=4?"Prosím, zadejte ještě další "+e(t,!0)+" znaky.":"Prosím, zadejte ještě dalších "+t+" znaků."},loadingMore:function(){return"Načítají se další výsledky…"},maximumSelected:function(n){var t=n.maximum;return 1==t?"Můžete zvolit jen jednu položku.":t<=4?"Můžete zvolit maximálně "+e(t,!1)+" položky.":"Můžete zvolit maximálně "+t+" položek."},noResults:function(){return"Nenalezeny žádné položky."},searching:function(){return"Vyhledávání…"},removeAllItems:function(){return"Odstraňte všechny položky"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/da.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/da",[],function(){return{errorLoading:function(){return"Resultaterne kunne ikke indlæses."},inputTooLong:function(e){return"Angiv venligst "+(e.input.length-e.maximum)+" tegn mindre"},inputTooShort:function(e){return"Angiv venligst "+(e.minimum-e.input.length)+" tegn mere"},loadingMore:function(){return"Indlæser flere resultater…"},maximumSelected:function(e){var n="Du kan kun vælge "+e.maximum+" emne";return 1!=e.maximum&&(n+="r"),n},noResults:function(){return"Ingen resultater fundet"},searching:function(){return"Søger…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/de.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/de",[],function(){return{errorLoading:function(){return"Die Ergebnisse konnten nicht geladen werden."},inputTooLong:function(e){return"Bitte "+(e.input.length-e.maximum)+" Zeichen weniger eingeben"},inputTooShort:function(e){return"Bitte "+(e.minimum-e.input.length)+" Zeichen mehr eingeben"},loadingMore:function(){return"Lade mehr Ergebnisse…"},maximumSelected:function(e){var n="Sie können nur "+e.maximum+" Element";return 1!=e.maximum&&(n+="e"),n+=" auswählen"},noResults:function(){return"Keine Übereinstimmungen gefunden"},searching:function(){return"Suche…"},removeAllItems:function(){return"Entferne alle Elemente"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/dsb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/dsb",[],function(){var n=["znamuško","znamušce","znamuška","znamuškow"],e=["zapisk","zapiska","zapiski","zapiskow"],u=function(n,e){return 1===n?e[0]:2===n?e[1]:n>2&&n<=4?e[2]:n>=5?e[3]:void 0};return{errorLoading:function(){return"Wuslědki njejsu se dali zacytaś."},inputTooLong:function(e){var a=e.input.length-e.maximum;return"Pšosym lašuj "+a+" "+u(a,n)},inputTooShort:function(e){var a=e.minimum-e.input.length;return"Pšosym zapódaj nanejmjenjej "+a+" "+u(a,n)},loadingMore:function(){return"Dalšne wuslědki se zacytaju…"},maximumSelected:function(n){return"Móžoš jano "+n.maximum+" "+u(n.maximum,e)+"wubraś."},noResults:function(){return"Žedne wuslědki namakane"},searching:function(){return"Pyta se…"},removeAllItems:function(){return"Remove all items"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/el.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/el",[],function(){return{errorLoading:function(){return"Τα αποτελέσματα δεν μπόρεσαν να φορτώσουν."},inputTooLong:function(n){var e=n.input.length-n.maximum,u="Παρακαλώ διαγράψτε "+e+" χαρακτήρ";return 1==e&&(u+="α"),1!=e&&(u+="ες"),u},inputTooShort:function(n){return"Παρακαλώ συμπληρώστε "+(n.minimum-n.input.length)+" ή περισσότερους χαρακτήρες"},loadingMore:function(){return"Φόρτωση περισσότερων αποτελεσμάτων…"},maximumSelected:function(n){var e="Μπορείτε να επιλέξετε μόνο "+n.maximum+" επιλογ";return 1==n.maximum&&(e+="ή"),1!=n.maximum&&(e+="ές"),e},noResults:function(){return"Δεν βρέθηκαν αποτελέσματα"},searching:function(){return"Αναζήτηση…"},removeAllItems:function(){return"Καταργήστε όλα τα στοιχεία"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/en.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Please delete "+n+" character";return 1!=n&&(r+="s"),r},inputTooShort:function(e){return"Please enter "+(e.minimum-e.input.length)+" or more characters"},loadingMore:function(){return"Loading more results…"},maximumSelected:function(e){var n="You can only select "+e.maximum+" item";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No results found"},searching:function(){return"Searching…"},removeAllItems:function(){return"Remove all items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/es.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Por favor, elimine "+n+" car";return r+=1==n?"ácter":"acteres"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Por favor, introduzca "+n+" car";return r+=1==n?"ácter":"acteres"},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var n="Sólo puede seleccionar "+e.maximum+" elemento";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Eliminar todos los elementos"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/et.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/et",[],function(){return{inputTooLong:function(e){var n=e.input.length-e.maximum,t="Sisesta "+n+" täht";return 1!=n&&(t+="e"),t+=" vähem"},inputTooShort:function(e){var n=e.minimum-e.input.length,t="Sisesta "+n+" täht";return 1!=n&&(t+="e"),t+=" rohkem"},loadingMore:function(){return"Laen tulemusi…"},maximumSelected:function(e){var n="Saad vaid "+e.maximum+" tulemus";return 1==e.maximum?n+="e":n+="t",n+=" valida"},noResults:function(){return"Tulemused puuduvad"},searching:function(){return"Otsin…"},removeAllItems:function(){return"Eemalda kõik esemed"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/eu.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/eu",[],function(){return{inputTooLong:function(e){var t=e.input.length-e.maximum,n="Idatzi ";return n+=1==t?"karaktere bat":t+" karaktere",n+=" gutxiago"},inputTooShort:function(e){var t=e.minimum-e.input.length,n="Idatzi ";return n+=1==t?"karaktere bat":t+" karaktere",n+=" gehiago"},loadingMore:function(){return"Emaitza gehiago kargatzen…"},maximumSelected:function(e){return 1===e.maximum?"Elementu bakarra hauta dezakezu":e.maximum+" elementu hauta ditzakezu soilik"},noResults:function(){return"Ez da bat datorrenik aurkitu"},searching:function(){return"Bilatzen…"},removeAllItems:function(){return"Kendu elementu guztiak"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/fa.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/fa",[],function(){return{errorLoading:function(){return"امکان بارگذاری نتایج وجود ندارد."},inputTooLong:function(n){return"لطفاً "+(n.input.length-n.maximum)+" کاراکتر را حذف نمایید"},inputTooShort:function(n){return"لطفاً تعداد "+(n.minimum-n.input.length)+" کاراکتر یا بیشتر وارد نمایید"},loadingMore:function(){return"در حال بارگذاری نتایج بیشتر..."},maximumSelected:function(n){return"شما تنها می‌توانید "+n.maximum+" آیتم را انتخاب نمایید"},noResults:function(){return"هیچ نتیجه‌ای یافت نشد"},searching:function(){return"در حال جستجو..."},removeAllItems:function(){return"همه موارد را حذف کنید"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/fi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/fi",[],function(){return{errorLoading:function(){return"Tuloksia ei saatu ladattua."},inputTooLong:function(n){return"Ole hyvä ja anna "+(n.input.length-n.maximum)+" merkkiä vähemmän"},inputTooShort:function(n){return"Ole hyvä ja anna "+(n.minimum-n.input.length)+" merkkiä lisää"},loadingMore:function(){return"Ladataan lisää tuloksia…"},maximumSelected:function(n){return"Voit valita ainoastaan "+n.maximum+" kpl"},noResults:function(){return"Ei tuloksia"},searching:function(){return"Haetaan…"},removeAllItems:function(){return"Poista kaikki kohteet"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/fr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/fr",[],function(){return{errorLoading:function(){return"Les résultats ne peuvent pas être chargés."},inputTooLong:function(e){var n=e.input.length-e.maximum;return"Supprimez "+n+" caractère"+(n>1?"s":"")},inputTooShort:function(e){var n=e.minimum-e.input.length;return"Saisissez au moins "+n+" caractère"+(n>1?"s":"")},loadingMore:function(){return"Chargement de résultats supplémentaires…"},maximumSelected:function(e){return"Vous pouvez seulement sélectionner "+e.maximum+" élément"+(e.maximum>1?"s":"")},noResults:function(){return"Aucun résultat trouvé"},searching:function(){return"Recherche en cours…"},removeAllItems:function(){return"Supprimer tous les éléments"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/gl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/gl",[],function(){return{errorLoading:function(){return"Non foi posíbel cargar os resultados."},inputTooLong:function(e){var n=e.input.length-e.maximum;return 1===n?"Elimine un carácter":"Elimine "+n+" caracteres"},inputTooShort:function(e){var n=e.minimum-e.input.length;return 1===n?"Engada un carácter":"Engada "+n+" caracteres"},loadingMore:function(){return"Cargando máis resultados…"},maximumSelected:function(e){return 1===e.maximum?"Só pode seleccionar un elemento":"Só pode seleccionar "+e.maximum+" elementos"},noResults:function(){return"Non se atoparon resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Elimina todos os elementos"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/he.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/he",[],function(){return{errorLoading:function(){return"שגיאה בטעינת התוצאות"},inputTooLong:function(n){var e=n.input.length-n.maximum,r="נא למחוק ";return r+=1===e?"תו אחד":e+" תווים"},inputTooShort:function(n){var e=n.minimum-n.input.length,r="נא להכניס ";return r+=1===e?"תו אחד":e+" תווים",r+=" או יותר"},loadingMore:function(){return"טוען תוצאות נוספות…"},maximumSelected:function(n){var e="באפשרותך לבחור עד ";return 1===n.maximum?e+="פריט אחד":e+=n.maximum+" פריטים",e},noResults:function(){return"לא נמצאו תוצאות"},searching:function(){return"מחפש…"},removeAllItems:function(){return"הסר את כל הפריטים"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/hi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hi",[],function(){return{errorLoading:function(){return"परिणामों को लोड नहीं किया जा सका।"},inputTooLong:function(n){var e=n.input.length-n.maximum,r=e+" अक्षर को हटा दें";return e>1&&(r=e+" अक्षरों को हटा दें "),r},inputTooShort:function(n){return"कृपया "+(n.minimum-n.input.length)+" या अधिक अक्षर दर्ज करें"},loadingMore:function(){return"अधिक परिणाम लोड हो रहे है..."},maximumSelected:function(n){return"आप केवल "+n.maximum+" आइटम का चयन कर सकते हैं"},noResults:function(){return"कोई परिणाम नहीं मिला"},searching:function(){return"खोज रहा है..."},removeAllItems:function(){return"सभी वस्तुओं को हटा दें"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/hr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hr",[],function(){function n(n){var e=" "+n+" znak";return n%10<5&&n%10>0&&(n%100<5||n%100>19)?n%10>1&&(e+="a"):e+="ova",e}return{errorLoading:function(){return"Preuzimanje nije uspjelo."},inputTooLong:function(e){return"Unesite "+n(e.input.length-e.maximum)},inputTooShort:function(e){return"Unesite još "+n(e.minimum-e.input.length)},loadingMore:function(){return"Učitavanje rezultata…"},maximumSelected:function(n){return"Maksimalan broj odabranih stavki je "+n.maximum},noResults:function(){return"Nema rezultata"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Ukloni sve stavke"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/hsb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hsb",[],function(){var n=["znamješko","znamješce","znamješka","znamješkow"],e=["zapisk","zapiskaj","zapiski","zapiskow"],u=function(n,e){return 1===n?e[0]:2===n?e[1]:n>2&&n<=4?e[2]:n>=5?e[3]:void 0};return{errorLoading:function(){return"Wuslědki njedachu so začitać."},inputTooLong:function(e){var a=e.input.length-e.maximum;return"Prošu zhašej "+a+" "+u(a,n)},inputTooShort:function(e){var a=e.minimum-e.input.length;return"Prošu zapodaj znajmjeńša "+a+" "+u(a,n)},loadingMore:function(){return"Dalše wuslědki so začitaja…"},maximumSelected:function(n){return"Móžeš jenož "+n.maximum+" "+u(n.maximum,e)+"wubrać"},noResults:function(){return"Žane wuslědki namakane"},searching:function(){return"Pyta so…"},removeAllItems:function(){return"Remove all items"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/hu.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/hu",[],function(){return{errorLoading:function(){return"Az eredmények betöltése nem sikerült."},inputTooLong:function(e){return"Túl hosszú. "+(e.input.length-e.maximum)+" karakterrel több, mint kellene."},inputTooShort:function(e){return"Túl rövid. Még "+(e.minimum-e.input.length)+" karakter hiányzik."},loadingMore:function(){return"Töltés…"},maximumSelected:function(e){return"Csak "+e.maximum+" elemet lehet kiválasztani."},noResults:function(){return"Nincs találat."},searching:function(){return"Keresés…"},removeAllItems:function(){return"Távolítson el minden elemet"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/hy.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/hy",[],function(){return{errorLoading:function(){return"Արդյունքները հնարավոր չէ բեռնել։"},inputTooLong:function(n){return"Խնդրում ենք հեռացնել "+(n.input.length-n.maximum)+" նշան"},inputTooShort:function(n){return"Խնդրում ենք մուտքագրել "+(n.minimum-n.input.length)+" կամ ավել նշաններ"},loadingMore:function(){return"Բեռնվում են նոր արդյունքներ․․․"},maximumSelected:function(n){return"Դուք կարող եք ընտրել առավելագույնը "+n.maximum+" կետ"},noResults:function(){return"Արդյունքներ չեն գտնվել"},searching:function(){return"Որոնում․․․"},removeAllItems:function(){return"Հեռացնել բոլոր տարրերը"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/id.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/id",[],function(){return{errorLoading:function(){return"Data tidak boleh diambil."},inputTooLong:function(n){return"Hapuskan "+(n.input.length-n.maximum)+" huruf"},inputTooShort:function(n){return"Masukkan "+(n.minimum-n.input.length)+" huruf lagi"},loadingMore:function(){return"Mengambil data…"},maximumSelected:function(n){return"Anda hanya dapat memilih "+n.maximum+" pilihan"},noResults:function(){return"Tidak ada data yang sesuai"},searching:function(){return"Mencari…"},removeAllItems:function(){return"Hapus semua item"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/is.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/is",[],function(){return{inputTooLong:function(n){var t=n.input.length-n.maximum,e="Vinsamlegast styttið texta um "+t+" staf";return t<=1?e:e+"i"},inputTooShort:function(n){var t=n.minimum-n.input.length,e="Vinsamlegast skrifið "+t+" staf";return t>1&&(e+="i"),e+=" í viðbót"},loadingMore:function(){return"Sæki fleiri niðurstöður…"},maximumSelected:function(n){return"Þú getur aðeins valið "+n.maximum+" atriði"},noResults:function(){return"Ekkert fannst"},searching:function(){return"Leita…"},removeAllItems:function(){return"Fjarlægðu öll atriði"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/it.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/it",[],function(){return{errorLoading:function(){return"I risultati non possono essere caricati."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Per favore cancella "+n+" caratter";return t+=1!==n?"i":"e"},inputTooShort:function(e){return"Per favore inserisci "+(e.minimum-e.input.length)+" o più caratteri"},loadingMore:function(){return"Caricando più risultati…"},maximumSelected:function(e){var n="Puoi selezionare solo "+e.maximum+" element";return 1!==e.maximum?n+="i":n+="o",n},noResults:function(){return"Nessun risultato trovato"},searching:function(){return"Sto cercando…"},removeAllItems:function(){return"Rimuovi tutti gli oggetti"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ja.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ja",[],function(){return{errorLoading:function(){return"結果が読み込まれませんでした"},inputTooLong:function(n){return n.input.length-n.maximum+" 文字を削除してください"},inputTooShort:function(n){return"少なくとも "+(n.minimum-n.input.length)+" 文字を入力してください"},loadingMore:function(){return"読み込み中…"},maximumSelected:function(n){return n.maximum+" 件しか選択できません"},noResults:function(){return"対象が見つかりません"},searching:function(){return"検索しています…"},removeAllItems:function(){return"すべてのアイテムを削除"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ka.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ka",[],function(){return{errorLoading:function(){return"მონაცემების ჩატვირთვა შეუძლებელია."},inputTooLong:function(n){return"გთხოვთ აკრიფეთ "+(n.input.length-n.maximum)+" სიმბოლოთი ნაკლები"},inputTooShort:function(n){return"გთხოვთ აკრიფეთ "+(n.minimum-n.input.length)+" სიმბოლო ან მეტი"},loadingMore:function(){return"მონაცემების ჩატვირთვა…"},maximumSelected:function(n){return"თქვენ შეგიძლიათ აირჩიოთ არაუმეტეს "+n.maximum+" ელემენტი"},noResults:function(){return"რეზულტატი არ მოიძებნა"},searching:function(){return"ძიება…"},removeAllItems:function(){return"ამოიღე ყველა ელემენტი"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/km.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/km",[],function(){return{errorLoading:function(){return"មិនអាចទាញយកទិន្នន័យ"},inputTooLong:function(n){return"សូមលុបចេញ "+(n.input.length-n.maximum)+" អក្សរ"},inputTooShort:function(n){return"សូមបញ្ចូល"+(n.minimum-n.input.length)+" អក្សរ រឺ ច្រើនជាងនេះ"},loadingMore:function(){return"កំពុងទាញយកទិន្នន័យបន្ថែម..."},maximumSelected:function(n){return"អ្នកអាចជ្រើសរើសបានតែ "+n.maximum+" ជម្រើសប៉ុណ្ណោះ"},noResults:function(){return"មិនមានលទ្ធផល"},searching:function(){return"កំពុងស្វែងរក..."},removeAllItems:function(){return"លុបធាតុទាំងអស់"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ko.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ko",[],function(){return{errorLoading:function(){return"결과를 불러올 수 없습니다."},inputTooLong:function(n){return"너무 깁니다. "+(n.input.length-n.maximum)+" 글자 지워주세요."},inputTooShort:function(n){return"너무 짧습니다. "+(n.minimum-n.input.length)+" 글자 더 입력해주세요."},loadingMore:function(){return"불러오는 중…"},maximumSelected:function(n){return"최대 "+n.maximum+"개까지만 선택 가능합니다."},noResults:function(){return"결과가 없습니다."},searching:function(){return"검색 중…"},removeAllItems:function(){return"모든 항목 삭제"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/lt.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/lt",[],function(){function n(n,e,i,t){return n%10==1&&(n%100<11||n%100>19)?e:n%10>=2&&n%10<=9&&(n%100<11||n%100>19)?i:t}return{inputTooLong:function(e){var i=e.input.length-e.maximum,t="Pašalinkite "+i+" simbol";return t+=n(i,"į","ius","ių")},inputTooShort:function(e){var i=e.minimum-e.input.length,t="Įrašykite dar "+i+" simbol";return t+=n(i,"į","ius","ių")},loadingMore:function(){return"Kraunama daugiau rezultatų…"},maximumSelected:function(e){var i="Jūs galite pasirinkti tik "+e.maximum+" element";return i+=n(e.maximum,"ą","us","ų")},noResults:function(){return"Atitikmenų nerasta"},searching:function(){return"Ieškoma…"},removeAllItems:function(){return"Pašalinti visus elementus"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/lv.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/lv",[],function(){function e(e,n,u,i){return 11===e?n:e%10==1?u:i}return{inputTooLong:function(n){var u=n.input.length-n.maximum,i="Lūdzu ievadiet par "+u;return(i+=" simbol"+e(u,"iem","u","iem"))+" mazāk"},inputTooShort:function(n){var u=n.minimum-n.input.length,i="Lūdzu ievadiet vēl "+u;return i+=" simbol"+e(u,"us","u","us")},loadingMore:function(){return"Datu ielāde…"},maximumSelected:function(n){var u="Jūs varat izvēlēties ne vairāk kā "+n.maximum;return u+=" element"+e(n.maximum,"us","u","us")},noResults:function(){return"Sakritību nav"},searching:function(){return"Meklēšana…"},removeAllItems:function(){return"Noņemt visus vienumus"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/mk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/mk",[],function(){return{inputTooLong:function(n){var e=(n.input.length,n.maximum,"Ве молиме внесете "+n.maximum+" помалку карактер");return 1!==n.maximum&&(e+="и"),e},inputTooShort:function(n){var e=(n.minimum,n.input.length,"Ве молиме внесете уште "+n.maximum+" карактер");return 1!==n.maximum&&(e+="и"),e},loadingMore:function(){return"Вчитување резултати…"},maximumSelected:function(n){var e="Можете да изберете само "+n.maximum+" ставк";return 1===n.maximum?e+="а":e+="и",e},noResults:function(){return"Нема пронајдено совпаѓања"},searching:function(){return"Пребарување…"},removeAllItems:function(){return"Отстрани ги сите предмети"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ms.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ms",[],function(){return{errorLoading:function(){return"Keputusan tidak berjaya dimuatkan."},inputTooLong:function(n){return"Sila hapuskan "+(n.input.length-n.maximum)+" aksara"},inputTooShort:function(n){return"Sila masukkan "+(n.minimum-n.input.length)+" atau lebih aksara"},loadingMore:function(){return"Sedang memuatkan keputusan…"},maximumSelected:function(n){return"Anda hanya boleh memilih "+n.maximum+" pilihan"},noResults:function(){return"Tiada padanan yang ditemui"},searching:function(){return"Mencari…"},removeAllItems:function(){return"Keluarkan semua item"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/nb.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){return"Vennligst fjern "+(e.input.length-e.maximum)+" tegn"},inputTooShort:function(e){return"Vennligst skriv inn "+(e.minimum-e.input.length)+" tegn til"},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ne.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ne",[],function(){return{errorLoading:function(){return"नतिजाहरु देखाउन सकिएन।"},inputTooLong:function(n){var e=n.input.length-n.maximum,u="कृपया "+e+" अक्षर मेटाउनुहोस्।";return 1!=e&&(u+="कृपया "+e+" अक्षरहरु मेटाउनुहोस्।"),u},inputTooShort:function(n){return"कृपया बाँकी रहेका "+(n.minimum-n.input.length)+" वा अरु धेरै अक्षरहरु भर्नुहोस्।"},loadingMore:function(){return"अरु नतिजाहरु भरिँदैछन् …"},maximumSelected:function(n){var e="तँपाई "+n.maximum+" वस्तु मात्र छान्न पाउँनुहुन्छ।";return 1!=n.maximum&&(e="तँपाई "+n.maximum+" वस्तुहरु मात्र छान्न पाउँनुहुन्छ।"),e},noResults:function(){return"कुनै पनि नतिजा भेटिएन।"},searching:function(){return"खोजि हुँदैछ…"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/nl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/nl",[],function(){return{errorLoading:function(){return"De resultaten konden niet worden geladen."},inputTooLong:function(e){return"Gelieve "+(e.input.length-e.maximum)+" karakters te verwijderen"},inputTooShort:function(e){return"Gelieve "+(e.minimum-e.input.length)+" of meer karakters in te voeren"},loadingMore:function(){return"Meer resultaten laden…"},maximumSelected:function(e){var n=1==e.maximum?"kan":"kunnen",r="Er "+n+" maar "+e.maximum+" item";return 1!=e.maximum&&(r+="s"),r+=" worden geselecteerd"},noResults:function(){return"Geen resultaten gevonden…"},searching:function(){return"Zoeken…"},removeAllItems:function(){return"Verwijder alle items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/pl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/pl",[],function(){var n=["znak","znaki","znaków"],e=["element","elementy","elementów"],r=function(n,e){return 1===n?e[0]:n>1&&n<=4?e[1]:n>=5?e[2]:void 0};return{errorLoading:function(){return"Nie można załadować wyników."},inputTooLong:function(e){var t=e.input.length-e.maximum;return"Usuń "+t+" "+r(t,n)},inputTooShort:function(e){var t=e.minimum-e.input.length;return"Podaj przynajmniej "+t+" "+r(t,n)},loadingMore:function(){return"Trwa ładowanie…"},maximumSelected:function(n){return"Możesz zaznaczyć tylko "+n.maximum+" "+r(n.maximum,e)},noResults:function(){return"Brak wyników"},searching:function(){return"Trwa wyszukiwanie…"},removeAllItems:function(){return"Usuń wszystkie przedmioty"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ps.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ps",[],function(){return{errorLoading:function(){return"پايلي نه سي ترلاسه کېدای"},inputTooLong:function(n){var e=n.input.length-n.maximum,r="د مهربانۍ لمخي "+e+" توری ړنګ کړئ";return 1!=e&&(r=r.replace("توری","توري")),r},inputTooShort:function(n){return"لږ تر لږه "+(n.minimum-n.input.length)+" يا ډېر توري وليکئ"},loadingMore:function(){return"نوري پايلي ترلاسه کيږي..."},maximumSelected:function(n){var e="تاسو يوازي "+n.maximum+" قلم په نښه کولای سی";return 1!=n.maximum&&(e=e.replace("قلم","قلمونه")),e},noResults:function(){return"پايلي و نه موندل سوې"},searching:function(){return"لټول کيږي..."},removeAllItems:function(){return"ټول توکي لرې کړئ"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/pt-BR.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/pt-BR",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Apague "+n+" caracter";return 1!=n&&(r+="es"),r},inputTooShort:function(e){return"Digite "+(e.minimum-e.input.length)+" ou mais caracteres"},loadingMore:function(){return"Carregando mais resultados…"},maximumSelected:function(e){var n="Você só pode selecionar "+e.maximum+" ite";return 1==e.maximum?n+="m":n+="ns",n},noResults:function(){return"Nenhum resultado encontrado"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Remover todos os itens"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/pt.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/pt",[],function(){return{errorLoading:function(){return"Os resultados não puderam ser carregados."},inputTooLong:function(e){var r=e.input.length-e.maximum,n="Por favor apague "+r+" ";return n+=1!=r?"caracteres":"caractere"},inputTooShort:function(e){return"Introduza "+(e.minimum-e.input.length)+" ou mais caracteres"},loadingMore:function(){return"A carregar mais resultados…"},maximumSelected:function(e){var r="Apenas pode seleccionar "+e.maximum+" ";return r+=1!=e.maximum?"itens":"item"},noResults:function(){return"Sem resultados"},searching:function(){return"A procurar…"},removeAllItems:function(){return"Remover todos os itens"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ro.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/ro",[],function(){return{errorLoading:function(){return"Rezultatele nu au putut fi incărcate."},inputTooLong:function(e){var t=e.input.length-e.maximum,n="Vă rugăm să ștergeți"+t+" caracter";return 1!==t&&(n+="e"),n},inputTooShort:function(e){return"Vă rugăm să introduceți "+(e.minimum-e.input.length)+" sau mai multe caractere"},loadingMore:function(){return"Se încarcă mai multe rezultate…"},maximumSelected:function(e){var t="Aveți voie să selectați cel mult "+e.maximum;return t+=" element",1!==e.maximum&&(t+="e"),t},noResults:function(){return"Nu au fost găsite rezultate"},searching:function(){return"Căutare…"},removeAllItems:function(){return"Eliminați toate elementele"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/ru.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/ru",[],function(){function n(n,e,r,u){return n%10<5&&n%10>0&&n%100<5||n%100>20?n%10>1?r:e:u}return{errorLoading:function(){return"Невозможно загрузить результаты"},inputTooLong:function(e){var r=e.input.length-e.maximum,u="Пожалуйста, введите на "+r+" символ";return u+=n(r,"","a","ов"),u+=" меньше"},inputTooShort:function(e){var r=e.minimum-e.input.length,u="Пожалуйста, введите ещё хотя бы "+r+" символ";return u+=n(r,"","a","ов")},loadingMore:function(){return"Загрузка данных…"},maximumSelected:function(e){var r="Вы можете выбрать не более "+e.maximum+" элемент";return r+=n(e.maximum,"","a","ов")},noResults:function(){return"Совпадений не найдено"},searching:function(){return"Поиск…"},removeAllItems:function(){return"Удалить все элементы"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/sk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/sk",[],function(){var e={2:function(e){return e?"dva":"dve"},3:function(){return"tri"},4:function(){return"štyri"}};return{errorLoading:function(){return"Výsledky sa nepodarilo načítať."},inputTooLong:function(n){var t=n.input.length-n.maximum;return 1==t?"Prosím, zadajte o jeden znak menej":t>=2&&t<=4?"Prosím, zadajte o "+e[t](!0)+" znaky menej":"Prosím, zadajte o "+t+" znakov menej"},inputTooShort:function(n){var t=n.minimum-n.input.length;return 1==t?"Prosím, zadajte ešte jeden znak":t<=4?"Prosím, zadajte ešte ďalšie "+e[t](!0)+" znaky":"Prosím, zadajte ešte ďalších "+t+" znakov"},loadingMore:function(){return"Načítanie ďalších výsledkov…"},maximumSelected:function(n){return 1==n.maximum?"Môžete zvoliť len jednu položku":n.maximum>=2&&n.maximum<=4?"Môžete zvoliť najviac "+e[n.maximum](!1)+" položky":"Môžete zvoliť najviac "+n.maximum+" položiek"},noResults:function(){return"Nenašli sa žiadne položky"},searching:function(){return"Vyhľadávanie…"},removeAllItems:function(){return"Odstráňte všetky položky"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/sl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/sl",[],function(){return{errorLoading:function(){return"Zadetkov iskanja ni bilo mogoče naložiti."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Prosim zbrišite "+n+" znak";return 2==n?t+="a":1!=n&&(t+="e"),t},inputTooShort:function(e){var n=e.minimum-e.input.length,t="Prosim vpišite še "+n+" znak";return 2==n?t+="a":1!=n&&(t+="e"),t},loadingMore:function(){return"Nalagam več zadetkov…"},maximumSelected:function(e){var n="Označite lahko največ "+e.maximum+" predmet";return 2==e.maximum?n+="a":1!=e.maximum&&(n+="e"),n},noResults:function(){return"Ni zadetkov."},searching:function(){return"Iščem…"},removeAllItems:function(){return"Odstranite vse elemente"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/sq.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/sq",[],function(){return{errorLoading:function(){return"Rezultatet nuk mund të ngarkoheshin."},inputTooLong:function(e){var n=e.input.length-e.maximum,t="Të lutem fshi "+n+" karakter";return 1!=n&&(t+="e"),t},inputTooShort:function(e){return"Të lutem shkruaj "+(e.minimum-e.input.length)+" ose më shumë karaktere"},loadingMore:function(){return"Duke ngarkuar më shumë rezultate…"},maximumSelected:function(e){var n="Mund të zgjedhësh vetëm "+e.maximum+" element";return 1!=e.maximum&&(n+="e"),n},noResults:function(){return"Nuk u gjet asnjë rezultat"},searching:function(){return"Duke kërkuar…"},removeAllItems:function(){return"Hiq të gjitha sendet"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/sr-Cyrl.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sr-Cyrl",[],function(){function n(n,e,r,u){return n%10==1&&n%100!=11?e:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?r:u}return{errorLoading:function(){return"Преузимање није успело."},inputTooLong:function(e){var r=e.input.length-e.maximum,u="Обришите "+r+" симбол";return u+=n(r,"","а","а")},inputTooShort:function(e){var r=e.minimum-e.input.length,u="Укуцајте бар још "+r+" симбол";return u+=n(r,"","а","а")},loadingMore:function(){return"Преузимање још резултата…"},maximumSelected:function(e){var r="Можете изабрати само "+e.maximum+" ставк";return r+=n(e.maximum,"у","е","и")},noResults:function(){return"Ништа није пронађено"},searching:function(){return"Претрага…"},removeAllItems:function(){return"Уклоните све ставке"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/sr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sr",[],function(){function n(n,e,r,t){return n%10==1&&n%100!=11?e:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?r:t}return{errorLoading:function(){return"Preuzimanje nije uspelo."},inputTooLong:function(e){var r=e.input.length-e.maximum,t="Obrišite "+r+" simbol";return t+=n(r,"","a","a")},inputTooShort:function(e){var r=e.minimum-e.input.length,t="Ukucajte bar još "+r+" simbol";return t+=n(r,"","a","a")},loadingMore:function(){return"Preuzimanje još rezultata…"},maximumSelected:function(e){var r="Možete izabrati samo "+e.maximum+" stavk";return r+=n(e.maximum,"u","e","i")},noResults:function(){return"Ništa nije pronađeno"},searching:function(){return"Pretraga…"},removeAllItems:function(){return"Уклоните све ставке"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/sv.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/sv",[],function(){return{errorLoading:function(){return"Resultat kunde inte laddas."},inputTooLong:function(n){return"Vänligen sudda ut "+(n.input.length-n.maximum)+" tecken"},inputTooShort:function(n){return"Vänligen skriv in "+(n.minimum-n.input.length)+" eller fler tecken"},loadingMore:function(){return"Laddar fler resultat…"},maximumSelected:function(n){return"Du kan max välja "+n.maximum+" element"},noResults:function(){return"Inga träffar"},searching:function(){return"Söker…"},removeAllItems:function(){return"Ta bort alla objekt"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/th.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/th",[],function(){return{errorLoading:function(){return"ไม่สามารถค้นข้อมูลได้"},inputTooLong:function(n){return"โปรดลบออก "+(n.input.length-n.maximum)+" ตัวอักษร"},inputTooShort:function(n){return"โปรดพิมพ์เพิ่มอีก "+(n.minimum-n.input.length)+" ตัวอักษร"},loadingMore:function(){return"กำลังค้นข้อมูลเพิ่ม…"},maximumSelected:function(n){return"คุณสามารถเลือกได้ไม่เกิน "+n.maximum+" รายการ"},noResults:function(){return"ไม่พบข้อมูล"},searching:function(){return"กำลังค้นข้อมูล…"},removeAllItems:function(){return"ลบรายการทั้งหมด"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/tk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/tk",[],function(){return{errorLoading:function(){return"Netije ýüklenmedi."},inputTooLong:function(e){return e.input.length-e.maximum+" harp bozuň."},inputTooShort:function(e){return"Ýene-de iň az "+(e.minimum-e.input.length)+" harp ýazyň."},loadingMore:function(){return"Köpräk netije görkezilýär…"},maximumSelected:function(e){return"Diňe "+e.maximum+" sanysyny saýlaň."},noResults:function(){return"Netije tapylmady."},searching:function(){return"Gözlenýär…"},removeAllItems:function(){return"Remove all items"}}}),e.define,e.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/tr.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/tr",[],function(){return{errorLoading:function(){return"Sonuç yüklenemedi"},inputTooLong:function(n){return n.input.length-n.maximum+" karakter daha girmelisiniz"},inputTooShort:function(n){return"En az "+(n.minimum-n.input.length)+" karakter daha girmelisiniz"},loadingMore:function(){return"Daha fazla…"},maximumSelected:function(n){return"Sadece "+n.maximum+" seçim yapabilirsiniz"},noResults:function(){return"Sonuç bulunamadı"},searching:function(){return"Aranıyor…"},removeAllItems:function(){return"Tüm öğeleri kaldır"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/uk.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/uk",[],function(){function n(n,e,u,r){return n%100>10&&n%100<15?r:n%10==1?e:n%10>1&&n%10<5?u:r}return{errorLoading:function(){return"Неможливо завантажити результати"},inputTooLong:function(e){return"Будь ласка, видаліть "+(e.input.length-e.maximum)+" "+n(e.maximum,"літеру","літери","літер")},inputTooShort:function(n){return"Будь ласка, введіть "+(n.minimum-n.input.length)+" або більше літер"},loadingMore:function(){return"Завантаження інших результатів…"},maximumSelected:function(e){return"Ви можете вибрати лише "+e.maximum+" "+n(e.maximum,"пункт","пункти","пунктів")},noResults:function(){return"Нічого не знайдено"},searching:function(){return"Пошук…"},removeAllItems:function(){return"Видалити всі елементи"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/vi.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/vi",[],function(){return{inputTooLong:function(n){return"Vui lòng xóa bớt "+(n.input.length-n.maximum)+" ký tự"},inputTooShort:function(n){return"Vui lòng nhập thêm từ "+(n.minimum-n.input.length)+" ký tự trở lên"},loadingMore:function(){return"Đang lấy thêm kết quả…"},maximumSelected:function(n){return"Chỉ có thể chọn được "+n.maximum+" lựa chọn"},noResults:function(){return"Không tìm thấy kết quả"},searching:function(){return"Đang tìm…"},removeAllItems:function(){return"Xóa tất cả các mục"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/zh-CN.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/select2/i18n/zh-TW.js: -------------------------------------------------------------------------------- 1 | /*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ 2 | 3 | !function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-TW",[],function(){return{inputTooLong:function(n){return"請刪掉"+(n.input.length-n.maximum)+"個字元"},inputTooShort:function(n){return"請再輸入"+(n.minimum-n.input.length)+"個字元"},loadingMore:function(){return"載入中…"},maximumSelected:function(n){return"你只能選擇最多"+n.maximum+"項"},noResults:function(){return"沒有找到相符的項目"},searching:function(){return"搜尋中…"},removeAllItems:function(){return"刪除所有項目"}}}),n.define,n.require}(); -------------------------------------------------------------------------------- /static/admin/js/vendor/xregexp/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007-2017 Steven Levithan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /static/js/ipv6-s1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 07C160-ipv6 4 | 5 | 6 | 7 | 8 | 本站已支持 9 | IPv6 10 | 访问 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /static/js/jquery.lazyload.min.js: -------------------------------------------------------------------------------- 1 | /*! Lazy Load 1.9.1 - MIT license - Copyright 2010-2013 Mika Tuupola */ 2 | !function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!0,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document); -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/utils/__init__.py -------------------------------------------------------------------------------- /utils/aboutdb.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import time,re,os,unicodedata 3 | import django 4 | os.environ.setdefault('DJANGO_SETTINGS_MODULE','epg.settings') 5 | django.setup() 6 | from web.models import Crawl_log 7 | from web.models import Channel,Epg 8 | ##log(msg,level=1),日志写入数据库并打印 9 | ##gen_trans_m3u(上传文件目录,下载文件目录) 将已经上传的文件与频道名称匹配并保存 10 | def log(msg,level = 1): #写日志 1正常 2错误 11 | t = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()) + ' ' 12 | m = t + '\t' + str(msg) 13 | dbinfo = Crawl_log(msg=msg,level = level) 14 | dbinfo.save() 15 | print(m) 16 | ''' 17 | 获取生成HTML时需要的信息,频道列表、EPG统计 18 | ''' 19 | def get_html_info(need_date): 20 | channels = Channel.get_need_channels(Channel,'all') 21 | epgs = Epg.get_epgs(Epg, channels[1], need_date) 22 | epg_no = epgs.count() 23 | return ({ 24 | 'epg_no':epg_no, 25 | 'channels':channels[0], 26 | }) 27 | 28 | 29 | -------------------------------------------------------------------------------- /utils/crawl_channel_lists.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | ''' 3 | 获取所有来源的,频道列表(ID,频道名...),存入专门的列表库方便后面处理。 4 | ''' 5 | import django,os 6 | from web.models import Channel_list 7 | from crawl.spiders import epg_source 8 | from utils.aboutdb import log 9 | from utils.general import cht_to_chs 10 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'epg.settings') 11 | django.setup() 12 | def crawl(): 13 | for source in epg_source: 14 | if True == True: 15 | channels = [] 16 | #if source in ['zhongshu']: 17 | n = 0 18 | try: 19 | channels = epg_source[source]() 20 | for channel in channels:#转简体 21 | channel['name'] = cht_to_chs(channel['name']) 22 | save_ret = Channel_list.save_to_db(Channel_list,channels) 23 | msg = '频道整理--来源:%s,频道数量:%s,%s'%(source,len(channels),save_ret['msg']) 24 | except Exception as e: 25 | msg = '频道整理--来源:%s,获取错误:%s'%(source,e) 26 | log(msg) 27 | for r in channels: 28 | n += 1 29 | print(n,r['name'],','.join(r['id']),r['sort'],r['source'],r['url'],r['logo'],r['desc']) -------------------------------------------------------------------------------- /utils/general.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from .zhtools.langconv import * 3 | import datetime 4 | from pathlib import Path 5 | import re,os 6 | add_info_desc = '' #在节目表的标题里面添加信息 7 | add_info_title = '' #在节目表的描述里面添加信息 8 | ##存储数据的根目录 9 | BASE_DIR = Path(__file__).resolve().parent.parent 10 | root_dir = os.path.join(BASE_DIR,'download') 11 | crawl_info = { 12 | 'max_crawl_days': 2 , #需要采集几天节目 1为当天,2为今明两天 如果频道为多天一次获取,则不受此限制 13 | 'gen_xml_days': 2, #需要生成节目表的天数 1当天 2明天 不能大于need_days,否则出错 14 | 'del_days' : 14 , #删除多少天之前的节目 15 | 'recrawl_days' : 1 , #需要重新采集几天的节目,1为只重新采集当天节目 16 | 'retry_crawl_times' : 1 , #如果采集出错,重试次数 1为不重试,2为重试一次 3....重试两次 17 | 'change_source':1, #如果获取失败是否换源 18 | } 19 | dirs = { 20 | 'share' : "%s"%root_dir, 21 | 'testm3u_dir':'%s/test.m3u'%root_dir #生成用于测试的m3u的目录 22 | } 23 | #川流TV来源,需要的节目表 24 | chuanliu_Authorization='' 25 | headers = { 26 | 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' 27 | ' AppleWebKit/537.36 (KHTML, like Gecko)' 28 | ' Chrome/99.0.4844.82 Safari/537.36' 29 | } 30 | 31 | #生成数据时(不)生成此类别信息 32 | in_exclude_channel = { 33 | 'html':['香港','海外','澳门','台湾'],##生成html时不生成此类别信息 34 | 'xml':[],##生成xml的文件名,分类名 35 | 'include_html':[],##生成数据时,不会分类阻止也会提供频道信息 单频道 36 | 'include_xml':[],##生成数据时,不会分类阻止也会提供频道信息 单频道 37 | } 38 | xmlinfo = { 39 | 'all': { 40 | 'basename': 'e.xml', 41 | 'sortname': 'all' 42 | }, 43 | 'yangwei': { 44 | 'basename': 'cc.xml', 45 | 'sortname': ['央视', '卫视'] 46 | }, 47 | 'difang': { 48 | 'basename': 'difang.xml', 49 | 'sortname': ['数字付费', '辽宁','山东','湖北','四川'] 50 | }, 51 | 'oversea': { 52 | 'basename': 'gat.xml', 53 | 'sortname': ['香港', '台湾','海外'] 54 | }, 55 | } 56 | 57 | #繁体转简体 58 | def cht_to_chs(line): 59 | line = Converter('zh-hans').convert(line) 60 | line.encode('utf-8') 61 | return line 62 | #将多个频道ID解析为单个ID 解析为 {'tvmao':'cctv1','cctv':'cctv1'....} 63 | def channel_ids_to_dict(channel_id): 64 | channel_list = {} 65 | rs = re.findall('<(\w+):(.+?)>',channel_id) 66 | for r in rs: 67 | c = {r[0] : r[1]} 68 | channel_list.update(c) 69 | return channel_list 70 | def argvs_get(argv): #命令行运行时可使用的参数 recrawl(重新获取) channel(单独获取某一频道节目单) dt(单独某频道时,获取日期) save_to_db单独获取时,是否需要存至数据库 71 | # -r 重新获取 -n 获取某一单一频道信息 -d 某一频道的日期 -s 是否保存(如果只写-s为保存数据 72 | recrawl = 0 73 | cname = 0 #单独获取的某一频道名 74 | dt = 0 75 | save_to_db = 1 76 | if '-r' in argv: 77 | recrawl = 1 78 | if '-n' in argv and len(argv) >=argv.index('-n') + 2 and '-' not in argv[argv.index('-n') + 1]: 79 | cname = argv[argv.index('-n') + 1] 80 | if '-d' in argv and len(argv) >=argv.index('-d') + 2 and '-' not in argv[argv.index('-d') + 1]: 81 | dt = argv[argv.index('-d') + 1] 82 | dt = datetime.datetime.strptime(dt,'%Y%m%d').date() 83 | else: 84 | dt = datetime.datetime.now().date() 85 | if '-s' in argv and len(argv) >=argv.index('-s') + 2 and '-' not in argv[argv.index('-s') + 1]: 86 | save_to_db = int(argv[argv.index('-s') + 1]) 87 | if '-n' in argv and '-s' not in argv: 88 | save_to_db = 0 89 | return [recrawl,cname,dt,save_to_db] 90 | def noepgjson(name,id,need_date): 91 | title = '精彩节目-暂未提供节目预告信息' 92 | epgjsons = [] 93 | for x in range(24): 94 | start = datetime.datetime.combine(need_date,datetime.time(x,0,0)).strftime('%H:%M') 95 | end = datetime.datetime.combine(need_date,datetime.time(x,59,59)).strftime('%H:%M') 96 | epgjsons.append({ 97 | 'starttime':start, 98 | 'endtime':end, 99 | 'title':'%s%s'%(title,add_info_title), 100 | 'descr':add_info_desc, 101 | }) 102 | return epgjsons 103 | def noepg(name,id,need_date): 104 | hours_epg = [] 105 | tz = ' +0800' 106 | for x in range(24): 107 | start = datetime.datetime.combine(need_date, datetime.time(x, 0, 0)) 108 | end = datetime.datetime.combine(need_date, datetime.time(x, 59, 59)) 109 | starttime_str = start.strftime('%Y%m%d%H%M%M') + tz 110 | endtime_str = end.strftime('%Y%m%d%H%M%M') + tz 111 | hour_epg='''精彩节目-暂未提供节目预告信息'''%(starttime_str,endtime_str) 112 | hours_epg.append(hour_epg) 113 | xmlepg = ''.join(hours_epg) 114 | return xmlepg -------------------------------------------------------------------------------- /utils/zhtools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/utils/zhtools/__init__.py -------------------------------------------------------------------------------- /utils/zhtools/xpinyin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Translate chinese hanzi to pinyin by python 5 | Created by Eric Lo on 2010-05-20. 6 | Copyright (c) 2010 __lxneng@gmail.com__. http://lxneng.com All rights reserved. 7 | """ 8 | 9 | """ 10 | Forked by skydarkchen 11 | """ 12 | 13 | import os.path 14 | 15 | try: 16 | chr = unichr 17 | except NameError: 18 | pass 19 | 20 | VERSION = '0.3a' 21 | 22 | 23 | class Pinyin(object): 24 | """translate chinese hanzi to pinyin by python, inspired by flyerhzm’s 25 | `chinese\_pinyin`_ gem 26 | .. _chinese\_pinyin: https://github.com/flyerhzm/chinese_pinyin 27 | 28 | usage(python3) 29 | ----- 30 | :: 31 | >>> p = Pinyin() 32 | >>> p.get_pinyin("上海") 33 | 'shanghai' 34 | >>> p.get_pinyin("上海", tone=True) 35 | 'shang4hai3' 36 | >>> p.get_initials("上") 37 | 'S' 38 | >>> print(''.join(p.py2hz('shang4'))) 39 | 丄上姠尙尚蠰銄鑜 40 | >>> print(''.join(p.py2hz('a'))) 41 | 吖腌錒锕阿嗄阿阿啊阿 42 | """ 43 | 44 | data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), \ 45 | 'Mandarin.dat') 46 | 47 | def __init__(self): 48 | self.dict = {} 49 | self.revdict = {} 50 | for line in open(self.data_path): 51 | k, v = line.strip().split('\t') 52 | v = v.lower().split(' ') 53 | hz = chr(int('0x%s' % k, 16)) 54 | self.dict[hz] = v 55 | for vkey in v: 56 | self.revdict.setdefault(vkey, []) 57 | self.revdict[vkey].append(hz) 58 | 59 | def py2hz(self, pinyin): 60 | if pinyin == '': 61 | return [] 62 | pinyin = pinyin.lower() 63 | if pinyin[-1].isdigit(): 64 | return self.revdict.get(pinyin, []) 65 | ret = [] 66 | for i in range(1, 6): 67 | key = '%s%s' % (pinyin, i) 68 | ret += self.revdict.get(key, []) 69 | return ret 70 | 71 | def get_pinyin(self, chars='', splitter='', tone=False): 72 | result = [] 73 | for char in chars: 74 | v = self.dict.get(char, None) 75 | if v: 76 | v = v[0] 77 | if not tone and v[-1].isdigit(): 78 | v = v[:-1] 79 | else: 80 | v = char 81 | result.append(v) 82 | return splitter.join(result) 83 | 84 | def get_initials(self, char=''): 85 | if char == '': 86 | return '' 87 | return self.dict.get(char, [char])[0][0].upper() 88 | 89 | 90 | if __name__ == '__main__': 91 | import unittest 92 | 93 | class PinyinTestCase(unittest.TestCase): 94 | def setUp(self): 95 | import sys 96 | py = sys.version_info 97 | self.py3k = py >= (3, 0, 0) 98 | 99 | self.py = Pinyin() 100 | 101 | def to_unicode(self, s): 102 | if self.py3k: 103 | return s 104 | return s.decode('utf-8') 105 | 106 | def test_get_pinyin(self): ## test method names begin 'test*' 107 | s = self.to_unicode('上A2#海') 108 | a = self.to_unicode('shangA2#hai') 109 | aa = self.to_unicode('shang4A2#hai3') 110 | aaa = self.to_unicode('shang A 2 # hai') 111 | self.assertEqual(self.py.get_pinyin(s), a) 112 | self.assertEqual(self.py.get_pinyin(s, tone=True), aa) 113 | self.assertEqual(self.py.get_pinyin(s, splitter=' '), aaa) 114 | 115 | def test_get_initials(self): 116 | s = self.to_unicode('上') 117 | a = self.to_unicode('S') 118 | self.assertEqual(self.py.get_initials(s), a) 119 | 120 | def test_py2hz(self): 121 | s1 = self.to_unicode('shang4') 122 | s2 = self.to_unicode('a') 123 | a1 = self.to_unicode('丄上姠尙尚蠰銄鑜') 124 | a2 = self.to_unicode('吖腌錒锕阿嗄阿阿啊阿') 125 | self.assertEqual(''.join(self.py.py2hz(s1)), a1) 126 | self.assertEqual(''.join(self.py.py2hz(s2)), a2) 127 | 128 | unittest.main() 129 | -------------------------------------------------------------------------------- /web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/web/__init__.py -------------------------------------------------------------------------------- /web/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Channel,Epg,Crawl_log,Channel_list 3 | admin.site.site_header = '老张的EPG--频道配置' 4 | admin.site.site_title = "老张的EPG" 5 | admin.site.index_title = "后台首页" 6 | # Register your models here. 7 | class ChannelAdmin(admin.ModelAdmin): 8 | list_display = ('id', 'tvg_name', 'name','source','last_program_date','ineed') 9 | #list_per_page设置每页显示多少条记录 10 | list_per_page = 50 11 | #ordering设置默认排序字段 12 | ordering = ('id',) 13 | #设置哪些字段可以点击进入编辑界面 14 | list_display_links = ('tvg_name','name') 15 | #筛选器 16 | list_filter =('sort',) #过滤器 17 | search_fields =('tvg_name','name','channel_id' ) #搜索字段 18 | admin.site.register(Channel,ChannelAdmin) 19 | 20 | class EpgAdmin(admin.ModelAdmin): 21 | list_display = ('channel_id', 'starttime', 'title','program_date','source') 22 | date_hierarchy = 'program_date' 23 | search_fields =('channel_id', ) #搜索字段 24 | admin.site.register(Epg,EpgAdmin) 25 | class Crawl_logAdmin(admin.ModelAdmin): 26 | list_display = ('dt', 'msg', 'level') 27 | admin.site.register(Crawl_log,Crawl_logAdmin) 28 | class Channel_listAdmin(admin.ModelAdmin): 29 | list_display = ('inner_channel_id','inner_name','out_channel_id','out_name','source') 30 | list_filter =('source',) #过滤器 31 | list_display_links = ('inner_name','out_name') 32 | admin.site.register(Channel_list,Channel_listAdmin) 33 | -------------------------------------------------------------------------------- /web/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class WebConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'web' 7 | verbose_name = "信息查询及配置" 8 | -------------------------------------------------------------------------------- /web/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/supzhang/epg/66a0fd430394258d70d3522e3427af0a0e5b7eeb/web/migrations/__init__.py -------------------------------------------------------------------------------- /web/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 老张的EPG Since 2019 14 | 15 | 16 | 17 | 18 | 24 |
25 |

老张的EPG

26 |

节目表下载地址:XML地址

27 |

下载测试m3u文件:测试

28 |

EPG文件中提供{{ channel_no }}个频道 {{ crawl_day }}天内节目表共 {{ epg_no }} 条,
现在提供时间为: {{ start_date }} - {{ end_date }}

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {% for channel in channels %} 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {% endfor %} 65 | 66 | 67 |
#频道tvg-nametvg-id分类来源最新节目日期
需要显示:暂未提供节目表 的频道noepg9999所有没有七天内
{{ forloop.counter }}{{ channel.tvg_name }}{{ channel.name }}{{ channel.tvg_name }}{{ channel.id }}{{ channel.sort }}{{ channel.source }}{{ channel.last_program_date }}
68 |
69 | 70 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /web/templates/single_channel_epgs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 |
9 | 前一天 10 | 下一天 11 |
    12 | {% for epg in epgs %} 13 |
  • {{ epg.starttime }}  {{ epg.title }}  {{ epg.source }}
  • 14 | {% endfor %} 15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /web/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

此页面已经正常载入了,GOOD!

9 | 10 | -------------------------------------------------------------------------------- /web/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | urlpatterns = [ 4 | path('diyp/', views.diyp), 5 | path('web/', views.web_single_channel_epg), 6 | path('test/',views.d), 7 | ] 8 | -------------------------------------------------------------------------------- /web/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse,FileResponse 3 | from web.models import Channel,Epg 4 | from utils.general import noepgjson,crawl_info,root_dir 5 | from utils.aboutdb import get_html_info 6 | import datetime,re,json,os 7 | from dateutil import tz 8 | tz_sh = tz.gettz('Asia/Shanghai') 9 | def d(request): 10 | return render(request,'a.html') 11 | def index(request): 12 | crawl_days = crawl_info['gen_xml_days'] 13 | start_date = datetime.datetime.now().strftime(u'%Y{y}%m{m}%d{d}').format(y='年', m='月', d='日') 14 | start_date_no = datetime.datetime.now().strftime(u'%Y%m%d') 15 | end_date_date = datetime.datetime.now() + datetime.timedelta(days=crawl_days - 1) 16 | end_date = (end_date_date).strftime(u'%Y{y}%m{m}%d{d}').format(y='年', m='月', d='日') 17 | info = get_html_info(end_date_date.date()) 18 | channel_no = info['channels'].count() 19 | epg_no = info['epg_no'] 20 | 21 | ret = {'channel_no':channel_no, 22 | 'crawl_day':crawl_days, 23 | 'epg_no':epg_no, 24 | 'start_date':start_date, 25 | 'start_date_no':start_date_no, 26 | 'end_date':end_date, 27 | 'channels':info['channels'], 28 | 'root_dir':root_dir, 29 | 'n':0,} 30 | return render(request,"index.html",context = ret) 31 | def download(requests,title): 32 | file = open(os.path.join(root_dir,title),'rb') 33 | response = FileResponse(file) 34 | response['Content-Type']='application/octet-stream' 35 | return response 36 | def diyp(request): 37 | ret = single_channel_epg(request) 38 | ret_epgs = ret['epgs'] 39 | datas = [] 40 | if len(ret['epgs']) == 0: 41 | ret_epgs = noepgjson('name', 'id', datetime.datetime.now().date()) 42 | 43 | for epg in ret_epgs: 44 | epg1 = { 45 | 'start':epg['starttime'], 46 | 'end':epg['endtime'], 47 | 'title':epg['title'], 48 | 'desc':epg['descr'], 49 | } 50 | datas.append(epg1) 51 | 52 | ret1 = { 53 | "channel_name": ret['channel'], 54 | "date": ret['epg_date'].strftime('%Y-%m-%d'), 55 | "epg_data":datas, 56 | } 57 | try: 58 | j = json.dumps(ret1,ensure_ascii=False) 59 | except Exception as e: 60 | print(e,datas) 61 | j = 'abc' 62 | return HttpResponse(j,content_type='application/json') # 63 | #WEB查询某频道信息接口 64 | def web_single_channel_epg(request): 65 | ret = single_channel_epg(request) 66 | ret_epgs = ret['epgs'] 67 | if len(ret_epgs) == 0: 68 | epg = { 69 | 'start': '', 70 | 'end': '', 71 | 'title': '没有此日期节目信息--%s'%ret['msg'], 72 | 'desc': '', 73 | } 74 | ret_epgs = [epg] 75 | title = '%s -- %s'%(ret['channel'],ret['epg_date'].strftime('%Y-%m-%d')) 76 | 77 | tomorrow_date = (ret['epg_date'] + datetime.timedelta(days = 1)).strftime('%Y-%m-%d') 78 | tomorrow_url = '?ch=%s&date=%s'%(ret['tvg_name'],tomorrow_date) 79 | yesterday_date = (ret['epg_date'] - datetime.timedelta(days = 1)).strftime('%Y-%m-%d') 80 | yesterday_url = '?ch=%s&date=%s'%(ret['tvg_name'],yesterday_date) 81 | source = ret['source'] 82 | ret = { 83 | 'title':title, 84 | 'tomorrow_url':tomorrow_url, 85 | 'yesterday_url':yesterday_url, 86 | 'epgs':ret_epgs, 87 | 'source':source, 88 | } 89 | return render(request,'single_channel_epgs.html',context=ret) 90 | #请求某个频道数据的通用接口 91 | def single_channel_epg(request): 92 | tvg_name = '' 93 | success = 0 94 | epgs = [] 95 | need_date = datetime.datetime.now().date() #没有提供时间,则使用当天 96 | msg = '' 97 | if request.method == "GET" and 'ch' in request.GET and 'date' in request.GET: 98 | tvg_name = request.GET['ch'] 99 | if tvg_name in ["CCTV5 ","IPTV5 ","IPTV6 ","IPTV3 "]: 100 | tvg_name = tvg_name.strip() + '+' 101 | date_re = re.search('(\d{4})\D(\d+)+\D(\d+)', request.GET['date']) 102 | if date_re: 103 | need_date = datetime.date(int(date_re.group(1)), int(date_re.group(2)), int(date_re.group(3))) 104 | channels = Channel.get_spec_channel_strict(Channel,tvg_name) 105 | if channels.count() == 0: 106 | msg = '没有此频道' 107 | channel_name = tvg_name 108 | source = '' 109 | else: 110 | channel = channels.first() 111 | channel_name = channel.name 112 | epgs = Epg.get_single_epg(Epg,channel,need_date) 113 | source = channel.source 114 | 115 | 116 | if len(epgs) > 0: 117 | success = 1 118 | else: 119 | msg = '参数错误' 120 | channel_name = '未提供' 121 | ret = { 122 | 'success':success, 123 | 'msg':msg, 124 | 'epgs':epgs, 125 | 'channel':channel_name, 126 | 'tvg_name':tvg_name, 127 | 'epg_date':need_date, 128 | 'source':source, 129 | } 130 | return ret 131 | 132 | --------------------------------------------------------------------------------