mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Re-design the db structure & code flow for new add-on MediaFusion
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
FROM mcr.microsoft.com/playwright/python:v1.38.0-jammy
|
||||
|
||||
# Install python 3.11
|
||||
RUN apt-get update && apt-get install -y python3.11 python3-pip
|
||||
|
||||
RUN pip install pipenv
|
||||
|
||||
WORKDIR /mediafusion
|
||||
|
||||
# Install Python dependencies
|
||||
COPY Pipfile Pipfile.lock ./
|
||||
|
||||
RUN pipenv install
|
||||
|
||||
# Copy the rest of the code
|
||||
COPY . .
|
||||
|
||||
ARG PORT=8000
|
||||
ENV PORT=$PORT
|
||||
|
||||
|
||||
# Run the server
|
||||
CMD ["pipenv", "run", "uvicorn", "--host", "0.0.0.0", "--port", "${PORT}", "app.main:app"]
|
||||
@@ -22,6 +22,9 @@ bencodepy = "*"
|
||||
pydantic-settings = "*"
|
||||
pycryptodome = "*"
|
||||
seedrcc = "*"
|
||||
parse-torrent-title = {editable = true, git = "https://github.com/mhdzumair/parse-torrent-title"}
|
||||
playwright = "==1.38.0"
|
||||
playwright-stealth = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
||||
Generated
+335
-283
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f650867c93dd3313074582e67745a241c23acffb4589187066d091dd9b8e5096"
|
||||
"sha256": "0db3ee31c019d926ab8bc9af95a5d2af5aa5e01c0168273870becdc629067518"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.11"
|
||||
"python_version": "3"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -42,11 +42,11 @@
|
||||
},
|
||||
"beanie": {
|
||||
"hashes": [
|
||||
"sha256:180a6d5e854297be2da84689b03509e7dc4f40f00c58a894a17ad40a00630838",
|
||||
"sha256:df1bcac1d5834730d3a1e461814fd0dfa173d91daca047b4d8ddcbf54343b4fd"
|
||||
"sha256:2b03cfe43ff383526d8ec67a88d8f0e60e251f2631af180c30fb4ac1fb9d900b",
|
||||
"sha256:9721e3b0dc435e5a42b64e58d4f88bb9a447eb94c489de7ceb75854f45ec2a19"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.21.0"
|
||||
"version": "==1.22.6"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
@@ -68,94 +68,109 @@
|
||||
"sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
|
||||
"sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.0'",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2023.7.22"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96",
|
||||
"sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c",
|
||||
"sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710",
|
||||
"sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706",
|
||||
"sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020",
|
||||
"sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252",
|
||||
"sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad",
|
||||
"sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329",
|
||||
"sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a",
|
||||
"sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f",
|
||||
"sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6",
|
||||
"sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4",
|
||||
"sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a",
|
||||
"sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46",
|
||||
"sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2",
|
||||
"sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23",
|
||||
"sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace",
|
||||
"sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd",
|
||||
"sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982",
|
||||
"sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10",
|
||||
"sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2",
|
||||
"sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea",
|
||||
"sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09",
|
||||
"sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5",
|
||||
"sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149",
|
||||
"sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489",
|
||||
"sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9",
|
||||
"sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80",
|
||||
"sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592",
|
||||
"sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3",
|
||||
"sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6",
|
||||
"sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed",
|
||||
"sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c",
|
||||
"sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200",
|
||||
"sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a",
|
||||
"sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e",
|
||||
"sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d",
|
||||
"sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6",
|
||||
"sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623",
|
||||
"sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669",
|
||||
"sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3",
|
||||
"sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa",
|
||||
"sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9",
|
||||
"sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2",
|
||||
"sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f",
|
||||
"sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1",
|
||||
"sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4",
|
||||
"sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a",
|
||||
"sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8",
|
||||
"sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3",
|
||||
"sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029",
|
||||
"sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f",
|
||||
"sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959",
|
||||
"sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22",
|
||||
"sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7",
|
||||
"sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952",
|
||||
"sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346",
|
||||
"sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e",
|
||||
"sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d",
|
||||
"sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299",
|
||||
"sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd",
|
||||
"sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a",
|
||||
"sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3",
|
||||
"sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037",
|
||||
"sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94",
|
||||
"sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c",
|
||||
"sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858",
|
||||
"sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a",
|
||||
"sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449",
|
||||
"sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c",
|
||||
"sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918",
|
||||
"sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1",
|
||||
"sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c",
|
||||
"sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac",
|
||||
"sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"
|
||||
"sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843",
|
||||
"sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786",
|
||||
"sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e",
|
||||
"sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8",
|
||||
"sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4",
|
||||
"sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa",
|
||||
"sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d",
|
||||
"sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82",
|
||||
"sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7",
|
||||
"sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895",
|
||||
"sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d",
|
||||
"sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a",
|
||||
"sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382",
|
||||
"sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678",
|
||||
"sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b",
|
||||
"sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e",
|
||||
"sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741",
|
||||
"sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4",
|
||||
"sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596",
|
||||
"sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9",
|
||||
"sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69",
|
||||
"sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c",
|
||||
"sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77",
|
||||
"sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13",
|
||||
"sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459",
|
||||
"sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e",
|
||||
"sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7",
|
||||
"sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908",
|
||||
"sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a",
|
||||
"sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f",
|
||||
"sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8",
|
||||
"sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482",
|
||||
"sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d",
|
||||
"sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d",
|
||||
"sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545",
|
||||
"sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34",
|
||||
"sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86",
|
||||
"sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6",
|
||||
"sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe",
|
||||
"sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e",
|
||||
"sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc",
|
||||
"sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7",
|
||||
"sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd",
|
||||
"sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c",
|
||||
"sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557",
|
||||
"sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a",
|
||||
"sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89",
|
||||
"sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078",
|
||||
"sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e",
|
||||
"sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4",
|
||||
"sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403",
|
||||
"sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0",
|
||||
"sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89",
|
||||
"sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115",
|
||||
"sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9",
|
||||
"sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05",
|
||||
"sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a",
|
||||
"sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec",
|
||||
"sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56",
|
||||
"sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38",
|
||||
"sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479",
|
||||
"sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c",
|
||||
"sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e",
|
||||
"sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd",
|
||||
"sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186",
|
||||
"sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455",
|
||||
"sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c",
|
||||
"sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65",
|
||||
"sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78",
|
||||
"sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287",
|
||||
"sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df",
|
||||
"sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43",
|
||||
"sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1",
|
||||
"sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7",
|
||||
"sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989",
|
||||
"sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a",
|
||||
"sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63",
|
||||
"sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884",
|
||||
"sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649",
|
||||
"sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810",
|
||||
"sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828",
|
||||
"sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4",
|
||||
"sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2",
|
||||
"sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd",
|
||||
"sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5",
|
||||
"sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe",
|
||||
"sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293",
|
||||
"sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e",
|
||||
"sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e",
|
||||
"sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==3.2.0"
|
||||
"version": "==3.3.0"
|
||||
},
|
||||
"cinemagoer": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/cinemagoer/cinemagoer",
|
||||
"ref": "e4f39f42ce3d5873f2c9bdb48ff7355179a7c1e5"
|
||||
"ref": "19f024d4b8333af3c23aa50cef26c5e565110cb6"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
@@ -183,11 +198,11 @@
|
||||
},
|
||||
"fastapi": {
|
||||
"hashes": [
|
||||
"sha256:345844e6a82062f06a096684196aaf96c1198b25c06b72c1311b882aa2d8a35d",
|
||||
"sha256:5e5f17e826dbd9e9b5a5145976c5cd90bcaa61f2bf9a69aca423f2bcebe44d83"
|
||||
"sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e",
|
||||
"sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.103.1"
|
||||
"version": "==0.103.2"
|
||||
},
|
||||
"greenlet": {
|
||||
"hashes": [
|
||||
@@ -256,7 +271,7 @@
|
||||
"sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1",
|
||||
"sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"
|
||||
],
|
||||
"markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.0.2"
|
||||
},
|
||||
"h11": {
|
||||
@@ -325,11 +340,11 @@
|
||||
},
|
||||
"lazy-model": {
|
||||
"hashes": [
|
||||
"sha256:74406bd54cf96a89b73570d8409b538c660ae4d0bfb4b30bbe46db2658a5937d",
|
||||
"sha256:90dd0b26d3033fadc18f697ab9cdd66cdf8e55469f32f2d88efd92610468e2d6"
|
||||
"sha256:57c0e91e171530c4fca7aebc3ac05a163a85cddd941bf7527cc46c0ddafca47c",
|
||||
"sha256:5a3241775c253e36d9069d236be8378288a93d4fc53805211fd152e04cc9c342"
|
||||
],
|
||||
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
||||
"version": "==0.1.0b0"
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"lxml": {
|
||||
"hashes": [
|
||||
@@ -503,163 +518,193 @@
|
||||
"index": "pypi",
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"pycryptodome": {
|
||||
"parse-torrent-title": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/mhdzumair/parse-torrent-title",
|
||||
"hashes": [
|
||||
"sha256:01489bbdf709d993f3058e2996f8f40fee3f0ea4d995002e5968965fa2fe89fb",
|
||||
"sha256:10da29526a2a927c7d64b8f34592f461d92ae55fc97981aab5bbcde8cb465bb6",
|
||||
"sha256:12600268763e6fec3cefe4c2dcdf79bde08d0b6dc1813887e789e495cb9f3403",
|
||||
"sha256:157c9b5ba5e21b375f052ca78152dd309a09ed04703fd3721dce3ff8ecced148",
|
||||
"sha256:16bfd98dbe472c263ed2821284118d899c76968db1a6665ade0c46805e6b29a4",
|
||||
"sha256:363dd6f21f848301c2dcdeb3c8ae5f0dee2286a5e952a0f04954b82076f23825",
|
||||
"sha256:3811e31e1ac3069988f7a1c9ee7331b942e605dfc0f27330a9ea5997e965efb2",
|
||||
"sha256:422c89fd8df8a3bee09fb8d52aaa1e996120eafa565437392b781abec2a56e14",
|
||||
"sha256:4604816adebd4faf8810782f137f8426bf45fee97d8427fa8e1e49ea78a52e2c",
|
||||
"sha256:4944defabe2ace4803f99543445c27dd1edbe86d7d4edb87b256476a91e9ffa4",
|
||||
"sha256:51eae079ddb9c5f10376b4131be9589a6554f6fd84f7f655180937f611cd99a2",
|
||||
"sha256:53aee6be8b9b6da25ccd9028caf17dcdce3604f2c7862f5167777b707fbfb6cb",
|
||||
"sha256:62a1e8847fabb5213ccde38915563140a5b338f0d0a0d363f996b51e4a6165cf",
|
||||
"sha256:6f4b967bb11baea9128ec88c3d02f55a3e338361f5e4934f5240afcb667fdaec",
|
||||
"sha256:78d863476e6bad2a592645072cc489bb90320972115d8995bcfbee2f8b209918",
|
||||
"sha256:795bd1e4258a2c689c0b1f13ce9684fa0dd4c0e08680dcf597cf9516ed6bc0f3",
|
||||
"sha256:7a3d22c8ee63de22336679e021c7f2386f7fc465477d59675caa0e5706387944",
|
||||
"sha256:83c75952dcf4a4cebaa850fa257d7a860644c70a7cd54262c237c9f2be26f76e",
|
||||
"sha256:928078c530da78ff08e10eb6cada6e0dff386bf3d9fa9871b4bbc9fbc1efe024",
|
||||
"sha256:957b221d062d5752716923d14e0926f47670e95fead9d240fa4d4862214b9b2f",
|
||||
"sha256:9ad6f09f670c466aac94a40798e0e8d1ef2aa04589c29faa5b9b97566611d1d1",
|
||||
"sha256:9c8eda4f260072f7dbe42f473906c659dcbadd5ae6159dfb49af4da1293ae380",
|
||||
"sha256:b1d9701d10303eec8d0bd33fa54d44e67b8be74ab449052a8372f12a66f93fb9",
|
||||
"sha256:b6a610f8bfe67eab980d6236fdc73bfcdae23c9ed5548192bb2d530e8a92780e",
|
||||
"sha256:c9adee653fc882d98956e33ca2c1fb582e23a8af7ac82fee75bd6113c55a0413",
|
||||
"sha256:cb1be4d5af7f355e7d41d36d8eec156ef1382a88638e8032215c215b82a4b8ec",
|
||||
"sha256:d1497a8cd4728db0e0da3c304856cb37c0c4e3d0b36fcbabcc1600f18504fc54",
|
||||
"sha256:d20082bdac9218649f6abe0b885927be25a917e29ae0502eaf2b53f1233ce0c2",
|
||||
"sha256:e8ad74044e5f5d2456c11ed4cfd3e34b8d4898c0cb201c4038fe41458a82ea27",
|
||||
"sha256:f022a4fd2a5263a5c483a2bb165f9cb27f2be06f2f477113783efe3fe2ad887b",
|
||||
"sha256:f21efb8438971aa16924790e1c3dba3a33164eb4000106a55baaed522c261acf",
|
||||
"sha256:fc0a73f4db1e31d4a6d71b672a48f3af458f548059aa05e83022d5f61aac9c08"
|
||||
"sha256:556830ea268f808a3cee3fc6e9380ab3293b13a8172567a5a39b5d858a5387af",
|
||||
"sha256:9852cfba2b746683733d77d2019a24f57d5e8c2c4db395f6ff03035ea628a4bb"
|
||||
],
|
||||
"ref": "0ccb3aa7523063375ca8a6e3677a493ddba31e26"
|
||||
},
|
||||
"playwright": {
|
||||
"hashes": [
|
||||
"sha256:1c46a7ed7702b9f97b57737132f25e2052ef2e9541c3613d896e92739d2ea4ee",
|
||||
"sha256:22e4a49d61a20a21d6a4a90891d4d08df5091f3719272d7a31c4c7f0ff436683",
|
||||
"sha256:324e317c6ddc919a01e98ed182a54c88c0b6e775e91aea2996ed320b436c0f27",
|
||||
"sha256:33d6500d94c5e4608d3a74372d6f50ecbebca55dc55eaee3f70b21eaf02b17aa",
|
||||
"sha256:801029161725bd9a8c1ea2d29125074f7e54bfa7b0ef85c6dfb667023a0702c8",
|
||||
"sha256:ce5c2d2c49c97ea856129ac895dc7277df3c877db4a998340bd08efc3696e7fb",
|
||||
"sha256:d0288c8932d7f14bc231e4a6761ecf76fff879d1601cfa3b6f6aefd544468911"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.18.0"
|
||||
"version": "==1.38.0"
|
||||
},
|
||||
"playwright-stealth": {
|
||||
"hashes": [
|
||||
"sha256:b1b2bcf58eb6859aa53d42c49b91c4e27b74a6d13fc3d0c85eea513dd55efda3",
|
||||
"sha256:b504d951d00fac755c7d13665a29611d415180510bd7d23f14ebc89439ba2043"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.6"
|
||||
},
|
||||
"pycryptodome": {
|
||||
"hashes": [
|
||||
"sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6",
|
||||
"sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810",
|
||||
"sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a",
|
||||
"sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db",
|
||||
"sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33",
|
||||
"sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5",
|
||||
"sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551",
|
||||
"sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa",
|
||||
"sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4",
|
||||
"sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405",
|
||||
"sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc",
|
||||
"sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997",
|
||||
"sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb",
|
||||
"sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e",
|
||||
"sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9",
|
||||
"sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f",
|
||||
"sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e",
|
||||
"sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34",
|
||||
"sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631",
|
||||
"sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c",
|
||||
"sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde",
|
||||
"sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7",
|
||||
"sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa",
|
||||
"sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0",
|
||||
"sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea",
|
||||
"sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e",
|
||||
"sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400",
|
||||
"sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270",
|
||||
"sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f",
|
||||
"sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1",
|
||||
"sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434",
|
||||
"sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.19.0"
|
||||
},
|
||||
"pydantic": {
|
||||
"hashes": [
|
||||
"sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d",
|
||||
"sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"
|
||||
"sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7",
|
||||
"sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.0"
|
||||
"version": "==2.4.2"
|
||||
},
|
||||
"pydantic-core": {
|
||||
"hashes": [
|
||||
"sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3",
|
||||
"sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6",
|
||||
"sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418",
|
||||
"sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7",
|
||||
"sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc",
|
||||
"sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5",
|
||||
"sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7",
|
||||
"sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f",
|
||||
"sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48",
|
||||
"sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad",
|
||||
"sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef",
|
||||
"sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9",
|
||||
"sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58",
|
||||
"sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da",
|
||||
"sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149",
|
||||
"sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b",
|
||||
"sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881",
|
||||
"sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456",
|
||||
"sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98",
|
||||
"sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e",
|
||||
"sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c",
|
||||
"sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e",
|
||||
"sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb",
|
||||
"sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862",
|
||||
"sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728",
|
||||
"sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6",
|
||||
"sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf",
|
||||
"sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e",
|
||||
"sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd",
|
||||
"sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8",
|
||||
"sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987",
|
||||
"sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a",
|
||||
"sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2",
|
||||
"sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784",
|
||||
"sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b",
|
||||
"sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309",
|
||||
"sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7",
|
||||
"sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413",
|
||||
"sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2",
|
||||
"sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f",
|
||||
"sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6",
|
||||
"sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b",
|
||||
"sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3",
|
||||
"sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7",
|
||||
"sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d",
|
||||
"sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378",
|
||||
"sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8",
|
||||
"sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe",
|
||||
"sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7",
|
||||
"sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973",
|
||||
"sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad",
|
||||
"sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34",
|
||||
"sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb",
|
||||
"sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c",
|
||||
"sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465",
|
||||
"sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5",
|
||||
"sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588",
|
||||
"sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950",
|
||||
"sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70",
|
||||
"sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32",
|
||||
"sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7",
|
||||
"sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec",
|
||||
"sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67",
|
||||
"sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645",
|
||||
"sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db",
|
||||
"sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7",
|
||||
"sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170",
|
||||
"sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17",
|
||||
"sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb",
|
||||
"sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c",
|
||||
"sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819",
|
||||
"sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b",
|
||||
"sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d",
|
||||
"sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a",
|
||||
"sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525",
|
||||
"sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1",
|
||||
"sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76",
|
||||
"sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60",
|
||||
"sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b",
|
||||
"sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42",
|
||||
"sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd",
|
||||
"sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014",
|
||||
"sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d",
|
||||
"sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a",
|
||||
"sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa",
|
||||
"sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f",
|
||||
"sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26",
|
||||
"sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a",
|
||||
"sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64",
|
||||
"sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5",
|
||||
"sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057",
|
||||
"sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50",
|
||||
"sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b",
|
||||
"sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483",
|
||||
"sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b",
|
||||
"sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c",
|
||||
"sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9",
|
||||
"sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698",
|
||||
"sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362",
|
||||
"sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49",
|
||||
"sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282",
|
||||
"sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0",
|
||||
"sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a",
|
||||
"sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b",
|
||||
"sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1",
|
||||
"sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"
|
||||
"sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e",
|
||||
"sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33",
|
||||
"sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7",
|
||||
"sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7",
|
||||
"sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea",
|
||||
"sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4",
|
||||
"sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0",
|
||||
"sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7",
|
||||
"sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94",
|
||||
"sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff",
|
||||
"sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82",
|
||||
"sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd",
|
||||
"sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893",
|
||||
"sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e",
|
||||
"sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d",
|
||||
"sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901",
|
||||
"sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9",
|
||||
"sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c",
|
||||
"sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7",
|
||||
"sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891",
|
||||
"sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f",
|
||||
"sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a",
|
||||
"sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9",
|
||||
"sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5",
|
||||
"sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e",
|
||||
"sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a",
|
||||
"sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c",
|
||||
"sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f",
|
||||
"sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514",
|
||||
"sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b",
|
||||
"sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302",
|
||||
"sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096",
|
||||
"sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0",
|
||||
"sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27",
|
||||
"sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884",
|
||||
"sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a",
|
||||
"sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357",
|
||||
"sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430",
|
||||
"sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221",
|
||||
"sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325",
|
||||
"sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4",
|
||||
"sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05",
|
||||
"sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55",
|
||||
"sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875",
|
||||
"sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970",
|
||||
"sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc",
|
||||
"sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6",
|
||||
"sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f",
|
||||
"sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b",
|
||||
"sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d",
|
||||
"sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15",
|
||||
"sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118",
|
||||
"sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee",
|
||||
"sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e",
|
||||
"sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6",
|
||||
"sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208",
|
||||
"sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede",
|
||||
"sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3",
|
||||
"sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e",
|
||||
"sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada",
|
||||
"sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175",
|
||||
"sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a",
|
||||
"sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c",
|
||||
"sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f",
|
||||
"sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58",
|
||||
"sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f",
|
||||
"sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a",
|
||||
"sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a",
|
||||
"sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921",
|
||||
"sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e",
|
||||
"sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904",
|
||||
"sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776",
|
||||
"sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52",
|
||||
"sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf",
|
||||
"sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8",
|
||||
"sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f",
|
||||
"sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b",
|
||||
"sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63",
|
||||
"sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c",
|
||||
"sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f",
|
||||
"sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468",
|
||||
"sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e",
|
||||
"sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab",
|
||||
"sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2",
|
||||
"sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb",
|
||||
"sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb",
|
||||
"sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132",
|
||||
"sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b",
|
||||
"sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607",
|
||||
"sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934",
|
||||
"sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698",
|
||||
"sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e",
|
||||
"sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561",
|
||||
"sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de",
|
||||
"sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b",
|
||||
"sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a",
|
||||
"sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595",
|
||||
"sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402",
|
||||
"sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881",
|
||||
"sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429",
|
||||
"sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5",
|
||||
"sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7",
|
||||
"sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c",
|
||||
"sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531",
|
||||
"sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6",
|
||||
"sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.6.3"
|
||||
"version": "==2.10.1"
|
||||
},
|
||||
"pydantic-settings": {
|
||||
"hashes": [
|
||||
@@ -669,6 +714,13 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.0.3"
|
||||
},
|
||||
"pyee": {
|
||||
"hashes": [
|
||||
"sha256:2770c4928abc721f46b705e6a72b0c59480c4a69c9a83ca0b00bb994f1ea4b32",
|
||||
"sha256:9f066570130c554e9cc12de5a9d86f57c7ee47fece163bbdaa3e9c933cfbdfa5"
|
||||
],
|
||||
"version": "==9.0.4"
|
||||
},
|
||||
"pymongo": {
|
||||
"extras": [
|
||||
"srv"
|
||||
@@ -895,50 +947,50 @@
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e",
|
||||
"sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4",
|
||||
"sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0",
|
||||
"sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b",
|
||||
"sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0",
|
||||
"sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed",
|
||||
"sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208",
|
||||
"sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53",
|
||||
"sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c",
|
||||
"sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9",
|
||||
"sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3",
|
||||
"sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763",
|
||||
"sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e",
|
||||
"sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e",
|
||||
"sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8",
|
||||
"sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927",
|
||||
"sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267",
|
||||
"sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf",
|
||||
"sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5",
|
||||
"sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61",
|
||||
"sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a",
|
||||
"sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4",
|
||||
"sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98",
|
||||
"sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a",
|
||||
"sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501",
|
||||
"sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624",
|
||||
"sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9",
|
||||
"sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed",
|
||||
"sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74",
|
||||
"sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50",
|
||||
"sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d",
|
||||
"sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897",
|
||||
"sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6",
|
||||
"sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14",
|
||||
"sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec",
|
||||
"sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14",
|
||||
"sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459",
|
||||
"sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7",
|
||||
"sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179",
|
||||
"sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57",
|
||||
"sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6"
|
||||
"sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e",
|
||||
"sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6",
|
||||
"sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258",
|
||||
"sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce",
|
||||
"sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede",
|
||||
"sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce",
|
||||
"sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4",
|
||||
"sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4",
|
||||
"sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5",
|
||||
"sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01",
|
||||
"sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9",
|
||||
"sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9",
|
||||
"sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67",
|
||||
"sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9",
|
||||
"sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9",
|
||||
"sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301",
|
||||
"sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8",
|
||||
"sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615",
|
||||
"sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa",
|
||||
"sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4",
|
||||
"sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446",
|
||||
"sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4",
|
||||
"sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178",
|
||||
"sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af",
|
||||
"sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b",
|
||||
"sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe",
|
||||
"sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9",
|
||||
"sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09",
|
||||
"sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a",
|
||||
"sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063",
|
||||
"sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef",
|
||||
"sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1",
|
||||
"sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66",
|
||||
"sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231",
|
||||
"sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e",
|
||||
"sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec",
|
||||
"sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430",
|
||||
"sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce",
|
||||
"sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9",
|
||||
"sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa",
|
||||
"sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.0.20"
|
||||
"version": "==2.0.21"
|
||||
},
|
||||
"starlette": {
|
||||
"hashes": [
|
||||
@@ -958,11 +1010,11 @@
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
|
||||
"sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
|
||||
"sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0",
|
||||
"sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==4.7.1"
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==4.8.0"
|
||||
},
|
||||
"tzlocal": {
|
||||
"hashes": [
|
||||
@@ -974,11 +1026,11 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11",
|
||||
"sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"
|
||||
"sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2",
|
||||
"sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.0.4"
|
||||
"version": "==2.0.6"
|
||||
},
|
||||
"uvicorn": {
|
||||
"extras": [
|
||||
|
||||
@@ -1,25 +1,54 @@
|
||||
# Disclaimer :warning: :warning: :warning:
|
||||
# Media Fusion Stremio Addon :clapper:
|
||||
|
||||
> The contents of these scripts and files are created for Educational Purpose. Use of the Add-on is at your own risk.
|
||||
> This scrapper is written in python and serves API for [stremio](https://www.stremio.com/).
|
||||
> All the credits goes to Tamil Blasters. I encourage you to use the website.
|
||||

|
||||
|
||||
## :warning: Disclaimer :warning:
|
||||
|
||||

|
||||
> The content of this script is created strictly for educational purposes. Use of the Add-on is at your own risk.
|
||||
> This Add-on, written in Python, serves as an API for [stremio](https://www.stremio.com/).
|
||||
> There is no affiliation with any scraping sites.
|
||||
|
||||
# Tamil Blasters Stremio Addon
|
||||
## :sparkles: Features
|
||||
|
||||
Stremio Addon for Tamil Blasters Movies & Series.
|
||||

|
||||
- Provides catalogs for multiple languages: Tamil, Hindi, Malayalam, Kannada, English, and dubbed movies & series.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
- Supports streams for playback with torrent, Real Debrid, and Seedr integration.
|
||||
|
||||

|
||||
|
||||
1. Install from home page: https://882b9915d0fe-stremio-tamilblasters.baby-beamup.club
|
||||
2. You can search on the third-party addon page as "Tamil Blasters"
|
||||
3. enter this link in stremio search bar to
|
||||
install: https://882b9915d0fe-stremio-tamilblasters.baby-beamup.club/manifest.json
|
||||
## :rocket: Installation
|
||||
|
||||
# References
|
||||
1. Install Stremio from [here](https://www.stremio.com/downloads).
|
||||
2. Navigate to [Media Fusion](https://882b9915d0fe-mediafusion.baby-beamup.club) and click on the 'Configure Add-on' button.
|
||||
|
||||
## :hammer_and_wrench: Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Python**: This project uses Python version 3.11. Ensure you have it installed.
|
||||
- **MongoDB**: Set up a MongoDB server. You can use [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) to create a free MongoDB cluster.
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Pipenv**: Use [Pipenv](https://pipenv.pypa.io/en/latest/) for managing project dependencies. If you don't have it installed, you can install it with:
|
||||
```bash
|
||||
pip install pipenv
|
||||
```
|
||||
2. **Clone**: Clone this repository.
|
||||
```bash
|
||||
git clone https://github.com/mhdzumair/MediaFusion
|
||||
```
|
||||
3. **Environment Variables**: Create a `.env` file in the root directory with the following variables:
|
||||
```bash
|
||||
MONGO_URI=<Your_MongoDB_URI>
|
||||
SECRET_KEY=<Your_Random_32_Character_Secret>
|
||||
HOST_URL=http://127.0.0.1:8000
|
||||
```
|
||||
4. **For scraping instructions**: refer to the [scrapping README](/scrappers/README.md).
|
||||
|
||||
## :books: References
|
||||
|
||||
- [Stremio Generic Add-on Guide](https://stremio.github.io/stremio-addon-guide/basics)
|
||||
- [Stremio Add-on SDK API docs](https://github.com/Stremio/stremio-addon-sdk/tree/master/docs/api)
|
||||
@@ -28,3 +57,5 @@ Stremio Addon for Tamil Blasters Movies & Series.
|
||||
- [beautifulsoup4](https://beautiful-soup-4.readthedocs.io/en/latest/)
|
||||
- [cinemagoer](https://cinemagoer.readthedocs.io/en/latest/)
|
||||
- [beanie](https://roman-right.github.io/beanie/)
|
||||
- [parse-torrent-title](https://github.com/platelminto/parse-torrent-title)
|
||||
- [torrentio-scraper Stremio Add-on](https://github.com/TheBeastLT/torrentio-scraper)
|
||||
|
||||
+120
-62
@@ -1,5 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Literal, Optional
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from fastapi import FastAPI, Request, Response, BackgroundTasks, Depends, HTTPException
|
||||
@@ -7,22 +9,22 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse, FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from typing import Literal
|
||||
from urllib.parse import quote
|
||||
|
||||
from db import database, crud, schemas
|
||||
from db.config import settings
|
||||
from scrappers import tamil_blasters_scrapper as tb_scrapper
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from streaming_providers.realdebrid.api import router as realdebrid_router
|
||||
from streaming_providers.realdebrid.utils import get_direct_link_from_realdebrid
|
||||
from streaming_providers.seedr.api import router as seedr_router
|
||||
from streaming_providers.seedr.utils import get_direct_link_from_seedr
|
||||
from scrappers import tamil_blasters_scrapper as tb_scrapper
|
||||
from utils import crypto, torrent
|
||||
from utils.parser import generate_catalog_ids, clean_name
|
||||
from utils.parser import generate_catalog_ids
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(levelname)s::%(asctime)s - %(message)s", datefmt="%d-%b-%y %H:%M:%S", level=settings.logging_level
|
||||
format="%(levelname)s::%(asctime)s - %(message)s",
|
||||
datefmt="%d-%b-%y %H:%M:%S",
|
||||
level=settings.logging_level,
|
||||
)
|
||||
app = FastAPI()
|
||||
|
||||
@@ -65,12 +67,12 @@ async def stop_scheduler():
|
||||
app.state.scheduler.shutdown(wait=False)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@app.get("/", tags=["home"])
|
||||
async def get_home(request: Request):
|
||||
with open("resources/manifest.json") as file:
|
||||
manifest = json.load(file)
|
||||
return TEMPLATES.TemplateResponse(
|
||||
"home.html",
|
||||
"html/home.html",
|
||||
{
|
||||
"request": request,
|
||||
"name": manifest.get("name"),
|
||||
@@ -85,47 +87,81 @@ async def get_home(request: Request):
|
||||
"English Movies & Series",
|
||||
"Dubbed Movies & Series",
|
||||
],
|
||||
"logo": "static/tamilblasters.png",
|
||||
"logo": "static/images/mediafusion_logo.png",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/favicon.ico")
|
||||
async def get_favicon():
|
||||
return FileResponse("resources/tamilblasters.png", media_type="image/x-icon")
|
||||
return FileResponse(
|
||||
"resources/images/mediafusion_logo.png", media_type="image/x-icon"
|
||||
)
|
||||
|
||||
|
||||
@app.get("/configure")
|
||||
@app.get("/{secret_str}/configure")
|
||||
@app.get("/configure", tags=["configure"])
|
||||
@app.get("/{secret_str}/configure", tags=["configure"])
|
||||
async def configure(
|
||||
response: Response, request: Request, user_data: schemas.UserData = Depends(crypto.decrypt_user_data)
|
||||
response: Response,
|
||||
request: Request,
|
||||
user_data: schemas.UserData = Depends(crypto.decrypt_user_data),
|
||||
):
|
||||
response.headers.update(headers)
|
||||
response.headers.update(no_cache_headers)
|
||||
if user_data.streaming_provider:
|
||||
user_data.streaming_provider.token = None
|
||||
return TEMPLATES.TemplateResponse("configure.html", {"request": request, "user_data": user_data.model_dump()})
|
||||
return TEMPLATES.TemplateResponse(
|
||||
"html/configure.html", {"request": request, "user_data": user_data.model_dump()}
|
||||
)
|
||||
|
||||
|
||||
@app.get("/manifest.json")
|
||||
@app.get("/{secret_str}/manifest.json")
|
||||
async def get_manifest(response: Response, user_data: schemas.UserData = Depends(crypto.decrypt_user_data)):
|
||||
@app.get("/manifest.json", tags=["manifest"])
|
||||
@app.get("/{secret_str}/manifest.json", tags=["manifest"])
|
||||
async def get_manifest(
|
||||
response: Response, user_data: schemas.UserData = Depends(crypto.decrypt_user_data)
|
||||
):
|
||||
response.headers.update(headers)
|
||||
response.headers.update(no_cache_headers)
|
||||
with open("resources/manifest.json") as file:
|
||||
manifest = json.load(file)
|
||||
|
||||
user_catalog_ids = generate_catalog_ids(user_data.preferred_movie_languages, user_data.preferred_series_languages)
|
||||
filtered_catalogs = [cat for cat in manifest["catalogs"] if cat["id"] in user_catalog_ids]
|
||||
user_catalog_ids = generate_catalog_ids(
|
||||
user_data.preferred_movie_languages, user_data.preferred_series_languages
|
||||
)
|
||||
filtered_catalogs = [
|
||||
cat for cat in manifest["catalogs"] if cat["id"] in user_catalog_ids
|
||||
]
|
||||
manifest["catalogs"] = filtered_catalogs
|
||||
|
||||
return manifest
|
||||
|
||||
|
||||
@app.get("/{secret_str}/catalog/{catalog_type}/{catalog_id}.json", response_model=schemas.Movie)
|
||||
@app.get("/catalog/{catalog_type}/{catalog_id}.json", response_model=schemas.Movie)
|
||||
@app.get("/{secret_str}/catalog/{catalog_type}/{catalog_id}/skip={skip}.json", response_model=schemas.Movie)
|
||||
@app.get("/catalog/{catalog_type}/{catalog_id}/skip={skip}.json", response_model=schemas.Movie)
|
||||
@app.get(
|
||||
"/{secret_str}/catalog/{catalog_type}/{catalog_id}.json",
|
||||
response_model=schemas.Metas,
|
||||
response_model_exclude_none=True,
|
||||
response_model_by_alias=False,
|
||||
tags=["catalog"],
|
||||
)
|
||||
@app.get(
|
||||
"/catalog/{catalog_type}/{catalog_id}.json",
|
||||
response_model=schemas.Metas,
|
||||
response_model_exclude_none=True,
|
||||
response_model_by_alias=False,
|
||||
tags=["catalog"],
|
||||
)
|
||||
@app.get(
|
||||
"/{secret_str}/catalog/{catalog_type}/{catalog_id}/skip={skip}.json",
|
||||
response_model=schemas.Metas,
|
||||
response_model_exclude_none=True,
|
||||
response_model_by_alias=False,
|
||||
tags=["catalog"],
|
||||
)
|
||||
@app.get(
|
||||
"/catalog/{catalog_type}/{catalog_id}/skip={skip}.json",
|
||||
response_model=schemas.Metas,
|
||||
response_model_exclude_none=True,
|
||||
response_model_by_alias=False,
|
||||
tags=["catalog"],
|
||||
)
|
||||
async def get_catalog(
|
||||
response: Response,
|
||||
catalog_type: Literal["movie", "series"],
|
||||
@@ -133,13 +169,21 @@ async def get_catalog(
|
||||
skip: int = 0,
|
||||
):
|
||||
response.headers.update(headers)
|
||||
movies = schemas.Movie()
|
||||
movies.metas.extend(await crud.get_movies_and_series_meta(catalog_id, skip))
|
||||
return movies
|
||||
metas = schemas.Metas()
|
||||
metas.metas.extend(await crud.get_meta_list(catalog_type, catalog_id, skip))
|
||||
return metas
|
||||
|
||||
|
||||
@app.get("/{secret_str}/catalog/{catalog_type}/tamil_blasters/search={search_query}.json", response_model=schemas.Movie)
|
||||
@app.get("/catalog/{catalog_type}/tamil_blasters/search={search_query}.json", response_model=schemas.Movie)
|
||||
@app.get(
|
||||
"/{secret_str}/catalog/{catalog_type}/tamil_blasters/search={search_query}.json",
|
||||
response_model=schemas.Metas,
|
||||
tags=["search"],
|
||||
)
|
||||
@app.get(
|
||||
"/catalog/{catalog_type}/tamil_blasters/search={search_query}.json",
|
||||
response_model=schemas.Metas,
|
||||
tags=["search"],
|
||||
)
|
||||
async def search_movie(
|
||||
response: Response,
|
||||
catalog_type: Literal["movie", "series"],
|
||||
@@ -151,9 +195,11 @@ async def search_movie(
|
||||
return await crud.process_search_query(search_query, catalog_type)
|
||||
|
||||
|
||||
@app.get("/{secret_str}/meta/{catalog_type}/{meta_id}.json")
|
||||
@app.get("/meta/{catalog_type}/{meta_id}.json")
|
||||
async def get_meta(catalog_type: Literal["movie", "series"], meta_id: str, response: Response):
|
||||
@app.get("/{secret_str}/meta/{catalog_type}/{meta_id}.json", tags=["meta"])
|
||||
@app.get("/meta/{catalog_type}/{meta_id}.json", tags=["meta"])
|
||||
async def get_meta(
|
||||
catalog_type: Literal["movie", "series"], meta_id: str, response: Response
|
||||
):
|
||||
response.headers.update(headers)
|
||||
if catalog_type == "movie":
|
||||
return await crud.get_movie_meta(meta_id)
|
||||
@@ -164,17 +210,25 @@ async def get_meta(catalog_type: Literal["movie", "series"], meta_id: str, respo
|
||||
"/{secret_str}/stream/{catalog_type}/{video_id}.json",
|
||||
response_model=schemas.Streams,
|
||||
response_model_exclude_none=True,
|
||||
tags=["stream"],
|
||||
)
|
||||
@app.get(
|
||||
"/stream/{catalog_type}/{video_id}.json",
|
||||
response_model=schemas.Streams,
|
||||
response_model_exclude_none=True,
|
||||
tags=["stream"],
|
||||
)
|
||||
@app.get("/stream/{catalog_type}/{video_id}.json", response_model=schemas.Streams, response_model_exclude_none=True)
|
||||
@app.get(
|
||||
"/{secret_str}/stream/{catalog_type}/{video_id}:{season}:{episode}.json",
|
||||
response_model=schemas.Streams,
|
||||
response_model_exclude_none=True,
|
||||
tags=["stream"],
|
||||
)
|
||||
@app.get(
|
||||
"/stream/{catalog_type}/{video_id}:{season}:{episode}.json",
|
||||
response_model=schemas.Streams,
|
||||
response_model_exclude_none=True,
|
||||
tags=["stream"],
|
||||
)
|
||||
async def get_streams(
|
||||
catalog_type: Literal["movie", "series"],
|
||||
@@ -186,50 +240,46 @@ async def get_streams(
|
||||
user_data: schemas.UserData = Depends(crypto.decrypt_user_data),
|
||||
):
|
||||
response.headers.update(headers)
|
||||
streams = schemas.Streams()
|
||||
|
||||
if catalog_type == "movie":
|
||||
fetched_streams = await crud.get_movie_streams(user_data, video_id)
|
||||
fetched_streams = await crud.get_movie_streams(user_data, secret_str, video_id)
|
||||
else:
|
||||
fetched_streams = await crud.get_series_streams(user_data, video_id, season, episode)
|
||||
fetched_streams = await crud.get_series_streams(
|
||||
user_data, secret_str, video_id, season, episode
|
||||
)
|
||||
|
||||
if user_data.streaming_provider:
|
||||
for stream in fetched_streams:
|
||||
torrent_name = quote(clean_name(f"{stream.stream_name} {stream.description}"))
|
||||
base_proxy_url = (
|
||||
f"{settings.host_url}/{secret_str}/streaming_provider?info_hash={stream.infoHash}&name={torrent_name}"
|
||||
)
|
||||
proxy_url = f"{base_proxy_url}&episode={episode}" if episode is not None else base_proxy_url
|
||||
stream.url = proxy_url
|
||||
stream.infoHash = None
|
||||
stream.behaviorHints = {"notWebReady": True}
|
||||
|
||||
fetched_streams.reverse()
|
||||
streams.streams.extend(fetched_streams)
|
||||
return streams
|
||||
return {"streams": fetched_streams}
|
||||
|
||||
|
||||
@app.post("/scraper")
|
||||
@app.post("/scraper", tags=["scraper"])
|
||||
def run_scraper(
|
||||
background_tasks: BackgroundTasks,
|
||||
language: Literal["tamil", "malayalam", "telugu", "hindi", "kannada", "english"] = "tamil",
|
||||
language: Literal[
|
||||
"tamil", "malayalam", "telugu", "hindi", "kannada", "english"
|
||||
] = "tamil",
|
||||
video_type: Literal["hdrip", "tcrip", "dubbed", "series"] = "hdrip",
|
||||
pages: int = 1,
|
||||
start_page: int = 1,
|
||||
is_scrape_home: bool = False,
|
||||
):
|
||||
background_tasks.add_task(tb_scrapper.run_scraper, language, video_type, pages, start_page, is_scrape_home)
|
||||
background_tasks.add_task(
|
||||
tb_scrapper.run_scraper, language, video_type, pages, start_page
|
||||
)
|
||||
return {"message": "Scraping in background..."}
|
||||
|
||||
|
||||
@app.post("/encrypt-user-data")
|
||||
@app.post("/encrypt-user-data", tags=["user_data"])
|
||||
async def encrypt_user_data(user_data: schemas.UserData):
|
||||
encrypted_str = crypto.encrypt_user_data(user_data)
|
||||
return {"encrypted_str": encrypted_str}
|
||||
|
||||
|
||||
@app.get("/{secret_str}/streaming_provider")
|
||||
@app.get("/{secret_str}/streaming_provider", tags=["streaming_provider"])
|
||||
async def streaming_provider_endpoint(
|
||||
secret_str: str, info_hash: str, name: str, response: Response, episode: int = None
|
||||
secret_str: str,
|
||||
info_hash: str,
|
||||
response: Response,
|
||||
season: int = None,
|
||||
episode: int = None,
|
||||
):
|
||||
response.headers.update(headers)
|
||||
response.headers.update(no_cache_headers)
|
||||
@@ -238,20 +288,28 @@ async def streaming_provider_endpoint(
|
||||
if not user_data.streaming_provider:
|
||||
raise HTTPException(status_code=400, detail="No streaming provider set.")
|
||||
|
||||
magnet_link = torrent.convert_info_hash_to_magnet(info_hash, name)
|
||||
stream = await crud.get_stream_by_info_hash(info_hash)
|
||||
if not stream:
|
||||
raise HTTPException(status_code=400, detail="Stream not found.")
|
||||
|
||||
magnet_link = torrent.convert_info_hash_to_magnet(
|
||||
info_hash, stream.announce_list, stream.torrent_name
|
||||
)
|
||||
|
||||
episode_data = stream.get_episode(season, episode)
|
||||
|
||||
try:
|
||||
if user_data.streaming_provider.service == "seedr":
|
||||
video_url = get_direct_link_from_seedr(
|
||||
info_hash, magnet_link, user_data.streaming_provider.token, name, episode, 3, 1
|
||||
video_url = await get_direct_link_from_seedr(
|
||||
info_hash, magnet_link, user_data, stream, episode_data, 3, 1
|
||||
)
|
||||
else:
|
||||
video_url = get_direct_link_from_realdebrid(
|
||||
info_hash, magnet_link, user_data.streaming_provider.token, episode, 3, 1
|
||||
info_hash, magnet_link, user_data, stream, episode_data, 3, 1
|
||||
)
|
||||
except ProviderException as error:
|
||||
logging.info("Exception occurred: %s", error.message)
|
||||
video_url = f"{settings.host_url}/static/{error.video_file_name}"
|
||||
video_url = f"{settings.host_url}/static/exceptions/{error.video_file_name}"
|
||||
|
||||
return RedirectResponse(url=video_url, headers=response.headers)
|
||||
|
||||
|
||||
+11
-8
@@ -12,17 +12,17 @@ Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/catalog/movie/tamil_hd.json
|
||||
GET http://127.0.0.1:8000/catalog/movie/tamil_hdrip.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/catalog/movie/tamil_new/skip=10.json
|
||||
GET http://127.0.0.1:8000/catalog/movie/tamil_hdrip/skip=10.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/catalog/movie/tamil_dubbed.json
|
||||
GET http://127.0.0.1:8000/{{secret_str}}/catalog/series/tamil_series.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -32,22 +32,25 @@ Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/{{secret_str}}/stream/movie/tt11468258.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/meta/series/tb167521139113435.json
|
||||
GET http://127.0.0.1:8000/{{secret_str}}/meta/series/mf39644457048721.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/{{secret_str}}/stream/series/tb167521139113435:2:132.json
|
||||
GET http://127.0.0.1:8000/meta/series/mf113075207220135:1:1.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/meta/movie/ty219661034922033.json
|
||||
GET http://127.0.0.1:8000/{{secret_str}}/stream/series/mf113075207220135:1:1.json
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
|
||||
GET http://127.0.0.1:8000/meta/movie/tt11468258.json
|
||||
Accept: application/json
|
||||
+1
-1
@@ -5,7 +5,7 @@ class Settings(BaseSettings):
|
||||
mongo_uri: str
|
||||
git_rev: str = "beta"
|
||||
secret_key: str
|
||||
host_url: str = "https://882b9915d0fe-stremio-tamilblasters.baby-beamup.club"
|
||||
host_url: str = "https://882b9915d0fe-mediafusion.baby-beamup.club"
|
||||
logging_level: str = "INFO"
|
||||
|
||||
class Config:
|
||||
|
||||
+310
-144
@@ -2,220 +2,386 @@ import logging
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from beanie import WriteRules
|
||||
from imdb import Cinemagoer, IMDbDataAccessError
|
||||
|
||||
from db import schemas
|
||||
from db.models import TamilBlasterMovie
|
||||
from db.schemas import Stream
|
||||
from utils.parser import extract_stream_details
|
||||
from db.models import (
|
||||
MediaFusionMovieMetaData,
|
||||
MediaFusionSeriesMetaData,
|
||||
Streams,
|
||||
Season,
|
||||
Episode,
|
||||
MediaFusionMetaData,
|
||||
)
|
||||
from db.schemas import Stream, MetaIdProjection
|
||||
from utils.parser import extract_stream_details, get_catalogs
|
||||
|
||||
ia = Cinemagoer()
|
||||
|
||||
|
||||
async def get_movies_and_series_meta(catalog: str, skip: int = 0, limit: int = 100) -> list[schemas.Meta]:
|
||||
movies_meta = []
|
||||
movies = (
|
||||
await TamilBlasterMovie.find(TamilBlasterMovie.catalog == catalog)
|
||||
.sort("-created_at")
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.to_list()
|
||||
)
|
||||
async def get_meta_list(
|
||||
catalog_type: str, catalog: str, skip: int = 0, limit: int = 100
|
||||
) -> list[schemas.Meta]:
|
||||
|
||||
unique_names = []
|
||||
pipeline = [
|
||||
# Lookup to join with the Streams collection
|
||||
{
|
||||
"$lookup": {
|
||||
"from": "Streams",
|
||||
"localField": "streams.$id",
|
||||
"foreignField": "_id",
|
||||
"as": "joined_streams",
|
||||
}
|
||||
},
|
||||
# Unwind the joined streams to process each stream separately
|
||||
{"$unwind": "$joined_streams"},
|
||||
# Match the series based on catalog
|
||||
{"$match": {"joined_streams.catalog": catalog}},
|
||||
# Group by series ID and determine the latest date
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$_id",
|
||||
"latest_date": {"$max": "$joined_streams.created_at"},
|
||||
"title": {"$first": "$title"},
|
||||
"poster": {"$first": "$poster"},
|
||||
"type": {"$first": "$type"},
|
||||
}
|
||||
},
|
||||
# Rename _id to id
|
||||
{
|
||||
"$project": {
|
||||
"id": "$_id",
|
||||
"latest_date": 1,
|
||||
"title": 1,
|
||||
"poster": 1,
|
||||
"type": 1,
|
||||
}
|
||||
},
|
||||
# Sort by the latest_date in descending order
|
||||
{"$sort": {"latest_date": -1}},
|
||||
# Pagination
|
||||
{"$skip": skip},
|
||||
{"$limit": limit},
|
||||
]
|
||||
|
||||
for movie in movies:
|
||||
meta_data = schemas.Meta.model_validate(movie.model_dump())
|
||||
meta_data.id = movie.imdb_id if movie.imdb_id else movie.tamilblaster_id
|
||||
if movie.name not in unique_names:
|
||||
movies_meta.append(meta_data)
|
||||
unique_names.append(movie.name)
|
||||
return movies_meta
|
||||
|
||||
|
||||
async def get_movies_data(video_id: str, video_type: str = "movie") -> list[Optional[TamilBlasterMovie]]:
|
||||
if video_id.startswith("tt"):
|
||||
movie_data = await TamilBlasterMovie.find(
|
||||
TamilBlasterMovie.imdb_id == video_id, TamilBlasterMovie.type == video_type
|
||||
if catalog_type == "movie":
|
||||
meta_list = await MediaFusionMovieMetaData.aggregate(
|
||||
pipeline, projection_model=schemas.Meta
|
||||
).to_list()
|
||||
else:
|
||||
movie_data = await TamilBlasterMovie.find(
|
||||
TamilBlasterMovie.tamilblaster_id == video_id, TamilBlasterMovie.type == video_type
|
||||
meta_list = await MediaFusionSeriesMetaData.aggregate(
|
||||
pipeline, projection_model=schemas.Meta
|
||||
).to_list()
|
||||
return meta_list
|
||||
|
||||
|
||||
async def get_movie_data_by_id(
|
||||
movie_id: str, fetch_links: bool = False
|
||||
) -> Optional[MediaFusionMovieMetaData]:
|
||||
movie_data = await MediaFusionMovieMetaData.get(movie_id, fetch_links=fetch_links)
|
||||
return movie_data
|
||||
|
||||
|
||||
async def get_movie_streams(user_data, video_id: str) -> list[Stream]:
|
||||
movies_data = await get_movies_data(video_id)
|
||||
if not movies_data:
|
||||
async def get_series_data_by_id(
|
||||
series_id: str, fetch_links: bool = False
|
||||
) -> Optional[MediaFusionSeriesMetaData]:
|
||||
series_data = await MediaFusionSeriesMetaData.get(
|
||||
series_id, fetch_links=fetch_links
|
||||
)
|
||||
return series_data
|
||||
|
||||
|
||||
async def get_movie_streams(user_data, secret_str: str, video_id: str) -> list[Stream]:
|
||||
movie_data = await get_movie_data_by_id(video_id, True)
|
||||
if not movie_data:
|
||||
return []
|
||||
|
||||
stream_data = []
|
||||
for movie_data in movies_data:
|
||||
stream_data.extend(extract_stream_details(movie_data.name, movie_data.video_qualities, user_data))
|
||||
|
||||
return stream_data
|
||||
return extract_stream_details(movie_data.streams, user_data, secret_str)
|
||||
|
||||
|
||||
async def get_series_streams(user_data, video_id: str, season: int, episode: int) -> list[Stream]:
|
||||
series_data = await get_movies_data(video_id, video_type="series")
|
||||
async def get_series_streams(
|
||||
user_data, secret_str: str, video_id: str, season: int, episode: int
|
||||
) -> list[Stream]:
|
||||
series_data = await get_series_data_by_id(video_id, True)
|
||||
if not series_data:
|
||||
return []
|
||||
|
||||
stream_data = []
|
||||
for series in series_data:
|
||||
if "-" in series.episode:
|
||||
# Episode range detected in the database
|
||||
start_ep, end_ep = map(int, series.episode.split("-"))
|
||||
if start_ep <= episode <= end_ep and series.season == season:
|
||||
stream_data.extend(
|
||||
extract_stream_details(
|
||||
f"{series.name} {series.season}:{series.episode}", series.video_qualities, user_data
|
||||
)
|
||||
)
|
||||
elif int(series.episode) == episode and series.season == season:
|
||||
stream_data.extend(
|
||||
extract_stream_details(f"{series.name} {series.season}:{episode}", series.video_qualities, user_data)
|
||||
)
|
||||
matched_episode_streams = [
|
||||
stream for stream in series_data.streams if stream.get_episode(season, episode)
|
||||
]
|
||||
|
||||
return stream_data
|
||||
return extract_stream_details(
|
||||
matched_episode_streams, user_data, secret_str, season, episode
|
||||
)
|
||||
|
||||
|
||||
async def get_movie_meta(meta_id: str):
|
||||
movies_data = await get_movies_data(meta_id)
|
||||
if not movies_data:
|
||||
return {
|
||||
"meta": {
|
||||
"id": meta_id,
|
||||
"type": "movie",
|
||||
}
|
||||
}
|
||||
movie_data = await get_movie_data_by_id(meta_id)
|
||||
|
||||
if not movie_data:
|
||||
return {}
|
||||
|
||||
return {
|
||||
"meta": {
|
||||
"id": meta_id,
|
||||
"type": "movie",
|
||||
"name": movies_data[0].name,
|
||||
"poster": movies_data[0].poster,
|
||||
"background": movies_data[0].poster,
|
||||
"name": movie_data.title,
|
||||
"poster": movie_data.poster,
|
||||
"background": movie_data.poster,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def get_series_meta(meta_id: str):
|
||||
series_data = await get_movies_data(meta_id, video_type="series")
|
||||
series_data = await get_series_data_by_id(meta_id, True)
|
||||
|
||||
if not series_data:
|
||||
return {
|
||||
"meta": {
|
||||
"id": meta_id,
|
||||
"type": "series",
|
||||
}
|
||||
}
|
||||
return {}
|
||||
|
||||
metadata = {
|
||||
"meta": {
|
||||
"id": meta_id,
|
||||
"type": "series",
|
||||
"name": series_data[0].name,
|
||||
"poster": series_data[0].poster,
|
||||
"background": series_data[0].poster,
|
||||
"name": series_data.title,
|
||||
"poster": series_data.poster,
|
||||
"background": series_data.poster,
|
||||
"videos": [],
|
||||
}
|
||||
}
|
||||
for series in series_data:
|
||||
if "-" in series.episode:
|
||||
# Episode range detected
|
||||
start_ep, end_ep = map(int, series.episode.split("-"))
|
||||
for ep_num in range(start_ep, end_ep + 1):
|
||||
|
||||
# Loop through streams to populate the videos list
|
||||
for stream in series_data.streams:
|
||||
stream: Streams
|
||||
if stream.season: # Ensure the stream has season data
|
||||
for episode in stream.season.episodes:
|
||||
metadata["meta"]["videos"].append(
|
||||
{
|
||||
"id": f"{meta_id}:{series.season}:{ep_num}",
|
||||
"name": f"S{series.season} EP{ep_num}",
|
||||
"season": series.season,
|
||||
"episode": ep_num,
|
||||
"released": series.created_at,
|
||||
"id": f"{meta_id}:{stream.season.season_number}:{episode.episode_number}",
|
||||
"name": f"S{stream.season.season_number} EP{episode.episode_number}",
|
||||
"season": stream.season.season_number,
|
||||
"episode": episode.episode_number,
|
||||
"released": stream.created_at,
|
||||
}
|
||||
)
|
||||
else:
|
||||
episode = int(series.episode)
|
||||
metadata["meta"]["videos"].append(
|
||||
{
|
||||
"id": f"{meta_id}:{series.season}:{episode}",
|
||||
"name": f"S{series.season} EP{episode}",
|
||||
"season": series.season,
|
||||
"episode": episode,
|
||||
"released": series.created_at,
|
||||
}
|
||||
)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
def search_imdb(title: str):
|
||||
def search_imdb(title: str, year: int, retry: int = 5) -> dict:
|
||||
try:
|
||||
result = ia.search_movie(title)
|
||||
result = ia.search_movie(f"{title} {year}")
|
||||
except IMDbDataAccessError:
|
||||
return search_imdb(title)
|
||||
return search_imdb(title, year, retry - 1) if retry > 0 else {}
|
||||
for movie in result:
|
||||
if movie.get("title").lower() in title.lower():
|
||||
return f"tt{movie.movieID}"
|
||||
if movie.get("year") == year and movie.get("title").lower() in title.lower():
|
||||
return {
|
||||
"imdb_id": f"tt{movie.movieID}",
|
||||
"poster": movie.get("full-size cover url"),
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
async def save_movie_metadata(metadata: dict):
|
||||
movie_data = await TamilBlasterMovie.find_one(
|
||||
TamilBlasterMovie.name == metadata["name"],
|
||||
TamilBlasterMovie.catalog == metadata["catalog"],
|
||||
TamilBlasterMovie.season == metadata["season"],
|
||||
TamilBlasterMovie.episode == metadata["episode"],
|
||||
# Try to get the existing movie
|
||||
existing_movie = await MediaFusionMovieMetaData.find_one(
|
||||
{"title": metadata["title"], "year": metadata.get("year")}
|
||||
)
|
||||
|
||||
if movie_data:
|
||||
movie_data.video_qualities.update(metadata["video_qualities"])
|
||||
movie_data.created_at = metadata["created_at"]
|
||||
logging.debug("updated video qualities for: %s", metadata["name"])
|
||||
else:
|
||||
movie_data = TamilBlasterMovie.model_validate(metadata)
|
||||
movie_data.video_qualities = metadata["video_qualities"]
|
||||
if not existing_movie:
|
||||
# If the movie doesn't exist in our DB, search for IMDb ID
|
||||
imdb_data = search_imdb(metadata["title"], metadata.get("year"))
|
||||
meta_id = imdb_data.get("imdb_id")
|
||||
|
||||
series_data = await TamilBlasterMovie.find_one(
|
||||
TamilBlasterMovie.name == metadata["name"],
|
||||
TamilBlasterMovie.catalog == metadata["catalog"],
|
||||
TamilBlasterMovie.type == "series",
|
||||
)
|
||||
if series_data:
|
||||
movie_data.tamilblaster_id = series_data.tamilblaster_id
|
||||
movie_data.imdb_id = series_data.imdb_id
|
||||
if meta_id:
|
||||
# Check if the movie with the found IMDb ID already exists in our DB
|
||||
existing_movie = await MediaFusionMovieMetaData.get(meta_id)
|
||||
else:
|
||||
imdb_id = search_imdb(movie_data.name)
|
||||
if any(
|
||||
[
|
||||
metadata["type"] == "series" and metadata["episode"].isdigit() and imdb_id,
|
||||
all([metadata["type"] == "movie", imdb_id]),
|
||||
]
|
||||
):
|
||||
movie_data.imdb_id = imdb_id
|
||||
else:
|
||||
movie_data.tamilblaster_id = f"tb{uuid4().fields[-1]}"
|
||||
meta_id = f"mf{uuid4().fields[-1]}"
|
||||
# Update the poster from IMDb if available
|
||||
poster = imdb_data.get("poster") or metadata["poster"]
|
||||
else:
|
||||
poster = existing_movie.poster
|
||||
meta_id = existing_movie.id
|
||||
|
||||
logging.info(f"new movie '{metadata['name']}' added.")
|
||||
# Determine file index for the main movie file (largest file)
|
||||
largest_file = max(
|
||||
metadata["torrent_metadata"]["file_data"], key=lambda x: x["size"]
|
||||
)
|
||||
|
||||
await movie_data.save()
|
||||
if "language" in metadata:
|
||||
languages = (
|
||||
[metadata["language"]]
|
||||
if isinstance(metadata["language"], str)
|
||||
else metadata["language"]
|
||||
)
|
||||
else:
|
||||
languages = [metadata["scrap_language"]]
|
||||
|
||||
# Create the stream object
|
||||
new_stream = Streams(
|
||||
id=metadata["torrent_metadata"]["info_hash"],
|
||||
torrent_name=metadata["torrent_metadata"]["torrent_name"],
|
||||
announce_list=metadata["torrent_metadata"]["announce_list"],
|
||||
size=metadata["torrent_metadata"]["total_size"],
|
||||
filename=largest_file["filename"],
|
||||
file_index=largest_file["index"],
|
||||
languages=languages,
|
||||
resolution=metadata.get("resolution"),
|
||||
codec=metadata.get("codec"),
|
||||
quality=metadata.get("quality"),
|
||||
audio=metadata.get("audio"),
|
||||
encoder=metadata.get("encoder"),
|
||||
source=metadata["source"],
|
||||
catalog=get_catalogs(metadata["catalog"], languages),
|
||||
created_at=metadata["created_at"],
|
||||
)
|
||||
|
||||
if existing_movie:
|
||||
# Check if the stream with the same info_hash already exists
|
||||
await existing_movie.fetch_all_links()
|
||||
matching_stream = next(
|
||||
(stream for stream in existing_movie.streams if stream.id == new_stream.id),
|
||||
None,
|
||||
)
|
||||
if not matching_stream:
|
||||
existing_movie.streams.append(new_stream)
|
||||
await existing_movie.save(link_rule=WriteRules.WRITE)
|
||||
logging.info("Updated movie %s", existing_movie.title)
|
||||
else:
|
||||
# If the movie doesn't exist, create a new one
|
||||
movie_data = MediaFusionMovieMetaData(
|
||||
id=meta_id,
|
||||
title=metadata["title"],
|
||||
year=metadata["year"],
|
||||
poster=poster,
|
||||
streams=[new_stream],
|
||||
)
|
||||
await movie_data.insert(link_rule=WriteRules.WRITE)
|
||||
logging.info("Added movie %s", movie_data.title)
|
||||
|
||||
|
||||
async def save_series_metadata(metadata: dict):
|
||||
# Try to get the existing series
|
||||
series = await MediaFusionSeriesMetaData.find_one({"title": metadata["title"]})
|
||||
|
||||
if not series:
|
||||
# If the series doesn't exist in our DB, search for IMDb ID
|
||||
imdb_data = search_imdb(metadata["title"], metadata["year"])
|
||||
meta_id = imdb_data.get("imdb_id")
|
||||
|
||||
if meta_id:
|
||||
# Check if the series with the found IMDb ID already exists in our DB
|
||||
series = await MediaFusionSeriesMetaData.get(meta_id)
|
||||
|
||||
if not series:
|
||||
meta_id = meta_id or f"mf{uuid4().fields[-1]}"
|
||||
poster = imdb_data.get("poster") or metadata["poster"]
|
||||
|
||||
# Create an initial entry for the series
|
||||
series = MediaFusionSeriesMetaData(
|
||||
id=meta_id,
|
||||
title=metadata["title"],
|
||||
year=metadata["year"],
|
||||
poster=poster,
|
||||
streams=[],
|
||||
)
|
||||
await series.insert()
|
||||
logging.info("Added series %s", series.title)
|
||||
|
||||
await series.fetch_all_links()
|
||||
existing_stream = next(
|
||||
(
|
||||
s
|
||||
for s in series.streams
|
||||
if s.id == metadata["torrent_metadata"]["info_hash"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
if existing_stream:
|
||||
# If the stream already exists, return
|
||||
logging.info("Stream already exists for series %s", series.title)
|
||||
return
|
||||
|
||||
season_number = metadata["season"]
|
||||
|
||||
# Extract episodes
|
||||
episodes = [
|
||||
Episode(
|
||||
episode_number=file["episode"],
|
||||
filename=file["filename"],
|
||||
size=file["size"],
|
||||
file_index=file["index"],
|
||||
)
|
||||
for file in metadata["torrent_metadata"]["file_data"]
|
||||
if file["episode"]
|
||||
]
|
||||
|
||||
# Determine languages
|
||||
if "language" in metadata:
|
||||
languages = (
|
||||
[metadata["language"]]
|
||||
if isinstance(metadata["language"], str)
|
||||
else metadata["language"]
|
||||
)
|
||||
else:
|
||||
languages = [metadata["scrap_language"]]
|
||||
|
||||
# Create the stream
|
||||
stream = Streams(
|
||||
id=metadata["torrent_metadata"]["info_hash"],
|
||||
torrent_name=metadata["torrent_metadata"]["torrent_name"],
|
||||
announce_list=metadata["torrent_metadata"]["announce_list"],
|
||||
size=metadata["torrent_metadata"]["total_size"],
|
||||
languages=languages,
|
||||
resolution=metadata.get("resolution"),
|
||||
codec=metadata.get("codec"),
|
||||
quality=metadata.get("quality"),
|
||||
audio=metadata.get("audio"),
|
||||
encoder=metadata.get("encoder"),
|
||||
source=metadata["source"],
|
||||
catalog=get_catalogs(metadata["catalog"], languages),
|
||||
created_at=metadata["created_at"],
|
||||
season=Season(season_number=season_number, episodes=episodes),
|
||||
)
|
||||
|
||||
# Add the stream to the series
|
||||
series.streams.append(stream)
|
||||
|
||||
await series.save(link_rule=WriteRules.WRITE)
|
||||
logging.info("Updated series %s", series.title)
|
||||
|
||||
|
||||
async def process_search_query(search_query: str, catalog_type: str) -> dict:
|
||||
# Define the query with the text search and catalog type
|
||||
query = {"$text": {"$search": search_query}, "type": catalog_type}
|
||||
search_results = await TamilBlasterMovie.find(query).to_list()
|
||||
logging.debug("Found %s results for %s in %s", len(search_results), search_query, catalog_type)
|
||||
|
||||
movies_meta = []
|
||||
unique_names = []
|
||||
# Perform the search
|
||||
search_results = (
|
||||
await MediaFusionMetaData.find(query).project(MetaIdProjection).to_list()
|
||||
)
|
||||
|
||||
for movie in search_results:
|
||||
meta_data = schemas.Meta.model_validate(movie.model_dump())
|
||||
meta_data.id = movie.imdb_id or movie.tamilblaster_id
|
||||
if movie.name not in unique_names:
|
||||
movies_meta.append(meta_data)
|
||||
unique_names.append(movie.name)
|
||||
logging.debug(
|
||||
"Found %s results for %s in %s", len(search_results), search_query, catalog_type
|
||||
)
|
||||
|
||||
return {"metas": movies_meta}
|
||||
metas = []
|
||||
|
||||
for item in search_results:
|
||||
# Use the appropriate function to get the meta data
|
||||
if catalog_type == "movie":
|
||||
meta = await get_movie_meta(item.id)
|
||||
else:
|
||||
meta = await get_series_meta(item.id)
|
||||
|
||||
if not meta:
|
||||
continue
|
||||
|
||||
metas.append(meta["meta"]) # Extract the 'meta' key from the result
|
||||
|
||||
return {"metas": metas}
|
||||
|
||||
|
||||
async def get_stream_by_info_hash(info_hash: str) -> Streams | None:
|
||||
stream = await Streams.get(info_hash)
|
||||
return stream
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import motor.motor_asyncio
|
||||
from beanie import init_beanie
|
||||
|
||||
from db.config import settings
|
||||
from db.models import TamilBlasterMovie
|
||||
from db.models import MediaFusionSeriesMetaData, MediaFusionMovieMetaData, Streams
|
||||
|
||||
|
||||
async def init():
|
||||
@@ -10,4 +10,4 @@ async def init():
|
||||
client = motor.motor_asyncio.AsyncIOMotorClient(settings.mongo_uri)
|
||||
|
||||
# Init beanie with the Product document class
|
||||
await init_beanie(database=client.streamio, document_models=[TamilBlasterMovie])
|
||||
await init_beanie(database=client.mediafusion, document_models=[MediaFusionMovieMetaData, MediaFusionSeriesMetaData, Streams])
|
||||
|
||||
+61
-23
@@ -2,33 +2,71 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import pymongo
|
||||
from beanie import Document
|
||||
from pydantic import Field
|
||||
from pymongo import IndexModel
|
||||
from beanie import Document, Link
|
||||
from pydantic import BaseModel, Field
|
||||
from pymongo import IndexModel, ASCENDING
|
||||
|
||||
|
||||
class TamilBlasterMovie(Document):
|
||||
name: str
|
||||
catalog: str
|
||||
type: str
|
||||
season: Optional[int] = None
|
||||
episode: Optional[str] = None
|
||||
poster: str
|
||||
imdb_id: Optional[str] = None
|
||||
tamilblaster_id: Optional[str] = None
|
||||
class Episode(BaseModel):
|
||||
episode_number: int
|
||||
filename: str
|
||||
size: int
|
||||
file_index: int
|
||||
|
||||
|
||||
class Season(BaseModel):
|
||||
season_number: int
|
||||
episodes: list[Episode]
|
||||
|
||||
|
||||
class Streams(Document):
|
||||
id: str
|
||||
torrent_name: str
|
||||
size: int
|
||||
season: Optional[Season] = None
|
||||
filename: Optional[str] = None
|
||||
file_index: Optional[int] = None
|
||||
announce_list: list[str]
|
||||
languages: list[str]
|
||||
source: str
|
||||
catalog: list[str]
|
||||
created_at: datetime = Field(default_factory=datetime.now)
|
||||
video_qualities: dict
|
||||
resolution: Optional[str]
|
||||
codec: Optional[str]
|
||||
quality: Optional[str]
|
||||
audio: Optional[str]
|
||||
encoder: Optional[str]
|
||||
seeders: Optional[int] = None
|
||||
|
||||
def get_episode(self, season_number: int, episode_number: int) -> Optional[Episode]:
|
||||
"""
|
||||
Returns the Episode object for the given season and episode number.
|
||||
"""
|
||||
if self.season and self.season.season_number == season_number:
|
||||
for episode in self.season.episodes:
|
||||
if episode.episode_number == episode_number:
|
||||
return episode
|
||||
return None
|
||||
|
||||
|
||||
class MediaFusionMetaData(Document):
|
||||
id: str
|
||||
title: str
|
||||
year: Optional[int]
|
||||
poster: str
|
||||
streams: list[Link[Streams]]
|
||||
|
||||
class Settings:
|
||||
is_root = True
|
||||
indexes = [
|
||||
IndexModel(
|
||||
[
|
||||
("name", pymongo.ASCENDING),
|
||||
("catalog", pymongo.ASCENDING),
|
||||
("season", pymongo.ASCENDING),
|
||||
("episode", pymongo.ASCENDING),
|
||||
],
|
||||
unique=True,
|
||||
),
|
||||
IndexModel([("name", pymongo.TEXT)]),
|
||||
IndexModel([("title", ASCENDING), ("year", ASCENDING)], unique=True),
|
||||
IndexModel([("title", pymongo.TEXT)]),
|
||||
]
|
||||
|
||||
|
||||
class MediaFusionMovieMetaData(MediaFusionMetaData):
|
||||
type: str = "movie"
|
||||
|
||||
|
||||
class MediaFusionSeriesMetaData(MediaFusionMetaData):
|
||||
type: str = "series"
|
||||
|
||||
+29
-8
@@ -10,22 +10,23 @@ class Catalog(BaseModel):
|
||||
|
||||
|
||||
class Meta(BaseModel):
|
||||
id: Any
|
||||
name: str
|
||||
id: str
|
||||
name: str = Field(alias="title")
|
||||
type: str = Field(default="movie")
|
||||
poster: str
|
||||
videos: list | None = None
|
||||
|
||||
|
||||
class Movie(BaseModel):
|
||||
class Metas(BaseModel):
|
||||
metas: list[Meta] = []
|
||||
|
||||
|
||||
class Stream(BaseModel):
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
name: str
|
||||
description: str
|
||||
infoHash: str | None = None
|
||||
fileIdx: int | None = None
|
||||
url: str | None = None
|
||||
stream_name: str | None = Field(exclude=True)
|
||||
behaviorHints: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@@ -44,10 +45,26 @@ class StreamingProvider(BaseModel):
|
||||
class UserData(BaseModel):
|
||||
streaming_provider: StreamingProvider | None = None
|
||||
preferred_movie_languages: list[str] = Field(
|
||||
default=["Tamil", "Malayalam", "Telugu", "Hindi", "Kannada", "English", "Dubbed"]
|
||||
default=[
|
||||
"Tamil",
|
||||
"Malayalam",
|
||||
"Telugu",
|
||||
"Hindi",
|
||||
"Kannada",
|
||||
"English",
|
||||
"Dubbed",
|
||||
]
|
||||
)
|
||||
preferred_series_languages: list[str] = Field(
|
||||
default=["Tamil", "Malayalam", "Telugu", "Hindi", "Kannada", "English", "Dubbed"]
|
||||
default=[
|
||||
"Tamil",
|
||||
"Malayalam",
|
||||
"Telugu",
|
||||
"Hindi",
|
||||
"Kannada",
|
||||
"English",
|
||||
"Dubbed",
|
||||
]
|
||||
)
|
||||
|
||||
class Config:
|
||||
@@ -56,3 +73,7 @@ class UserData(BaseModel):
|
||||
|
||||
class AuthorizeData(BaseModel):
|
||||
device_code: str
|
||||
|
||||
|
||||
class MetaIdProjection(BaseModel):
|
||||
id: str
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -57,7 +63,8 @@
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6 col-md-8 col-sm-10 config-container">
|
||||
<h3 class="text-center mb-5">TamilBlasters Addon Configuration</h3>
|
||||
<img class="logo" src="/static/images/mediafusion_logo.png">
|
||||
<h3 class="text-center mb-5">MediaFusion Addon Configuration</h3>
|
||||
|
||||
<!-- Configuration Form -->
|
||||
<form id="configForm">
|
||||
@@ -137,7 +144,7 @@
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.3/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
<script src="/static/config_script.js"></script>
|
||||
<script src="/static/js/config_script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -79,9 +79,9 @@
|
||||
<img class="logo" src="{{logo}}">
|
||||
<h3 class="text-center mb-5"><b>{{name}}</b> - {{version}}</h3>
|
||||
<h5 class="description">
|
||||
Watch Tamil, Hindi, Malayalam, Kannada, English, and dubbed movies & series with the TamilBlasters add-on.
|
||||
Watch Tamil, Hindi, Malayalam, Kannada, English, and dubbed movies & series with the MediaFusion add-on.
|
||||
Created by Mohamed Zumair.
|
||||
<a href="https://github.com/mhdzumair/tamilblasters_streamio_addon" target="_blank">Source Code on GitHub</a>.
|
||||
<a href="https://github.com/mhdzumair/MediaFusion" target="_blank">Source Code on GitHub</a>.
|
||||
</h5>
|
||||
|
||||
<h4 class="mt-4">This add-on has:</h4>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 178 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"id": "mhdzumair.addons.mediafusion",
|
||||
"version": "1.0.0",
|
||||
"version": "3.0.0",
|
||||
"name": "Media Fusion",
|
||||
"contactEmail": "mhdzumair@gmail",
|
||||
"description": "Watch Tamil, Hindi, Malayalam, Kannada, English, and dubbed movies & series with the Media Fusion add-on. Source: https://github.com/mhdzumair/MediaFusion",
|
||||
"logo": "https://882b9915d0fe-stremio-tamilblasters.baby-beamup.club/static/tamilblasters.png",
|
||||
"logo": "http://127.0.0.1:8000/static/images/mediafusion_logo.png",
|
||||
"behaviorHints": {
|
||||
"configurable": true,
|
||||
"configurationRequired": true
|
||||
"configurationRequired": false
|
||||
},
|
||||
"resources": [
|
||||
"catalog",
|
||||
@@ -30,7 +30,7 @@
|
||||
],
|
||||
"idPrefixes": [
|
||||
"tt",
|
||||
"tb"
|
||||
"mf"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Scrapping with Media Fusion
|
||||
|
||||
Currently, the project supports scraping from TamilBlasters. Here's how to use the scraper:
|
||||
|
||||
1. **Setup Playwright**:
|
||||
|
||||
If you intend to use the scraper with Playwright, you need to ensure the Playwright browser binaries are installed. Specifically, for Firefox:
|
||||
|
||||
```bash
|
||||
pipenv run playwright install firefox
|
||||
```
|
||||
|
||||
2. **Get Help on Available Options **:
|
||||
|
||||
To understand the available options for the scraper, run:
|
||||
|
||||
```bash
|
||||
pipenv run python3 -m scrappers.tamil_blasters_scrapper --help
|
||||
```
|
||||
|
||||
This will display the available options:
|
||||
|
||||
```bash
|
||||
usage: tamil_blasters_scrapper.py [-h] [--all] [-l {tamil,malayalam,telugu,hindi,kannada,english}] [-t {hdrip,tcrip,dubbed,series}] [-p PAGES] [-s START_PAGES] [-k SEARCH_KEYWORD] [--scrap-with-playwright] [--proxy-url PROXY_URL]
|
||||
|
||||
Scrap Movie metadata from TamilBlasters
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--all scrap all type of movies & series
|
||||
-l {tamil,malayalam,telugu,hindi,kannada,english}, --language {tamil,malayalam,telugu,hindi,kannada,english}
|
||||
scrap movie language
|
||||
-t {hdrip,tcrip,dubbed,series}, --video-type {hdrip,tcrip,dubbed,series}
|
||||
scrap movie video type
|
||||
-p PAGES, --pages PAGES
|
||||
number of scrap pages
|
||||
-s START_PAGES, --start-pages START_PAGES
|
||||
page number to start scrap.
|
||||
-k SEARCH_KEYWORD, --search-keyword SEARCH_KEYWORD
|
||||
search keyword to scrap movies & series. ex: 'bigg boss'
|
||||
--scrap-with-playwright
|
||||
scrap with playwright
|
||||
--proxy-url PROXY_URL
|
||||
proxy url to scrap. ex: socks5://127.0.0.1:1080
|
||||
```
|
||||
|
||||
3. **Run the Scraper**:
|
||||
|
||||
Execute the scraper with the desired options based on your requirements.
|
||||
|
||||
e.g. To scrap all movies & series, run:
|
||||
|
||||
```bash
|
||||
pipenv run python3 -m scrappers.tamil_blasters_scrapper --all --scrap-with-playwright
|
||||
```
|
||||
|
||||
Note: You may have to solve the cloudflare validation challenge manually when its required.
|
||||
@@ -0,0 +1,90 @@
|
||||
import logging
|
||||
|
||||
import cloudscraper
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
from db import crud
|
||||
from utils.torrent import extract_torrent_metadata
|
||||
|
||||
|
||||
def get_scrapper_session(proxy_url=None):
|
||||
session = requests.session()
|
||||
session.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
|
||||
}
|
||||
adapter = HTTPAdapter(
|
||||
max_retries=Retry(total=10, read=10, connect=10, backoff_factor=0.5)
|
||||
)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
if proxy_url:
|
||||
session.proxies = {
|
||||
"http": proxy_url,
|
||||
"https": proxy_url,
|
||||
}
|
||||
scraper = cloudscraper.create_scraper(
|
||||
browser={"browser": "chrome", "platform": "windows", "mobile": False},
|
||||
delay=10,
|
||||
sess=session,
|
||||
)
|
||||
return scraper
|
||||
|
||||
|
||||
async def check_cloudflare_validation(page):
|
||||
if await page.title() == "Just a moment...":
|
||||
logging.info("Cloudflare validation required")
|
||||
await page.wait_for_selector("a#elUserSignIn", timeout=9999999)
|
||||
|
||||
|
||||
async def get_page_content(page, url):
|
||||
await page.goto(url)
|
||||
await check_cloudflare_validation(page)
|
||||
return await page.content()
|
||||
|
||||
|
||||
async def download_and_save_torrent(
|
||||
torrent_element,
|
||||
metadata: dict,
|
||||
media_type: str,
|
||||
page_link: str,
|
||||
scraper=None,
|
||||
page=None,
|
||||
):
|
||||
torrent_link = torrent_element.get("href")
|
||||
logging.info(f"Downloading torrent: {torrent_link}")
|
||||
|
||||
if scraper:
|
||||
response = scraper.get(torrent_link)
|
||||
torrent_metadata = extract_torrent_metadata(response.content)
|
||||
elif page:
|
||||
async with page.expect_download() as download_info:
|
||||
try:
|
||||
await page.goto(torrent_link)
|
||||
except:
|
||||
pass
|
||||
download = await download_info.value
|
||||
torrent_path = await download.path()
|
||||
with open(torrent_path, "rb") as torrent_file:
|
||||
torrent_metadata = extract_torrent_metadata(torrent_file.read())
|
||||
|
||||
if not torrent_metadata:
|
||||
logging.error(f"Info hash not found for {torrent_link}")
|
||||
return False
|
||||
|
||||
metadata.update({"torrent_metadata": torrent_metadata})
|
||||
|
||||
# Saving the metadata
|
||||
if media_type == "series":
|
||||
if not metadata.get("season"):
|
||||
logging.error(f"Season not found for {page_link}")
|
||||
return False
|
||||
await crud.save_series_metadata(metadata)
|
||||
else:
|
||||
if metadata.get("season"):
|
||||
await crud.save_series_metadata(metadata)
|
||||
else:
|
||||
await crud.save_movie_metadata(metadata)
|
||||
|
||||
return True
|
||||
@@ -3,169 +3,241 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
import re
|
||||
|
||||
import cloudscraper
|
||||
import requests
|
||||
import PTN
|
||||
from bs4 import BeautifulSoup
|
||||
from dateutil.parser import parse as dateparser
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
from playwright.async_api import async_playwright
|
||||
from playwright_stealth import stealth_async
|
||||
|
||||
from db import database, crud
|
||||
from utils.site_data import HOMEPAGE, TAMIL_BLASTER_LINKS
|
||||
from utils.torrent import get_info_hash_from_url
|
||||
from db import database
|
||||
from scrappers.helpers import (
|
||||
get_page_content,
|
||||
get_scrapper_session,
|
||||
download_and_save_torrent,
|
||||
)
|
||||
|
||||
HOMEPAGE = "https://www.tamilblasters.life"
|
||||
TAMIL_BLASTER_LINKS = {
|
||||
"tamil": {
|
||||
"hdrip": "7-tamil-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"tcrip": "8-tamil-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"dubbed": "9-tamil-dubbed-movies-bdrips-hdrips-dvdscr-hdcam-in-multi-audios",
|
||||
"series": "63-tamil-new-web-series-tv-shows",
|
||||
},
|
||||
"malayalam": {
|
||||
"tcrip": "75-malayalam-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": "74-malayalam-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": "76-malayalam-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": "98-malayalam-new-web-series-tv-shows",
|
||||
},
|
||||
"telugu": {
|
||||
"tcrip": "79-telugu-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": "78-telugu-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": "80-telugu-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": "96-telugu-new-web-series-tv-shows",
|
||||
},
|
||||
"hindi": {
|
||||
"tcrip": "87-hindi-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": "86-hindi-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": "88-hindi-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": "forums/forum/89-hindi-new-web-series-tv-shows",
|
||||
},
|
||||
"kannada": {
|
||||
"tcrip": "83-kannada-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": "82-kannada-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": "84-kannada-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": "103-kannada-new-web-series-tv-shows",
|
||||
},
|
||||
"english": {
|
||||
"tcrip": "52-english-movies-hdcam-dvdscr-predvd",
|
||||
"hdrip": "53-english-movies-hdrips-bdrips-dvdrips",
|
||||
"series": "92-english-web-series-tv-shows",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_scrapper_session():
|
||||
session = requests.session()
|
||||
session.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Linux; Android 7.1.2; MI 5X; Flow) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/347.0.0.268 Mobile Safari/537.36"
|
||||
}
|
||||
adapter = HTTPAdapter(max_retries=Retry(total=10, read=10, connect=10, backoff_factor=0.3))
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
scraper = cloudscraper.create_scraper(
|
||||
browser={"browser": "chrome", "platform": "android", "desktop": False}, delay=10, sess=session
|
||||
)
|
||||
return scraper
|
||||
async def get_search_results(page, keyword, page_number=1):
|
||||
search_link = f"{HOMEPAGE}/index.php?/search/&q={keyword}&type=forums_topic&page={page_number}&search_and_or=or&search_in=titles&sortby=relevancy"
|
||||
# Get page content and initialize BeautifulSoup
|
||||
page_content = await get_page_content(page, search_link)
|
||||
soup = BeautifulSoup(page_content, "html.parser")
|
||||
|
||||
return soup
|
||||
|
||||
|
||||
def extract_info_hash(movie_page):
|
||||
try:
|
||||
magnet_link = movie_page.find("a", class_="magnet-plugin").get("href")
|
||||
info_hash = re.search(r"urn:btih:(.{32,40})&", magnet_link).group(1)
|
||||
return info_hash
|
||||
except AttributeError:
|
||||
logging.warning("magnet link not found")
|
||||
try:
|
||||
torrent_link = movie_page.select_one("a[data-fileext='torrent']").get("href")
|
||||
return get_info_hash_from_url(torrent_link)
|
||||
except AttributeError:
|
||||
logging.warning("Torrent link not found either")
|
||||
except TypeError:
|
||||
logging.error("Not able to parse magnet link")
|
||||
async def process_movie(
|
||||
movie,
|
||||
scraper=None,
|
||||
page=None,
|
||||
keyword=None,
|
||||
language=None,
|
||||
media_type=None,
|
||||
supported_forums=None,
|
||||
):
|
||||
if keyword:
|
||||
movie_link = movie.find("a", {"data-linktype": "link"})
|
||||
forum_link = movie.find("a", href=re.compile(r"forums/forum/")).get("href")
|
||||
forum_id = re.search(r"forums/forum/([^/]+)/", forum_link)[1]
|
||||
if forum_id not in supported_forums:
|
||||
logging.error(f"Unsupported forum {forum_id}")
|
||||
return
|
||||
# Extracting language and media_type from supported_forums
|
||||
language = supported_forums[forum_id]["language"]
|
||||
media_type = supported_forums[forum_id]["media_type"]
|
||||
else:
|
||||
movie_link = movie.find("a")
|
||||
|
||||
|
||||
async def scrap_page(url, language, video_type):
|
||||
scraper = get_scrapper_session()
|
||||
response = scraper.get(url)
|
||||
response.raise_for_status()
|
||||
|
||||
tamil_blasters = BeautifulSoup(response.content, "html.parser")
|
||||
|
||||
try:
|
||||
movies = tamil_blasters.find("ol").select("li[data-rowid]")
|
||||
except AttributeError:
|
||||
logging.error(f"No data found for {language}:{video_type}")
|
||||
if not movie_link:
|
||||
logging.error(f"Movie link not found")
|
||||
return
|
||||
|
||||
for movie in movies:
|
||||
movie = movie.find("a")
|
||||
season = episode = None
|
||||
try:
|
||||
if video_type == "series":
|
||||
data = re.search(
|
||||
r"^(.+\(\d{4}\)).*S(\d+).*EP?\s?(\d+|\(\d+\s?-\s?\d+\))(.+)",
|
||||
movie.text.strip(),
|
||||
)
|
||||
title, season, video_quality = (
|
||||
data[1],
|
||||
int(data[2]),
|
||||
data[4].strip("[] "),
|
||||
)
|
||||
episode = str(int(data[3])) if data[3].isdigit() else data[3].strip("()")
|
||||
metadata = {"type": "series"}
|
||||
else:
|
||||
data = re.search(r"^(.+\(\d{4}\))(.+)", movie.text.strip())
|
||||
title, video_quality = re.sub(r"\s+", " ", data[1].strip()), data[2].strip("[] ")
|
||||
metadata = {"type": "movie"}
|
||||
except TypeError:
|
||||
logging.error(f"not able to parse: {movie.text.strip()}")
|
||||
continue
|
||||
logging.info(f"getting movie data for '{title}'")
|
||||
metadata = PTN.parse(re.sub(r"\s+", " ", movie_link.text))
|
||||
page_link = movie_link.get("href")
|
||||
|
||||
page_link = movie.get("href")
|
||||
response = scraper.get(page_link)
|
||||
movie_page = BeautifulSoup(response.content, "html.parser")
|
||||
info_hash = extract_info_hash(movie_page)
|
||||
if not info_hash:
|
||||
logging.error(f"info hash not found for {page_link}")
|
||||
continue
|
||||
try:
|
||||
if scraper: # If using the scraper
|
||||
response = scraper.get(page_link)
|
||||
movie_page_content = response.content
|
||||
else: # If using playwright
|
||||
movie_page_content = await get_page_content(page, page_link)
|
||||
|
||||
poster = movie_page.select_one("div[data-commenttype='forums'] img[data-src]").get("data-src")
|
||||
created_at = dateparser(movie_page.find("time").get("datetime"))
|
||||
movie_page = BeautifulSoup(movie_page_content, "html.parser")
|
||||
|
||||
# Extracting other details
|
||||
poster_element = movie_page.select_one(
|
||||
"div[data-commenttype='forums'] img[data-src]"
|
||||
)
|
||||
poster = poster_element.get("data-src") if poster_element else None
|
||||
|
||||
datetime_element = movie_page.select_one("time")
|
||||
created_at = (
|
||||
dateparser(datetime_element.get("datetime")) if datetime_element else None
|
||||
)
|
||||
|
||||
# Updating metadata
|
||||
metadata.update(
|
||||
{
|
||||
"name": title,
|
||||
"catalog": f"{language}_{video_type}",
|
||||
"video_qualities": {video_quality: info_hash},
|
||||
"catalog": f"{language}_{media_type}",
|
||||
"poster": poster,
|
||||
"created_at": created_at,
|
||||
"season": season,
|
||||
"episode": episode,
|
||||
"scrap_language": language.title(),
|
||||
"source": "TamilBlasters",
|
||||
}
|
||||
)
|
||||
await crud.save_movie_metadata(metadata)
|
||||
|
||||
# Extracting torrent details
|
||||
torrent_elements = movie_page.select("a[data-fileext='torrent']")
|
||||
|
||||
if not torrent_elements:
|
||||
logging.error(f"No torrents found for {page_link}")
|
||||
return
|
||||
|
||||
for torrent_element in torrent_elements:
|
||||
await download_and_save_torrent(
|
||||
torrent_element,
|
||||
scraper=scraper,
|
||||
page=page,
|
||||
metadata=metadata,
|
||||
media_type=media_type,
|
||||
page_link=page_link,
|
||||
)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"Error processing movie {page_link}: {e}", exc_info=True, stack_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
async def scrap_homepage():
|
||||
scraper = get_scrapper_session()
|
||||
response = scraper.get(HOMEPAGE)
|
||||
async def scrap_page(url, language, media_type, proxy_url=None):
|
||||
scraper = get_scrapper_session(proxy_url)
|
||||
response = scraper.get(url)
|
||||
if response.status_code == 403:
|
||||
logging.error(
|
||||
"Cloudflare validation required. Run with --scrap-with-playwright"
|
||||
)
|
||||
return
|
||||
|
||||
response.raise_for_status()
|
||||
tamil_blasters = BeautifulSoup(response.content, "html.parser")
|
||||
movie_list_div = tamil_blasters.select(
|
||||
"div[id='ipsLayout_mainArea'] div[class='ipsWidget_inner ipsPad ipsType_richText']"
|
||||
)[2]
|
||||
movie_list = movie_list_div.find_all("p")[2:-2]
|
||||
movies = tamil_blasters.select("li[data-rowid]")
|
||||
|
||||
for movie in movie_list:
|
||||
if re.search(r"S(\d+).*EP?\s?(\d+|\(\d+\s?-\s?\d+\))", movie.text.strip()):
|
||||
continue
|
||||
for movie in movies:
|
||||
await process_movie(
|
||||
movie, scraper=scraper, language=language, media_type=media_type
|
||||
)
|
||||
|
||||
data = re.search(r"^(.+\(\d{4}\))", movie.text.strip())
|
||||
try:
|
||||
title = re.sub(r"\s+", " ", data[1].strip())
|
||||
except TypeError:
|
||||
logging.error(movie.text)
|
||||
continue
|
||||
|
||||
logging.info(f"getting movie data for '{title}'")
|
||||
video_qualities = movie.find_all("a")[:-1]
|
||||
metadata = {
|
||||
"name": title,
|
||||
"catalog": "any_any",
|
||||
"video_qualities": {},
|
||||
"type": "movie",
|
||||
"season": None,
|
||||
"episode": None,
|
||||
async def scrap_page_with_playwright(url, language, media_type, proxy_url=None):
|
||||
async with async_playwright() as p:
|
||||
# Launch a new browser session
|
||||
browser = await p.firefox.launch(
|
||||
headless=False,
|
||||
proxy={"server": proxy_url} if proxy_url else None,
|
||||
)
|
||||
page = await browser.new_page()
|
||||
await stealth_async(page)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
page_content = await get_page_content(page, url)
|
||||
tamil_blasters = BeautifulSoup(page_content, "html.parser")
|
||||
|
||||
movies = tamil_blasters.select("li[data-rowid]")
|
||||
|
||||
for movie in movies:
|
||||
await process_movie(
|
||||
movie, page=page, language=language, media_type=media_type
|
||||
)
|
||||
|
||||
await browser.close()
|
||||
|
||||
|
||||
async def scrap_search_keyword(keyword, proxy_url=None):
|
||||
supported_forums = {
|
||||
TAMIL_BLASTER_LINKS[language][media_type]: {
|
||||
"language": language,
|
||||
"media_type": media_type,
|
||||
}
|
||||
for language in TAMIL_BLASTER_LINKS
|
||||
for media_type in TAMIL_BLASTER_LINKS[language]
|
||||
}
|
||||
|
||||
for video_quality in video_qualities:
|
||||
video_quality_name = video_quality.text.strip("[]")
|
||||
async with async_playwright() as p:
|
||||
# Launch a new browser session
|
||||
browser = await p.firefox.launch(
|
||||
headless=False,
|
||||
proxy={"server": proxy_url} if proxy_url else None,
|
||||
)
|
||||
page = await browser.new_page()
|
||||
await stealth_async(page)
|
||||
await asyncio.sleep(2)
|
||||
|
||||
page_link = video_quality.get("href")
|
||||
response = scraper.get(page_link)
|
||||
movie_page = BeautifulSoup(response.content, "html.parser")
|
||||
info_hash = extract_info_hash(movie_page)
|
||||
if not info_hash:
|
||||
logging.error(f"info hash not found for {page_link}")
|
||||
continue
|
||||
soup = await get_search_results(page, keyword)
|
||||
results_element = soup.find("div", {"data-role": "resultsArea"})
|
||||
|
||||
poster = movie_page.select_one("div[data-commenttype='forums'] img[data-src]").get("data-src")
|
||||
metadata["created_at"] = dateparser(movie_page.find("time").get("datetime"))
|
||||
metadata["poster"] = poster
|
||||
metadata["video_qualities"][video_quality_name] = info_hash
|
||||
results_count = int(re.search(r"\d+", results_element.find("p").text).group())
|
||||
logging.info(f"Found {results_count} results for {keyword}")
|
||||
|
||||
if all(
|
||||
[
|
||||
metadata.get("created_at"),
|
||||
metadata.get("poster"),
|
||||
metadata.get("video_qualities"),
|
||||
]
|
||||
):
|
||||
await crud.save_movie_metadata(metadata)
|
||||
movies = results_element.select("li[data-role='activityItem']")
|
||||
if results_count > 25:
|
||||
number_of_pages = math.ceil(results_count / 25)
|
||||
logging.info(f"Found {number_of_pages} pages for {keyword}")
|
||||
for page_number in range(2, number_of_pages + 1):
|
||||
soup = await get_search_results(page, keyword, page_number)
|
||||
movies.extend(soup.select("li[data-role='activityItem']"))
|
||||
await asyncio.sleep(random.randint(2, 5))
|
||||
|
||||
for movie in movies:
|
||||
await process_movie(
|
||||
movie, page=page, keyword=keyword, supported_forums=supported_forums
|
||||
)
|
||||
|
||||
await browser.close()
|
||||
|
||||
|
||||
async def run_scraper(
|
||||
@@ -173,39 +245,91 @@ async def run_scraper(
|
||||
video_type: str = None,
|
||||
pages: int = None,
|
||||
start_page: int = None,
|
||||
is_scrape_home: bool = True,
|
||||
search_keyword: str = None,
|
||||
scrap_with_playwright: bool = None,
|
||||
proxy_url: str = None,
|
||||
):
|
||||
await database.init()
|
||||
if is_scrape_home:
|
||||
await scrap_homepage()
|
||||
else:
|
||||
try:
|
||||
scrap_link = TAMIL_BLASTER_LINKS[language][video_type]
|
||||
except KeyError:
|
||||
logging.error(f"Unsupported language or video type: {language}_{video_type}")
|
||||
return
|
||||
for page in range(start_page, pages + start_page):
|
||||
scrap_link = f"{scrap_link}/page/{page}/"
|
||||
logging.info(f"Scrap page: {page}")
|
||||
await scrap_page(scrap_link, language, video_type)
|
||||
if search_keyword:
|
||||
await scrap_search_keyword(search_keyword, proxy_url)
|
||||
return
|
||||
try:
|
||||
scrap_link = f"{HOMEPAGE}/index.php?/forums/forum/{TAMIL_BLASTER_LINKS[language][video_type]}"
|
||||
except KeyError:
|
||||
logging.error(f"Unsupported language or video type: {language}_{video_type}")
|
||||
return
|
||||
for page in range(start_page, pages + start_page):
|
||||
scrap_link = f"{scrap_link}/page/{page}/"
|
||||
logging.info(f"Scrap page: {page}")
|
||||
if scrap_with_playwright is True:
|
||||
await scrap_page_with_playwright(
|
||||
scrap_link, language, video_type, proxy_url
|
||||
)
|
||||
else:
|
||||
await scrap_page(scrap_link, language, video_type, proxy_url)
|
||||
|
||||
logging.info(f"Scrap completed for : {language}_{video_type}")
|
||||
|
||||
|
||||
async def run_schedule_scrape(pages: int = 1, start_page: int = 1):
|
||||
async def run_schedule_scrape(
|
||||
pages: int = 1,
|
||||
start_page: int = 1,
|
||||
scrap_with_playwright: bool = None,
|
||||
proxy_url: str = None,
|
||||
):
|
||||
for language in TAMIL_BLASTER_LINKS:
|
||||
for video_type in TAMIL_BLASTER_LINKS[language]:
|
||||
await run_scraper(language, video_type, pages=pages, start_page=start_page, is_scrape_home=False)
|
||||
await run_scraper(is_scrape_home=True)
|
||||
await run_scraper(
|
||||
language,
|
||||
video_type,
|
||||
pages=pages,
|
||||
start_page=start_page,
|
||||
scrap_with_playwright=scrap_with_playwright,
|
||||
proxy_url=proxy_url,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Scrap Movie metadata from TamilBlasters")
|
||||
parser.add_argument("--all", action="store_true", help="scrap all type of movies & series")
|
||||
parser.add_argument("--home", action="store_true", help="scrap home page")
|
||||
parser.add_argument("-l", "--language", help="scrap movie language", default="tamil")
|
||||
parser.add_argument("-t", "--video-type", help="scrap movie video type", default="hdrip")
|
||||
parser.add_argument("-p", "--pages", type=int, default=1, help="number of scrap pages")
|
||||
parser.add_argument("-s", "--start-pages", type=int, default=1, help="page number to start scrap.")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Scrap Movie metadata from TamilBlasters"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--all", action="store_true", help="scrap all type of movies & series"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--language",
|
||||
help="scrap movie language",
|
||||
default="tamil",
|
||||
choices=["tamil", "malayalam", "telugu", "hindi", "kannada", "english"],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--video-type",
|
||||
help="scrap movie video type",
|
||||
default="hdrip",
|
||||
choices=["hdrip", "tcrip", "dubbed", "series"],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--pages", type=int, default=1, help="number of scrap pages"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--start-pages", type=int, default=1, help="page number to start scrap."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--search-keyword",
|
||||
help="search keyword to scrap movies & series. ex: 'bigg boss'",
|
||||
default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--scrap-with-playwright", action="store_true", help="scrap with playwright"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--proxy-url",
|
||||
help="proxy url to scrap. ex: socks5://127.0.0.1:1080",
|
||||
default=None,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -214,6 +338,20 @@ if __name__ == "__main__":
|
||||
level=logging.INFO,
|
||||
)
|
||||
if args.all:
|
||||
asyncio.run(run_schedule_scrape(args.pages, args.start_pages))
|
||||
asyncio.run(
|
||||
run_schedule_scrape(
|
||||
args.pages, args.start_pages, args.scrap_with_playwright, args.proxy_url
|
||||
)
|
||||
)
|
||||
else:
|
||||
asyncio.run(run_scraper(args.language, args.video_type, args.pages, args.start_pages, args.home))
|
||||
asyncio.run(
|
||||
run_scraper(
|
||||
args.language,
|
||||
args.video_type,
|
||||
args.pages,
|
||||
args.start_pages,
|
||||
args.search_keyword,
|
||||
args.scrap_with_playwright,
|
||||
args.proxy_url,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -25,7 +25,13 @@ class RealDebrid:
|
||||
self.disable_access_token()
|
||||
|
||||
def _make_request(
|
||||
self, method: str, url: str, data=None, params=None, is_return_none=False, is_expected_to_fail=False
|
||||
self,
|
||||
method: str,
|
||||
url: str,
|
||||
data=None,
|
||||
params=None,
|
||||
is_return_none=False,
|
||||
is_expected_to_fail=False,
|
||||
) -> dict:
|
||||
if method == "GET":
|
||||
response = requests.get(url, params=params, headers=self.headers)
|
||||
@@ -64,8 +70,12 @@ class RealDebrid:
|
||||
def initialize_headers(self):
|
||||
if self.encoded_token:
|
||||
token_data = self.decode_token_str(self.encoded_token)
|
||||
access_token_data = self.get_token(token_data["client_id"], token_data["client_secret"], token_data["code"])
|
||||
self.headers = {"Authorization": f"Bearer {access_token_data['access_token']}"}
|
||||
access_token_data = self.get_token(
|
||||
token_data["client_id"], token_data["client_secret"], token_data["code"]
|
||||
)
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {access_token_data['access_token']}"
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def encode_token_data(client_id: str, client_secret: str, code: str):
|
||||
@@ -110,18 +120,24 @@ class RealDebrid:
|
||||
if "client_secret" not in response_data:
|
||||
return response_data
|
||||
|
||||
token_data = self.get_token(response_data["client_id"], response_data["client_secret"], device_code)
|
||||
token_data = self.get_token(
|
||||
response_data["client_id"], response_data["client_secret"], device_code
|
||||
)
|
||||
|
||||
if "access_token" in token_data:
|
||||
token = self.encode_token_data(
|
||||
response_data["client_id"], response_data["client_secret"], token_data["refresh_token"]
|
||||
response_data["client_id"],
|
||||
response_data["client_secret"],
|
||||
token_data["refresh_token"],
|
||||
)
|
||||
return {"token": token}
|
||||
else:
|
||||
return token_data
|
||||
|
||||
def add_magent_link(self, magnet_link):
|
||||
return self._make_request("POST", f"{self.BASE_URL}/torrents/addMagnet", data={"magnet": magnet_link})
|
||||
return self._make_request(
|
||||
"POST", f"{self.BASE_URL}/torrents/addMagnet", data={"magnet": magnet_link}
|
||||
)
|
||||
|
||||
def get_user_torrent_list(self):
|
||||
return self._make_request("GET", f"{self.BASE_URL}/torrents")
|
||||
@@ -130,34 +146,44 @@ class RealDebrid:
|
||||
return self._make_request("GET", f"{self.BASE_URL}/torrents/info/{torrent_id}")
|
||||
|
||||
def get_torrent_instant_availability(self, torrent_hash):
|
||||
return self._make_request("GET", f"{self.BASE_URL}/torrents/instantAvailability/{torrent_hash}")
|
||||
return self._make_request(
|
||||
"GET", f"{self.BASE_URL}/torrents/instantAvailability/{torrent_hash}"
|
||||
)
|
||||
|
||||
def disable_access_token(self):
|
||||
return self._make_request("GET", f"{self.BASE_URL}/disable_access_token", is_return_none=True)
|
||||
return self._make_request(
|
||||
"GET", f"{self.BASE_URL}/disable_access_token", is_return_none=True
|
||||
)
|
||||
|
||||
def start_torrent_download(self, torrent_id, file_ids="all"):
|
||||
return self._make_request(
|
||||
"POST", f"{self.BASE_URL}/torrents/selectFiles/{torrent_id}", data={"files": file_ids}, is_return_none=True
|
||||
"POST",
|
||||
f"{self.BASE_URL}/torrents/selectFiles/{torrent_id}",
|
||||
data={"files": file_ids},
|
||||
is_return_none=True,
|
||||
)
|
||||
|
||||
def get_available_torrent(self, info_hash, episode: int | None) -> dict[str, Any] | None:
|
||||
def get_available_torrent(self, info_hash, filename: str) -> dict[str, Any] | None:
|
||||
available_torrents = self.get_user_torrent_list()
|
||||
for torrent in available_torrents:
|
||||
if torrent["hash"] == info_hash:
|
||||
if episode is None:
|
||||
return torrent
|
||||
torrent_name = PTN.parse(torrent["filename"])
|
||||
if "episode" in torrent_name and int(torrent_name["episode"]) == episode:
|
||||
return torrent
|
||||
if torrent["hash"] == info_hash and torrent["filename"] == filename:
|
||||
return torrent
|
||||
|
||||
def create_download_link(self, link):
|
||||
response = self._make_request(
|
||||
"POST", f"{self.BASE_URL}/unrestrict/link", data={"link": link}, is_expected_to_fail=True
|
||||
"POST",
|
||||
f"{self.BASE_URL}/unrestrict/link",
|
||||
data={"link": link},
|
||||
is_expected_to_fail=True,
|
||||
)
|
||||
if "download" in response:
|
||||
return response
|
||||
|
||||
if "error_code" in response:
|
||||
if response["error_code"] == 23:
|
||||
raise ProviderException("Exceed remote traffic limit", "exceed_remote_traffic_limit.mp4")
|
||||
raise ProviderException(f"Failed to create download link. response: {response}", "api_error.mp4")
|
||||
raise ProviderException(
|
||||
"Exceed remote traffic limit", "exceed_remote_traffic_limit.mp4"
|
||||
)
|
||||
raise ProviderException(
|
||||
f"Failed to create download link. response: {response}", "api_error.mp4"
|
||||
)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import time
|
||||
|
||||
from db.models import Streams, Episode
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from streaming_providers.realdebrid.client import RealDebrid
|
||||
from utils.parser import select_episode_file
|
||||
|
||||
|
||||
def check_existing_torrent(
|
||||
rd_client, info_hash: str, episode: int | None, max_retries: int, retry_interval: int
|
||||
rd_client, info_hash: str, filename: str, max_retries: int, retry_interval: int
|
||||
) -> dict | None:
|
||||
"""Check if the torrent is already in torrent list and return the direct link if available."""
|
||||
retries = 0
|
||||
torrent_info = rd_client.get_available_torrent(info_hash, episode)
|
||||
torrent_info = rd_client.get_available_torrent(info_hash, filename)
|
||||
if not torrent_info:
|
||||
return None
|
||||
torrent_id = torrent_info.get("id")
|
||||
@@ -22,13 +23,17 @@ def check_existing_torrent(
|
||||
elif torrent_info["status"] == "waiting_files_selection":
|
||||
return torrent_info
|
||||
elif torrent_info["status"] == "magnet_error":
|
||||
raise ProviderException("Failed to add magnet link to Real-Debrid", "transfer_error.mp4")
|
||||
raise ProviderException(
|
||||
"Failed to add magnet link to Real-Debrid", "transfer_error.mp4"
|
||||
)
|
||||
time.sleep(retry_interval)
|
||||
retries += 1
|
||||
raise ProviderException("Torrent not downloaded yet.", "torrent_not_downloaded.mp4")
|
||||
|
||||
|
||||
def wait_for_file_selection(rd_client, torrent_id: str, max_retries: int, retry_interval: int):
|
||||
def wait_for_file_selection(
|
||||
rd_client, torrent_id: str, max_retries: int, retry_interval: int
|
||||
):
|
||||
"""Wait for the torrent status to be 'waiting_files_selection'."""
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
@@ -37,10 +42,15 @@ def wait_for_file_selection(rd_client, torrent_id: str, max_retries: int, retry_
|
||||
return
|
||||
time.sleep(retry_interval)
|
||||
retries += 1
|
||||
raise ProviderException("Not enough seeders available for parse magnet link", "torrent_not_downloaded.mp4")
|
||||
raise ProviderException(
|
||||
"Not enough seeders available for parse magnet link",
|
||||
"torrent_not_downloaded.mp4",
|
||||
)
|
||||
|
||||
|
||||
def wait_for_torrent_download(rd_client, torrent_id: str, max_retries: int, retry_interval: int) -> str:
|
||||
def wait_for_torrent_download(
|
||||
rd_client, torrent_id: str, max_retries: int, retry_interval: int
|
||||
) -> str:
|
||||
"""Wait for the torrent to be downloaded and return the direct link."""
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
@@ -54,11 +64,20 @@ def wait_for_torrent_download(rd_client, torrent_id: str, max_retries: int, retr
|
||||
|
||||
|
||||
def get_direct_link_from_realdebrid(
|
||||
info_hash: str, magnet_link: str, token: str, episode: int | None, max_retries=5, retry_interval=5
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
user_data: UserData,
|
||||
stream: Streams,
|
||||
episode_data: Episode = None,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
) -> str:
|
||||
rd_client = RealDebrid(encoded_token=token)
|
||||
rd_client = RealDebrid(encoded_token=user_data.streaming_provider.token)
|
||||
filename = episode_data.filename if episode_data else stream.filename
|
||||
|
||||
response_data = check_existing_torrent(rd_client, info_hash, episode, max_retries, retry_interval)
|
||||
response_data = check_existing_torrent(
|
||||
rd_client, info_hash, filename, max_retries, retry_interval
|
||||
)
|
||||
if response_data and "download" in response_data:
|
||||
return response_data.get("download")
|
||||
|
||||
@@ -66,7 +85,9 @@ def get_direct_link_from_realdebrid(
|
||||
if response_data is None:
|
||||
response_data = rd_client.add_magent_link(magnet_link)
|
||||
if "id" not in response_data:
|
||||
raise ProviderException("Failed to add magnet link to Real-Debrid", "transfer_error.mp4")
|
||||
raise ProviderException(
|
||||
"Failed to add magnet link to Real-Debrid", "transfer_error.mp4"
|
||||
)
|
||||
|
||||
torrent_id = response_data["id"]
|
||||
wait_for_file_selection(rd_client, torrent_id, max_retries, retry_interval)
|
||||
@@ -74,14 +95,14 @@ def get_direct_link_from_realdebrid(
|
||||
# Retrieve torrent information
|
||||
torrent_info = rd_client.get_torrent_info(torrent_id)
|
||||
if not torrent_info["files"]:
|
||||
raise ProviderException("No files available for this torrent", "transfer_error.mp4")
|
||||
raise ProviderException(
|
||||
"No files available for this torrent", "transfer_error.mp4"
|
||||
)
|
||||
|
||||
# If episode number is provided, search for that episode
|
||||
if episode:
|
||||
selected_file = select_episode_file(torrent_info["files"], episode, "path")
|
||||
else:
|
||||
# Otherwise, select the largest file for download
|
||||
selected_file = max(torrent_info["files"], key=lambda x: x["bytes"])
|
||||
# select the file to download
|
||||
selected_file = [
|
||||
file for file in torrent_info["files"] if file["path"] == filename
|
||||
][0]
|
||||
|
||||
file_id = selected_file["id"]
|
||||
rd_client.start_torrent_download(torrent_id, file_id)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from seedrcc import Seedr
|
||||
|
||||
from db.models import Streams, Episode
|
||||
from db.schemas import UserData
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from utils.parser import select_episode_file
|
||||
from utils.parser import clean_name
|
||||
|
||||
|
||||
def check_torrent_status(seedr, info_hash: str):
|
||||
@@ -27,7 +31,9 @@ def add_magnet_and_get_torrent(seedr, magnet_link: str, info_hash: str):
|
||||
if "error" in transfer:
|
||||
if transfer["error"] == "invalid_token":
|
||||
raise ProviderException("Invalid Seedr token", "invalid_token.mp4")
|
||||
raise ProviderException("Error transferring magnet link to Seedr", "transfer_error.mp4")
|
||||
raise ProviderException(
|
||||
"Error transferring magnet link to Seedr", "transfer_error.mp4"
|
||||
)
|
||||
|
||||
# Return the appropriate torrent name based on the transfer result
|
||||
if transfer["result"] is True and "title" in transfer:
|
||||
@@ -36,13 +42,23 @@ def add_magnet_and_get_torrent(seedr, magnet_link: str, info_hash: str):
|
||||
torrent = check_torrent_status(seedr, info_hash)
|
||||
if torrent:
|
||||
return torrent["name"]
|
||||
elif transfer["result"] in ("not_enough_space_added_to_wishlist", "not_enough_space_wishlist_full"):
|
||||
raise ProviderException("Not enough space in Seedr account to add this torrent", "not_enough_space.mp4")
|
||||
elif transfer["result"] in (
|
||||
"not_enough_space_added_to_wishlist",
|
||||
"not_enough_space_wishlist_full",
|
||||
):
|
||||
raise ProviderException(
|
||||
"Not enough space in Seedr account to add this torrent",
|
||||
"not_enough_space.mp4",
|
||||
)
|
||||
|
||||
raise ProviderException("Error transferring magnet link to Seedr", "transfer_error.mp4")
|
||||
raise ProviderException(
|
||||
"Error transferring magnet link to Seedr", "transfer_error.mp4"
|
||||
)
|
||||
|
||||
|
||||
def wait_for_torrent_to_complete(seedr, info_hash: str, max_retries: int, retry_interval: int):
|
||||
def wait_for_torrent_to_complete(
|
||||
seedr, info_hash: str, max_retries: int, retry_interval: int
|
||||
):
|
||||
"""Waits for a torrent with the given info_hash to complete downloading."""
|
||||
retries = 0
|
||||
while retries < max_retries:
|
||||
@@ -57,29 +73,27 @@ def wait_for_torrent_to_complete(seedr, info_hash: str, max_retries: int, retry_
|
||||
raise ProviderException("Torrent not downloaded yet.", "torrent_not_downloaded.mp4")
|
||||
|
||||
|
||||
def get_file_details_from_folder(seedr, folder_id: int, episode: int | None):
|
||||
"""Gets the details of the largest file in a given folder."""
|
||||
def get_file_details_from_folder(seedr, folder_id: int, filename: str):
|
||||
"""Gets the details of the file in a given folder."""
|
||||
folder_content = seedr.listContents(folder_id)
|
||||
if episode:
|
||||
return select_episode_file(folder_content["files"], episode, "name")
|
||||
return max(folder_content["files"], key=lambda x: x["size"])
|
||||
return [f for f in folder_content["files"] if f["name"] == filename][0]
|
||||
|
||||
|
||||
def get_direct_link_from_seedr(
|
||||
async def get_direct_link_from_seedr(
|
||||
info_hash: str,
|
||||
magnet_link: str,
|
||||
token: str,
|
||||
torrent_name: str,
|
||||
episode: str | None,
|
||||
user_data: UserData,
|
||||
stream: Streams,
|
||||
episode_data: Episode = None,
|
||||
max_retries=5,
|
||||
retry_interval=5,
|
||||
) -> str:
|
||||
"""Gets a direct download link from Seedr using a magnet link and token."""
|
||||
seedr = Seedr(token=token)
|
||||
seedr = Seedr(token=user_data.streaming_provider.token)
|
||||
|
||||
# Check for existing torrent or folder
|
||||
torrent = check_torrent_status(seedr, info_hash)
|
||||
folder = check_folder_status(seedr, torrent_name)
|
||||
folder = check_folder_status(seedr, clean_name(stream.torrent_name))
|
||||
|
||||
# Handle the torrent based on its status or if it's already in a folder
|
||||
if folder:
|
||||
@@ -89,15 +103,21 @@ def get_direct_link_from_seedr(
|
||||
folder_title = torrent["name"]
|
||||
else:
|
||||
folder_title = add_magnet_and_get_torrent(seedr, magnet_link, info_hash)
|
||||
if clean_name(stream.torrent_name) != folder_title:
|
||||
logging.warning(
|
||||
f"Torrent name mismatch: '{clean_name(stream.torrent_name)}' != '{folder_title}'."
|
||||
)
|
||||
folder = check_folder_status(seedr, folder_title)
|
||||
if not folder:
|
||||
wait_for_torrent_to_complete(seedr, info_hash, max_retries, retry_interval)
|
||||
folder = check_folder_status(seedr, folder_title)
|
||||
folder_id = folder["id"]
|
||||
if torrent_name != folder_title:
|
||||
seedr.renameFolder(folder_id, torrent_name)
|
||||
|
||||
selected_file = get_file_details_from_folder(seedr, folder_id, episode)
|
||||
selected_file = get_file_details_from_folder(
|
||||
seedr,
|
||||
folder_id,
|
||||
clean_name(episode_data.filename if episode_data else stream.filename),
|
||||
)
|
||||
video_link = seedr.fetchFile(selected_file["folder_file_id"])["url"]
|
||||
|
||||
return video_link
|
||||
|
||||
+82
-49
@@ -1,52 +1,70 @@
|
||||
import PTN
|
||||
import math
|
||||
import re
|
||||
|
||||
from db.schemas import Stream
|
||||
from streaming_providers.exceptions import ProviderException
|
||||
from db.config import settings
|
||||
from db.models import Streams
|
||||
from db.schemas import Stream, UserData
|
||||
from utils.site_data import ALWAYS_ENABLED, LANGUAGE_CATALOGS
|
||||
|
||||
|
||||
def extract_stream_details(stream_name, video_qualities: dict, user_data) -> list[Stream]:
|
||||
def extract_stream_details(
|
||||
streams: list[Streams],
|
||||
user_data: UserData,
|
||||
secret_str: str,
|
||||
season: int = None,
|
||||
episode: int = None,
|
||||
) -> list[Stream]:
|
||||
stream_list = []
|
||||
for quality_string, hash_value in video_qualities.items():
|
||||
stream_details = {}
|
||||
|
||||
# Quality
|
||||
quality = re.search(
|
||||
r"(4K|\d{3,4}p|HQ HDRip|HDRip|HQ PreDVDRip|HQ Real-PreDVDRip|HQ-Real PreDVDRip|HQ-S Print)", quality_string
|
||||
# Sort the streams by created_at time
|
||||
streams = sorted(streams, key=lambda x: x.created_at, reverse=True)
|
||||
|
||||
for stream_data in streams:
|
||||
quality_detail = " - ".join(
|
||||
filter(
|
||||
None,
|
||||
[
|
||||
stream_data.quality,
|
||||
stream_data.resolution,
|
||||
stream_data.codec,
|
||||
stream_data.audio,
|
||||
],
|
||||
)
|
||||
)
|
||||
if quality:
|
||||
quality = quality.group(0)
|
||||
else:
|
||||
quality = "N/A"
|
||||
|
||||
# Size
|
||||
size = re.search(r"(\d+(\.\d+)?(MB|GB))", quality_string)
|
||||
if size:
|
||||
size = size.group(0)
|
||||
else:
|
||||
size = "N/A"
|
||||
episode_data = stream_data.get_episode(season, episode)
|
||||
|
||||
# Languages
|
||||
lang_dict = {
|
||||
"Tam": "Tamil",
|
||||
"Mal": "Malayalam",
|
||||
"Tel": "Telugu",
|
||||
"Kan": "Kannada",
|
||||
"Hin": "Hindi",
|
||||
"Eng": "English",
|
||||
description_parts = [
|
||||
quality_detail,
|
||||
convert_bytes_to_readable(
|
||||
episode_data.size if episode_data else stream_data.size
|
||||
),
|
||||
" + ".join(stream_data.languages),
|
||||
stream_data.source,
|
||||
user_data.streaming_provider.service.title()
|
||||
if user_data.streaming_provider
|
||||
else "Torrent",
|
||||
]
|
||||
description = ", ".join(filter(lambda x: bool(x), description_parts))
|
||||
|
||||
stream_details = {
|
||||
"name": "MediaFusion",
|
||||
"description": description,
|
||||
"infoHash": stream_data.id,
|
||||
"fileIdx": episode_data.file_index
|
||||
if episode_data
|
||||
else stream_data.file_index,
|
||||
"behaviorHints": {"bingeGroup": f"MediaFusion-{quality_detail}"},
|
||||
}
|
||||
|
||||
languages = []
|
||||
for short, full in lang_dict.items():
|
||||
if re.search(f"({short}|{full})", quality_string, re.IGNORECASE):
|
||||
languages.append(full)
|
||||
|
||||
stream_details["name"] = "TamilBlasters"
|
||||
stream_details["stream_name"] = stream_name
|
||||
streaming_provider = user_data.streaming_provider.service.title() if user_data.streaming_provider else "Torrent"
|
||||
stream_details["description"] = f"{quality}, {size}, {' + '.join(languages)}, {streaming_provider}"
|
||||
stream_details["infoHash"] = hash_value
|
||||
if user_data.streaming_provider:
|
||||
base_proxy_url = f"{settings.host_url}/{secret_str}/streaming_provider?info_hash={stream_data.id}"
|
||||
if episode_data:
|
||||
base_proxy_url += f"&season={season}&episode={episode}"
|
||||
stream_details["url"] = base_proxy_url
|
||||
stream_details.pop("infoHash")
|
||||
stream_details.pop("fileIdx")
|
||||
stream_details["behaviorHints"]["notWebReady"] = True
|
||||
|
||||
stream_list.append(Stream(**stream_details))
|
||||
|
||||
@@ -64,25 +82,40 @@ def generate_catalog_ids(preferred_movie_languages, preferred_series_languages):
|
||||
catalog_ids.extend(catalogs["series"])
|
||||
|
||||
# Handle the dubbed option
|
||||
if "Dubbed" in preferred_movie_languages and language + "_dubbed" in catalogs["movie"]:
|
||||
if (
|
||||
"Dubbed" in preferred_movie_languages
|
||||
and language + "_dubbed" in catalogs["movie"]
|
||||
):
|
||||
catalog_ids.append(language.lower() + "_dubbed")
|
||||
|
||||
return catalog_ids
|
||||
|
||||
|
||||
def clean_name(name: str) -> str:
|
||||
# Only allow alphanumeric characters, spaces, and `.,:_-`
|
||||
cleaned_name = re.sub(r"[^a-zA-Z0-9 .,:_-]", "", name)
|
||||
# Only allow alphanumeric characters, spaces, and `.,;:_~-[]()`
|
||||
cleaned_name = re.sub(r"[^a-zA-Z0-9 .,;:_~\-()\[\]]", "", name)
|
||||
return cleaned_name
|
||||
|
||||
|
||||
def select_episode_file(torrent_files: list, episode: int, file_name_key: str) -> dict:
|
||||
"""Select the file with the specified episode number."""
|
||||
def convert_bytes_to_readable(size_bytes: int) -> str:
|
||||
"""
|
||||
Convert a size in bytes into a more human-readable format.
|
||||
"""
|
||||
if size_bytes == 0:
|
||||
return "0B"
|
||||
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return f"{s} {size_name[i]}"
|
||||
|
||||
for file in torrent_files:
|
||||
torrent_data = PTN.parse(file[file_name_key])
|
||||
file_episode = torrent_data.get("episode")
|
||||
if file_episode and int(file_episode) == episode:
|
||||
return file
|
||||
else:
|
||||
raise ProviderException(f"Episode {episode} not found in this torrent", "episode_not_found.mp4")
|
||||
|
||||
def get_catalogs(catalog: str, languages: list[str]) -> list[str]:
|
||||
base_catalogs = ["hdrip", "tcrip", "dubbed", "series"]
|
||||
base_catalog = catalog.split("_")[1]
|
||||
|
||||
if base_catalog not in base_catalogs:
|
||||
return [catalog]
|
||||
|
||||
# Generate the catalog for each supported language
|
||||
return [f"{lang.lower()}_{base_catalog}" for lang in languages]
|
||||
|
||||
+24
-69
@@ -1,73 +1,28 @@
|
||||
HOMEPAGE = "https://www.1tamilblasters.lat"
|
||||
|
||||
TAMIL_BLASTER_LINKS = {
|
||||
"tamil": {
|
||||
"hdrip": f"{HOMEPAGE}/index.php?/forums/forum/7-tamil-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"tcrip": f"{HOMEPAGE}/index.php?/forums/forum/8-tamil-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"dubbed": f"{HOMEPAGE}/index.php?/forums/forum/9-tamil-dubbed-movies-bdrips-hdrips-dvdscr-hdcam-in-multi-audios",
|
||||
"series": f"{HOMEPAGE}/index.php?/forums/forum/63-tamil-new-web-series-tv-shows",
|
||||
},
|
||||
"malayalam": {
|
||||
"tcrip": f"{HOMEPAGE}/index.php?/forums/forum/75-malayalam-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": f"{HOMEPAGE}/index.php?/forums/forum/74-malayalam-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": f"{HOMEPAGE}/index.php?/forums/forum/76-malayalam-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": f"{HOMEPAGE}/index.php?/forums/forum/98-malayalam-new-web-series-tv-shows",
|
||||
},
|
||||
"telugu": {
|
||||
"tcrip": f"{HOMEPAGE}/index.php?/forums/forum/79-telugu-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": f"{HOMEPAGE}/index.php?/forums/forum/78-telugu-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": f"{HOMEPAGE}/index.php?/forums/forum/80-telugu-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": f"{HOMEPAGE}/index.php?/forums/forum/96-telugu-new-web-series-tv-shows",
|
||||
},
|
||||
"hindi": {
|
||||
"tcrip": f"{HOMEPAGE}/index.php?/forums/forum/87-hindi-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": f"{HOMEPAGE}/index.php?/forums/forum/86-hindi-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": f"{HOMEPAGE}/index.php?/forums/forum/88-hindi-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": f"{HOMEPAGE}/index.php?/forums/forum/89-hindi-new-web-series-tv-shows",
|
||||
},
|
||||
"kannada": {
|
||||
"tcrip": f"{HOMEPAGE}/index.php?/forums/forum/83-kannada-new-movies-tcrip-dvdscr-hdcam-predvd",
|
||||
"hdrip": f"{HOMEPAGE}/index.php?/forums/forum/82-kannada-new-movies-hdrips-bdrips-dvdrips-hdtv",
|
||||
"dubbed": f"{HOMEPAGE}/index.php?/forums/forum/84-kannada-dubbed-movies-bdrips-hdrips-dvdscr-hdcam",
|
||||
"series": f"{HOMEPAGE}/index.php?/forums/forum/103-kannada-new-web-series-tv-shows",
|
||||
},
|
||||
"english": {
|
||||
"tcrip": f"{HOMEPAGE}/index.php?/forums/forum/52-english-movies-hdcam-dvdscr-predvd",
|
||||
"hdrip": f"{HOMEPAGE}/index.php?/forums/forum/53-english-movies-hdrips-bdrips-dvdrips",
|
||||
"series": f"{HOMEPAGE}/index.php?/forums/forum/92-english-web-series-tv-shows",
|
||||
},
|
||||
}
|
||||
|
||||
LANGUAGE_CATALOGS = {
|
||||
"Tamil": {"movie": ["tamil_hdrip", "tamil_tcrip", "tamil_dubbed"], "series": ["tamil_series"]},
|
||||
"Malayalam": {"movie": ["malayalam_hdrip", "malayalam_tcrip", "malayalam_dubbed"], "series": ["malayalam_series"]},
|
||||
"Telugu": {"movie": ["telugu_hdrip", "telugu_tcrip", "telugu_dubbed"], "series": ["telugu_series"]},
|
||||
"Hindi": {"movie": ["hindi_hdrip", "hindi_tcrip", "hindi_dubbed"], "series": ["hindi_series"]},
|
||||
"Kannada": {"movie": ["kannada_hdrip", "kannada_tcrip", "kannada_dubbed"], "series": ["kannada_series"]},
|
||||
"English": {"movie": ["english_hdrip", "english_tcrip"], "series": ["english_series"]},
|
||||
"Tamil": {
|
||||
"movie": ["tamil_hdrip", "tamil_tcrip", "tamil_dubbed"],
|
||||
"series": ["tamil_series"],
|
||||
},
|
||||
"Malayalam": {
|
||||
"movie": ["malayalam_hdrip", "malayalam_tcrip", "malayalam_dubbed"],
|
||||
"series": ["malayalam_series"],
|
||||
},
|
||||
"Telugu": {
|
||||
"movie": ["telugu_hdrip", "telugu_tcrip", "telugu_dubbed"],
|
||||
"series": ["telugu_series"],
|
||||
},
|
||||
"Hindi": {
|
||||
"movie": ["hindi_hdrip", "hindi_tcrip", "hindi_dubbed"],
|
||||
"series": ["hindi_series"],
|
||||
},
|
||||
"Kannada": {
|
||||
"movie": ["kannada_hdrip", "kannada_tcrip", "kannada_dubbed"],
|
||||
"series": ["kannada_series"],
|
||||
},
|
||||
"English": {
|
||||
"movie": ["english_hdrip", "english_tcrip"],
|
||||
"series": ["english_series"],
|
||||
},
|
||||
}
|
||||
|
||||
ALWAYS_ENABLED = ["tamil_blasters"]
|
||||
|
||||
TRACKERS = [
|
||||
"udp://tracker.openbittorrent.com:80/announce",
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"udp://tracker.pomf.se:80/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://tracker.internetwarriors.net:1337/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.coppersurfer.tk:6969/announce",
|
||||
"udp://tracker.leechers-paradise.org:6969/announce",
|
||||
"udp://9.rarbg.to:2710/announce",
|
||||
"udp://9.rarbg.me:2710/announce",
|
||||
"http://tracker3.itzmx.com:8080/announce",
|
||||
"udp://ipv4.tracker.harry.lu:80/announce",
|
||||
"http://125.227.35.196:6969/announce",
|
||||
"http://210.244.71.25:6969/announce",
|
||||
"http://210.244.71.26:6969/announce",
|
||||
"http://213.159.215.198:6970/announce",
|
||||
"http://37.19.5.139:6969/announce",
|
||||
"http://37.19.5.155:6881/announce",
|
||||
"http://46.4.109.148:6969/announce",
|
||||
"http://87.248.186.252:8080/announce",
|
||||
]
|
||||
|
||||
+70
-15
@@ -1,37 +1,92 @@
|
||||
import bencodepy
|
||||
import hashlib
|
||||
import logging
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
|
||||
from utils.site_data import TRACKERS
|
||||
import PTN
|
||||
import bencodepy
|
||||
|
||||
from utils.parser import clean_name
|
||||
|
||||
TRACKERS = [
|
||||
"udp://tracker.openbittorrent.com:80/announce",
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"udp://tracker.pomf.se:80/announce",
|
||||
"udp://exodus.desync.com:6969/announce",
|
||||
"udp://tracker.internetwarriors.net:1337/announce",
|
||||
"udp://tracker.tiny-vps.com:6969/announce",
|
||||
"udp://tracker.coppersurfer.tk:6969/announce",
|
||||
"udp://tracker.leechers-paradise.org:6969/announce",
|
||||
"udp://9.rarbg.to:2710/announce",
|
||||
"udp://9.rarbg.me:2710/announce",
|
||||
"http://tracker3.itzmx.com:8080/announce",
|
||||
"udp://ipv4.tracker.harry.lu:80/announce",
|
||||
]
|
||||
|
||||
|
||||
def get_info_hash_from_url(torrent_url):
|
||||
def extract_torrent_metadata(content) -> dict:
|
||||
try:
|
||||
response = requests.get(torrent_url)
|
||||
response.raise_for_status()
|
||||
|
||||
torrent_data = bencodepy.decode(response.content)
|
||||
torrent_data = bencodepy.decode(content)
|
||||
|
||||
info = torrent_data[b"info"]
|
||||
info_encoded = bencodepy.encode(info)
|
||||
|
||||
m = hashlib.sha1()
|
||||
m.update(info_encoded)
|
||||
return m.hexdigest()
|
||||
info_hash = m.hexdigest()
|
||||
|
||||
# Extract file size, file list, and announce list
|
||||
if b"files" in info:
|
||||
total_size = sum(file[b"length"] for file in info[b"files"])
|
||||
file_data = []
|
||||
for idx, file in enumerate(info[b"files"]):
|
||||
filename = file[b"path"][0].decode()
|
||||
parsed_data = PTN.parse(filename)
|
||||
file_data.append(
|
||||
{
|
||||
"filename": filename,
|
||||
"size": file[b"length"],
|
||||
"index": idx,
|
||||
"season": parsed_data.get("season"),
|
||||
"episode": parsed_data.get("episode"),
|
||||
}
|
||||
)
|
||||
else:
|
||||
total_size = info[b"length"]
|
||||
filename = info[b"name"].decode()
|
||||
parsed_data = PTN.parse(filename)
|
||||
file_data = [
|
||||
{
|
||||
"filename": filename,
|
||||
"size": total_size,
|
||||
"index": 0,
|
||||
"season": parsed_data.get("season"),
|
||||
"episode": parsed_data.get("episode"),
|
||||
}
|
||||
]
|
||||
|
||||
announce_list = [
|
||||
tracker[0].decode() for tracker in torrent_data.get(b"announce-list", [])
|
||||
]
|
||||
torrent_name = info.get(b"name", b"").decode() or file_data[0]["filename"]
|
||||
|
||||
return {
|
||||
"info_hash": info_hash,
|
||||
"announce_list": announce_list,
|
||||
"total_size": total_size,
|
||||
"file_data": file_data,
|
||||
"torrent_name": torrent_name,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error occurred: {e}")
|
||||
return False
|
||||
return {}
|
||||
|
||||
|
||||
def convert_info_hash_to_magnet(info_hash: str, name: str = "") -> str:
|
||||
def convert_info_hash_to_magnet(info_hash: str, trackers: list[str], name: str) -> str:
|
||||
magnet_link = f"magnet:?xt=urn:btih:{info_hash}"
|
||||
if name:
|
||||
encoded_name = quote(name, safe="")
|
||||
magnet_link += f"&dn={encoded_name}"
|
||||
for tracker in TRACKERS:
|
||||
encoded_name = quote(clean_name(name), safe="")
|
||||
magnet_link += f"&dn={encoded_name}"
|
||||
for tracker in trackers or TRACKERS:
|
||||
encoded_tracker = quote(tracker, safe="")
|
||||
magnet_link += f"&tr={encoded_tracker}"
|
||||
return magnet_link
|
||||
|
||||
Reference in New Issue
Block a user