1
|
"""
|
2
|
MIT License
|
3
|
|
4
|
Copyright (C) 2023 ROCKY4546
|
5
|
https://github.com/rocky4546
|
6
|
|
7
|
This file is part of Cabernet
|
8
|
|
9
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
10
|
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
11
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
12
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
13
|
is furnished to do so, subject to the following conditions:
|
14
|
|
15
|
The above copyright notice and this permission notice shall be included in all copies or
|
16
|
substantial portions of the Software.
|
17
|
"""
|
18
|
|
19
|
import html
|
20
|
import importlib
|
21
|
import importlib.resources
|
22
|
import json
|
23
|
import re
|
24
|
|
25
|
from lib.plugins.plugin_channels import PluginChannels
|
26
|
from lib.common.decorators import handle_json_except
|
27
|
from lib.common.decorators import handle_url_except
|
28
|
import lib.common.utils as utils
|
29
|
from ..lib import daddylive
|
30
|
from .. import resources
|
31
|
|
32
|
class Channels(PluginChannels):
|
33
|
|
34
|
def __init__(self, _instance_obj):
|
35
|
super().__init__(_instance_obj)
|
36
|
|
37
|
self.search_url = re.compile(b'iframe src=\"(.*?)\" width')
|
38
|
self.search_m3u8 = re.compile(b'source:\'(.*?)\'')
|
39
|
self.search_ch = re.compile(r'div class="grid-item">'
|
40
|
+ r'<a href=\"(\D+(\d+).php.*?)\" target.*?<strong>(.*?)</strong>')
|
41
|
self.ch_db_list = None
|
42
|
|
43
|
def get_channels(self):
|
44
|
self.ch_db_list = self.db.get_channels(self.plugin_obj.name, self.instance_key)
|
45
|
|
46
|
ch_list = self.get_channel_list()
|
47
|
if len(ch_list) == 0:
|
48
|
self.logger.warning('DaddyLive channel list is empty from provider, not updating Cabernet')
|
49
|
return
|
50
|
self.logger.info("{}: Found {} stations on instance {}"
|
51
|
.format(self.plugin_obj.name, len(ch_list), self.instance_key))
|
52
|
ch_list = sorted(ch_list, key=lambda d: d['name'])
|
53
|
ch_num = 1
|
54
|
for ch in ch_list:
|
55
|
ch['number'] = ch_num
|
56
|
ch_num += 1
|
57
|
return ch_list
|
58
|
|
59
|
@handle_url_except(timeout=10.0)
|
60
|
@handle_json_except
|
61
|
def get_channel_ref(self, _channel_id):
|
62
|
"""
|
63
|
gets the referer required to obtain the ts or stream files from server
|
64
|
"""
|
65
|
text = self.get_uri_data(self.plugin_obj.unc_daddylive_base +
|
66
|
self.plugin_obj.unc_daddylive_stream.format(_channel_id))
|
67
|
m = re.search(self.search_url, text)
|
68
|
if not m:
|
69
|
# unable to obtain the url, abort
|
70
|
self.logger.info('{}: {} Unable to obtain url, aborting'
|
71
|
.format(self.plugin_obj.name, _channel_id))
|
72
|
return
|
73
|
return m[1].decode('utf8')
|
74
|
|
75
|
@handle_url_except(timeout=10.0)
|
76
|
@handle_json_except
|
77
|
def get_channel_uri(self, _channel_id):
|
78
|
json_needs_updating = False
|
79
|
ch_url = self.get_channel_ref(_channel_id)
|
80
|
if not ch_url:
|
81
|
return
|
82
|
|
83
|
header = {
|
84
|
'User-agent': utils.DEFAULT_USER_AGENT,
|
85
|
'Referer': self.plugin_obj.unc_daddylive_base + self.plugin_obj.unc_daddylive_stream.format(_channel_id)}
|
86
|
|
87
|
text = self.get_uri_data(ch_url, _header=header)
|
88
|
m = re.search(self.search_m3u8, text)
|
89
|
if not m:
|
90
|
# unable to obtain the url, abort
|
91
|
self.logger.notice('{}: {} Unable to obtain m3u8, aborting'
|
92
|
.format(self.plugin_obj.name, _channel_id))
|
93
|
return
|
94
|
stream_url = m[1].decode('utf8')
|
95
|
m3u8_uri = self.get_best_stream(stream_url, _channel_id, ch_url)
|
96
|
if not m3u8_uri:
|
97
|
return stream_url
|
98
|
return m3u8_uri
|
99
|
|
100
|
def get_channel_list(self):
|
101
|
ch_list = []
|
102
|
results = []
|
103
|
|
104
|
# first get the list of channels to get from the epg plugin
|
105
|
tvg_list = self.get_tvg_reference()
|
106
|
epg_plugins = {u['plugin']: u for u in tvg_list}.keys()
|
107
|
for plugin in epg_plugins:
|
108
|
zones = {u['zone']: u for u in tvg_list if u['plugin'] == plugin}.keys()
|
109
|
for zone in zones:
|
110
|
ch_ids = [n['id'] for n in tvg_list if n['zone'] == zone]
|
111
|
chs = self.plugin_obj.plugins[plugin].plugin_obj \
|
112
|
.get_channel_list_ext(zone, ch_ids)
|
113
|
if chs is None:
|
114
|
return
|
115
|
ch_list.extend(chs)
|
116
|
|
117
|
# get the list of channels from the first URL
|
118
|
uri1 = self.plugin_obj.unc_daddylive_base + self.plugin_obj.unc_daddylive_channels
|
119
|
text1 = self.get_uri_data(uri1).decode()
|
120
|
if text1 is None:
|
121
|
return
|
122
|
text1 = text1.replace('\n', ' ')
|
123
|
match_list1 = re.findall(self.search_ch, text1)
|
124
|
|
125
|
# get the list of channels from the local file
|
126
|
file_path = './plugins/provider_video_daddylive/resources/list.index'
|
127
|
with open(file_path, 'r') as f:
|
128
|
text2 = f.read()
|
129
|
match_list2 = re.findall(self.search_ch, text2)
|
130
|
|
131
|
# merge the two lists and remove duplicates
|
132
|
match_list = list(set(match_list1 + match_list2))
|
133
|
|
134
|
# url, id, name
|
135
|
for m in match_list:
|
136
|
if len(m) != 3:
|
137
|
self.logger.warning(
|
138
|
'get_channel_list - DaddyLive channel extraction failed. Extraction procedure needs updating')
|
139
|
return None
|
140
|
uid = m[1]
|
141
|
name = html.unescape(m[2])
|
142
|
if name.lower().startswith('the '):
|
143
|
name = name[4:]
|
144
|
group = None
|
145
|
ch = [d for d in tvg_list if d['name'] == name]
|
146
|
if len(ch):
|
147
|
tvg_id = ch[0]['id']
|
148
|
ch = [d for d in ch_list if d['id'] == tvg_id]
|
149
|
|
150
|
if len(ch):
|
151
|
ch = ch[0]
|
152
|
ch_db_data = self.ch_db_list.get(uid)
|
153
|
if ch_db_data is not None:
|
154
|
ch['enabled'] = ch_db_data[0]['enabled']
|
155
|
ch['id'] = ch_db_data[0]['uid']
|
156
|
ch['name'] = ch_db_data[0]['json']['name']
|
157
|
ch['HD'] = ch_db_data[0]['json']['HD']
|
158
|
if ch_db_data[0]['json']['thumbnail'] == ch['thumbnail']:
|
159
|
thumb = ch_db_data[0]['json']['thumbnail']
|
160
|
thumb_size = ch_db_data[0]['json']['thumbnail_size']
|
161
|
else:
|
162
|
thumb = ch['thumbnail']
|
163
|
thumb_size = self.get_thumbnail_size(thumb, uid)
|
164
|
ch['thumbnail'] = thumb
|
165
|
ch['thumbnail_size'] = thumb_size
|
166
|
ch['ref_url'] = ch_db_data[0]['json']['ref_url']
|
167
|
ch['Header'] = ch_db_data[0]['json']['Header']
|
168
|
ch['use_date_on_m3u8_key'] = False
|
169
|
else:
|
170
|
ch['id'] = uid
|
171
|
ch['name'] = name
|
172
|
ch['thumbnail_size'] = self.get_thumbnail_size(ch['thumbnail'], uid)
|
173
|
|
174
|
ref_url = self.get_channel_ref(uid)
|
175
|
if not ref_url:
|
176
|
self.logger.notice('{} BAD CHANNEL found {}:{}'
|
177
|
.format(self.plugin_obj.name, uid, name))
|
178
|
header = None
|
179
|
else:
|
180
|
header = {'User-agent': utils.DEFAULT_USER_AGENT,
|
181
|
'Referer': ref_url}
|
182
|
ch['Header'] = header
|
183
|
ch['ref_url'] = ref_url
|
184
|
ch['use_date_on_m3u8_key'] = False
|
185
|
self.logger.debug('{} New Channel Added {}:{}'.format(self.plugin_obj.name, uid, name))
|
186
|
|
187
|
group = [n.get('group') for n in tvg_list if n['name'] == ch['name']]
|
188
|
if len(group):
|
189
|
group = group[0]
|
190
|
ch['groups_other'] = group
|
191
|
results.append(ch)
|
192
|
ch['found'] = True
|
193
|
continue
|
194
|
|
195
|
ch_db_data = self.ch_db_list.get(uid)
|
196
|
if ch_db_data is not None:
|
197
|
enabled = ch_db_data[0]['enabled']
|
198
|
hd = ch_db_data[0]['json']['HD']
|
199
|
thumb = ch_db_data[0]['json']['thumbnail']
|
200
|
thumb_size = ch_db_data[0]['json']['thumbnail_size']
|
201
|
ref_url = self.get_channel_ref(uid)
|
202
|
self.logger.debug('{} Updating Channel {}:{}'.format(self.plugin_obj.name, uid, name))
|
203
|
else:
|
204
|
self.logger.debug('{} New Channel Added {}:{}'.format(self.plugin_obj.name, uid, name))
|
205
|
enabled = True
|
206
|
hd = 0
|
207
|
thumb = None
|
208
|
thumb_size = None
|
209
|
ref_url = self.get_channel_ref(uid)
|
210
|
|
211
|
if not ref_url:
|
212
|
self.logger.notice('{} BAD CHANNEL found {}:{}'
|
213
|
.format(self.plugin_obj.name, uid, name))
|
214
|
header = None
|
215
|
else:
|
216
|
header = {'User-agent': utils.DEFAULT_USER_AGENT,
|
217
|
'Referer': ref_url}
|
218
|
|
219
|
channel = {
|
220
|
'id': uid,
|
221
|
'enabled': enabled,
|
222
|
'callsign': uid,
|
223
|
'number': 0,
|
224
|
'name': name,
|
225
|
'HD': hd,
|
226
|
'group_hdtv': None,
|
227
|
'group_sdtv': None,
|
228
|
'groups_other': None,
|
229
|
'thumbnail': thumb,
|
230
|
'thumbnail_size': thumb_size,
|
231
|
'VOD': False,
|
232
|
'Header': header,
|
233
|
'ref_url': ref_url,
|
234
|
'use_date_on_m3u8_key': False,
|
235
|
}
|
236
|
results.append(channel)
|
237
|
|
238
|
found_tvg_list = [u for u in ch_list if u.get('found') is None]
|
239
|
for ch in found_tvg_list:
|
240
|
self.logger.warning(
|
241
|
'{} Channel {} {} from channel_list.json not found on providers site'.format(self.plugin_obj.name,
|
242
|
ch['id'], ch['name']))
|
243
|
found_tvg_list = [u for u in ch_list if u.get('found') is not None]
|
244
|
for ch in found_tvg_list:
|
245
|
del ch['found']
|
246
|
|
247
|
return results
|
248
|
|
249
|
def get_tvg_reference(self):
|
250
|
"""
|
251
|
Returns a list of channels with zone and channel id info
|
252
|
This is sorted so we can get all channels from each zone together
|
253
|
"""
|
254
|
if self.config_obj.data[self.plugin_obj.name.lower()]['epg-plugin'] == 'ALL':
|
255
|
ch_list = json.loads(
|
256
|
importlib.resources.read_text(resources, resource='channel_list.json'))
|
257
|
ch_list = sorted(ch_list, key=lambda d: d['zone'])
|
258
|
return ch_list
|
259
|
else:
|
260
|
return []
|