first commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.idea/
|
||||||
|
dist/
|
||||||
|
__pycache__/
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 j2qk3b
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
83
README.md
Normal file
83
README.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# AnyBT: An Open Sourced Decentralized BitTorrent Search Engine
|
||||||
|
|
||||||
|
Introducing AnyBT, a tool for searching magnet link of all kinds of BitTorrent contents. It's based on a decentralized data protocol - [Glitter Protocol](https://twitter.com/GlitterProtocol).
|
||||||
|
|
||||||
|
You could use AnyBT to search for magnet links base on file names and displays search results with a simple interface.
|
||||||
|
|
||||||
|
There is also [a web version of AnyBT](https://anybt.eth.limo) works on [ENS](https://ens.domains/) and [IPFS](https://ipfs.tech/) available if you do not have Python environment.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To get started, please follow these steps:
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python (v3.7 or higher)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Installing the tool, and you should make sure `your/download/path` in the `$PATH`, then you can use the command line tool.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install anybt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
- `terms`:Specifies search terms to be queried. Required:yes.
|
||||||
|
|
||||||
|
- `-p <page>` or `--page <page>`:Specifies the page of results to display. Default: 0.
|
||||||
|
|
||||||
|
- `-l <limit>` or `--limit <limit>`:Specifies the number of per page to display. Default: 10.
|
||||||
|
|
||||||
|
- `-s <sort type>` or `--sort <sort type>`:Specifies the sorting sequence of results to display. Default: none.
|
||||||
|
- `hot` :sort by the file heat
|
||||||
|
- `size` :sort by the size of file
|
||||||
|
- `date` :sort by the original publication time of the file
|
||||||
|
|
||||||
|
- `-t <filter type>` or `--type <fliter type>`:Specifies the category of result to display. Default: all.
|
||||||
|
- `video` :video categories.
|
||||||
|
- `document`:document categories.
|
||||||
|
- `image` :image categories.
|
||||||
|
- `music` :music categories.
|
||||||
|
- `software` :software categories.
|
||||||
|
- `package` :package categories.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
1. Search for keyword "Chaplin":
|
||||||
|
|
||||||
|
```shell
|
||||||
|
anybt Chaplin
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Search for keywords "Charlie Chaplin" and page 1, limit 5:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
anybt "Charlie Chaplin" -p 1 -l 5
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Search for keywords "Charlie Chaplin" and order by file size:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
anybt "Charlie Chaplin" -s size
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Search for keywords "Charlie Chaplin" and only keep the video resource:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
anybt "Charlie Chaplin" -t video
|
||||||
|
```
|
||||||
|
|
||||||
|
## Built With
|
||||||
|
|
||||||
|
- [glitter-sdk-py](https://github.com/glitternetwork/glitter-sdk-py) A Python SDK for interacting with the Glitter Protocol.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you would like to contribute to this project, feel free to fork the repository and submit a pull request with your changes.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
574
anybt.py
Normal file
574
anybt.py
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
import re
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import datetime
|
||||||
|
from typing import List
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_sql(sql_tpl: str, args: (list, tuple)):
|
||||||
|
return sql_tpl % escape_args(args)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_args(args):
|
||||||
|
if isinstance(args, (tuple, list)):
|
||||||
|
return tuple(literal(arg) for arg in args)
|
||||||
|
elif isinstance(args, dict):
|
||||||
|
return {key: literal(val) for (key, val) in args.items()}
|
||||||
|
else:
|
||||||
|
# If it's not a dictionary let's try escaping it anyways.
|
||||||
|
# Worst case it will throw a Value error
|
||||||
|
return escape(args)
|
||||||
|
|
||||||
|
|
||||||
|
def literal(obj):
|
||||||
|
"""Alias for escape()
|
||||||
|
|
||||||
|
Non-standard, for internal use; do not use this in your applications.
|
||||||
|
"""
|
||||||
|
return escape(obj, encoders)
|
||||||
|
|
||||||
|
|
||||||
|
def escape(obj, mapping=None):
|
||||||
|
"""Escape whatever value you pass to it.
|
||||||
|
|
||||||
|
Non-standard, for internal use; do not use this in your applications.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return "'" + escape_string(obj) + "'"
|
||||||
|
if isinstance(obj, (bytes, bytearray)):
|
||||||
|
ret = escape_bytes(obj)
|
||||||
|
return ret
|
||||||
|
return escape_item(obj, "utf8mb4", mapping=mapping)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_item(val, charset, mapping=None):
|
||||||
|
if mapping is None:
|
||||||
|
mapping = encoders
|
||||||
|
encoder = mapping.get(type(val))
|
||||||
|
|
||||||
|
# Fallback to default when no encoder found
|
||||||
|
if not encoder:
|
||||||
|
try:
|
||||||
|
encoder = mapping[str]
|
||||||
|
except KeyError:
|
||||||
|
raise TypeError("no default type converter defined")
|
||||||
|
|
||||||
|
if encoder in (escape_dict, escape_sequence):
|
||||||
|
val = encoder(val, charset, mapping)
|
||||||
|
else:
|
||||||
|
val = encoder(val, mapping)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def escape_dict(val, charset, mapping=None):
|
||||||
|
n = {}
|
||||||
|
for k, v in val.items():
|
||||||
|
quoted = escape_item(v, charset, mapping)
|
||||||
|
n[k] = quoted
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
def escape_sequence(val, charset, mapping=None):
|
||||||
|
n = []
|
||||||
|
for item in val:
|
||||||
|
quoted = escape_item(item, charset, mapping)
|
||||||
|
n.append(quoted)
|
||||||
|
return "(" + ",".join(n) + ")"
|
||||||
|
|
||||||
|
|
||||||
|
def escape_set(val, charset, mapping=None):
|
||||||
|
return ",".join([escape_item(x, charset, mapping) for x in val])
|
||||||
|
|
||||||
|
|
||||||
|
def escape_bool(value, mapping=None):
|
||||||
|
return str(int(value))
|
||||||
|
|
||||||
|
|
||||||
|
def escape_int(value, mapping=None):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_float(value, mapping=None):
|
||||||
|
s = repr(value)
|
||||||
|
if s in ("inf", "nan"):
|
||||||
|
return s
|
||||||
|
if "e" not in s:
|
||||||
|
s += "e0"
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
_escape_table = [chr(x) for x in range(128)]
|
||||||
|
_escape_table[0] = "\\0"
|
||||||
|
_escape_table[ord("\\")] = "\\\\"
|
||||||
|
_escape_table[ord("\n")] = "\\n"
|
||||||
|
_escape_table[ord("\r")] = "\\r"
|
||||||
|
_escape_table[ord("\032")] = "\\Z"
|
||||||
|
_escape_table[ord('"')] = '\\"'
|
||||||
|
_escape_table[ord("'")] = "\\'"
|
||||||
|
|
||||||
|
|
||||||
|
def escape_string(value, mapping=None):
|
||||||
|
"""escapes *value* without adding quote.
|
||||||
|
|
||||||
|
Value should be unicode
|
||||||
|
"""
|
||||||
|
return value.translate(_escape_table)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_bytes_prefixed(value, mapping=None):
|
||||||
|
return "_binary'%s'" % value.decode("ascii", "surrogateescape").translate(
|
||||||
|
_escape_table
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_bytes(value, mapping=None):
|
||||||
|
return "'%s'" % value.decode("ascii", "surrogateescape").translate(_escape_table)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_str(value, mapping=None):
|
||||||
|
return "'%s'" % escape_string(str(value), mapping)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_None(value, mapping=None):
|
||||||
|
return "NULL"
|
||||||
|
|
||||||
|
|
||||||
|
def escape_timedelta(obj, mapping=None):
|
||||||
|
seconds = int(obj.seconds) % 60
|
||||||
|
minutes = int(obj.seconds // 60) % 60
|
||||||
|
hours = int(obj.seconds // 3600) % 24 + int(obj.days) * 24
|
||||||
|
if obj.microseconds:
|
||||||
|
fmt = "'{0:02d}:{1:02d}:{2:02d}.{3:06d}'"
|
||||||
|
else:
|
||||||
|
fmt = "'{0:02d}:{1:02d}:{2:02d}'"
|
||||||
|
return fmt.format(hours, minutes, seconds, obj.microseconds)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_time(obj, mapping=None):
|
||||||
|
if obj.microsecond:
|
||||||
|
fmt = "'{0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}'"
|
||||||
|
else:
|
||||||
|
fmt = "'{0.hour:02}:{0.minute:02}:{0.second:02}'"
|
||||||
|
return fmt.format(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_datetime(obj, mapping=None):
|
||||||
|
if obj.microsecond:
|
||||||
|
fmt = "'{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}.{0.microsecond:06}'"
|
||||||
|
else:
|
||||||
|
fmt = "'{0.year:04}-{0.month:02}-{0.day:02} {0.hour:02}:{0.minute:02}:{0.second:02}'"
|
||||||
|
return fmt.format(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_date(obj, mapping=None):
|
||||||
|
fmt = "'{0.year:04}-{0.month:02}-{0.day:02}'"
|
||||||
|
return fmt.format(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def escape_struct_time(obj, mapping=None):
|
||||||
|
return escape_datetime(datetime.datetime(*obj[:6]))
|
||||||
|
|
||||||
|
|
||||||
|
def Decimal2Literal(o, d):
|
||||||
|
return format(o, "f")
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_second_fraction(s):
|
||||||
|
if not s:
|
||||||
|
return 0
|
||||||
|
# Pad zeros to ensure the fraction length in microseconds
|
||||||
|
s = s.ljust(6, "0")
|
||||||
|
return int(s[:6])
|
||||||
|
|
||||||
|
|
||||||
|
DATETIME_RE = re.compile(
|
||||||
|
r"(\d{1,4})-(\d{1,2})-(\d{1,2})[T ](\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_datetime(obj):
|
||||||
|
"""Returns a DATETIME or TIMESTAMP column value as a datetime object:
|
||||||
|
|
||||||
|
>>> datetime_or_None('2007-02-25 23:06:20')
|
||||||
|
datetime.datetime(2007, 2, 25, 23, 6, 20)
|
||||||
|
>>> datetime_or_None('2007-02-25T23:06:20')
|
||||||
|
datetime.datetime(2007, 2, 25, 23, 6, 20)
|
||||||
|
|
||||||
|
Illegal values are returned as None:
|
||||||
|
|
||||||
|
>>> datetime_or_None('2007-02-31T23:06:20') is None
|
||||||
|
True
|
||||||
|
>>> datetime_or_None('0000-00-00 00:00:00') is None
|
||||||
|
True
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (bytes, bytearray)):
|
||||||
|
obj = obj.decode("ascii")
|
||||||
|
|
||||||
|
m = DATETIME_RE.match(obj)
|
||||||
|
if not m:
|
||||||
|
return convert_date(obj)
|
||||||
|
|
||||||
|
try:
|
||||||
|
groups = list(m.groups())
|
||||||
|
groups[-1] = _convert_second_fraction(groups[-1])
|
||||||
|
return datetime.datetime(*[int(x) for x in groups])
|
||||||
|
except ValueError:
|
||||||
|
return convert_date(obj)
|
||||||
|
|
||||||
|
|
||||||
|
TIMEDELTA_RE = re.compile(r"(-)?(\d{1,3}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_timedelta(obj):
|
||||||
|
"""Returns a TIME column as a timedelta object:
|
||||||
|
|
||||||
|
>>> timedelta_or_None('25:06:17')
|
||||||
|
datetime.timedelta(1, 3977)
|
||||||
|
>>> timedelta_or_None('-25:06:17')
|
||||||
|
datetime.timedelta(-2, 83177)
|
||||||
|
|
||||||
|
Illegal values are returned as None:
|
||||||
|
|
||||||
|
>>> timedelta_or_None('random crap') is None
|
||||||
|
True
|
||||||
|
|
||||||
|
Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
|
||||||
|
can accept values as (+|-)DD HH:MM:SS. The latter format will not
|
||||||
|
be parsed correctly by this function.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (bytes, bytearray)):
|
||||||
|
obj = obj.decode("ascii")
|
||||||
|
|
||||||
|
m = TIMEDELTA_RE.match(obj)
|
||||||
|
if not m:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
try:
|
||||||
|
groups = list(m.groups())
|
||||||
|
groups[-1] = _convert_second_fraction(groups[-1])
|
||||||
|
negate = -1 if groups[0] else 1
|
||||||
|
hours, minutes, seconds, microseconds = groups[1:]
|
||||||
|
|
||||||
|
tdelta = (
|
||||||
|
datetime.timedelta(
|
||||||
|
hours=int(hours),
|
||||||
|
minutes=int(minutes),
|
||||||
|
seconds=int(seconds),
|
||||||
|
microseconds=int(microseconds),
|
||||||
|
)
|
||||||
|
* negate
|
||||||
|
)
|
||||||
|
return tdelta
|
||||||
|
except ValueError:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_time(obj):
|
||||||
|
"""Returns a TIME column as a time object:
|
||||||
|
|
||||||
|
>>> time_or_None('15:06:17')
|
||||||
|
datetime.time(15, 6, 17)
|
||||||
|
|
||||||
|
Illegal values are returned as None:
|
||||||
|
|
||||||
|
>>> time_or_None('-25:06:17') is None
|
||||||
|
True
|
||||||
|
>>> time_or_None('random crap') is None
|
||||||
|
True
|
||||||
|
|
||||||
|
Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
|
||||||
|
can accept values as (+|-)DD HH:MM:SS. The latter format will not
|
||||||
|
be parsed correctly by this function.
|
||||||
|
|
||||||
|
Also note that MySQL's TIME column corresponds more closely to
|
||||||
|
Python's timedelta and not time. However if you want TIME columns
|
||||||
|
to be treated as time-of-day and not a time offset, then you can
|
||||||
|
use set this function as the converter for FIELD_TYPE.TIME.
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (bytes, bytearray)):
|
||||||
|
obj = obj.decode("ascii")
|
||||||
|
|
||||||
|
m = TIME_RE.match(obj)
|
||||||
|
if not m:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
try:
|
||||||
|
groups = list(m.groups())
|
||||||
|
groups[-1] = _convert_second_fraction(groups[-1])
|
||||||
|
hours, minutes, seconds, microseconds = groups
|
||||||
|
return datetime.time(
|
||||||
|
hour=int(hours),
|
||||||
|
minute=int(minutes),
|
||||||
|
second=int(seconds),
|
||||||
|
microsecond=int(microseconds),
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def convert_date(obj):
|
||||||
|
"""Returns a DATE column as a date object:
|
||||||
|
|
||||||
|
>>> date_or_None('2007-02-26')
|
||||||
|
datetime.date(2007, 2, 26)
|
||||||
|
|
||||||
|
Illegal values are returned as None:
|
||||||
|
|
||||||
|
>>> date_or_None('2007-02-31') is None
|
||||||
|
True
|
||||||
|
>>> date_or_None('0000-00-00') is None
|
||||||
|
True
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(obj, (bytes, bytearray)):
|
||||||
|
obj = obj.decode("ascii")
|
||||||
|
try:
|
||||||
|
return datetime.date(*[int(x) for x in obj.split("-", 2)])
|
||||||
|
except ValueError:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def through(x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
convert_bit = through
|
||||||
|
|
||||||
|
encoders = {
|
||||||
|
bool: escape_bool,
|
||||||
|
int: escape_int,
|
||||||
|
float: escape_float,
|
||||||
|
str: escape_str,
|
||||||
|
bytes: escape_bytes,
|
||||||
|
tuple: escape_sequence,
|
||||||
|
list: escape_sequence,
|
||||||
|
set: escape_sequence,
|
||||||
|
frozenset: escape_sequence,
|
||||||
|
dict: escape_dict,
|
||||||
|
type(None): escape_None,
|
||||||
|
datetime.date: escape_date,
|
||||||
|
datetime.datetime: escape_datetime,
|
||||||
|
datetime.timedelta: escape_timedelta,
|
||||||
|
datetime.time: escape_time,
|
||||||
|
time.struct_time: escape_struct_time,
|
||||||
|
Decimal: Decimal2Literal,
|
||||||
|
}
|
||||||
|
DECIMAL = 0
|
||||||
|
TINY = 1
|
||||||
|
SHORT = 2
|
||||||
|
LONG = 3
|
||||||
|
FLOAT = 4
|
||||||
|
DOUBLE = 5
|
||||||
|
NULL = 6
|
||||||
|
TIMESTAMP = 7
|
||||||
|
LONGLONG = 8
|
||||||
|
INT24 = 9
|
||||||
|
DATE = 10
|
||||||
|
TIME = 11
|
||||||
|
DATETIME = 12
|
||||||
|
YEAR = 13
|
||||||
|
NEWDATE = 14
|
||||||
|
VARCHAR = 15
|
||||||
|
BIT = 16
|
||||||
|
JSON = 245
|
||||||
|
NEWDECIMAL = 246
|
||||||
|
ENUM = 247
|
||||||
|
SET = 248
|
||||||
|
TINY_BLOB = 249
|
||||||
|
MEDIUM_BLOB = 250
|
||||||
|
LONG_BLOB = 251
|
||||||
|
BLOB = 252
|
||||||
|
VAR_STRING = 253
|
||||||
|
STRING = 254
|
||||||
|
GEOMETRY = 255
|
||||||
|
|
||||||
|
CHAR = TINY
|
||||||
|
INTERVAL = ENUM
|
||||||
|
|
||||||
|
decoders = {
|
||||||
|
BIT: convert_bit,
|
||||||
|
TINY: int,
|
||||||
|
SHORT: int,
|
||||||
|
LONG: int,
|
||||||
|
FLOAT: float,
|
||||||
|
DOUBLE: float,
|
||||||
|
LONGLONG: int,
|
||||||
|
INT24: int,
|
||||||
|
YEAR: int,
|
||||||
|
TIMESTAMP: convert_datetime,
|
||||||
|
DATETIME: convert_datetime,
|
||||||
|
TIME: convert_timedelta,
|
||||||
|
DATE: convert_date,
|
||||||
|
BLOB: through,
|
||||||
|
TINY_BLOB: through,
|
||||||
|
MEDIUM_BLOB: through,
|
||||||
|
LONG_BLOB: through,
|
||||||
|
STRING: through,
|
||||||
|
VAR_STRING: through,
|
||||||
|
VARCHAR: through,
|
||||||
|
DECIMAL: Decimal,
|
||||||
|
NEWDECIMAL: Decimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
# for MySQLdb compatibility
|
||||||
|
conversions = encoders.copy()
|
||||||
|
conversions.update(decoders)
|
||||||
|
Thing2Literal = escape_str
|
||||||
|
|
||||||
|
encoders = {k: v for (k, v) in conversions.items() if type(k) is not int}
|
||||||
|
|
||||||
|
|
||||||
|
def format_file_size(size_bytes):
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes} B"
|
||||||
|
elif size_bytes < 1024 * 1024:
|
||||||
|
return f"{size_bytes // 1024} KB"
|
||||||
|
elif size_bytes < 1024 * 1024 * 1024:
|
||||||
|
return f"{size_bytes // (1024 * 1024)} MB"
|
||||||
|
else:
|
||||||
|
return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"
|
||||||
|
|
||||||
|
|
||||||
|
def get_icon(c):
|
||||||
|
icons = {
|
||||||
|
'video': ':movie_camera:',
|
||||||
|
'document': ':green_book:',
|
||||||
|
'music': ':musical_note:',
|
||||||
|
'software': ':laptop_computer:',
|
||||||
|
'image': ':art:'
|
||||||
|
}
|
||||||
|
return icons.get(c, ':rocket:')
|
||||||
|
|
||||||
|
|
||||||
|
def query_string_prepare(queries: list):
|
||||||
|
query_string = []
|
||||||
|
for query in queries:
|
||||||
|
query_string.append(str(query))
|
||||||
|
|
||||||
|
rst = " ".join(query_string)
|
||||||
|
return rst
|
||||||
|
|
||||||
|
|
||||||
|
def highlight_prepare(fields: List[str]):
|
||||||
|
place_hold = ",".join(["\"{}\""] * len(fields))
|
||||||
|
option = """{"highlight":{ "style":"html","fields":[""" + place_hold.format(*fields) + """]}}"""
|
||||||
|
option = option.translate({ord('"'): "\""})
|
||||||
|
return "/*+ SET_VAR(full_text_option='%s')*/" % option
|
||||||
|
|
||||||
|
|
||||||
|
def get_sql(args):
|
||||||
|
query = args.terms
|
||||||
|
queries = ["{}:{}^{}".format("file_name", "\"" + query + "\"", 1.0)]
|
||||||
|
query_str = query_string_prepare(queries)
|
||||||
|
highlight = highlight_prepare(["file_name"])
|
||||||
|
|
||||||
|
order_type = get_order_type(args.sort)
|
||||||
|
filter_type = get_filter_type(args.type)
|
||||||
|
|
||||||
|
if filter_type == 'all':
|
||||||
|
sql = prepare_sql(
|
||||||
|
"select {} _id ,category ,file_name ,firstadd_utc_timestamp ,filesize ,total_count from library.dht where query_string_recency(%s) ".format(
|
||||||
|
highlight), [query_str])
|
||||||
|
else:
|
||||||
|
query_str = [query_str, filter_type]
|
||||||
|
sql = prepare_sql(
|
||||||
|
"select {} _id ,category ,file_name ,firstadd_utc_timestamp ,filesize ,total_count from library.dht where query_string_recency(%s) and category=%s ".format(
|
||||||
|
highlight), query_str)
|
||||||
|
|
||||||
|
page = args.page * args.limit
|
||||||
|
if order_type == 'none':
|
||||||
|
sql += "limit {},{}".format(page, args.limit)
|
||||||
|
else:
|
||||||
|
sql += "order by {} desc limit {},{}".format(order_type, page, args.limit)
|
||||||
|
|
||||||
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
def get_filter_type(filter_type):
|
||||||
|
ftypes = {
|
||||||
|
'video': 'video',
|
||||||
|
'document': 'document',
|
||||||
|
'music': 'music',
|
||||||
|
'image': 'image',
|
||||||
|
'software': 'software',
|
||||||
|
}
|
||||||
|
|
||||||
|
return ftypes.get(filter_type, 'all')
|
||||||
|
|
||||||
|
|
||||||
|
def get_order_type(order_type):
|
||||||
|
otypes = {
|
||||||
|
'hot': 'total_count',
|
||||||
|
'size': 'filesize',
|
||||||
|
'date': 'firstadd_utc_timestamp'
|
||||||
|
}
|
||||||
|
return otypes.get(order_type, 'none')
|
||||||
|
|
||||||
|
|
||||||
|
def query(args):
|
||||||
|
sql = get_sql(args)
|
||||||
|
endpoint = "https://gateway.magnode.ru/blockved/glitterchain/index/sql/simple_query"
|
||||||
|
req = {"sql": sql, "arguments": []}
|
||||||
|
r = requests.post(endpoint, json=req, timeout=30)
|
||||||
|
if r.status_code != 200:
|
||||||
|
return
|
||||||
|
rst = r.json()
|
||||||
|
return rst
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.align import Align
|
||||||
|
from rich.tree import Tree
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('terms', type=str,
|
||||||
|
help='the desired terms for searching.')
|
||||||
|
parser.add_argument('-p', '--page', type=int, default=0,
|
||||||
|
help='The page of results you would like to display.')
|
||||||
|
parser.add_argument('-l', '--limit', type=int, default=10,
|
||||||
|
help='The limit of per page you would like to display.')
|
||||||
|
parser.add_argument('-s', '--sort', type=str, default='none',
|
||||||
|
help='The sort of results you would like to display.')
|
||||||
|
parser.add_argument('-t', '--type', type=str, default='all',
|
||||||
|
help='The type of results you would like to display.')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
rst = query(args)
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
table = Table(show_header=True, header_style="bold magenta", expand=True)
|
||||||
|
table_centered = Align.center(table)
|
||||||
|
link = "You could also try a web version running on ENS & IPFS:\n https://anybt.eth.limo"
|
||||||
|
table.add_column(link)
|
||||||
|
table.add_column("ext", style="dim", no_wrap=True)
|
||||||
|
|
||||||
|
for row in rst['result']:
|
||||||
|
row = row['row']
|
||||||
|
category = get_icon(row["category"]["value"]) + " " + row["category"]["value"]
|
||||||
|
ext = Tree(category)
|
||||||
|
ext.add(format_file_size(float(row["filesize"]["value"])))
|
||||||
|
ext.add("{} Hot".format(int(float(row["total_count"]["value"]))))
|
||||||
|
ext.add(time.strftime("%Y-%m-%d", time.localtime(float(row["firstadd_utc_timestamp"]["value"]))))
|
||||||
|
|
||||||
|
content = Tree(row["_highlight_file_name"]["value"].replace("<mark>", "[red]").replace("</mark>", "[/red]"))
|
||||||
|
magnet_link = "magnet:?xt=urn:btih:{}".format(row["_id"]["value"])
|
||||||
|
content.add(Text(magnet_link, overflow="fold"))
|
||||||
|
|
||||||
|
table.add_row(content, ext)
|
||||||
|
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
160
poetry.lock
generated
Normal file
160
poetry.lock
generated
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2024.2.2"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
files = [
|
||||||
|
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
|
||||||
|
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "2.0.12"
|
||||||
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5.0"
|
||||||
|
files = [
|
||||||
|
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
|
||||||
|
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
unicode-backport = ["unicodedata2"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.6"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
files = [
|
||||||
|
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||||
|
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown-it-py"
|
||||||
|
version = "2.2.0"
|
||||||
|
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"},
|
||||||
|
{file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mdurl = ">=0.1,<1.0"
|
||||||
|
typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||||
|
code-style = ["pre-commit (>=3.0,<4.0)"]
|
||||||
|
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
|
||||||
|
linkify = ["linkify-it-py (>=1,<3)"]
|
||||||
|
plugins = ["mdit-py-plugins"]
|
||||||
|
profiling = ["gprof2dot"]
|
||||||
|
rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||||
|
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mdurl"
|
||||||
|
version = "0.1.2"
|
||||||
|
description = "Markdown URL utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||||
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.17.2"
|
||||||
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"},
|
||||||
|
{file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
plugins = ["importlib-metadata"]
|
||||||
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.29.0"
|
||||||
|
description = "Python HTTP for Humans."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"},
|
||||||
|
{file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2017.4.17"
|
||||||
|
charset-normalizer = ">=2,<4"
|
||||||
|
idna = ">=2.5,<4"
|
||||||
|
urllib3 = ">=1.21.1,<1.27"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||||
|
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "13.7.1"
|
||||||
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
files = [
|
||||||
|
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
|
||||||
|
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
markdown-it-py = ">=2.2.0"
|
||||||
|
pygments = ">=2.13.0,<3.0.0"
|
||||||
|
typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.7.1"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
|
||||||
|
{file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "1.26.18"
|
||||||
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||||
|
files = [
|
||||||
|
{file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
|
||||||
|
{file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
|
||||||
|
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||||
|
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.0"
|
||||||
|
python-versions = "^3.7"
|
||||||
|
content-hash = "335e9a92084c5c9e64d579449afccff6ac8914b0a535911f690364cd072bfa1d"
|
||||||
18
pyproject.toml
Normal file
18
pyproject.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "anybt"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "An Open Sourced Decentralized BitTorrent Search Engine"
|
||||||
|
authors = ["j2qk3b"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.7"
|
||||||
|
requests = "2.29.0"
|
||||||
|
rich = "^13.7.1"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
anybt = "anybt:main"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
Reference in New Issue
Block a user