summaryrefslogtreecommitdiffhomepage
path: root/libs/subliminal_patch/providers/karagarga.py
blob: 9e0705c0a2cccc73fc884d575d6bb6f20806a4e4 (plain)
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# -*- coding: utf-8 -*-

import datetime
import logging

from bs4 import BeautifulSoup as bso
from requests import Session
from subliminal.cache import region as cache_region
from subliminal.exceptions import AuthenticationError
from subliminal.exceptions import ConfigurationError
from subliminal_patch.core import Movie
from subliminal_patch.providers import Provider
from subliminal_patch.providers.utils import update_matches
from subliminal_patch.subtitle import Subtitle
from subzero.language import Language

logger = logging.getLogger(__name__)

_PROVIDER_NAME = "karagarga"
_BASE_URL = "https://karagarga.in"


class KaragargaSubtitle(Subtitle):
    provider_name = _PROVIDER_NAME
    hash_verifiable = False

    def __init__(self, language, page_link, release_info, downloads):
        super().__init__(language, page_link=page_link)

        self.release_info = release_info
        self.downloads = downloads
        self._matches = {"title", "year"}

    def get_matches(self, video):
        update_matches(self._matches, video, self.release_info, type="movie")

        return self._matches

    @property
    def id(self):
        return self.page_link


_NO_LOGGED_IN_REDIRECT = 302
_EXPIRATION_TIME = datetime.timedelta(weeks=1).total_seconds()


class KaragargaProvider(Provider):
    provider_name = _PROVIDER_NAME

    # Only english for now
    languages = {Language.fromietf("en")}

    video_types = (Movie,)
    subtitle_class = KaragargaSubtitle
    _session: Session

    def __init__(self, username: str, password: str, f_username=None, f_password=None):
        if not username or not password:
            raise ConfigurationError("Username/password not provided")

        self._username = username
        self._password = password
        self._f_username = f_username or username
        self._f_password = f_password or password

    def initialize(self):
        self._session = Session()
        self._session.headers.update(
            {"authority": "karagarga.in", "user-agent": "Bazarr"}
        )
        self._login()

    def terminate(self):
        self._session.close()

    def _login(self):
        self._login_main()
        self._login_forum()

    def _login_main(self):
        data = {
            "username": self._username,
            "password": self._password,
        }

        self._session.post(f"{_BASE_URL}/takelogin.php", data=data)

        if "pass" not in self._session.cookies:
            raise AuthenticationError("Invalid username/password")

        logger.debug("Karagarga login: OK")

    def _login_forum(self):
        params = {
            "app": "core",
            "module": "global",
            "section": "login",
            "do": "process",
        }

        data = {
            # What's the origin of this key?
            "auth_key": "880ea6a14ea49e853634fbdc5015a024",
            #
            "referer": "https://forum.karagarga.in/",
            "ips_username": self._username,
            "ips_password": self._password,
            "rememberMe": "1",
            "anonymous": "1",
        }

        self._session.post(
            "https://forum.karagarga.in/index.php", params=params, data=data
        )

        if not {"session_id", "pass_hash"}.issubset(self._session.cookies.keys()):
            raise AuthenticationError("Invalid forum username/password")

        logger.debug("Karagarga forum login: OK")

    @cache_region.cache_on_arguments(expiration_time=_EXPIRATION_TIME)
    def _cached_get(self, url, params):
        response = self._session.get(url, params=params)
        if response.status_code == _NO_LOGGED_IN_REDIRECT:
            raise AuthenticationError("Not logged in")

        return response.content

    def _search_movie(self, title, year):
        params = {"search": title, "status": "completed"}
        content = self._cached_get(f"{_BASE_URL}/pots.php", params)

        soup = bso(content, "html.parser")

        table = soup.find("table", {"cellspacing": "5"})

        if table is None:
            logger.debug("Failed to get table. Returning []")
            return []

        subtitles = []
        scans = 0

        for tr_ in table.find_all("tr"):  # type: ignore
            if "forum.karagarga" not in str(tr_):
                continue

            found_tds = tr_.find_all("td")
            if len(found_tds) != 11:
                continue

            title = found_tds[1].text

            if f"({year}" not in title:
                logger.debug("Year doesn't match: %s", title)
                continue

            logger.debug("Movie matched: %s", title)

            requested_language = found_tds[5].text
            if "English" not in requested_language:
                continue

            forum_item = found_tds[9]

            if "approved" not in str(forum_item):
                logger.debug("Non-approved subtitle: %s", title)
                continue

            try:
                forum_url = str(forum_item.find("a").get("href"))
            except AttributeError:
                continue

            if scans > 2:
                logger.debug("Forum scans limit exceeded")
                break

            subtitles += self._parse_from_forum(forum_url, Language.fromietf("en"))
            scans += 1

        return subtitles

    def _parse_from_forum(self, url, language):
        logger.debug("Scanning forum for subs: %s", url)

        content = self._cached_get(url, {})

        soup = bso(content, "html.parser")

        for post in soup.find_all("div", {"class": "post entry-content"}):
            yield from _gen_subtitles(post, language)

    def list_subtitles(self, video, languages):
        subtitles = self._search_movie(video.title, video.year)
        if not subtitles:
            return []

        subtitles.sort(key=lambda x: x.downloads, reverse=True)
        # Always return the most downloaded subtitle from the forum

        return [subtitles[0]]

    def download_subtitle(self, subtitle):
        response = self._session.get(subtitle.page_link, allow_redirects=True)
        response.raise_for_status()

        subtitle.content = response.content


def _gen_subtitles(post, language):
    seen_urls = set()

    for potential in post.select("p,li.attachment,div"):
        downloads = potential.find("span", {"class": "desc lighter"})
        if not downloads:
            continue

        try:
            download_count = int(downloads.text.split()[0].strip())
            item = [a_ for a_ in potential.find_all("a") if a_.find("strong")][0]
            release_info = item.find("strong").text
        except (AttributeError, KeyError, ValueError) as error:
            logger.debug("Error parsing post: %s", error)
            continue

        url = item.get("href")

        if not url or url in seen_urls:
            continue

        seen_urls.add(url)

        subtitle = KaragargaSubtitle(language, url, release_info, download_count)
        logger.debug("Valid subtitle found: %s - %s", release_info, subtitle)
        yield subtitle