1# coding: utf-8
2from __future__ import unicode_literals
3
4from .common import InfoExtractor
5from ..compat import compat_str
6from ..utils import (
7    clean_html,
8    int_or_none,
9    str_or_none,
10    strip_or_none,
11)
12
13
14class MindsBaseIE(InfoExtractor):
15    _VALID_URL_BASE = r'https?://(?:www\.)?minds\.com/'
16
17    def _call_api(self, path, video_id, resource, query=None):
18        api_url = 'https://www.minds.com/api/' + path
19        token = self._get_cookies(api_url).get('XSRF-TOKEN')
20        return self._download_json(
21            api_url, video_id, 'Downloading %s JSON metadata' % resource, headers={
22                'Referer': 'https://www.minds.com/',
23                'X-XSRF-TOKEN': token.value if token else '',
24            }, query=query)
25
26
27class MindsIE(MindsBaseIE):
28    IE_NAME = 'minds'
29    _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'(?:media|newsfeed|archive/view)/(?P<id>[0-9]+)'
30    _TESTS = [{
31        'url': 'https://www.minds.com/media/100000000000086822',
32        'md5': '215a658184a419764852239d4970b045',
33        'info_dict': {
34            'id': '100000000000086822',
35            'ext': 'mp4',
36            'title': 'Minds intro sequence',
37            'thumbnail': r're:https?://.+\.png',
38            'uploader_id': 'ottman',
39            'upload_date': '20130524',
40            'timestamp': 1369404826,
41            'uploader': 'Bill Ottman',
42            'view_count': int,
43            'like_count': int,
44            'dislike_count': int,
45            'tags': ['animation'],
46            'comment_count': int,
47            'license': 'attribution-cc',
48        },
49    }, {
50        # entity.type == 'activity' and empty title
51        'url': 'https://www.minds.com/newsfeed/798025111988506624',
52        'md5': 'b2733a74af78d7fd3f541c4cbbaa5950',
53        'info_dict': {
54            'id': '798022190320226304',
55            'ext': 'mp4',
56            'title': '798022190320226304',
57            'uploader': 'ColinFlaherty',
58            'upload_date': '20180111',
59            'timestamp': 1515639316,
60            'uploader_id': 'ColinFlaherty',
61        },
62    }, {
63        'url': 'https://www.minds.com/archive/view/715172106794442752',
64        'only_matching': True,
65    }, {
66        # youtube perma_url
67        'url': 'https://www.minds.com/newsfeed/1197131838022602752',
68        'only_matching': True,
69    }]
70
71    def _real_extract(self, url):
72        entity_id = self._match_id(url)
73        entity = self._call_api(
74            'v1/entities/entity/' + entity_id, entity_id, 'entity')['entity']
75        if entity.get('type') == 'activity':
76            if entity.get('custom_type') == 'video':
77                video_id = entity['entity_guid']
78            else:
79                return self.url_result(entity['perma_url'])
80        else:
81            assert(entity['subtype'] == 'video')
82            video_id = entity_id
83        # 1080p and webm formats available only on the sources array
84        video = self._call_api(
85            'v2/media/video/' + video_id, video_id, 'video')
86
87        formats = []
88        for source in (video.get('sources') or []):
89            src = source.get('src')
90            if not src:
91                continue
92            formats.append({
93                'format_id': source.get('label'),
94                'height': int_or_none(source.get('size')),
95                'url': src,
96            })
97        self._sort_formats(formats)
98
99        entity = video.get('entity') or entity
100        owner = entity.get('ownerObj') or {}
101        uploader_id = owner.get('username')
102
103        tags = entity.get('tags')
104        if tags and isinstance(tags, compat_str):
105            tags = [tags]
106
107        thumbnail = None
108        poster = video.get('poster') or entity.get('thumbnail_src')
109        if poster:
110            urlh = self._request_webpage(poster, video_id, fatal=False)
111            if urlh:
112                thumbnail = urlh.geturl()
113
114        return {
115            'id': video_id,
116            'title': entity.get('title') or video_id,
117            'formats': formats,
118            'description': clean_html(entity.get('description')) or None,
119            'license': str_or_none(entity.get('license')),
120            'timestamp': int_or_none(entity.get('time_created')),
121            'uploader': strip_or_none(owner.get('name')),
122            'uploader_id': uploader_id,
123            'uploader_url': 'https://www.minds.com/' + uploader_id if uploader_id else None,
124            'view_count': int_or_none(entity.get('play:count')),
125            'like_count': int_or_none(entity.get('thumbs:up:count')),
126            'dislike_count': int_or_none(entity.get('thumbs:down:count')),
127            'tags': tags,
128            'comment_count': int_or_none(entity.get('comments:count')),
129            'thumbnail': thumbnail,
130        }
131
132
133class MindsFeedBaseIE(MindsBaseIE):
134    _PAGE_SIZE = 150
135
136    def _entries(self, feed_id):
137        query = {'limit': self._PAGE_SIZE, 'sync': 1}
138        i = 1
139        while True:
140            data = self._call_api(
141                'v2/feeds/container/%s/videos' % feed_id,
142                feed_id, 'page %s' % i, query)
143            entities = data.get('entities') or []
144            for entity in entities:
145                guid = entity.get('guid')
146                if not guid:
147                    continue
148                yield self.url_result(
149                    'https://www.minds.com/newsfeed/' + guid,
150                    MindsIE.ie_key(), guid)
151            query['from_timestamp'] = data['load-next']
152            if not (query['from_timestamp'] and len(entities) == self._PAGE_SIZE):
153                break
154            i += 1
155
156    def _real_extract(self, url):
157        feed_id = self._match_id(url)
158        feed = self._call_api(
159            'v1/%s/%s' % (self._FEED_PATH, feed_id),
160            feed_id, self._FEED_TYPE)[self._FEED_TYPE]
161
162        return self.playlist_result(
163            self._entries(feed['guid']), feed_id,
164            strip_or_none(feed.get('name')),
165            feed.get('briefdescription'))
166
167
168class MindsChannelIE(MindsFeedBaseIE):
169    _FEED_TYPE = 'channel'
170    IE_NAME = 'minds:' + _FEED_TYPE
171    _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'(?!(?:newsfeed|media|api|archive|groups)/)(?P<id>[^/?&#]+)'
172    _FEED_PATH = 'channel'
173    _TEST = {
174        'url': 'https://www.minds.com/ottman',
175        'info_dict': {
176            'id': 'ottman',
177            'title': 'Bill Ottman',
178            'description': 'Co-creator & CEO @minds',
179        },
180        'playlist_mincount': 54,
181    }
182
183
184class MindsGroupIE(MindsFeedBaseIE):
185    _FEED_TYPE = 'group'
186    IE_NAME = 'minds:' + _FEED_TYPE
187    _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'groups/profile/(?P<id>[0-9]+)'
188    _FEED_PATH = 'groups/group'
189    _TEST = {
190        'url': 'https://www.minds.com/groups/profile/785582576369672204/feed/videos',
191        'info_dict': {
192            'id': '785582576369672204',
193            'title': 'Cooking Videos',
194        },
195        'playlist_mincount': 1,
196    }
197