summaryrefslogtreecommitdiff
path: root/src/shoki
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2024-01-01 11:23:22 -0500
committerAndrew Opalach <andrew@akon.city> 2024-01-01 11:23:22 -0500
commit130a0edc0405e53b45f8b2f8da0b94356480a644 (patch)
tree76cda2f7e0098bdcb6c54dc152bf3e95467fd44e /src/shoki
parent099126fdca625c5f6c219c5a1a7ecbc0e145988f (diff)
downloadcamu-130a0edc0405e53b45f8b2f8da0b94356480a644.tar.gz
camu-130a0edc0405e53b45f8b2f8da0b94356480a644.tar.bz2
camu-130a0edc0405e53b45f8b2f8da0b94356480a644.zip
wip
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/shoki')
-rw-r--r--src/shoki/cpy/lib.pxd13
-rw-r--r--src/shoki/cpy/post.pxd58
-rw-r--r--src/shoki/cpy/result.pxd8
-rw-r--r--src/shoki/cpy/setup.py6
-rw-r--r--src/shoki/cpy/shoki.pyx132
-rw-r--r--src/shoki/cpy/str.pxd11
-rw-r--r--src/shoki/cpy/types.pxd11
-rw-r--r--src/shoki/meson.build14
-rw-r--r--src/shoki/py/__init__.py0
-rw-r--r--src/shoki/py/client.py51
-rw-r--r--src/shoki/py/config.def.py19
-rw-r--r--src/shoki/py/log.py30
-rw-r--r--src/shoki/py/post.py70
-rw-r--r--src/shoki/py/provider.py39
-rw-r--r--src/shoki/py/providers/__init__.py0
-rw-r--r--src/shoki/py/providers/fanbox.py130
-rw-r--r--src/shoki/py/providers/instagram.py96
-rw-r--r--src/shoki/py/providers/pixiv.py113
-rw-r--r--src/shoki/py/providers/searx.py93
-rw-r--r--src/shoki/py/providers/twitter.py424
-rw-r--r--src/shoki/py/providers/youtube.py122
-rw-r--r--src/shoki/py/query_parser.py39
-rw-r--r--src/shoki/py/search.py35
-rw-r--r--src/shoki/src/packet_ext.c91
-rw-r--r--src/shoki/src/packet_ext.h8
-rw-r--r--src/shoki/src/post.c55
-rw-r--r--src/shoki/src/post.h78
-rw-r--r--src/shoki/src/post_cache.c30
-rw-r--r--src/shoki/src/post_cache.h13
-rw-r--r--src/shoki/src/search.c99
-rw-r--r--src/shoki/src/search.h30
31 files changed, 0 insertions, 1918 deletions
diff --git a/src/shoki/cpy/lib.pxd b/src/shoki/cpy/lib.pxd
deleted file mode 100644
index 5749141..0000000
--- a/src/shoki/cpy/lib.pxd
+++ /dev/null
@@ -1,13 +0,0 @@
-include "types.pxd"
-
-cdef extern from "<al/lib.h>":
- void al_free(void *ptr)
- void al_malloc(size_t size)
- void al_strlen(char *s)
- void al_bzero(void *s, size_t n)
-
-cdef extern from "<al/log.h>":
- s32 al_log_info(const char *ns, const char *, ...)
- s32 al_log_warn(const char *ns, const char *, ...)
- s32 al_log_error(const char *ns, const char *, ...)
- s32 al_log_debug(const char *ns, const char *, ...)
diff --git a/src/shoki/cpy/post.pxd b/src/shoki/cpy/post.pxd
deleted file mode 100644
index 0b0a97e..0000000
--- a/src/shoki/cpy/post.pxd
+++ /dev/null
@@ -1,58 +0,0 @@
-include "str.pxd"
-
-cdef extern from "../../shoki/src/post.h":
- ctypedef struct sho_optional_int:
- s64 i
- bool set
-
- cdef struct sho_post_date:
- u8 type
- s64 timestamp
-
- cdef struct sho_post_media:
- u8 type
- str url
- str thumbnail_url
-
- ctypedef struct sho_dates_array:
- u32 size
- u32 alloc
- sho_post_date *data
-
- ctypedef struct sho_media_array:
- u32 size
- u32 alloc
- sho_post_media *data
-
- cdef struct sho_post_user:
- str unique_id
- str username
- str display_name
- str profile_image_url
-
- cdef struct sho_post_ref:
- str unique_id
-
- cdef struct sho_post:
- u16 version
- u8 type
- str unique_id
- str raw_responses
- str url
- sho_dates_array dates
- sho_post_user author
- str title
- str text
- sho_optional_int likes
- sho_optional_int reposts
- sho_optional_int quotes
- sho_optional_int comments
- sho_optional_int views
- sho_media_array media
- sho_post_ref post
- sho_post_ref quoted
- sho_post_ref in_reply_to
-
- void sho_post_reset(sho_post *post)
- void sho_post_add_media(sho_post *post, u8 type, str *url, str *thumbnail_url)
- void sho_post_add_date(sho_post *post, u8 type, s64 timestamp)
diff --git a/src/shoki/cpy/result.pxd b/src/shoki/cpy/result.pxd
deleted file mode 100644
index 1bebb55..0000000
--- a/src/shoki/cpy/result.pxd
+++ /dev/null
@@ -1,8 +0,0 @@
-include "post.pxd"
-
-cdef extern from "../../shoki/src/search.h":
- cdef struct sho_result:
- pass
-
- void sho_result_add_post(sho_result *res, s32 page, sho_post *post)
- void sho_result_add_to_list(sho_result *res, s32 page, str *unique_id)
diff --git a/src/shoki/cpy/setup.py b/src/shoki/cpy/setup.py
deleted file mode 100644
index 1b491f1..0000000
--- a/src/shoki/cpy/setup.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from Cython.Build import cythonize
-from Cython.Compiler import Options
-from setuptools import Extension
-Options.embed = True
-alabaster_inc = "../../../subprojects/libalabaster/include"
-cythonize([Extension("shoki", ["shoki.pyx"], include_dirs=[alabaster_inc], extra_link_args=[])], language_level=3)
diff --git a/src/shoki/cpy/shoki.pyx b/src/shoki/cpy/shoki.pyx
deleted file mode 100644
index 6d1dd56..0000000
--- a/src/shoki/cpy/shoki.pyx
+++ /dev/null
@@ -1,132 +0,0 @@
-cimport lib
-cimport str
-cimport post
-cimport result
-
-import sys
-sys.path.append('./py')
-
-cdef public int log_info(char *msg) except -1:
- return lib.al_log_info("shoki", "%s", msg)
-
-cdef public int log_warn(char *msg) except -1:
- return lib.al_log_warn("shoki", "%s", msg)
-
-cdef public int log_error(char *msg) except -1:
- return lib.al_log_error("shoki", "%s", msg)
-
-cdef public int log_debug(char *msg) except -1:
- return lib.al_log_debug("shoki", "%s", msg)
-
-def encode_str(str):
- return str.encode('UTF-8')
-
-def decode_str(str):
- return str.decode('UTF-8')
-
-class OutputHook(object):
- def __init__(self):
- self.fd = sys.__stdout__
-
- def __getattr__(self, attr):
- return getattr(self.fd, attr)
-
- def write(self, msg):
- if len(msg) > 0 and msg != '\n':
- log_debug(encode_str(msg))
-
-hook = OutputHook()
-sys.stdout = hook
-sys.stderr = hook
-
-class ShokiLogger():
- def debug(self, msg):
- log_debug(encode_str(msg))
-
- def info(self, msg):
- log_info(encode_str(msg))
-
- def warning(self, msg):
- log_warn(encode_str(msg))
-
- def error(self, msg):
- log_error(encode_str(msg))
-
-import log
-log.set_logger(ShokiLogger())
-
-def exception_handler(exception_type, exception, traceback):
- log.error(repr(exception))
-sys.excepthook = exception_handler
-
-import client
-from post import PostType, DateType
-
-shoki = client.ShokiClient()
-for name, p in client.ALL_PROVIDERS.items():
- if p[0].init():
- shoki.register_provider(name, p)
-
-cdef public int sho_client_search(str.str *provider, str.str *query) except -1:
- cdef char *c_str0 = str.al_str_to_c_str(provider)
- cdef char *c_str1 = str.al_str_to_c_str(query)
- ret = shoki.search(decode_str(c_str0), decode_str(c_str1))
- lib.al_free(c_str0)
- lib.al_free(c_str1)
- return ret
-
-cdef public int py_str_to_str(str.str *s, char *py_c_str) except -1:
- str.al_str_from(s, py_c_str)
- return 0
-
-cdef public int sho_client_get_page(result.sho_result *res, int search_id, int num) except -1:
- cdef post.sho_post cpost
- cdef str.str url
- cdef str.str thumbnail_url
- cdef str.str unique_id
- page = shoki.get_page(search_id, num)
- if not page: return -1
- for py_post in page['posts']:
- post.sho_post_reset(&cpost)
- cpost.version = py_post.version
- cpost.type = py_post.type.value
- py_str_to_str(&cpost.unique_id, encode_str(py_post.unique_id))
- py_str_to_str(&cpost.url, encode_str(py_post.url))
- for date in py_post.dates:
- post.sho_post_add_date(&cpost, date.value, py_post.dates[date])
- py_str_to_str(&cpost.author.unique_id, encode_str(py_post.author.unique_id))
- py_str_to_str(&cpost.author.username, encode_str(py_post.author.username))
- py_str_to_str(&cpost.author.display_name, encode_str(py_post.author.display_name))
- py_str_to_str(&cpost.author.profile_image_url, encode_str(py_post.author.profile_image_url))
- if cpost.type == PostType.REPOST.value:
- py_str_to_str(&cpost.post.unique_id, encode_str(py_post.post.unique_id))
- result.sho_result_add_post(res, num, &cpost)
- continue
- py_str_to_str(&cpost.title, encode_str(py_post.title))
- py_str_to_str(&cpost.text, encode_str(py_post.text))
- cpost.likes.set = py_post.likes != None
- if cpost.likes.set:
- cpost.likes.i = py_post.likes
- cpost.reposts.set = py_post.reposts != None
- if cpost.reposts.set:
- cpost.reposts.i = py_post.reposts
- cpost.quotes.set = py_post.quotes != None
- if cpost.quotes.set:
- cpost.quotes.i = py_post.quotes
- cpost.comments.set = py_post.comments != None
- if cpost.comments.set:
- cpost.comments.i = py_post.comments
- cpost.views.set = py_post.views != None
- if cpost.views.set:
- cpost.views.i = py_post.views
- for media in py_post.media:
- py_str_to_str(&url, encode_str(media.url))
- py_str_to_str(&thumbnail_url, encode_str(media.thumbnail_url))
- post.sho_post_add_media(&cpost, 0, &url, &thumbnail_url)
- py_str_to_str(&cpost.quoted.unique_id, encode_str(py_post.quoted.unique_id))
- py_str_to_str(&cpost.in_reply_to.unique_id, encode_str(py_post.in_reply_to.unique_id))
- result.sho_result_add_post(res, num, &cpost)
- for py_unique_id in page['list']:
- py_str_to_str(&unique_id, encode_str(py_unique_id))
- result.sho_result_add_to_list(res, num, &unique_id)
- return 0
diff --git a/src/shoki/cpy/str.pxd b/src/shoki/cpy/str.pxd
deleted file mode 100644
index a12ced4..0000000
--- a/src/shoki/cpy/str.pxd
+++ /dev/null
@@ -1,11 +0,0 @@
-include "types.pxd"
-
-cdef extern from "<al/str.h>":
- ctypedef struct str:
- u32 len
- s32 alloc
- char *data
-
- void al_str_from(str *s, const char *str)
- char *al_str_to_c_str(str *s)
-
diff --git a/src/shoki/cpy/types.pxd b/src/shoki/cpy/types.pxd
deleted file mode 100644
index fec35a5..0000000
--- a/src/shoki/cpy/types.pxd
+++ /dev/null
@@ -1,11 +0,0 @@
-ctypedef unsigned char u8;
-ctypedef char s8;
-ctypedef unsigned short u16;
-ctypedef short s16;
-ctypedef unsigned int u32;
-ctypedef int s32;
-ctypedef unsigned long u64;
-ctypedef long s64;
-ctypedef float f32;
-ctypedef double f64;
-ctypedef bint bool
diff --git a/src/shoki/meson.build b/src/shoki/meson.build
deleted file mode 100644
index 557d497..0000000
--- a/src/shoki/meson.build
+++ /dev/null
@@ -1,14 +0,0 @@
-shoki_src = [
- 'src/search.c',
- 'src/post.c',
- 'src/packet_ext.c',
- 'src/post_cache.c'
-]
-shoki_inc = []
-shoki_deps = [common_deps]
-
-python = dependency('python-3.11-embed', required: true)
-shoki_deps += [python]
-
-shoki = declare_dependency(sources: shoki_src,
- include_directories: shoki_inc, dependencies: shoki_deps)
diff --git a/src/shoki/py/__init__.py b/src/shoki/py/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/shoki/py/__init__.py
+++ /dev/null
diff --git a/src/shoki/py/client.py b/src/shoki/py/client.py
deleted file mode 100644
index 3986060..0000000
--- a/src/shoki/py/client.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import log
-import config
-from providers.twitter import TwitterProvider
-from providers.youtube import YoutubeProvider
-
-ALL_PROVIDERS = {
- 'twitter': (TwitterProvider(
- config.TWITTER_ACCESS_TOKEN, config.TWITTER_ACCESS_TOKEN_SECRET,
- config.TWITTER_CONSUMER_TOKEN, config.TWITTER_CONSUMER_TOKEN_SECRET,
- config.TWITTER_COOKIES_PATH), []),
- 'youtube': (YoutubeProvider(), [])
-}
-
-class ShokiClient():
- def __init__(self):
- self.providers = {}
- self.searches = {}
- self.inc = 0
-
- def register_provider(self, name, p):
- log.info('Registered provider \'{}\'.'.format(name))
- self.providers[name] = p
-
- def get_providers(self):
- return self.providers.keys()
-
- def search(self, provider, query):
- if provider not in self.providers:
- return -1
- search_id = self.inc
- p = self.providers[provider]
- try:
- search = p[0].search(query, *p[1])
- self.searches[search_id] = search
- self.inc += 1
- except Exception as e:
- log.error(repr(e))
- return -1
- return search_id
-
- def get_page(self, search_id, num):
- if search_id not in self.searches:
- return None
- try:
- return self.searches[search_id].get_page(num)
- except Exception as e:
- log.error(repr(e))
- return None
-
- def get_playback_data(self, unique_id):
- return ''
diff --git a/src/shoki/py/config.def.py b/src/shoki/py/config.def.py
deleted file mode 100644
index 237d658..0000000
--- a/src/shoki/py/config.def.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from pathlib import Path
-
-BASE_DIR = ''
-
-TWITTER_ACCESS_TOKEN = ''
-TWITTER_ACCESS_TOKEN_SECRET = ''
-TWITTER_CONSUMER_TOKEN = ''
-TWITTER_CONSUMER_TOKEN_SECRET = ''
-TWITTER_COOKIES_PATH = '{}/cookies_twitter.txt'.format(BASE_DIR)
-TWITTER_TIMEOUT = 5 # seconds
-
-FANBOX_SESSID = ''
-
-PIXIV_REFRESH_TOKEN = ''
-
-INSTAGRAM_USERNAME = ''
-INSTAGRAM_PASSWORD = ''
-INSTAGRAM_SETTINGS_PATH = Path('{}/ig_settings.json'.format(BASE_DIR))
-INSTAGRAM_USER_AGENT = 'Instagram 146.0.0.27.125 (iPhone12,1; iOS 13_3; en_US; en-US; scale=2.00; 1656x3584; 190542906)'
diff --git a/src/shoki/py/log.py b/src/shoki/py/log.py
deleted file mode 100644
index c36c54e..0000000
--- a/src/shoki/py/log.py
+++ /dev/null
@@ -1,30 +0,0 @@
-class DefaultLogger():
- def debug(self, msg):
- print(msg)
-
- def info(self, msg):
- print(msg)
-
- def warning(self, msg):
- print(msg)
-
- def error(self, msg):
- print(msg)
-
-LOGGER = DefaultLogger()
-
-def set_logger(logger):
- global LOGGER
- LOGGER = logger
-
-def debug(msg):
- LOGGER.debug(msg)
-
-def info(msg):
- LOGGER.info(msg)
-
-def warning(msg):
- LOGGER.warning(msg)
-
-def error(msg):
- LOGGER.error(msg)
diff --git a/src/shoki/py/post.py b/src/shoki/py/post.py
deleted file mode 100644
index d489298..0000000
--- a/src/shoki/py/post.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import dataclasses
-from dataclasses import field
-from typing import Optional
-from json import JSONEncoder
-from enum import Enum
-
-class PostType(Enum):
- UNKNOWN = -1
- POST = 0
- REPOST = 1
-
-class DateType(Enum):
- CREATED = 0
- EDITED = 1
- ARCHIVED = 2
-
-@dataclasses.dataclass
-class Media():
- url: str = ''
- base64_data: str = ''
-
-@dataclasses.dataclass
-class Image(Media):
- type: str = 'image'
- thumbnail_url: str = ''
-
-@dataclasses.dataclass
-class Video(Media):
- type: str = 'video'
- thumbnail_url: str = ''
-
-@dataclasses.dataclass
-class Audio(Media):
- type: str = 'audio'
-
-@dataclasses.dataclass
-class User():
- unique_id: str = ''
- username: str = ''
- display_name: str = ''
- profile_image_url: str = ''
-
-@dataclasses.dataclass
-class PostRef():
- unique_id: str = ''
-
-@dataclasses.dataclass
-class Post():
- version: int = 2
- type: 'PostType' = PostType.UNKNOWN
- unique_id: str = ''
- raw_responses: dict[str, str] = field(default_factory=lambda: {})
- url: str = ''
- dates: dict['DateType', int] = field(default_factory=lambda: {})
- author: 'User' = field(default_factory=lambda: User())
- title: str = ''
- text: str = ''
- likes: Optional[int] = None
- reposts: Optional[int] = None
- quotes: Optional[int] = None
- comments: Optional[int] = None
- views: Optional[int] = None
- media: list['Media'] = field(default_factory=lambda: [])
- post: 'PostRef' = field(default_factory=lambda: PostRef())
- quoted: 'PostRef' = field(default_factory=lambda: PostRef())
- in_reply_to: 'PostRef' = field(default_factory=lambda: PostRef())
-
-class PostEncoder(JSONEncoder):
- def default(self, obj):
- return obj.__dict__
diff --git a/src/shoki/py/provider.py b/src/shoki/py/provider.py
deleted file mode 100644
index a719d01..0000000
--- a/src/shoki/py/provider.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import httpx
-import json
-import log
-
-class Provider():
- def __init__(self):
- self.cookies = {}
- self.headers = {}
- self.unique_id_map = {}
-
- def init(self):
- return True
-
- # TODO: retries.
- def get(self, url, params = None):
- log.debug('HTTP request {} {}.'.format(url, params))
- r = httpx.get(url, params=params, cookies=self.cookies, headers=self.headers)
- # TODO: Non-200 = err, support redirects (if not already done in httpx)?
- if r.status_code == 403 or r.status_code == 404:
- log.error('HTTP {}.'.format(r.status_code))
- return None
- log.debug('HTTP {}.'.format(r.status_code))
- return r
-
- def get_json(self, url, params = None):
- try:
- r = self.get(url, params)
- if not r:
- return None
- return json.loads(r.content)
- except json.JSONDecodeError as e:
- log.error(repr(e))
- return None
-
- def get_obj_from_unique_id(self, unique_id):
- return None
-
- def search(self, query, *extra_args):
- return None
diff --git a/src/shoki/py/providers/__init__.py b/src/shoki/py/providers/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/src/shoki/py/providers/__init__.py
+++ /dev/null
diff --git a/src/shoki/py/providers/fanbox.py b/src/shoki/py/providers/fanbox.py
deleted file mode 100644
index 7739be3..0000000
--- a/src/shoki/py/providers/fanbox.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import urllib.parse
-import log
-from plugin import Plugin
-from search import Search
-from post import Post, Image
-
-BASE_URL = "https://api.fanbox.cc/"
-
-PAGES_PER_QUERY = 2
-
-class FanboxSearch(Search):
- def __init__(self, plugin, creator_id):
- super().__init__()
- self.p = plugin
- self.creator_id = creator_id
- self.pagination_pages = []
- self.pagination_complete = False
-
- def paginate(self):
- res = self.p.get_json(BASE_URL + 'post.paginateCreator', { 'creatorId': self.creator_id })
- if not res or 'body' not in res or len(res['body']) == 0:
- return False
- for p in res['body']:
- params = urllib.parse.parse_qs(urllib.parse.urlparse(p).query)
- page = {
- 'url': p,
- 'params': params,
- 'data': None,
- 'limit': -1 if 'limit' not in params else int(params['limit'][0])
- }
- self.pagination_pages.append(page)
- return True
-
- def load_pagination_page(self, p):
- if not p['data']:
- res = self.p.get_json(BASE_URL + 'post.listCreator', p['params'])
- if not res:
- return False
- p['data'] = res['body']['items']
- # The get_post_at_index logic assumes limit will always be exact.
- p['limit'] = len(p['data'])
- return True
-
- def get_post_at_index(self, index):
- if not self.pagination_complete:
- if not self.paginate():
- return None
- self.pagination_complete = True
- page = None
- count = 0
- for p in self.pagination_pages:
- if p['limit'] == -1:
- if not self.load_pagination_page(p):
- return None
- ncount = count + p['limit']
- if ncount > index:
- page = p
- break
- count = ncount
- if not page or not self.load_pagination_page(page) or count + page['limit'] <= index:
- return None
- return page['data'][index - count]
-
- def add_images_to_map(self, body):
- if 'imageMap' not in body:
- return
- for k in body['imageMap'].keys():
- self.p.image_map[k] = body['imageMap'][k]['originalUrl']
-
- def load_page(self, index):
- self.pages[index] = []
- s = index * PAGES_PER_QUERY
- for i in range(s, s + PAGES_PER_QUERY):
- p = self.get_post_at_index(i)
- # Break to return what we have, which could be less than expected.
- if not p:
- self.completed = True
- break
- unique_id = 'fanbox:{}'.format(p['id'])
- if unique_id not in self.p.unique_id_map:
- res = self.p.get_json(
- BASE_URL + 'post.info', { 'postId': p['id'] }
- )
- if not res or 'body' not in res:
- # TODO: Log this.
- continue
- if res['body']['type'] == 'article':
- self.add_images_to_map(res['body']['body'])
- self.p.unique_id_map[unique_id] = res['body']
- # TODO: Should store Post object here instead.
- body = self.p.unique_id_map[unique_id]
- text = ''
- media = []
- # If body is None, that post is restricted (not subbed or from a higher tier than your sub)
- if body['body']:
- if body['type'] == 'image':
- text = body['body']['text']
- for i in body['body']['images']:
- media.append(Image(url=i['originalUrl'], thumbnail_url=''))
- elif body['type'] == 'article':
- for b in body['body']['blocks']:
- if b['type'] == 'p':
- text += b['text'] + '\n'
- elif b['type'] == 'image':
- media.append(Image(url=self.p.image_map[b['imageId']], thumbnail_url=''))
- else:
- log.warn('Unhandled block type.')
- else:
- log.warn('Unhandled post type.')
- # TODO: Raw response
- self.pages[index].append(Post(
- unique_id=unique_id, url='', title=body['title'], text=text, media=media))
- return True
-
-class FanboxPlugin(Plugin):
- def __init__(self, sessid):
- super().__init__()
- self.image_map = {}
- self.headers.update({
- 'accept': 'application/json, text/plain, */*',
- 'accept-encoding': 'gzip, deflate, br',
- 'accept-language': 'ja,en-US;q=0.9,en;q=0.8',
- 'origin': 'https://www.fanbox.cc',
- 'referer': 'https://www.fanbox.cc/',
- 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36'
- })
- self.cookies['FANBOXSESSID'] = sessid
-
- def search(self, query, *extra_args):
- return FanboxSearch(self, query)
diff --git a/src/shoki/py/providers/instagram.py b/src/shoki/py/providers/instagram.py
deleted file mode 100644
index 0146ba3..0000000
--- a/src/shoki/py/providers/instagram.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import os
-import config
-import log
-from plugin import Plugin
-from search import Search
-from instagrapi import Client
-from instagrapi.exceptions import UserNotFound, LoginRequired, ChallengeRequired
-from post import User, Post, Image, Video
-
-REACH_LIMIT = 3
-
-class InstagramSearch(Search):
- def __init__(self, cl, pk):
- super().__init__()
- self.cl = cl
- self.pk = pk
- self.pagination_page = 0
- self.pagination_cursor = None
-
- def load_page(self, index):
- if index >= self.pagination_page + REACH_LIMIT:
- return False
- request_satisfied = False
- for _ in range(self.pagination_page, index + 1):
- if self.pagination_page > 0 and not self.pagination_cursor:
- self.completed = True
- return False
- try:
- media, self.pagination_cursor = self.cl.user_medias_paginated_v1(
- self.pk, 0, end_cursor=self.pagination_cursor)
- except LoginRequired:
- return False
- except ChallengeRequired:
- return False
- self.pages[self.pagination_page] = []
- for m in media:
- unique_id = 'instagram:p:{}'.format(m.id)
- media = []
- if m.media_type == 8: # Album
- for r in m.resources:
- if r.media_type == 1: # Photo
- media.append(Image(url=r.thumbnail_url))
- elif r.media_type == 2: # Video
- media.append(Video(url=r.video_url, thumbnail_url=r.thumbnail_url))
- elif m.media_type == 2: # Video
- #print(m.product_type) # feed, igtv, or clips
- media.append(Video(url=m.video_url, thumbnail_url=m.thumbnail_url))
- elif m.media_type == 1: # Photo
- media.append(Image(url=m.thumbnail_url))
- kwargs = {}
- kwargs['unique_id'] = unique_id
- kwargs['raw_responses'] = { 'tile': m.json() }
- kwargs['url'] = 'https://www.instagram.com/p/{}'.format(m.code)
- user = User(unique_id='instagram:u:{}'.format(m.user.pk))
- if m.user.username:
- user.username = m.user.username
- if m.user.full_name:
- user.display_name = m.user.full_name
- kwargs['author'] = user
- kwargs['title'] = m.title
- kwargs['text'] = m.caption_text
- kwargs['media'] = media
- self.pages[self.pagination_page].append(Post(**kwargs))
- if self.pagination_page == index:
- request_satisfied = True
- self.pagination_page += 1
- return request_satisfied
-
-class InstagramPlugin(Plugin):
- def __init__(self):
- super().__init__()
- self.cl = Client()
- self.cl.set_user_agent(config.INSTAGRAM_USER_AGENT)
-
- def init(self):
- if os.path.exists(config.INSTAGRAM_SETTINGS_PATH):
- self.cl.load_settings(config.INSTAGRAM_SETTINGS_PATH)
- if self.cl.login(config.INSTAGRAM_USERNAME, config.INSTAGRAM_PASSWORD):
- return True
- if not self.cl.login(config.INSTAGRAM_USERNAME, config.INSTAGRAM_PASSWORD):
- return False
- self.cl.dump_settings(config.INSTAGRAM_SETTINGS_PATH)
- return True
-
- def search(self, query, *extra_args):
- try:
- user = self.cl.user_info_by_username_v1(query)
- except UserNotFound:
- return None
- except LoginRequired:
- log.error('Login required.')
- return None
- except ChallengeRequired:
- log.error('Challenge required.')
- return None
- return InstagramSearch(self.cl, user.pk)
diff --git a/src/shoki/py/providers/pixiv.py b/src/shoki/py/providers/pixiv.py
deleted file mode 100644
index 9276d61..0000000
--- a/src/shoki/py/providers/pixiv.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import json
-import requests
-from post import User, Image, Post
-from search import Search
-from plugin import Plugin
-from query_parser import QueryParser
-from pixivpy3 import *
-
-class PixivBase(Search):
- def __init__(self, userdata, arg):
- super().__init__()
- self.plugin = userdata
- self.next_page = 0
-
- def api_load_page(self, index):
- return False, None
-
- def load_page(self, index):
- request_satisfied = False
- for i in range(self.next_page, index + 1):
- if not self.next_qs:
- break
- self.pages[i] = []
- res, self.next_qs = self.api_load_page(i)
- if not res:
- break
- if i == index:
- request_satisfied = True
- self.next_page += 1
- return request_satisfied
-
- def make_post(self, post):
- if len(post['meta_pages']) == 0:
- post['meta_pages'].append({
- 'image_urls': {
- "medium": post['image_urls']['medium'],
- "original": post['meta_single_page']['original_image_url']
- }
- })
- kwargs = {}
- kwargs['unique_id'] = 'pixiv:i:{}'.format(post['id'])
- kwargs['raw_responses'] = { 'api': json.dumps(post) }
- kwargs['url'] = 'https://www.pixiv.net/en/artworks/{}'.format(post['id'])
- kwargs['author'] = User(
- unique_id='pixiv:u:{}'.format(post['user']['id']),
- username=post['user']['account'], display_name=post['user']['name'])
- kwargs['title'] = post['title']
- kwargs['text'] = post['caption']
- kwargs['likes'] = post['total_bookmarks']
- if 'total_comments' in post:
- kwargs['comments'] = post['total_comments']
- media = []
- for img in post['meta_pages']:
- media.append(Image(
- url=img['image_urls']['original'], thumbnail_url=img['image_urls']['medium']))
- kwargs['media'] = media
- return Post(**kwargs)
-
-class PixivFollowing(PixivBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
- self.next_qs = { "user_id": arg, "restrict": "public" }
-
- def api_load_page(self, index):
- obj = self.plugin.api.user_following(**self.next_qs)
- if 'user_previews' not in obj:
- return False, None
- for u in obj['user_previews']:
- user = u['user']
- self.pages[index].append(User(
- unique_id='pixiv:u:{}'.format(user['id']), username=user['account'],
- display_name=user['name']))
- return True, self.plugin.api.parse_qs(obj['next_url'])
-
-class PixivUser(PixivBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
- self.next_qs = { 'user_id': arg, 'type': 'illust' }
-
- def api_load_page(self, index):
- obj = self.plugin.api.user_illusts(**self.next_qs)
- if 'illusts' not in obj:
- return False, None
- for post in obj['illusts']:
- self.pages[index].append(self.make_post(post))
- return True, self.plugin.api.parse_qs(obj['next_url'])
-
-class PixivBookmarks(PixivBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
- self.next_qs = { 'user_id': arg, 'restrict': 'public' }
-
- def api_load_page(self, index):
- obj = self.plugin.api.user_bookmarks_illust(**self.next_qs)
- if 'illusts' not in obj:
- return False, None
- for post in obj['illusts']:
- self.pages[index].append(self.make_post(post))
- return True, self.plugin.api.parse_qs(obj['next_url'])
-
-class PixivPlugin(Plugin):
- def __init__(self, refresh_token):
- super().__init__()
- self.parser = QueryParser(self, 'following')
- self.parser.add_command('following', PixivFollowing)
- self.parser.add_command('user', PixivUser)
- self.parser.add_command('bookmarks', PixivBookmarks)
- self.api = AppPixivAPI()
- self.api.requests = requests.Session()
- self.api.auth(refresh_token=refresh_token)
-
- def search(self, query, *extra_args):
- return self.parser.parse_query(query)
diff --git a/src/shoki/py/providers/searx.py b/src/shoki/py/providers/searx.py
deleted file mode 100644
index b536f28..0000000
--- a/src/shoki/py/providers/searx.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import sys
-sys.path.append('../../../subprojects/searxng')
-import json
-from searx import settings
-from searx.utils import gen_useragent
-from searx.engines import load_engine, register_engine
-from plugin import Plugin
-from search import Search
-from post import Post, Image
-
-# TODO: Put these on the Plugin object.
-ENGINES = {}
-
-ENGINE_INITIAL_INDEX = {
- 'google images': 0,
- 'google': 1,
- 'bing images': 0
-}
-
-class SearxSearch(Search):
- def __init__(self, plugin, query, engine):
- super().__init__()
- global ENGINES
- self.query = query
- self.plugin = plugin
- self.key = engine.replace(' ', '_')
- self.engine = ENGINES[engine]
- self.index = ENGINE_INITIAL_INDEX[engine]
-
- def load_page(self, index):
- self.pages[index] = []
- res = self.plugin.send_http_request(self.engine.request(self.query, {
- 'language': 'en-US',
- 'safesearch': 0,
- 'time_range': None,
- 'pageno': self.index + index,
- 'cookies': self.plugin.cookies,
- 'headers': self.plugin.headers,
- 'data': None
- }))
- if not res:
- return False
- try:
- ret = self.engine.response(res)
- except:
- return False
- if len(ret) == 0:
- # TODO: Test.
- self.completed = True
- return False
- for i, p in enumerate(ret):
- if 'url' not in p:
- continue
- # Don't add this to the global map because it's not actually unique.
- unique_id = '{}:{}_{}:{}'.format(self.key, self.query, i, index)
- media = []
- if 'img_src' in p:
- media.append(Image(url=p['img_src'], thumbnail_url=''))
- elif 'image_url' in p:
- media.append(Image(url=p['image_url'], thumbnail_url=''))
- kwargs = {}
- kwargs['unique_id'] = unique_id
- kwargs['raw_responses'] = { 'result': json.dumps(p) }
- kwargs['url'] = p['url']
- kwargs['title'] = p['title']
- kwargs['text'] = p['content']
- kwargs['media'] = media
- self.pages[index].append(Post(**kwargs))
- return True
-
-class SearxPlugin(Plugin):
- def __init__(self):
- super().__init__()
- global ENGINES
- self.headers = {
- 'User-Agent': gen_useragent(),
- 'Accept-Language': 'en-US,en;q=0.5',
- }
- for engine_data in settings['engines']:
- engine = load_engine(engine_data)
- if engine:
- register_engine(engine)
- ENGINES[engine.name] = engine
-
- def send_http_request(self, params):
- self.cookies.update(params['cookies'])
- self.headers.update(params['headers'])
- return self.get(params['url'], params=params['data'])
-
- def search(self, query, *extra_args):
- if not extra_args:
- return None
- return SearxSearch(self, query, extra_args[0])
diff --git a/src/shoki/py/providers/twitter.py b/src/shoki/py/providers/twitter.py
deleted file mode 100644
index 4770dda..0000000
--- a/src/shoki/py/providers/twitter.py
+++ /dev/null
@@ -1,424 +0,0 @@
-import json
-import config
-import http.cookiejar
-from datetime import timezone
-
-import log
-from provider import Provider
-from search import Search
-from post import PostType, DateType, PostRef, Post, User, Image, Video
-from query_parser import QueryParser
-
-# Official Twitter API
-#from twitter import Twitter2, TwitterError, OAuth
-
-# GraphQL API
-import snscrape.modules.twitter
-from snscrape.base import ScraperException
-from snscrape.modules.twitter import (TwitterSearchScraper, TwitterProfileScraper,
- TwitterUserScraper, TwitterTweetScraper, TweetRef)
-
-# Don't attempt to load a page 4 or more pages past the current page.
-REACH_LIMIT = 4
-
-# Quite incomplete API based backend. Should *not* be used for archiving.
-# HOLD: I can't test this without paying $100 for the X API WTFFF.
-'''
-class TwitterBase(Search):
- ALL_PARAMS = {
- 'tweet.fields': 'attachments,author_id,context_annotations,conversation_id,created_at,entities,geo,id,in_reply_to_user_id,lang,public_metrics,possibly_sensitive,referenced_tweets,reply_settings,source,text,withheld',
- 'user.fields': 'created_at,description,entities,id,location,name,pinned_tweet_id,profile_image_url,protected,public_metrics,url,username,verified,withheld',
- 'media.fields': 'duration_ms,height,media_key,preview_image_url,type,url,width,public_metrics',
- 'place.fields': 'contained_within,country,country_code,full_name,geo,id,name,place_type',
- 'poll.fields': 'duration_minutes,end_datetime,id,options,voting_status',
- }
- ALL_EXPRESSIONS = 'author_id,referenced_tweets.id,referenced_tweets.id.author_id,entities.mentions.username,attachments.poll_ids,attachments.media_keys,in_reply_to_user_id,geo.place_id'
-
- SOME_PARAMS = {
- 'tweet.fields': 'attachments,author_id,text,entities,referenced_tweets',
- 'user.fields': 'id,name,profile_image_url,url,username',
- 'media.fields': 'duration_ms,height,media_key,preview_image_url,type,url,width,public_metrics',
- 'place.fields': '',
- 'poll.fields': '',
- }
- SOME_EXPRESSIONS = 'author_id,referenced_tweets.id,referenced_tweets.id.author_id,entities.mentions.username,attachments.media_keys,in_reply_to_user_id'
-
- def __init__(self, userdata, arg):
- super().__init__()
- self.provider = userdata
- self.params = self.SOME_PARAMS.copy()
- self.media_map = {}
- self.user_id = None
- self.pagination_page = 0
- self.pagination_token = None
- self.arg = arg
-
- def add_attachemnts(self, l, r, data):
- if 'media_keys' not in data:
- return
- for m in data['media_keys']:
- if m in r:
- continue
- r.append(m)
- if m in self.media_map:
- l.append(Image(url=self.media_map[m], thumbnail_url=''))
-
- def add_entity_urls(self, data):
- if 'urls' in data:
- for u in data['urls']:
- if 'media_key' in u and u['media_key'] not in self.media_map:
- self.media_map[u['media_key']] = u['expanded_url']
-
- def make_post(self, tweet):
- print(json.dumps(tweet, indent=4))
- media = []
- repeats = []
- if 'referenced_tweets' in t:
- print(len(referenced_tweets))
- #for rt in t['referenced_tweets']:
- # if rt['id'] in self.tweet_map:
- # rrt = self.tweet_map[rt['id']]
- # if 'entities' in rrt:
- # self.add_entity_urls(rrt['entities'])
- # if 'attachments' in rrt:
- # self.add_attachemnts(media, repeats, rrt['attachments'])
- if 'attachments' in t:
- self.add_attachemnts(media, repeats, t['attachments'])
- unique_id = 'twitter:t:{}'.format(t['id'])
- url = 'https://twitter.com/{}/status/{}'.format(t['author_id'], t['id'])
- kwargs = {}
- kwargs['unique_id'] = unique_id
- kwargs['raw_responses'] = { 'tweet' : json.dumps(t) }
- kwargs['url'] = url
- kwargs['author'] = User(unique_id='twitter:u:{}'.format(t['author_id']))
- kwargs['title'] = ''
- kwargs['text'] = t['text']
- kwargs['media'] = media
- return Post(**kwargs)
-
- def add_items_from_search(self, data):
- if 'includes' in data:
- includes = data['includes']
- if 'media' in includes:
- for i in includes['media']:
- if 'url' in i:
- self.media_map[i['media_key']] = i['url']
- #if 'tweets' in includes:
- # for t in includes['tweets']:
- # self.tweet_map[t['id']] = t
- if 'data' not in data or not data['data']:
- return False
- l = data['data'] if type(data['data']) == list else [data['data']]
- for t in l:
- if 'entities' in t:
- self.add_entity_urls(t['entities'])
- post = self.make_post(t)
- self.pages[self.pagination_page].append(post)
- return True
-
- def add_user_ids_from_search(self, data):
- if not data['data']:
- return False
- for u in data['data']:
- self.pages[self.pagination_page].append(User(
- 'twitter:u:{}'.format(u['id']), username=u['username'], display_name=u['name']))
- return True
-
- def should_process_index(self, index):
- if index - self.pagination_page >= REACH_LIMIT:
- return False
- if self.pagination_page > 0 and not self.pagination_token:
- self.completed = True
- return False
- if self.pagination_token:
- self.params['pagination_token'] = self.pagination_token
- self.pages[self.pagination_page] = []
- return True
-
- def handle_data(self, data, user_ids=False):
- if 'next_token' not in data['meta'].keys():
- self.pagination_token = None
- else:
- self.pagination_token = data['meta']['next_token']
- if user_ids:
- if not self.add_user_ids_from_search(data):
- return False
- else:
- if not self.add_items_from_search(data):
- return False
- self.pagination_page += 1
- return True
-
- def get_user_id(self, username):
- if self.user_id:
- return True
- try:
- data = self.provider.t.users.by.username._username(
- _username=username, _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- self.user_id = data['data']['id']
- return True
-
-class TwitterSearch(TwitterBase):
- def load_page(self, index):
- for _ in range(self.pagination_page, index + 1):
- if not self.should_process_index(index):
- return False
- try:
- data = self.provider.t.tweets.search.recent(
- query=self.arg, expansions=self.SOME_EXPRESSIONS, params=self.params,
- sort_order='relevancy', max_results=25, _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- return self.handle_data(data)
-
-class TwitterUser(TwitterBase):
- def __init__(self, userdata, arg):
- if arg.startswith('@'):
- arg = arg[1:]
- super().__init__(userdata, arg)
-
- def load_page(self, index):
- for _ in range(self.pagination_page, index + 1):
- if not self.should_process_index(index):
- return False
- if not self.get_user_id(self.arg):
- return False
- try:
- data = self.provider.t.users._id.tweets(
- _id=self.user_id, expansions=self.SOME_EXPRESSIONS, params=self.params,
- max_results=25, _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- return self.handle_data(data)
-
-class TwitterTimeline(TwitterBase):
- def __init__(self, userdata, arg):
- if not arg:
- arg = 'pizzabelly'
- super().__init__(userdata, arg)
- self.params['exclude'] = 'replies'
-
- def load_page(self, index):
- for _ in range(self.pagination_page, index + 1):
- if not self.should_process_index(index):
- return False
- if not self.get_user_id(self.arg):
- return False
- try:
- data = self.provider.t.users._id.timelines.reverse_chronological(
- _id=self.user_id, expansions=self.SOME_EXPRESSIONS, params=self.params,
- max_results=25, _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- return self.handle_data(data)
-
-class TwitterLikes(TwitterBase):
- def __init__(self, userdata, arg):
- if arg.startswith('@'):
- arg = arg[1:]
- super().__init__(userdata, arg)
-
- def load_page(self, index):
- for _ in range(self.pagination_page, index + 1):
- if not self.should_process_index(index):
- return False
- if not self.get_user_id(self.arg):
- return False
- try:
- data = self.provider.t.users._id.liked_tweets(
- _id=self.user_id, expansions=self.SOME_EXPRESSIONS, params=self.params,
- max_results=25, _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- return self.handle_data(data)
-
-class TwitterTweet(TwitterBase):
- def load_page(self, index):
- for _ in range(self.pagination_page, index + 1):
- if not self.should_process_index(index):
- return False
- try:
- data = self.provider.t.tweets(
- ids=self.arg, expansions=self.SOME_EXPRESSIONS, params=self.params,
- _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- return self.handle_data(data)
-
-class TwitterFollowing(TwitterBase):
- def __init__(self, userdata, arg):
- if arg.startswith('@'):
- arg = arg[1:]
- super().__init__(userdata, arg)
- del self.params['media.fields']
- del self.params['place.fields']
- del self.params['poll.fields']
-
- def load_page(self, index):
- for _ in range(self.pagination_page, index + 1):
- if not self.should_process_index(index):
- return False
- if not self.get_user_id(self.arg):
- return False
- try:
- data = self.provider.t.users._id.following(
- _id=self.user_id, params=self.params, max_results=25, _timeout=config.TWITTER_TIMEOUT)
- except TwitterError as e:
- log.error(repr(e))
- return False
- return self.handle_data(data, True)
-'''
-
-class TwitterScrapeBase(Search):
- # Posts per emulated page because snscrape returns an iterator.
- POSTS_PER_PAGE = 8
-
- def __init__(self, userdata, arg):
- super().__init__()
- self.provider = userdata
- self.cookies = self.provider.cookies
- self.arg = arg
- self.iterator = None
- self.iter_page = 0
- self.sent_hashes = []
-
- def parse_media(self, l):
- media = []
- for m in l:
- if isinstance(m, snscrape.modules.twitter.Photo):
- media.append(Image(url=m.fullUrl, thumbnail_url=m.previewUrl))
- elif isinstance(m, snscrape.modules.twitter.Video):
- videos = sorted(m.variants, key=lambda x: x.bitrate if x.bitrate else 0, reverse=True)
- media.append(Video(url=videos[0].url, thumbnail_url=m.thumbnailUrl))
- elif isinstance(m, snscrape.modules.twitter.Gif):
- gifs = sorted(m.variants, key=lambda x: x.bitrate if x.bitrate else 0, reverse=True)
- media.append(Video(url=gifs[0].url, thumbnail_url=m.thumbnailUrl))
- return media
-
- def make_post(self, tweet):
- kwargs = {}
- kwargs['unique_id'] = 'twitter:t:{}'.format(tweet.id)
- if isinstance(tweet, TweetRef):
- return Post(**kwargs)
- kwargs['raw_responses'] = tweet.rawResponses
- h = tweet.rawResponses['hash']
- if h not in self.sent_hashes:
- self.sent_hashes.append(h)
- else:
- del kwargs['raw_responses']['api']
- kwargs['url'] = tweet.url
- kwargs['dates'] = {
- DateType.CREATED: tweet.date.replace(tzinfo=timezone.utc).timestamp()
- }
- author = User(unique_id='twitter:u:{}'.format(tweet.user.id), username='@' + tweet.user.username,
- profile_image_url=tweet.user.profileImageUrl)
- if tweet.user.displayname:
- author.display_name = tweet.user.displayname
- kwargs['author'] = author
- if tweet.retweetedTweet:
- kwargs['type'] = PostType.REPOST
- kwargs['post'] = PostRef(unique_id='twitter:t:{}'.format(tweet.retweetedTweet.id))
- return Post(**kwargs)
- kwargs['type'] = PostType.POST
- kwargs['text'] = tweet.rawContent
- kwargs['likes'] = tweet.likeCount
- kwargs['reposts'] = tweet.retweetCount
- kwargs['quotes'] = tweet.quoteCount
- kwargs['comments'] = tweet.replyCount
- kwargs['views'] = tweet.viewCount
- if tweet.media:
- kwargs['media'] = self.parse_media(tweet.media)
- if tweet.inReplyToTweetId:
- kwargs['in_reply_to'] = PostRef(unique_id='twitter:t:{}'.format(tweet.inReplyToTweetId))
- if tweet.quotedTweet:
- kwargs['quoted'] = PostRef(unique_id='twitter:t:{}'.format(tweet.quotedTweet.id))
- return Post(**kwargs)
-
- def add_post(self, i, post):
- self.pages[i]['posts'].append(post)
-
- def add_post_to_list(self, i, post):
- self.add_post(i, post)
- self.pages[i]['list'].append(post.unique_id)
-
- def load_page(self, index):
- if not self.iterator or index - self.iter_page >= REACH_LIMIT:
- return False
- if index in self.pages:
- return True
- # It's possible to load some "pages", but not enough to satisfy the requested index.
- request_satisfied = False
- for i in range(self.iter_page, index + 1):
- if self.completed:
- break
- self.pages[i] = { 'posts': [], 'list': [] }
- for _ in range(0, self.POSTS_PER_PAGE):
- try:
- tweet = next(self.iterator)
- except StopIteration:
- self.completed = True
- break
- except ScraperException as e:
- log.error(repr(e))
- continue
- self.add_post_to_list(i, self.make_post(tweet))
- if tweet.retweetedTweet:
- self.add_post(i, self.make_post(tweet.retweetedTweet))
- if tweet.quotedTweet:
- self.add_post(i, self.make_post(tweet.quotedTweet))
- if i == index:
- request_satisfied = True
- self.iter_page += 1
- return request_satisfied
-
-class TwitterScrapeSearch(TwitterScrapeBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
- self.iterator = TwitterSearchScraper(self.arg, top=True, cookies=self.cookies).get_items()
-
-class TwitterScrapeUser(TwitterScrapeBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
- if (self.arg.startswith('@')):
- self.arg = self.arg[1:]
- self.iterator = TwitterProfileScraper(self.arg, cookies=self.cookies).get_items()
- #self.iterator = TwitterUserScraper(self.arg, cookies=self.cookies).get_items()
-
-class TwitterScrapeTweet(TwitterScrapeBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
- self.single = True
- if (self.arg.startswith('https://')):
- self.arg = self.arg[self.arg.rfind('/') + 1:]
- question = self.arg.find('?')
- if question >= 0:
- self.arg = self.arg[:question]
- self.iterator = TwitterTweetScraper(self.arg, cookies=self.cookies).get_items()
-
-class TwitterProvider(Provider):
- def __init__(self, access_key, access_secret, consumer_key, consumer_secret, cookies_path):
- super().__init__()
- self.parser = QueryParser(self, 'search')
- self.parser.add_command('search', TwitterScrapeSearch)
- self.parser.add_command('user', TwitterScrapeUser)
- self.parser.add_command('tweet', TwitterScrapeTweet)
- #self.parser.add_command('timeline', TwitterTimeline)
- #self.parser.add_command('likes', TwitterLikes)
- #self.parser.add_command('following', TwitterFollowing)
- #self.t = Twitter2(auth=OAuth(access_key, access_secret, consumer_key, consumer_secret), retry=True)
- # Cookies are used by the scrape backend.
- cookie_jar = http.cookiejar.MozillaCookieJar()
- cookie_jar.load(filename=cookies_path, ignore_expires=True)
- for c in cookie_jar:
- self.cookies[c.name] = c.value
-
- def search(self, query, *extra_args):
- return self.parser.parse_query(query)
diff --git a/src/shoki/py/providers/youtube.py b/src/shoki/py/providers/youtube.py
deleted file mode 100644
index 3cbf0c8..0000000
--- a/src/shoki/py/providers/youtube.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import log
-from provider import Provider
-from search import Search
-from post import Post, PostType, Image, Video
-from query_parser import QueryParser
-from yt_dlp import YoutubeDL
-
-class YDLLogger():
- def debug(self, msg):
- if msg.startswith('[debug] '):
- log.debug(msg)
- else:
- log.info(msg)
-
- def info(self, msg):
- pass
-
- def warning(self, msg):
- log.warning(msg)
-
- def error(self, msg):
- log.error(msg)
-
-ydl_opts = {
- 'quiet': False,
- 'logger': YDLLogger(),
- 'cachedir': False,
-# 'cookiefile': '',
-}
-
-ydl = YoutubeDL(ydl_opts)
-
-def get_playback_url(data, video=True):
- if 'entries' in data:
- if len(data['entries']) == 0:
- return None
- data = data['entries'][0]
-
- if 'formats' not in data:
- if 'url' in data:
- return data['url']
- return None
-
- # Filter out hls temporarily.
- data['formats'] = list(filter(lambda f: not f['protocol'].startswith('m3u8'), data['formats']))
-
- if len(data['formats']) == 0:
- return None
-
- url = data['formats'][0]['url']
-
- # audio_ext?
- has_audio = list(filter(lambda f: 'acodec' not in f or f['acodec'] != 'none', data['formats']))
-
- if video:
- if len(has_audio) > 0:
- data['formats'] = has_audio
- try:
- data['formats'] = list(filter(lambda f: 'quality' in f, data['formats']))
- url = max(data['formats'], key=lambda f: f['quality'])['url']
- except:
- pass
- else:
- if len(has_audio) == 0:
- return None
- data['formats'] = has_audio
- try:
- audio_only = list(filter(lambda f: f['vcodec'] == 'none', data['formats']))
- if len(audio_only) > 0:
- data['formats'] = audio_only
- else:
- data['formats'] = list(filter(lambda f: f['ext'] in ['mp4'], data['formats']))
- except:
- pass
- try:
- data['formats'] = list(filter(lambda f: 'abr' in f, data['formats']))
- url = max(data['formats'], key=lambda f: f['abr'])['url']
- except:
- pass
-
- return url
-
-class YoutubeBase(Search):
- def __init__(self, userdata, arg):
- super().__init__()
- self.provider = userdata
- self.query = arg
-
- def load_page(self, index):
- self.pages[index] = { 'posts': [], 'list': [] }
- res = self.get_info()
- if res:
- url = get_playback_url(res)
- if not url:
- return False
- media = [Video(url=url)]
- self.pages[index]['posts'].append(Post(type=PostType.POST, unique_id='', url='', title='', text='', media=media))
- return True
-
-class YoutubeSearch(YoutubeBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
-
- def get_info(self):
- return ydl.extract_info('ytsearch1:{}'.format(self.query), download=False)
-
-class YoutubeLink(YoutubeBase):
- def __init__(self, userdata, arg):
- super().__init__(userdata, arg)
-
- def get_info(self):
- return ydl.extract_info(self.query, download=False)
-
-class YoutubeProvider(Provider):
- def __init__(self):
- super().__init__()
- self.parser = QueryParser(self, 'search')
- self.parser.add_command('search', YoutubeSearch)
- self.parser.add_command('link', YoutubeLink)
-
- def search(self, query, *extra_args):
- return self.parser.parse_query(query)
diff --git a/src/shoki/py/query_parser.py b/src/shoki/py/query_parser.py
deleted file mode 100644
index ee23bef..0000000
--- a/src/shoki/py/query_parser.py
+++ /dev/null
@@ -1,39 +0,0 @@
-class QueryParser():
- def __init__(self, userdata, default_command):
- self.commands = {}
- self.escaped_commands = {}
- self.userdata = userdata
- self.default_command = default_command
-
- def add_command(self, name, search):
- self.commands[name] = search
- self.escaped_commands['\\' + name] = name
-
- def parse_query(self, query):
- arg = query
- search = self.commands[self.default_command]
-
- sp = query.split(' ')
-
- for i, word in enumerate(sp):
- col = word.find(':')
- if col == -1:
- continue
- command = word[0:col]
- if command in self.escaped_commands:
- query = query.replace(command, self.escaped_commands[command], 1)
- continue
- if command in self.commands:
- if col + 1 < len(word):
- arg = word[col+1:]
- else:
- arg = None
- search = self.commands[command]
- if i == 0:
- if len(sp) > 1:
- query = query.replace(word + ' ', '', 1)
- else:
- query = query.replace(word, '', 1)
- else:
- query = query.replace(' ' + word + ' ', '', 1)
- return search(self.userdata, arg)
diff --git a/src/shoki/py/search.py b/src/shoki/py/search.py
deleted file mode 100644
index 4134851..0000000
--- a/src/shoki/py/search.py
+++ /dev/null
@@ -1,35 +0,0 @@
-class Search():
- def __init__(self):
- self.pages = {}
- self.iter_page = 0
- self.iter_index = 0
- self.single = False
- self.completed = False
-
- def __iter__(self):
- self.iter_page = 0
- self.iter_index = 0
- return self
-
- def __next__(self):
- if self.iter_index >= len(self.pages[self.iter_page]):
- self.iter_page += 1
- self.iter_index = 0
- if self.iter_page not in self.pages:
- if not self.load_page(self.iter_page):
- raise StopIteration
- return self.pages[self.iter_page][self.iter_index]
-
- def load_page(self, index):
- return False
-
- def get_page(self, index):
- if index not in self.pages:
- if self.completed:
- if self.single:
- return self.pages[0]
- else:
- return None
- if not self.load_page(index):
- return None
- return self.pages[index]
diff --git a/src/shoki/src/packet_ext.c b/src/shoki/src/packet_ext.c
deleted file mode 100644
index 26ba9ee..0000000
--- a/src/shoki/src/packet_ext.c
+++ /dev/null
@@ -1,91 +0,0 @@
-#include "search.h"
-#include "packet_ext.h"
-
-static void aki_packet_write_sho_optional_int(struct aki_packet *packet, sho_optional_int *o)
-{
- AKI_PACKET_WRITE_TYPE(packet, s64, o->i);
- AKI_PACKET_WRITE_TYPE(packet, bool, o->set);
-}
-
-void aki_packet_write_sho_post(struct aki_packet *packet, struct sho_post *post)
-{
- AKI_PACKET_WRITE_TYPE(packet, u16, post->version);
- AKI_PACKET_WRITE_TYPE(packet, u8, post->type);
- aki_packet_write_string(packet, &post->unique_id);
- aki_packet_write_string(packet, &post->url);
- AKI_PACKET_WRITE_TYPE(packet, u32, post->dates.size);
- struct sho_post_date *date;
- al_array_foreach_ptr(post->dates, i, date) {
- AKI_PACKET_WRITE_TYPE(packet, u8, date->type);
- AKI_PACKET_WRITE_TYPE(packet, s64, date->timestamp);
- }
- aki_packet_write_string(packet, &post->author.unique_id);
- aki_packet_write_string(packet, &post->author.username);
- aki_packet_write_string(packet, &post->author.display_name);
- aki_packet_write_string(packet, &post->author.profile_image_url);
- aki_packet_write_string(packet, &post->title);
- aki_packet_write_string(packet, &post->text);
- aki_packet_write_sho_optional_int(packet, &post->likes);
- aki_packet_write_sho_optional_int(packet, &post->reposts);
- aki_packet_write_sho_optional_int(packet, &post->quotes);
- aki_packet_write_sho_optional_int(packet, &post->comments);
- aki_packet_write_sho_optional_int(packet, &post->views);
- AKI_PACKET_WRITE_TYPE(packet, u32, post->media.size);
- struct sho_post_media *media;
- al_array_foreach_ptr(post->media, i, media) {
- AKI_PACKET_WRITE_TYPE(packet, u8, media->type);
- aki_packet_write_string(packet, &media->url);
- aki_packet_write_string(packet, &media->thumbnail_url);
- }
- aki_packet_write_string(packet, &post->post.unique_id);
- aki_packet_write_string(packet, &post->quoted.unique_id);
- aki_packet_write_string(packet, &post->in_reply_to.unique_id);
-}
-
-static void aki_packet_read_sho_optional_int(struct aki_packet *packet, sho_optional_int *o)
-{
- AKI_PACKET_READ_TYPE(packet, s64, o->i);
- AKI_PACKET_READ_TYPE(packet, bool, o->set);
-}
-
-void aki_packet_read_sho_post(struct aki_packet *packet, struct sho_post *post)
-{
- sho_post_reset(post);
- AKI_PACKET_READ_TYPE(packet, u16, post->version);
- AKI_PACKET_READ_TYPE(packet, u8, post->type);
- aki_packet_read_string(packet, &post->unique_id);
- aki_packet_read_string(packet, &post->url);
- u32 dates_size;
- AKI_PACKET_READ_TYPE(packet, u32, dates_size);
- al_array_reserve(post->dates, dates_size);
- for (u32 i = 0; i < dates_size; i++) {
- struct sho_post_date date;
- AKI_PACKET_READ_TYPE(packet, u8, date.type);
- AKI_PACKET_READ_TYPE(packet, s64, date.timestamp);
- al_array_push(post->dates, date);
- }
- aki_packet_read_string(packet, &post->author.unique_id);
- aki_packet_read_string(packet, &post->author.username);
- aki_packet_read_string(packet, &post->author.display_name);
- aki_packet_read_string(packet, &post->author.profile_image_url);
- aki_packet_read_string(packet, &post->title);
- aki_packet_read_string(packet, &post->text);
- aki_packet_read_sho_optional_int(packet, &post->likes);
- aki_packet_read_sho_optional_int(packet, &post->reposts);
- aki_packet_read_sho_optional_int(packet, &post->quotes);
- aki_packet_read_sho_optional_int(packet, &post->comments);
- aki_packet_read_sho_optional_int(packet, &post->views);
- u32 media_size;
- AKI_PACKET_READ_TYPE(packet, u32, media_size);
- al_array_reserve(post->media, media_size);
- for (u32 i = 0; i < media_size; i++) {
- struct sho_post_media media;
- AKI_PACKET_READ_TYPE(packet, u8, media.type);
- aki_packet_read_string(packet, &media.url);
- aki_packet_read_string(packet, &media.thumbnail_url);
- al_array_push(post->media, media);
- }
- aki_packet_read_string(packet, &post->post.unique_id);
- aki_packet_read_string(packet, &post->quoted.unique_id);
- aki_packet_read_string(packet, &post->in_reply_to.unique_id);
-}
diff --git a/src/shoki/src/packet_ext.h b/src/shoki/src/packet_ext.h
deleted file mode 100644
index ff0fda1..0000000
--- a/src/shoki/src/packet_ext.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-
-#include <aki/common.h>
-
-#include "post.h"
-
-void aki_packet_write_sho_post(struct aki_packet *packet, struct sho_post *post);
-void aki_packet_read_sho_post(struct aki_packet *packet, struct sho_post *post);
diff --git a/src/shoki/src/post.c b/src/shoki/src/post.c
deleted file mode 100644
index 22fb7ef..0000000
--- a/src/shoki/src/post.c
+++ /dev/null
@@ -1,55 +0,0 @@
-#include "post.h"
-
-void sho_post_reset(struct sho_post *post)
-{
- al_bzero(post, sizeof(struct sho_post));
- al_array_init(post->dates);
- al_array_init(post->media);
-}
-
-void sho_post_add_media(struct sho_post *post, u8 type, str *url, str *thumbnail_url)
-{
- al_array_push(post->media, ((struct sho_post_media){
- .type = type, .url = *url, .thumbnail_url = *thumbnail_url
- }));
-}
-
-void sho_post_add_date(struct sho_post *post, u8 type, s64 timestamp)
-{
- al_array_push(post->dates, ((struct sho_post_date){
- .type = type, .timestamp = timestamp
- }));
-}
-
-void sho_post_clone(struct sho_post *dest, struct sho_post *src)
-{
- sho_post_reset(dest);
- dest->version = src->version;
- dest->type = src->type;
- al_str_clone(&dest->unique_id, &src->unique_id);
- al_str_clone(&dest->url, &src->url);
- al_array_clone(dest->dates, src->dates);
- al_str_clone(&dest->author.unique_id, &src->author.unique_id);
- al_str_clone(&dest->author.username, &src->author.username);
- al_str_clone(&dest->author.display_name, &src->author.display_name);
- al_str_clone(&dest->author.profile_image_url, &src->author.profile_image_url);
- al_str_clone(&dest->title, &src->title);
- al_str_clone(&dest->text, &src->text);
- dest->likes = src->likes;
- dest->reposts = src->reposts;
- dest->quotes = src->quotes;
- dest->comments = src->comments;
- dest->views = src->views;
- struct sho_post_media *media;
- struct sho_post_media media_copy;
- al_array_foreach_ptr(src->media, i, media) {
- al_bzero(&media_copy, sizeof(struct sho_post_media));
- media_copy.type = media->type;
- al_str_clone(&media_copy.url, &media->url);
- al_str_clone(&media_copy.thumbnail_url, &media->thumbnail_url);
- al_array_push(dest->media, media_copy);
- }
- al_str_clone(&dest->post.unique_id, &src->post.unique_id);
- al_str_clone(&dest->quoted.unique_id, &src->quoted.unique_id);
- al_str_clone(&dest->in_reply_to.unique_id, &src->in_reply_to.unique_id);
-}
diff --git a/src/shoki/src/post.h b/src/shoki/src/post.h
deleted file mode 100644
index 6814b0f..0000000
--- a/src/shoki/src/post.h
+++ /dev/null
@@ -1,78 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <al/str.h>
-#include <al/array.h>
-
-enum {
- SHOKI_MEDIA_IMAGE = 0,
- SHOKI_MEDIA_VIDEO,
- SHOKI_MEDIA_AUDIO
-};
-
-enum {
- SHOKI_POST_POST = 0,
- SHOKI_POST_REPOST
-};
-
-enum {
- SHOKI_DATE_CREATED = 0,
- SHOKI_DATE_EDITED,
- SHOKI_DATE_ARCHIVED
-};
-
-typedef struct {
- s64 i;
- bool set;
-} sho_optional_int;
-
-struct sho_post_date {
- u8 type;
- s64 timestamp;
-};
-
-struct sho_post_media {
- u8 type;
- str url;
- str thumbnail_url;
-};
-
-typedef array(struct sho_post_date) sho_dates_array;
-typedef array(struct sho_post_media) sho_media_array;
-
-struct sho_post_user {
- str unique_id;
- str username;
- str display_name;
- str profile_image_url;
-};
-
-struct sho_post_ref {
- str unique_id;
-};
-
-struct sho_post {
- u16 version;
- u8 type;
- str unique_id;
- str raw_responses;
- str url;
- sho_dates_array dates;
- struct sho_post_user author;
- str title;
- str text;
- sho_optional_int likes;
- sho_optional_int reposts;
- sho_optional_int quotes;
- sho_optional_int comments;
- sho_optional_int views;
- sho_media_array media;
- struct sho_post_ref post; // reposted post.
- struct sho_post_ref quoted;
- struct sho_post_ref in_reply_to;
-};
-
-void sho_post_reset(struct sho_post *post);
-void sho_post_add_media(struct sho_post *post, u8 type, str *url, str *thumbnail_url);
-void sho_post_add_date(struct sho_post *post, u8 type, s64 timestamp);
-void sho_post_clone(struct sho_post *dest, struct sho_post *src);
diff --git a/src/shoki/src/post_cache.c b/src/shoki/src/post_cache.c
deleted file mode 100644
index a8f02eb..0000000
--- a/src/shoki/src/post_cache.c
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "post_cache.h"
-
-void sho_post_cache_init(struct sho_post_cache *cache)
-{
- al_array_init(cache->cache);
-}
-
-void sho_post_cache_push(struct sho_post_cache *cache, struct sho_post *post)
-{
- struct sho_post *check = sho_post_cache_get(cache, &post->unique_id);
- if (!check) {
- struct sho_post *npost = al_alloc_object(struct sho_post);
- sho_post_clone(npost, post);
- al_array_push(cache->cache, npost);
- }
-}
-
-struct sho_post *sho_post_cache_get(struct sho_post_cache *cache, str *unique_id)
-{
- struct sho_post *post;
- al_array_foreach(cache->cache, i, post) {
- if (al_str_eq(&post->unique_id, unique_id)) {
- return post;
- }
- if (al_str_eq(&post->author.unique_id, unique_id)) {
- return post;
- }
- }
- return NULL;
-}
diff --git a/src/shoki/src/post_cache.h b/src/shoki/src/post_cache.h
deleted file mode 100644
index 80cd0b7..0000000
--- a/src/shoki/src/post_cache.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include <al/array.h>
-
-#include "post.h"
-
-struct sho_post_cache {
- array(struct sho_post *) cache;
-};
-
-void sho_post_cache_init(struct sho_post_cache *cache);
-void sho_post_cache_push(struct sho_post_cache *cache, struct sho_post *post);
-struct sho_post *sho_post_cache_get(struct sho_post_cache *cache, str *unique_id);
diff --git a/src/shoki/src/search.c b/src/shoki/src/search.c
deleted file mode 100644
index b4b2157..0000000
--- a/src/shoki/src/search.c
+++ /dev/null
@@ -1,99 +0,0 @@
-#include <al/log.h>
-
-#include "../cpy/shoki.c"
-
-#include "search.h"
-
-bool sho_python_init(void)
-{
- PyPreConfig pre;
- PyPreConfig_InitPythonConfig(&pre);
- pre.utf8_mode = 1;
- Py_PreInitialize(&pre);
- if (PyImport_AppendInittab("shoki", PyInit_shoki) == -1) {
- al_log_error("shoki", "Could not extend in-built modules table.");
- sho_python_close();
- return false;
- }
- Py_Initialize();
- PyObject *module = PyImport_ImportModule("shoki");
- if (!module) {
- PyErr_Print();
- al_log_error("shoki", "Could not import module.");
- sho_python_close();
- return false;
- }
- return true;
-}
-
-void sho_result_init(struct sho_result *res)
-{
- al_array_init(res->pages);
-}
-
-static struct sho_result_page *page_at_index(struct sho_result *res, s32 num)
-{
- struct sho_result_page *page;
- al_array_foreach_ptr(res->pages, i, page) {
- if (page->num == num) {
- return page;
- }
- }
- return NULL;
-}
-
-void sho_result_add_post(struct sho_result *res, s32 num, struct sho_post *post)
-{
- struct sho_result_page *page = page_at_index(res, num);
- if (!page) {
- al_array_push(res->pages, (struct sho_result_page){0});
- page = &al_array_last(res->pages);
- page->num = num;
- al_array_init(page->posts);
- al_array_init(page->list);
- }
- al_array_push(page->posts, *post);
-}
-
-void sho_result_add_to_list(struct sho_result *res, s32 num, str *unique_id)
-{
- struct sho_result_page *page = page_at_index(res, num);
- al_array_push(page->list, *unique_id);
-}
-
-void sho_search_init(struct sho_search *search)
-{
- search->id = -1;
- search->page = 0;
- sho_result_init(&search->result);
-}
-
-s32 sho_search_more_results(struct sho_search *search, str *provider, str *query)
-{
- if (search->id < 0) {
- search->id = sho_client_search(provider, query);
- } else {
- search->page++;
- }
- if (search->id < 0) return -1;
- al_log_info("shoki", "Loading page %i (%.*s).", search->page, AL_STR_PRINTF(query));
- if (sho_client_get_page(&search->result, search->id, search->page) < 0) {
- return -1;
- }
- return search->page;
-}
-
-void sho_search_free(struct sho_search *search)
-{
- struct sho_result_page *page;
- al_array_foreach_ptr(search->result.pages, i, page) {
- al_array_free(page->posts);
- al_array_free(page->list);
- }
- al_array_free(search->result.pages);
-}
-
-void sho_python_close(void)
-{
- if (Py_IsInitialized()) Py_Finalize();
-}
diff --git a/src/shoki/src/search.h b/src/shoki/src/search.h
deleted file mode 100644
index 97e40ae..0000000
--- a/src/shoki/src/search.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#pragma once
-
-#include "post.h"
-
-struct sho_result_page {
- s32 num;
- array(struct sho_post) posts;
- array(str) list;
-};
-
-struct sho_result {
- array(struct sho_result_page) pages;
-};
-
-struct sho_search {
- s32 id;
- s32 page;
- struct sho_result result;
-};
-
-bool sho_python_init(void);
-void sho_python_close(void);
-
-void sho_result_init(struct sho_result *res);
-void sho_result_add_post(struct sho_result *res, s32 num, struct sho_post *post);
-void sho_result_add_to_list(struct sho_result *res, s32 num, str *unique_id);
-
-void sho_search_init(struct sho_search *search);
-s32 sho_search_more_results(struct sho_search *search, str *provider, str *query);
-void sho_search_free(struct sho_search *s);