Skip to content

Commit

Permalink
Publish typescript definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
isaackogan committed May 3, 2024
1 parent c4770d6 commit 0ac4752
Show file tree
Hide file tree
Showing 9 changed files with 2,708 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dev
test.py
.env
build
badges.py
1 change: 1 addition & 0 deletions TikTokLive/proto/custom_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import betterproto

from TikTokLive.proto import *
from TikTokLive.proto import User
from TikTokLive.proto.proto_utils import badge_match_user, SUBSCRIBER_BADGE_PATTERN, MODERATOR_BADGE_PATTERN, \
TOP_GIFTER_BADGE_PATTERN, MEMBER_LEVEL_BADGE_PATTERN, GIFTER_LEVEL_BADGE_PATTERN

Expand Down
9 changes: 9 additions & 0 deletions scripts/typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# TikTokLive TypeScript Declarations

A potential use-case for TikTokLive is to create a web server that forwards events from Python to your front-end TypeScript application.

In this case, type definitions for TikTokLive events are useful. After all, what's the point of a strictly typed language without types?

This module generates typedefs for TikTokLive to put into your project.

See the [package](package) folder for the NPM package `@TikTokLive/types`.
165 changes: 165 additions & 0 deletions scripts/typescript/compiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from typing import Type, List, cast, Union, get_type_hints, TypedDict, Optional, Dict

import betterproto
import jinja2

import TikTokLive.proto.custom_proto as custom_proto
import TikTokLive.proto.tiktok_proto as tiktok_proto
from TikTokLive.events import Event

EventsList: List[Type[Event]] = cast(Union, Event).__args__

InputClassMapping: Type = TypedDict(
'InputClassMapping',
{
"name": str,
"mappings": dict[str, Type],
"super": Optional[Type[betterproto.Message]]
}
)

InputEnumMapping: Type = TypedDict(
'InputEnumMapping',
{
"name": str,
"mappings": dict[str, int]
}
)

PrimitiveMappings: Dict[Type, str] = {
str: "string",
int: "number",
float: "number",
bool: "boolean",
bytes: "unknown"
}


def process_event_class(c: Type[Event]) -> InputClassMapping:
"""
Generate a type def of an event
:param c: The event class to check
:return: The type def
"""

base_message = None
for base in c.__bases__:
if issubclass(base, betterproto.Message):
base_message = base

bases_hints = get_type_hints(base_message) if base_message else {}
mappings: dict[str, Type] = {}

for member_name, member_type in get_type_hints(c).items():

if member_name.startswith("_"):
continue

if member_name in bases_hints and member_type == bases_hints[member_name]:
continue

mappings[member_name] = member_type

return {'mappings': mappings, 'super': base_message, 'name': c.__name__}


def process_proto_class(c: Type[betterproto.Message]) -> InputClassMapping:
mappings: dict[str, Type] = {}

# Look for instances of subclassing
base_message = None
for base in c.__bases__:
if base != betterproto.Message:
base_message = base

bases_hints = get_type_hints(base_message) if base_message else {}

for member_name, member_type in get_type_hints(c).items():

if member_name.startswith("_"):
continue

if member_name in bases_hints and member_type == bases_hints[member_name]:
continue

mappings[member_name] = member_type

return {'mappings': mappings, 'super': base_message, 'name': c.__name__}


def process_proto_enum(c: Type[betterproto.Enum]) -> InputEnumMapping:
return {
'mappings': {member.name: member.value for member in c},
'name': c.__name__
}


def module_classes(module) -> List[Type]:
md = module.__dict__
return [
md[c] for c in md if (
isinstance(md[c], type) and md[c].__module__ == module.__name__
)
]


def build_ts_defs(
enums: List[InputEnumMapping],
classes: List[InputClassMapping],
events: List[InputClassMapping],
primitives: Dict[Type, str]
) -> str:
env: jinja2.Environment = jinja2.Environment(
trim_blocks=True,
lstrip_blocks=True,
loader=jinja2.FileSystemLoader('./'),
)

template = jinja2.Template = env.get_template('ts_template.jinja2')

return template.render({
'enums': enums,
'classes': classes,
'events': events,
'primitives': primitives
})


if __name__ == '__main__':

all_proto: list[Type] = [
*module_classes(tiktok_proto),
*module_classes(custom_proto)
]

enum_defs: list[InputEnumMapping] = []
class_defs: list[InputClassMapping] = []

for class_meta in all_proto:

if issubclass(class_meta, betterproto.Enum):
enum_defs.append(
process_proto_enum(class_meta)
)

if issubclass(class_meta, betterproto.Message):
class_defs.append(
process_proto_class(class_meta)
)

event_defs: list[InputClassMapping] = []

for class_meta in EventsList:
event_defs.append(
process_event_class(class_meta)
)

with open("./package/TikTokLive.d.ts", "w") as file:
file.write(build_ts_defs(
enums=enum_defs,
classes=class_defs,
events=event_defs,
primitives=PrimitiveMappings
))
Loading

0 comments on commit 0ac4752

Please sign in to comment.