This commit is contained in:
parent
572eb05019
commit
2c62f0f948
19 changed files with 266 additions and 302 deletions
|
@ -72,16 +72,17 @@ ME = {
|
||||||
"owner": config.ID,
|
"owner": config.ID,
|
||||||
"publicKeyPem": get_pubkey_as_pem(config.KEY_PATH),
|
"publicKeyPem": get_pubkey_as_pem(config.KEY_PATH),
|
||||||
},
|
},
|
||||||
"tag": [] # TODO tag support
|
"tag": [], # TODO tag support
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BaseActor:
|
class BaseActor:
|
||||||
def __init__(self, ap_actor: RawObject, **_) -> None:
|
def __init__(self, ap_actor: RawObject, **_) -> None:
|
||||||
if (ap_type := ap_actor.get("type")) not in ACTOR_TYPES:
|
if (ap_type := ap_actor.get("type")) not in ACTOR_TYPES:
|
||||||
raise ValueError(f"Unexpected actor type: {ap_type}")
|
raise ValueError(f"Unexpected actor type: {ap_type}")
|
||||||
|
|
||||||
self._ap_actor = ap_actor
|
self._ap_actor = ap_actor
|
||||||
self._ap_type : str = ap_type # type: ignore
|
self._ap_type: str = ap_type # type: ignore
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_actor(self) -> RawObject:
|
def ap_actor(self) -> RawObject:
|
||||||
|
@ -101,8 +102,7 @@ class BaseActor:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def share_inbox_url(self) -> str:
|
def share_inbox_url(self) -> str:
|
||||||
return self.ap_actor.get("endpoints", {}).get("sharedInbox") \
|
return self.ap_actor.get("endpoints", {}).get("sharedInbox") or self.inbox_url
|
||||||
or self.inbox_url
|
|
||||||
|
|
||||||
|
|
||||||
class VisibilityEnum(str, enum.Enum):
|
class VisibilityEnum(str, enum.Enum):
|
||||||
|
@ -125,7 +125,6 @@ def handle_visibility(ap_object: dict) -> VisibilityEnum:
|
||||||
|
|
||||||
def wrap_ap_object(ap_object: dict) -> dict:
|
def wrap_ap_object(ap_object: dict) -> dict:
|
||||||
if ap_object["type"] in ["Note"]:
|
if ap_object["type"] in ["Note"]:
|
||||||
|
|
||||||
if "@context" in ap_object:
|
if "@context" in ap_object:
|
||||||
del ap_object["@context"]
|
del ap_object["@context"]
|
||||||
|
|
||||||
|
|
19
app/actor.py
19
app/actor.py
|
@ -24,9 +24,7 @@ async def fetch_actor(
|
||||||
in db."""
|
in db."""
|
||||||
exist_actor = (
|
exist_actor = (
|
||||||
await db_session.scalars(
|
await db_session.scalars(
|
||||||
select(models.Actor).where(
|
select(models.Actor).where(models.Actor.ap_id == actor_id)
|
||||||
models.Actor.ap_id == actor_id
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
).one_or_none()
|
).one_or_none()
|
||||||
|
|
||||||
|
@ -38,10 +36,7 @@ async def fetch_actor(
|
||||||
return exist_actor
|
return exist_actor
|
||||||
|
|
||||||
|
|
||||||
async def save_actor(
|
async def save_actor(ap_object: dict, db_session: AsyncSession) -> "ActorModel":
|
||||||
ap_object: dict,
|
|
||||||
db_session: AsyncSession
|
|
||||||
) -> "ActorModel":
|
|
||||||
"""Save actor to db."""
|
"""Save actor to db."""
|
||||||
logger.info("save actor " + ap_object["id"])
|
logger.info("save actor " + ap_object["id"])
|
||||||
actor = models.Actor(
|
actor = models.Actor(
|
||||||
|
@ -64,20 +59,16 @@ def _handle(
|
||||||
if not ap_id.hostname:
|
if not ap_id.hostname:
|
||||||
raise ValueError(f"Invalid actor ID {ap_id}")
|
raise ValueError(f"Invalid actor ID {ap_id}")
|
||||||
|
|
||||||
handle = '@' + ap_object["preferredUsername"] + '@' + ap_id.hostname
|
handle = "@" + ap_object["preferredUsername"] + "@" + ap_id.hostname
|
||||||
|
|
||||||
return handle
|
return handle
|
||||||
|
|
||||||
|
|
||||||
async def get_public_key(
|
async def get_public_key(db_session: AsyncSession, key_id: str) -> str:
|
||||||
db_session: AsyncSession,
|
|
||||||
key_id: str
|
|
||||||
) -> str:
|
|
||||||
"""Give key id and reutrn public key."""
|
"""Give key id and reutrn public key."""
|
||||||
existing_actor = (
|
existing_actor = (
|
||||||
await db_session.scalars(
|
await db_session.scalars(
|
||||||
select(models.Actor).where(
|
select(models.Actor).where(models.Actor.ap_id == key_id.split("#")[0])
|
||||||
models.Actor.ap_id == key_id.split("#")[0])
|
|
||||||
)
|
)
|
||||||
).one_or_none()
|
).one_or_none()
|
||||||
public_key = existing_actor.ap_actor["publicKey"]["publicKeyPem"]
|
public_key = existing_actor.ap_actor["publicKey"]["publicKeyPem"]
|
||||||
|
|
88
app/boxes.py
88
app/boxes.py
|
@ -29,7 +29,6 @@ from uuid import uuid4
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def allocate_outbox_id() -> str:
|
def allocate_outbox_id() -> str:
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
@ -122,8 +121,7 @@ async def process_incoming(
|
||||||
ap_object["object"]["id"],
|
ap_object["object"]["id"],
|
||||||
)
|
)
|
||||||
relates_to_inbox_object = await get_inbox_object(
|
relates_to_inbox_object = await get_inbox_object(
|
||||||
db_session,
|
db_session, ap_object["object"]["id"]
|
||||||
ap_object["object"]["id"]
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if ap_object["object"].startswith(BASE_URL):
|
if ap_object["object"].startswith(BASE_URL):
|
||||||
|
@ -132,15 +130,13 @@ async def process_incoming(
|
||||||
ap_object["object"],
|
ap_object["object"],
|
||||||
)
|
)
|
||||||
relates_to_inbox_object = await get_inbox_object(
|
relates_to_inbox_object = await get_inbox_object(
|
||||||
db_session,
|
db_session, ap_object["object"]
|
||||||
ap_object["object"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_object(
|
def build_object(
|
||||||
object,
|
object,
|
||||||
relates_to_inbox_object = None,
|
relates_to_inbox_object=None,
|
||||||
relates_to_outbox_object = None,
|
relates_to_outbox_object=None,
|
||||||
) -> InboxObject:
|
) -> InboxObject:
|
||||||
inbox_object = models.InboxObject(
|
inbox_object = models.InboxObject(
|
||||||
actor_id=actor.id,
|
actor_id=actor.id,
|
||||||
|
@ -172,8 +168,8 @@ async def process_incoming(
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
elif "Undo" == ap_object["type"]:
|
elif "Undo" == ap_object["type"]:
|
||||||
#inbox_object = build_object(ap_object)
|
# inbox_object = build_object(ap_object)
|
||||||
#db_session.add(inbox_object)
|
# db_session.add(inbox_object)
|
||||||
if await _handle_undo(db_session, ap_object):
|
if await _handle_undo(db_session, ap_object):
|
||||||
return True
|
return True
|
||||||
elif ap_object["type"] in ["Accept", "Rejact"]:
|
elif ap_object["type"] in ["Accept", "Rejact"]:
|
||||||
|
@ -181,7 +177,9 @@ async def process_incoming(
|
||||||
if isinstance(follow_id, dict):
|
if isinstance(follow_id, dict):
|
||||||
follow_id = follow_id["id"]
|
follow_id = follow_id["id"]
|
||||||
|
|
||||||
relate_following_object = (await db_session.execute(
|
relate_following_object = (
|
||||||
|
(
|
||||||
|
await db_session.execute(
|
||||||
select(models.OutboxObject)
|
select(models.OutboxObject)
|
||||||
.where(models.OutboxObject.ap_id == follow_id)
|
.where(models.OutboxObject.ap_id == follow_id)
|
||||||
.options(
|
.options(
|
||||||
|
@ -190,7 +188,10 @@ async def process_incoming(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).unique().scalar_one_or_none()
|
)
|
||||||
|
.unique()
|
||||||
|
.scalar_one_or_none()
|
||||||
|
)
|
||||||
|
|
||||||
if "Accept" == ap_object["type"]:
|
if "Accept" == ap_object["type"]:
|
||||||
try:
|
try:
|
||||||
|
@ -257,9 +258,9 @@ async def _handle_follow(
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
inbox_object: InboxObject,
|
inbox_object: InboxObject,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if ME["id"] != inbox_object.ap_object["object"]: #type: ignore
|
if ME["id"] != inbox_object.ap_object["object"]: # type: ignore
|
||||||
# await db_session.delete(ap_object)
|
# await db_session.delete(ap_object)
|
||||||
logger.warning("no match follow object!" + inbox_object.ap_object["id"]) #type: ignore
|
logger.warning("no match follow object!" + inbox_object.ap_object["id"]) # type: ignore
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if MANUALLY_APPROVES_FOLLOWERS:
|
if MANUALLY_APPROVES_FOLLOWERS:
|
||||||
|
@ -277,7 +278,7 @@ async def _send_accept(
|
||||||
follower = models.Follower(
|
follower = models.Follower(
|
||||||
actor_id=inbox_object.actor_id,
|
actor_id=inbox_object.actor_id,
|
||||||
inbox_object_id=inbox_object.id,
|
inbox_object_id=inbox_object.id,
|
||||||
ap_actor_id=inbox_object.ap_object["actor"], #type: ignore
|
ap_actor_id=inbox_object.ap_object["actor"], # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -296,9 +297,9 @@ async def _send_accept(
|
||||||
"id": build_object_id(reply_id),
|
"id": build_object_id(reply_id),
|
||||||
"type": "Accept",
|
"type": "Accept",
|
||||||
"actor": ME["id"],
|
"actor": ME["id"],
|
||||||
"object": inbox_object.ap_object["id"], #type: ignore
|
"object": inbox_object.ap_object["id"], # type: ignore
|
||||||
}
|
}
|
||||||
#TODO outcoming
|
# TODO outcoming
|
||||||
|
|
||||||
await save_to_outbox(
|
await save_to_outbox(
|
||||||
db_session,
|
db_session,
|
||||||
|
@ -312,17 +313,14 @@ async def _send_accept(
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
|
|
||||||
async def _handle_undo(
|
async def _handle_undo(db_session: AsyncSession, inbox_object: dict) -> bool:
|
||||||
db_session: AsyncSession,
|
|
||||||
inbox_object: dict
|
|
||||||
) -> bool:
|
|
||||||
if inbox_object["object"]["object"] != ME["id"]:
|
if inbox_object["object"]["object"] != ME["id"]:
|
||||||
logger.warning("Wrong undo object! "
|
logger.warning("Wrong undo object! " + inbox_object["object"]["actor"])
|
||||||
+ inbox_object["object"]["actor"])
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if "Follow" == inbox_object["object"]["type"]:
|
if "Follow" == inbox_object["object"]["type"]:
|
||||||
relate_object = (await db_session.execute(
|
relate_object = (
|
||||||
|
await db_session.execute(
|
||||||
select(models.InboxObject)
|
select(models.InboxObject)
|
||||||
.where(models.InboxObject.ap_id == inbox_object["object"]["id"])
|
.where(models.InboxObject.ap_id == inbox_object["object"]["id"])
|
||||||
.options(
|
.options(
|
||||||
|
@ -334,8 +332,8 @@ async def _handle_undo(
|
||||||
).scalar_one_or_none() # type: ignore
|
).scalar_one_or_none() # type: ignore
|
||||||
|
|
||||||
if relate_object:
|
if relate_object:
|
||||||
relate_object.undo_id=inbox_object["object"]["id"]
|
relate_object.undo_id = inbox_object["object"]["id"]
|
||||||
relate_object.is_deleted=True
|
relate_object.is_deleted = True
|
||||||
|
|
||||||
await db_session.execute(
|
await db_session.execute(
|
||||||
delete(models.Follower).where(
|
delete(models.Follower).where(
|
||||||
|
@ -348,10 +346,7 @@ async def _handle_undo(
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def send_follow(
|
async def send_follow(db_session: AsyncSession, acct: str):
|
||||||
db_session : AsyncSession,
|
|
||||||
acct : str
|
|
||||||
):
|
|
||||||
await _send_follow(db_session, acct)
|
await _send_follow(db_session, acct)
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
await db_session.flush()
|
await db_session.flush()
|
||||||
|
@ -362,7 +357,7 @@ async def _handle_announce(
|
||||||
inbox_object: InboxObject,
|
inbox_object: InboxObject,
|
||||||
relates_to_outbox_object: models.OutboxObject | None,
|
relates_to_outbox_object: models.OutboxObject | None,
|
||||||
relates_to_inbox_object: models.InboxObject | None,
|
relates_to_inbox_object: models.InboxObject | None,
|
||||||
) -> bool :
|
) -> bool:
|
||||||
if relates_to_outbox_object:
|
if relates_to_outbox_object:
|
||||||
relates_to_outbox_object.announces_count = ( # type: ignore
|
relates_to_outbox_object.announces_count = ( # type: ignore
|
||||||
models.OutboxObject.announces_count + 1
|
models.OutboxObject.announces_count + 1
|
||||||
|
@ -378,7 +373,7 @@ async def _handle_like(
|
||||||
inbox_object: InboxObject,
|
inbox_object: InboxObject,
|
||||||
relates_to_outbox_object: models.OutboxObject | None,
|
relates_to_outbox_object: models.OutboxObject | None,
|
||||||
relates_to_inbox_object: models.InboxObject | None,
|
relates_to_inbox_object: models.InboxObject | None,
|
||||||
) -> bool :
|
) -> bool:
|
||||||
if relates_to_outbox_object:
|
if relates_to_outbox_object:
|
||||||
relates_to_outbox_object.likes_count = ( # type: ignore
|
relates_to_outbox_object.likes_count = ( # type: ignore
|
||||||
models.OutboxObject.likes_count + 1
|
models.OutboxObject.likes_count + 1
|
||||||
|
@ -390,8 +385,8 @@ async def _handle_like(
|
||||||
|
|
||||||
|
|
||||||
async def _send_follow(
|
async def _send_follow(
|
||||||
db_session : AsyncSession,
|
db_session: AsyncSession,
|
||||||
actor_url : str,
|
actor_url: str,
|
||||||
):
|
):
|
||||||
actor = await fetch_actor(db_session, actor_url)
|
actor = await fetch_actor(db_session, actor_url)
|
||||||
|
|
||||||
|
@ -409,7 +404,7 @@ async def _send_follow(
|
||||||
follow_id,
|
follow_id,
|
||||||
out,
|
out,
|
||||||
relates_to_actor_id=actor.id, # type: ignore
|
relates_to_actor_id=actor.id, # type: ignore
|
||||||
activity_object_ap_id=actor.ap_id
|
activity_object_ap_id=actor.ap_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
await post(
|
await post(
|
||||||
|
@ -485,7 +480,6 @@ async def _send_create(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -493,10 +487,7 @@ async def _compute_recipients(
|
||||||
db_session: AsyncSession,
|
db_session: AsyncSession,
|
||||||
ap_object: dict,
|
ap_object: dict,
|
||||||
) -> set[str]:
|
) -> set[str]:
|
||||||
|
async def process_collection(db_session, url) -> list[Actor]:
|
||||||
async def process_collection(
|
|
||||||
db_session,
|
|
||||||
url) -> list[Actor]:
|
|
||||||
if url == BASE_URL + "/followers":
|
if url == BASE_URL + "/followers":
|
||||||
followers = (
|
followers = (
|
||||||
(
|
(
|
||||||
|
@ -535,14 +526,13 @@ async def _compute_recipients(
|
||||||
|
|
||||||
|
|
||||||
async def save_to_inbox(
|
async def save_to_inbox(
|
||||||
db_session : AsyncSession,
|
db_session: AsyncSession,
|
||||||
inbox_id : str,
|
inbox_id: str,
|
||||||
ap_object : dict,
|
ap_object: dict,
|
||||||
relates_to_inbox_object_id: int | None = None,
|
relates_to_inbox_object_id: int | None = None,
|
||||||
relates_to_outbox_object_id: int | None = None,
|
relates_to_outbox_object_id: int | None = None,
|
||||||
relates_to_actor_id: int | None = None,
|
relates_to_actor_id: int | None = None,
|
||||||
) -> InboxObject:
|
) -> InboxObject:
|
||||||
|
|
||||||
ap_type = ap_object["type"]
|
ap_type = ap_object["type"]
|
||||||
ap_id = ap_object["id"]
|
ap_id = ap_object["id"]
|
||||||
visibility = handle_visibility(ap_object)
|
visibility = handle_visibility(ap_object)
|
||||||
|
@ -552,7 +542,7 @@ async def save_to_inbox(
|
||||||
ap_object=ap_object,
|
ap_object=ap_object,
|
||||||
ap_id=ap_id,
|
ap_id=ap_id,
|
||||||
ap_type=ap_type,
|
ap_type=ap_type,
|
||||||
visibility =visibility,
|
visibility=visibility,
|
||||||
relates_to_inbox_object_id=relates_to_inbox_object_id,
|
relates_to_inbox_object_id=relates_to_inbox_object_id,
|
||||||
relates_to_outbox_object_id=relates_to_outbox_object_id,
|
relates_to_outbox_object_id=relates_to_outbox_object_id,
|
||||||
relates_to_actor_id=relates_to_actor_id,
|
relates_to_actor_id=relates_to_actor_id,
|
||||||
|
@ -566,9 +556,9 @@ async def save_to_inbox(
|
||||||
|
|
||||||
|
|
||||||
async def save_to_outbox(
|
async def save_to_outbox(
|
||||||
db_session : AsyncSession,
|
db_session: AsyncSession,
|
||||||
outbox_id : str,
|
outbox_id: str,
|
||||||
ap_object : dict,
|
ap_object: dict,
|
||||||
activity_object_ap_id: str | None = None,
|
activity_object_ap_id: str | None = None,
|
||||||
relates_to_inbox_object_id: int | None = None,
|
relates_to_inbox_object_id: int | None = None,
|
||||||
relates_to_outbox_object_id: int | None = None,
|
relates_to_outbox_object_id: int | None = None,
|
||||||
|
@ -583,7 +573,7 @@ async def save_to_outbox(
|
||||||
ap_object=ap_object,
|
ap_object=ap_object,
|
||||||
ap_id=ap_id,
|
ap_id=ap_id,
|
||||||
ap_type=ap_type,
|
ap_type=ap_type,
|
||||||
visibility =visibility,
|
visibility=visibility,
|
||||||
activity_object_ap_id=activity_object_ap_id,
|
activity_object_ap_id=activity_object_ap_id,
|
||||||
relates_to_inbox_object_id=relates_to_inbox_object_id,
|
relates_to_inbox_object_id=relates_to_inbox_object_id,
|
||||||
relates_to_outbox_object_id=relates_to_outbox_object_id,
|
relates_to_outbox_object_id=relates_to_outbox_object_id,
|
||||||
|
|
|
@ -35,28 +35,30 @@ class Config(pydantic.BaseModel):
|
||||||
ROOT_DIR = Path().parent.resolve()
|
ROOT_DIR = Path().parent.resolve()
|
||||||
_CONFIG_FILE = os.getenv("FOXHOLE_CONFIG_FILE", "config.toml")
|
_CONFIG_FILE = os.getenv("FOXHOLE_CONFIG_FILE", "config.toml")
|
||||||
|
|
||||||
|
|
||||||
def load_config() -> Config:
|
def load_config() -> Config:
|
||||||
try:
|
try:
|
||||||
return Config.parse_obj(
|
return Config.parse_obj(
|
||||||
tomli.loads((ROOT_DIR / "data" / _CONFIG_FILE).read_text())
|
tomli.loads((ROOT_DIR / "data" / _CONFIG_FILE).read_text())
|
||||||
)
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise ValueError(
|
raise ValueError(f"{_CONFIG_FILE} is missing")
|
||||||
f"{_CONFIG_FILE} is missing"
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_version_commit() -> str:
|
def get_version_commit() -> str:
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
'+' +
|
"+"
|
||||||
subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"])
|
+ subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"])
|
||||||
.split()[0]
|
.split()[0]
|
||||||
.decode()
|
.decode()
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return "+dev"
|
return "+dev"
|
||||||
|
|
||||||
|
|
||||||
CONFIG = load_config()
|
CONFIG = load_config()
|
||||||
DOMAIN = CONFIG.domain
|
DOMAIN = CONFIG.domain
|
||||||
|
|
||||||
|
@ -67,9 +69,9 @@ _SCHEME = "https" if CONFIG.https else "http"
|
||||||
ID = f"{_SCHEME}://{DOMAIN}"
|
ID = f"{_SCHEME}://{DOMAIN}"
|
||||||
BASE_URL = ID
|
BASE_URL = ID
|
||||||
USERNAME = CONFIG.username
|
USERNAME = CONFIG.username
|
||||||
KEY_PATH = (ROOT_DIR / os.getenv("FOXHOLE_KEY_PATH", "data/key.pem"))
|
KEY_PATH = ROOT_DIR / os.getenv("FOXHOLE_KEY_PATH", "data/key.pem")
|
||||||
|
|
||||||
VERSION = "Foxhole-"+ MAIN_VERSION + get_version_commit()
|
VERSION = "Foxhole-" + MAIN_VERSION + get_version_commit()
|
||||||
USER_AGENT = "Fediverse Application/" + VERSION
|
USER_AGENT = "Fediverse Application/" + VERSION
|
||||||
AP_CONTENT_TYPE = "application/activity+json"
|
AP_CONTENT_TYPE = "application/activity+json"
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,14 @@ async_engine = create_async_engine(
|
||||||
)
|
)
|
||||||
async_session = sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False)
|
async_session = sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False)
|
||||||
|
|
||||||
|
|
||||||
class Base(DeclarativeBase):
|
class Base(DeclarativeBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
metadata_obj = MetaData()
|
metadata_obj = MetaData()
|
||||||
|
|
||||||
|
|
||||||
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
||||||
async with async_session() as session: # type: ignore
|
async with async_session() as session: # type: ignore
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -12,36 +12,30 @@ from Crypto.Signature import PKCS1_v1_5
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HttpSignature:
|
class HttpSignature:
|
||||||
"""
|
"""
|
||||||
calculation and verification of HTTP signatures
|
calculation and verification of HTTP signatures
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def calculation_digest(
|
def calculation_digest(cls, body: bytes, algorithm: str = "sha-256") -> str:
|
||||||
cls,
|
|
||||||
body : bytes,
|
|
||||||
algorithm : str ="sha-256"
|
|
||||||
)-> str :
|
|
||||||
"""
|
"""
|
||||||
Calculates the digest header value for a given HTTP body
|
Calculates the digest header value for a given HTTP body
|
||||||
"""
|
"""
|
||||||
if "sha-256" == algorithm:
|
if "sha-256" == algorithm:
|
||||||
h = SHA256.new()
|
h = SHA256.new()
|
||||||
h.update(body)
|
h.update(body)
|
||||||
return "SHA-256=" + \
|
return "SHA-256=" + base64.b64encode(h.digest()).decode("utf-8")
|
||||||
base64.b64encode(h.digest()).decode("utf-8")
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"No support algorithm {algorithm}")
|
raise ValueError(f"No support algorithm {algorithm}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def verify_signature(
|
def verify_signature(
|
||||||
cls,
|
cls,
|
||||||
signature_string : str,
|
signature_string: str,
|
||||||
signature : bytes,
|
signature: bytes,
|
||||||
pubkey,
|
pubkey,
|
||||||
) -> bool :
|
) -> bool:
|
||||||
pubkey = RSA.importKey(pubkey)
|
pubkey = RSA.importKey(pubkey)
|
||||||
signer = PKCS1_v1_5.new(pubkey)
|
signer = PKCS1_v1_5.new(pubkey)
|
||||||
digest = SHA256.new()
|
digest = SHA256.new()
|
||||||
|
@ -66,12 +60,12 @@ class HttpSignature:
|
||||||
@classmethod
|
@classmethod
|
||||||
def build_signature_string(
|
def build_signature_string(
|
||||||
cls,
|
cls,
|
||||||
method : str,
|
method: str,
|
||||||
path : str,
|
path: str,
|
||||||
signed_headers : list,
|
signed_headers: list,
|
||||||
body_digest : str | None,
|
body_digest: str | None,
|
||||||
headers,
|
headers,
|
||||||
) -> str :
|
) -> str:
|
||||||
signed_string = []
|
signed_string = []
|
||||||
for signed_header in signed_headers:
|
for signed_header in signed_headers:
|
||||||
if signed_header == "(request-target)":
|
if signed_header == "(request-target)":
|
||||||
|
@ -87,9 +81,7 @@ class HTTPXSigAuth(httpx.Auth):
|
||||||
def __init__(self, key) -> None:
|
def __init__(self, key) -> None:
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
||||||
def auth_flow(
|
def auth_flow(self, r: httpx.Request):
|
||||||
self, r: httpx.Request
|
|
||||||
):
|
|
||||||
bodydigest = None
|
bodydigest = None
|
||||||
if r.content:
|
if r.content:
|
||||||
bh = hashlib.new("sha256")
|
bh = hashlib.new("sha256")
|
||||||
|
@ -122,6 +114,7 @@ class HTTPXSigAuth(httpx.Auth):
|
||||||
r.headers["signature"] = sig_value
|
r.headers["signature"] = sig_value
|
||||||
yield r
|
yield r
|
||||||
|
|
||||||
|
|
||||||
k = KEY_PATH.read_text()
|
k = KEY_PATH.read_text()
|
||||||
k = RSA.importKey(k)
|
k = RSA.importKey(k)
|
||||||
auth = HTTPXSigAuth(k)
|
auth = HTTPXSigAuth(k)
|
||||||
|
|
|
@ -16,9 +16,9 @@ from app.database import AsyncSession
|
||||||
from app.actor import get_public_key
|
from app.actor import get_public_key
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
requests_loader = pyld.documentloader.requests.requests_document_loader() # type: ignore
|
requests_loader = pyld.documentloader.requests.requests_document_loader() # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _loader(url, options):
|
def _loader(url, options):
|
||||||
if options is None:
|
if options is None:
|
||||||
options = {}
|
options = {}
|
||||||
|
@ -80,7 +80,9 @@ async def verify_signature(
|
||||||
signer = PKCS1_v1_5.new(pubkey)
|
signer = PKCS1_v1_5.new(pubkey)
|
||||||
digest = SHA256.new()
|
digest = SHA256.new()
|
||||||
digest.update(to_be_signed.encode("utf-8"))
|
digest.update(to_be_signed.encode("utf-8"))
|
||||||
return signer.verify(digest, base64.b64decode(signature)) # pylint: disable=not-callable
|
return signer.verify(
|
||||||
|
digest, base64.b64decode(signature)
|
||||||
|
) # pylint: disable=not-callable
|
||||||
|
|
||||||
|
|
||||||
def generate_signature(doc: ap.RawObject, key) -> None:
|
def generate_signature(doc: ap.RawObject, key) -> None:
|
||||||
|
|
14
app/main.py
14
app/main.py
|
@ -35,14 +35,12 @@ from app.hysql import get_index_status
|
||||||
|
|
||||||
def _check_0rtt_early_data(request: Request) -> None:
|
def _check_0rtt_early_data(request: Request) -> None:
|
||||||
"""Disable TLS1.3 0-RTT requests for non-GET."""
|
"""Disable TLS1.3 0-RTT requests for non-GET."""
|
||||||
if request.headers.get("Early-Data", None) == "1" \
|
if request.headers.get("Early-Data", None) == "1" and request.method != "GET":
|
||||||
and request.method != "GET":
|
|
||||||
raise fastapi.HTTPException(status_code=425, detail="Too early")
|
raise fastapi.HTTPException(status_code=425, detail="Too early")
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
docs_url=None, redoc_url=None,
|
docs_url=None, redoc_url=None, dependencies=[Depends(_check_0rtt_early_data)]
|
||||||
dependencies=[Depends(_check_0rtt_early_data)]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
@ -55,6 +53,7 @@ logger.add("output.log", level="DEBUG")
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
class ActivityPubResponse(JSONResponse):
|
class ActivityPubResponse(JSONResponse):
|
||||||
"""Simple wrap JSONresponse return ActivityPub response."""
|
"""Simple wrap JSONresponse return ActivityPub response."""
|
||||||
|
|
||||||
media_type = "application/activity+json"
|
media_type = "application/activity+json"
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,12 +136,7 @@ async def outbox(
|
||||||
logger.info("True token")
|
logger.info("True token")
|
||||||
note_content = to_html(payload["content"]).replace("\n", "")
|
note_content = to_html(payload["content"]).replace("\n", "")
|
||||||
|
|
||||||
await _send_create(
|
await _send_create(db_session, "Note", note_content, payload["visibility"])
|
||||||
db_session,
|
|
||||||
"Note",
|
|
||||||
note_content,
|
|
||||||
payload["visibility"]
|
|
||||||
)
|
|
||||||
return Response(status_code=200)
|
return Response(status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,12 @@ class Actor(Base, BaseActor):
|
||||||
|
|
||||||
handle = mapped_column(String, nullable=True, index=True)
|
handle = mapped_column(String, nullable=True, index=True)
|
||||||
|
|
||||||
is_blocked = mapped_column(Boolean, nullable=False, default=False, server_default="0")
|
is_blocked = mapped_column(
|
||||||
is_deleted = mapped_column(Boolean, nullable=False, default=False, server_default="0")
|
Boolean, nullable=False, default=False, server_default="0"
|
||||||
|
)
|
||||||
|
is_deleted = mapped_column(
|
||||||
|
Boolean, nullable=False, default=False, server_default="0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InboxObject(Base, BaseObject):
|
class InboxObject(Base, BaseObject):
|
||||||
|
@ -179,7 +183,9 @@ class Follower(Base):
|
||||||
created_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
created_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
||||||
updated_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
updated_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
||||||
|
|
||||||
actor_id = mapped_column(Integer, ForeignKey("actor.id"), nullable=False, unique=True)
|
actor_id = mapped_column(
|
||||||
|
Integer, ForeignKey("actor.id"), nullable=False, unique=True
|
||||||
|
)
|
||||||
actor: Mapped[Actor] = relationship(Actor, uselist=False)
|
actor: Mapped[Actor] = relationship(Actor, uselist=False)
|
||||||
|
|
||||||
inbox_object_id = mapped_column(Integer, ForeignKey("inbox.id"), nullable=False)
|
inbox_object_id = mapped_column(Integer, ForeignKey("inbox.id"), nullable=False)
|
||||||
|
@ -195,7 +201,9 @@ class Following(Base):
|
||||||
created_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
created_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
||||||
updated_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
updated_at = mapped_column(DateTime(timezone=True), nullable=False, default=now)
|
||||||
|
|
||||||
actor_id = mapped_column(Integer, ForeignKey("actor.id"), nullable=False, unique=True)
|
actor_id = mapped_column(
|
||||||
|
Integer, ForeignKey("actor.id"), nullable=False, unique=True
|
||||||
|
)
|
||||||
actor: Mapped[Actor] = relationship(Actor, uselist=False)
|
actor: Mapped[Actor] = relationship(Actor, uselist=False)
|
||||||
|
|
||||||
outbox_object_id = mapped_column(Integer, ForeignKey("outbox.id"), nullable=False)
|
outbox_object_id = mapped_column(Integer, ForeignKey("outbox.id"), nullable=False)
|
||||||
|
|
|
@ -36,7 +36,8 @@ LIST_STATUS_REGEXP = re.compile(r"\[( |X|-)\]\s")
|
||||||
LIST_LEVEL_REGEXP = re.compile(r"(\s*)(.+)$")
|
LIST_LEVEL_REGEXP = re.compile(r"(\s*)(.+)$")
|
||||||
|
|
||||||
HEADLINE_REGEXP = re.compile(
|
HEADLINE_REGEXP = re.compile(
|
||||||
r"^(\*+)(?:\s+(.+?))?(?:\s+\[#(.+)\])?(\s+.*?)(?:\s+:(.+):)?$")
|
r"^(\*+)(?:\s+(.+?))?(?:\s+\[#(.+)\])?(\s+.*?)(?:\s+:(.+):)?$"
|
||||||
|
)
|
||||||
KEYWORD_REGEXP = re.compile(r"^(\s*)#\+([^:]+):(\s+(.*)|$)")
|
KEYWORD_REGEXP = re.compile(r"^(\s*)#\+([^:]+):(\s+(.*)|$)")
|
||||||
COMMENT_REGEXP = re.compile(r"^(\s*)#(.*)")
|
COMMENT_REGEXP = re.compile(r"^(\s*)#(.*)")
|
||||||
ATTRIBUTE_REGEXP = re.compile(r"(?:^|\s+)(:[-\w]+)\s+(.*)$")
|
ATTRIBUTE_REGEXP = re.compile(r"(?:^|\s+)(:[-\w]+)\s+(.*)$")
|
||||||
|
@ -172,7 +173,7 @@ class Parser(object):
|
||||||
num = index + 1
|
num = index + 1
|
||||||
while num < end:
|
while num < end:
|
||||||
if node.matchend(num, lines):
|
if node.matchend(num, lines):
|
||||||
node.preparse(lines[index + 1:num])
|
node.preparse(lines[index + 1 : num])
|
||||||
return node, num
|
return node, num
|
||||||
num += 1
|
num += 1
|
||||||
return None, index
|
return None, index
|
||||||
|
@ -188,7 +189,7 @@ class Parser(object):
|
||||||
if node.matchend(num, lines):
|
if node.matchend(num, lines):
|
||||||
break
|
break
|
||||||
num += 1
|
num += 1
|
||||||
node.preparse(lines[index + 1:num])
|
node.preparse(lines[index + 1 : num])
|
||||||
return node, num
|
return node, num
|
||||||
|
|
||||||
def parse_headline(self, index, lines):
|
def parse_headline(self, index, lines):
|
||||||
|
@ -260,7 +261,7 @@ class Parser(object):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
str_children = [str(child) for child in self.children]
|
str_children = [str(child) for child in self.children]
|
||||||
return self.__class__.__name__ + '(' + ','.join(str_children) + ')'
|
return self.__class__.__name__ + "(" + ",".join(str_children) + ")"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
@ -274,7 +275,8 @@ class Headline(Parser):
|
||||||
keyword=None,
|
keyword=None,
|
||||||
priority=None,
|
priority=None,
|
||||||
tags=[],
|
tags=[],
|
||||||
todo_keywords=TODO_KEYWORDS):
|
todo_keywords=TODO_KEYWORDS,
|
||||||
|
):
|
||||||
super(Headline, self).__init__()
|
super(Headline, self).__init__()
|
||||||
self.title = title
|
self.title = title
|
||||||
self.stars = stars
|
self.stars = stars
|
||||||
|
@ -311,7 +313,7 @@ class Headline(Parser):
|
||||||
)
|
)
|
||||||
|
|
||||||
def id(self):
|
def id(self):
|
||||||
hid = 'org-{0}'.format(sha1(self.title.encode()).hexdigest()[:10])
|
hid = "org-{0}".format(sha1(self.title.encode()).hexdigest()[:10])
|
||||||
if self.properties:
|
if self.properties:
|
||||||
return self.properties.get("CUSTOM_ID", hid)
|
return self.properties.get("CUSTOM_ID", hid)
|
||||||
return hid
|
return hid
|
||||||
|
@ -319,18 +321,18 @@ class Headline(Parser):
|
||||||
def toc(self):
|
def toc(self):
|
||||||
b = ""
|
b = ""
|
||||||
if self.keyword:
|
if self.keyword:
|
||||||
b = b + "<span class=\"todo\">{0}</span>".format(self.keyword)
|
b = b + '<span class="todo">{0}</span>'.format(self.keyword)
|
||||||
if self.priority:
|
if self.priority:
|
||||||
b = b + "<span class=\"priority\">{0}</span>".format(self.priority)
|
b = b + '<span class="priority">{0}</span>'.format(self.priority)
|
||||||
|
|
||||||
b = b + self.inlinetext(self.title).to_html()
|
b = b + self.inlinetext(self.title).to_html()
|
||||||
|
|
||||||
for tag in self.tags:
|
for tag in self.tags:
|
||||||
b = b + "<span class=\"tag\">{0}</span>".format(tag)
|
b = b + '<span class="tag">{0}</span>'.format(tag)
|
||||||
return b.strip()
|
return b.strip()
|
||||||
|
|
||||||
def to_html(self):
|
def to_html(self):
|
||||||
b = "<h{0} id=\"{1}\">{2}</h{0}>".format(
|
b = '<h{0} id="{1}">{2}</h{0}>'.format(
|
||||||
self.stars,
|
self.stars,
|
||||||
self.id(),
|
self.id(),
|
||||||
self.toc(),
|
self.toc(),
|
||||||
|
@ -413,13 +415,13 @@ class Block(Parser):
|
||||||
class Center(Block):
|
class Center(Block):
|
||||||
def __init__(self, params=""):
|
def __init__(self, params=""):
|
||||||
super(Center, self).__init__("center", params)
|
super(Center, self).__init__("center", params)
|
||||||
self.element = "<div style=\"text-align: center;\">\n{0}\n</div>"
|
self.element = '<div style="text-align: center;">\n{0}\n</div>'
|
||||||
|
|
||||||
|
|
||||||
class Verse(Block):
|
class Verse(Block):
|
||||||
def __init__(self, params=""):
|
def __init__(self, params=""):
|
||||||
super(Verse, self).__init__("verse", params)
|
super(Verse, self).__init__("verse", params)
|
||||||
self.element = "<p class=\"verse\">\n{0}\n</p>"
|
self.element = '<p class="verse">\n{0}\n</p>'
|
||||||
|
|
||||||
def add_child(self, node):
|
def add_child(self, node):
|
||||||
self.children.append(node)
|
self.children.append(node)
|
||||||
|
@ -453,7 +455,7 @@ class Src(Block):
|
||||||
super(Src, self).__init__("src", params)
|
super(Src, self).__init__("src", params)
|
||||||
self.language = language
|
self.language = language
|
||||||
self.highlight_code = highlight
|
self.highlight_code = highlight
|
||||||
self.element = "<pre class=\"src src-{0}\">\n{1}\n</pre>"
|
self.element = '<pre class="src src-{0}">\n{1}\n</pre>'
|
||||||
self.needparse = False
|
self.needparse = False
|
||||||
self.escape = False
|
self.escape = False
|
||||||
self.parsed_nodes = ()
|
self.parsed_nodes = ()
|
||||||
|
@ -482,7 +484,7 @@ class Example(Src):
|
||||||
class BlockResult(Parser):
|
class BlockResult(Parser):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(BlockResult, self).__init__()
|
super(BlockResult, self).__init__()
|
||||||
self.element = "<pre class=\"example\">\n{0}\n</pre>"
|
self.element = '<pre class="example">\n{0}\n</pre>'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def match(cls, line):
|
def match(cls, line):
|
||||||
|
@ -512,7 +514,7 @@ class ListItem(Parser):
|
||||||
content = line
|
content = line
|
||||||
status_match = LIST_STATUS_REGEXP.match(line)
|
status_match = LIST_STATUS_REGEXP.match(line)
|
||||||
if status_match:
|
if status_match:
|
||||||
status, content = status_match[1], content[len("[ ] "):]
|
status, content = status_match[1], content[len("[ ] ") :]
|
||||||
|
|
||||||
node = cls(status)
|
node = cls(status)
|
||||||
node.add_child(node.inlinetext(content))
|
node.add_child(node.inlinetext(content))
|
||||||
|
@ -524,8 +526,7 @@ class ListItem(Parser):
|
||||||
|
|
||||||
if self.checkbox == "HTML":
|
if self.checkbox == "HTML":
|
||||||
if self.status == "X":
|
if self.status == "X":
|
||||||
node = self.inlinetext(
|
node = self.inlinetext('<input type="checkbox" checked="checked" />')
|
||||||
'<input type="checkbox" checked="checked" />')
|
|
||||||
else:
|
else:
|
||||||
node = self.inlinetext('<input type="checkbox" />')
|
node = self.inlinetext('<input type="checkbox" />')
|
||||||
node.needparse = False
|
node.needparse = False
|
||||||
|
@ -641,7 +642,7 @@ class TableRow(Parser):
|
||||||
self.is_sep = False
|
self.is_sep = False
|
||||||
self.header = header
|
self.header = header
|
||||||
self.element = "<tr>\n{0}\n</tr>"
|
self.element = "<tr>\n{0}\n</tr>"
|
||||||
self.parsed_nodes = ("tablecolumn", )
|
self.parsed_nodes = ("tablecolumn",)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def match(cls, line):
|
def match(cls, line):
|
||||||
|
@ -671,7 +672,7 @@ class Table(Parser):
|
||||||
super(Table, self).__init__()
|
super(Table, self).__init__()
|
||||||
self.element = "<table>\n{0}\n</table>"
|
self.element = "<table>\n{0}\n</table>"
|
||||||
self.keyword = keyword
|
self.keyword = keyword
|
||||||
self.parsed_nodes = ("tablerow", )
|
self.parsed_nodes = ("tablerow",)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def match(cls, line):
|
def match(cls, line):
|
||||||
|
@ -777,7 +778,7 @@ class Section(Parser):
|
||||||
|
|
||||||
def to_html(self):
|
def to_html(self):
|
||||||
text = "<li>"
|
text = "<li>"
|
||||||
text += "<a href=\"#{0}\">{1}</a>".format(
|
text += '<a href="#{0}">{1}</a>'.format(
|
||||||
self.headline.id(),
|
self.headline.id(),
|
||||||
self.headline.toc(),
|
self.headline.toc(),
|
||||||
)
|
)
|
||||||
|
@ -785,7 +786,8 @@ class Section(Parser):
|
||||||
return text + "</li>"
|
return text + "</li>"
|
||||||
|
|
||||||
text += "\n<ul>\n{0}\n</ul>\n</li>".format(
|
text += "\n<ul>\n{0}\n</ul>\n</li>".format(
|
||||||
"\n".join([child.to_html() for child in self.children]))
|
"\n".join([child.to_html() for child in self.children])
|
||||||
|
)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
@ -794,9 +796,10 @@ class Toc(Parser):
|
||||||
super(Toc, self).__init__()
|
super(Toc, self).__init__()
|
||||||
self.element = (
|
self.element = (
|
||||||
'<div id="table-of-contents">'
|
'<div id="table-of-contents">'
|
||||||
'<h2>Table of Contents</h2>'
|
"<h2>Table of Contents</h2>"
|
||||||
'<div id="text-table-of-contents">'
|
'<div id="text-table-of-contents">'
|
||||||
'\n<ul>\n{0}\n</ul>\n</div></div>')
|
"\n<ul>\n{0}\n</ul>\n</div></div>"
|
||||||
|
)
|
||||||
|
|
||||||
def add_child(self, node):
|
def add_child(self, node):
|
||||||
last = self.last_child()
|
last = self.last_child()
|
||||||
|
|
|
@ -16,83 +16,73 @@ import os
|
||||||
# _inline_regexp = r"(^|.*?(?<![/\\])){0}(.+?(?<![/\\])){0}(.*?|$)"
|
# _inline_regexp = r"(^|.*?(?<![/\\])){0}(.+?(?<![/\\])){0}(.*?|$)"
|
||||||
_inline_regexp = r"(^|.*?(?<![/\\])){0}(.+?(?<![/\\])){0}(.*?|$)"
|
_inline_regexp = r"(^|.*?(?<![/\\])){0}(.+?(?<![/\\])){0}(.*?|$)"
|
||||||
|
|
||||||
BOLD_REGEXP = re.compile(_inline_regexp.format('\\*'))
|
BOLD_REGEXP = re.compile(_inline_regexp.format("\\*"))
|
||||||
CODE_REGEXP = re.compile(_inline_regexp.format('(?:\\=|`)'))
|
CODE_REGEXP = re.compile(_inline_regexp.format("(?:\\=|`)"))
|
||||||
ITALIC_REGEXP = re.compile(_inline_regexp.format('(?:\\*\\*|\\/)'))
|
ITALIC_REGEXP = re.compile(_inline_regexp.format("(?:\\*\\*|\\/)"))
|
||||||
DELETE_REGEXP = re.compile(_inline_regexp.format('\\+'))
|
DELETE_REGEXP = re.compile(_inline_regexp.format("\\+"))
|
||||||
VERBATIM_REGEXP = re.compile(_inline_regexp.format('~'))
|
VERBATIM_REGEXP = re.compile(_inline_regexp.format("~"))
|
||||||
UNDERLINE_REGEXP = re.compile(_inline_regexp.format('_'))
|
UNDERLINE_REGEXP = re.compile(_inline_regexp.format("_"))
|
||||||
|
|
||||||
PERCENT_REGEXP = re.compile(r"\[(\d+/\d+|\d+%)\]")
|
PERCENT_REGEXP = re.compile(r"\[(\d+/\d+|\d+%)\]")
|
||||||
|
|
||||||
HR_REGEXP = re.compile(r"^\s*\-{5,}\s*")
|
HR_REGEXP = re.compile(r"^\s*\-{5,}\s*")
|
||||||
FN_REGEXP = re.compile(r"(^|.*?(?<![/\\]))(\[fn:(.+?)\])(.*?|$)")
|
FN_REGEXP = re.compile(r"(^|.*?(?<![/\\]))(\[fn:(.+?)\])(.*?|$)")
|
||||||
IMG_REGEXP = re.compile(r"^[.](png|gif|jpe?g|svg|tiff?)$")
|
IMG_REGEXP = re.compile(r"^[.](png|gif|jpe?g|svg|tiff?)$")
|
||||||
LINK_REGEXP = re.compile(r'\[\[(.+?)\](?:\[(.+?)\])?\]')
|
LINK_REGEXP = re.compile(r"\[\[(.+?)\](?:\[(.+?)\])?\]")
|
||||||
VIDEO_REGEXP = re.compile(r"^[.](webm|mp4)$")
|
VIDEO_REGEXP = re.compile(r"^[.](webm|mp4)$")
|
||||||
|
|
||||||
NEWLINE_REGEXP = re.compile(r"(^|.*?(?<![/\\]))(\\\\(\s*)$)")
|
NEWLINE_REGEXP = re.compile(r"(^|.*?(?<![/\\]))(\\\\(\s*)$)")
|
||||||
BLANKLINE_REGEXP = re.compile(r"^(\s*)$")
|
BLANKLINE_REGEXP = re.compile(r"^(\s*)$")
|
||||||
|
|
||||||
TIMESTAMP_REGEXP = re.compile(
|
TIMESTAMP_REGEXP = re.compile(
|
||||||
r"^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d{2}:\d{2})?( \+\d+[dwmy])?>")
|
r"^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d{2}:\d{2})?( \+\d+[dwmy])?>"
|
||||||
|
)
|
||||||
|
|
||||||
_html_escape = (
|
_html_escape = (
|
||||||
("&", "&"),
|
("&", "&"),
|
||||||
("'", "'"),
|
("'", "'"),
|
||||||
("<", "<"),
|
("<", "<"),
|
||||||
(">", ">"),
|
(">", ">"),
|
||||||
("\"", """),
|
('"', """),
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/tsroten/zhon/blob/develop/zhon/hanzi.py
|
# https://github.com/tsroten/zhon/blob/develop/zhon/hanzi.py
|
||||||
_chinese_non_stops = (
|
_chinese_non_stops = (
|
||||||
# Fullwidth ASCII variants
|
# Fullwidth ASCII variants
|
||||||
'\uFF02\uFF03\uFF04\uFF05\uFF06\uFF07\uFF08\uFF09\uFF0A\uFF0B\uFF0C\uFF0D'
|
"\uFF02\uFF03\uFF04\uFF05\uFF06\uFF07\uFF08\uFF09\uFF0A\uFF0B\uFF0C\uFF0D"
|
||||||
'\uFF0F\uFF1A\uFF1B\uFF1C\uFF1D\uFF1E\uFF20\uFF3B\uFF3C\uFF3D\uFF3E\uFF3F'
|
"\uFF0F\uFF1A\uFF1B\uFF1C\uFF1D\uFF1E\uFF20\uFF3B\uFF3C\uFF3D\uFF3E\uFF3F"
|
||||||
'\uFF40\uFF5B\uFF5C\uFF5D\uFF5E\uFF5F\uFF60'
|
"\uFF40\uFF5B\uFF5C\uFF5D\uFF5E\uFF5F\uFF60"
|
||||||
|
|
||||||
# Halfwidth CJK punctuation
|
# Halfwidth CJK punctuation
|
||||||
'\uFF62\uFF63\uFF64'
|
"\uFF62\uFF63\uFF64"
|
||||||
|
|
||||||
# CJK symbols and punctuation
|
# CJK symbols and punctuation
|
||||||
'\u3000\u3001\u3003'
|
"\u3000\u3001\u3003"
|
||||||
|
|
||||||
# CJK angle and corner brackets
|
# CJK angle and corner brackets
|
||||||
'\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011'
|
"\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011"
|
||||||
|
|
||||||
# CJK brackets and symbols/punctuation
|
# CJK brackets and symbols/punctuation
|
||||||
'\u3014\u3015\u3016\u3017\u3018\u3019\u301A\u301B\u301C\u301D\u301E\u301F'
|
"\u3014\u3015\u3016\u3017\u3018\u3019\u301A\u301B\u301C\u301D\u301E\u301F"
|
||||||
|
|
||||||
# Other CJK symbols
|
# Other CJK symbols
|
||||||
'\u3030'
|
"\u3030"
|
||||||
|
|
||||||
# Special CJK indicators
|
# Special CJK indicators
|
||||||
'\u303E\u303F'
|
"\u303E\u303F"
|
||||||
|
|
||||||
# Dashes
|
# Dashes
|
||||||
'\u2013\u2014'
|
"\u2013\u2014"
|
||||||
|
|
||||||
# Quotation marks and apostrophe
|
# Quotation marks and apostrophe
|
||||||
'\u2018\u2019\u201B\u201C\u201D\u201E\u201F'
|
"\u2018\u2019\u201B\u201C\u201D\u201E\u201F"
|
||||||
|
|
||||||
# General punctuation
|
# General punctuation
|
||||||
'\u2026\u2027'
|
"\u2026\u2027"
|
||||||
|
|
||||||
# Overscores and underscores
|
# Overscores and underscores
|
||||||
'\uFE4F'
|
"\uFE4F"
|
||||||
|
|
||||||
# Small form variants
|
# Small form variants
|
||||||
'\uFE51\uFE54'
|
"\uFE51\uFE54"
|
||||||
|
|
||||||
# Latin punctuation
|
# Latin punctuation
|
||||||
'\u00B7')
|
"\u00B7"
|
||||||
|
)
|
||||||
|
|
||||||
_chinese_stops = (
|
_chinese_stops = (
|
||||||
'\uFF01' # Fullwidth exclamation mark
|
"\uFF01" # Fullwidth exclamation mark
|
||||||
'\uFF1F' # Fullwidth question mark
|
"\uFF1F" # Fullwidth question mark
|
||||||
'\uFF61' # Halfwidth ideographic full stop
|
"\uFF61" # Halfwidth ideographic full stop
|
||||||
'\u3002' # Ideographic full stop
|
"\u3002" # Ideographic full stop
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,7 +93,7 @@ def html_escape(text):
|
||||||
|
|
||||||
|
|
||||||
def match_chinese(ch):
|
def match_chinese(ch):
|
||||||
if '\u4e00' <= ch <= '\u9fff':
|
if "\u4e00" <= ch <= "\u9fff":
|
||||||
return True
|
return True
|
||||||
if ch in _chinese_stops:
|
if ch in _chinese_stops:
|
||||||
return True
|
return True
|
||||||
|
@ -186,18 +176,17 @@ class InlineParser(object):
|
||||||
)
|
)
|
||||||
char_map = dict(chars)
|
char_map = dict(chars)
|
||||||
single_char = lines[index]
|
single_char = lines[index]
|
||||||
double_char = lines[index:index + 2]
|
double_char = lines[index : index + 2]
|
||||||
for char in chars:
|
for char in chars:
|
||||||
c1 = len(char[0]) == 1 and char[0] == single_char
|
c1 = len(char[0]) == 1 and char[0] == single_char
|
||||||
c2 = len(char[0]) == 2 and char[0] == double_char
|
c2 = len(char[0]) == 2 and char[0] == double_char
|
||||||
|
|
||||||
if c1 or c2:
|
if c1 or c2:
|
||||||
node, num = getattr(self, "parse_" + char_map[char[0]])(
|
node, num = getattr(self, "parse_" + char_map[char[0]])(index, lines)
|
||||||
index, lines)
|
|
||||||
if node:
|
if node:
|
||||||
return node, num
|
return node, num
|
||||||
|
|
||||||
if lines[index:index + 3] == "[fn":
|
if lines[index : index + 3] == "[fn":
|
||||||
node, num = self.parse_fn(index, lines)
|
node, num = self.parse_fn(index, lines)
|
||||||
if node:
|
if node:
|
||||||
return node, num
|
return node, num
|
||||||
|
@ -232,7 +221,7 @@ class InlineParser(object):
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{}({})'.format(self.__class__.__name__, self.content.strip())
|
return "{}({})".format(self.__class__.__name__, self.content.strip())
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
@ -308,7 +297,7 @@ class Verbatim(InlineParser):
|
||||||
class Underline(InlineParser):
|
class Underline(InlineParser):
|
||||||
def __init__(self, content):
|
def __init__(self, content):
|
||||||
super(Underline, self).__init__(content)
|
super(Underline, self).__init__(content)
|
||||||
self.element = "<span style=\"text-decoration:underline\">{0}</span>"
|
self.element = '<span style="text-decoration:underline">{0}</span>'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def match(cls, line, index):
|
def match(cls, line, index):
|
||||||
|
@ -350,9 +339,9 @@ class Link(InlineParser):
|
||||||
|
|
||||||
def to_html(self):
|
def to_html(self):
|
||||||
if self.is_img():
|
if self.is_img():
|
||||||
return "<img src=\"{0}\"/>".format(self.content)
|
return '<img src="{0}"/>'.format(self.content)
|
||||||
if self.is_vedio():
|
if self.is_vedio():
|
||||||
return "<video src=\"{0}\">{0}</video>".format(self.content)
|
return '<video src="{0}">{0}</video>'.format(self.content)
|
||||||
if self.desc:
|
if self.desc:
|
||||||
return '<a href="{0}">{1}</a>'.format(self.content, self.desc)
|
return '<a href="{0}">{1}</a>'.format(self.content, self.desc)
|
||||||
return '<a href="{0}">{1}</a>'.format(self.content, self.content)
|
return '<a href="{0}">{1}</a>'.format(self.content, self.content)
|
||||||
|
@ -361,7 +350,9 @@ class Link(InlineParser):
|
||||||
class Fn(InlineParser):
|
class Fn(InlineParser):
|
||||||
def __init__(self, content):
|
def __init__(self, content):
|
||||||
super(Fn, self).__init__(content)
|
super(Fn, self).__init__(content)
|
||||||
self.element = '<sup><a id="fnr:{0}" class="footref" href="#fn.{0}">{0}</a></sup>'
|
self.element = (
|
||||||
|
'<sup><a id="fnr:{0}" class="footref" href="#fn.{0}">{0}</a></sup>'
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def match(cls, line, index):
|
def match(cls, line, index):
|
||||||
|
|
|
@ -7,6 +7,8 @@ from typing import TypeVar
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Worker(Generic[T]):
|
class Worker(Generic[T]):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._loop = asyncio.get_event_loop()
|
self._loop = asyncio.get_event_loop()
|
||||||
|
|
|
@ -14,7 +14,6 @@ from app.database import SessionLocal
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def async_db_session():
|
async def async_db_session():
|
||||||
async with async_session() as session: # type: ignore
|
async with async_session() as session: # type: ignore
|
||||||
|
|
|
@ -64,7 +64,6 @@ class ActorFactory(factory.alchemy.SQLAlchemyModelFactory):
|
||||||
ap_id = "stub"
|
ap_id = "stub"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def inbox_prechecker(
|
async def inbox_prechecker(
|
||||||
request: fastapi.Request,
|
request: fastapi.Request,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
domain = "test.foxhole.me"
|
domain = "test.foxhole.me"
|
||||||
username = "testfox"
|
username = "testfox"
|
||||||
name = "test233"
|
name = "test233"
|
||||||
|
|
|
@ -18,7 +18,6 @@ from app import models
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_inbox_follow_request(
|
def test_inbox_follow_request(
|
||||||
db: Session,
|
db: Session,
|
||||||
client: TestClient,
|
client: TestClient,
|
||||||
|
@ -29,7 +28,7 @@ def test_inbox_follow_request(
|
||||||
ap_id = ra.ap_id # type: ignore
|
ap_id = ra.ap_id # type: ignore
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(ap_id).mock(return_value=httpx.Response(200,json=ra.ap_actor))
|
respx_mock.get(ap_id).mock(return_value=httpx.Response(200, json=ra.ap_actor))
|
||||||
respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
# send follower request
|
# send follower request
|
||||||
|
@ -103,7 +102,7 @@ def test_inbox_announce_activity(
|
||||||
app.dependency_overrides[precheck.inbox_prechecker] = factories.inbox_prechecker
|
app.dependency_overrides[precheck.inbox_prechecker] = factories.inbox_prechecker
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(ap_id).mock(return_value=httpx.Response(200,json=ra.ap_actor))
|
respx_mock.get(ap_id).mock(return_value=httpx.Response(200, json=ra.ap_actor))
|
||||||
respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
from app.models import now
|
from app.models import now
|
||||||
|
@ -135,7 +134,7 @@ def test_inbox_announce_activity(
|
||||||
ap_object=note_object,
|
ap_object=note_object,
|
||||||
ap_id=note_id,
|
ap_id=note_id,
|
||||||
ap_type="Note",
|
ap_type="Note",
|
||||||
visibility =VisibilityEnum.PUBLIC,
|
visibility=VisibilityEnum.PUBLIC,
|
||||||
)
|
)
|
||||||
|
|
||||||
db.add(outbox_object)
|
db.add(outbox_object)
|
||||||
|
@ -172,7 +171,7 @@ def test_inbox_like_activity(
|
||||||
app.dependency_overrides[precheck.inbox_prechecker] = factories.inbox_prechecker
|
app.dependency_overrides[precheck.inbox_prechecker] = factories.inbox_prechecker
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(ap_id).mock(return_value=httpx.Response(200,json=ra.ap_actor))
|
respx_mock.get(ap_id).mock(return_value=httpx.Response(200, json=ra.ap_actor))
|
||||||
respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
respx_mock.post(ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
from app.models import now
|
from app.models import now
|
||||||
|
@ -204,7 +203,7 @@ def test_inbox_like_activity(
|
||||||
ap_object=note_object,
|
ap_object=note_object,
|
||||||
ap_id=note_id,
|
ap_id=note_id,
|
||||||
ap_type="Note",
|
ap_type="Note",
|
||||||
visibility =VisibilityEnum.PUBLIC,
|
visibility=VisibilityEnum.PUBLIC,
|
||||||
)
|
)
|
||||||
|
|
||||||
db.add(outbox_object)
|
db.add(outbox_object)
|
||||||
|
|
|
@ -31,7 +31,6 @@ def test_index__ap(db: Session, client: TestClient, accept: str):
|
||||||
assert response.json() == ap.ME
|
assert response.json() == ap.ME
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_follow_only_status(
|
async def test_follow_only_status(
|
||||||
client: TestClient,
|
client: TestClient,
|
||||||
|
@ -43,15 +42,14 @@ async def test_follow_only_status(
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(remote_ap_id).mock(
|
respx_mock.get(remote_ap_id).mock(
|
||||||
return_value=httpx.Response(200,json=ra.ap_actor))
|
return_value=httpx.Response(200, json=ra.ap_actor)
|
||||||
respx_mock.post(remote_ap_id + "/inbox").mock(
|
)
|
||||||
return_value=httpx.Response(202))
|
respx_mock.post(remote_ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/outbox",
|
"/outbox",
|
||||||
headers={"Authorization": "Basic test-token"},
|
headers={"Authorization": "Basic test-token"},
|
||||||
content='{"visibility": "followers-only","content": "note content"}'
|
content='{"visibility": "followers-only","content": "note content"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -31,13 +31,13 @@ async def test_outbox_send_follow_request(
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(remote_ap_id).mock(
|
respx_mock.get(remote_ap_id).mock(
|
||||||
return_value=httpx.Response(200,json=ra.ap_actor))
|
return_value=httpx.Response(200, json=ra.ap_actor)
|
||||||
respx_mock.post(remote_ap_id + "/inbox").mock(
|
)
|
||||||
return_value=httpx.Response(202))
|
respx_mock.post(remote_ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
from app.boxes import send_follow
|
from app.boxes import send_follow
|
||||||
|
|
||||||
async with async_db_session as db_session: #type: ignore
|
async with async_db_session as db_session: # type: ignore
|
||||||
await send_follow(db_session, remote_ap_id)
|
await send_follow(db_session, remote_ap_id)
|
||||||
|
|
||||||
# And the Follow activity was created in the outbox
|
# And the Follow activity was created in the outbox
|
||||||
|
@ -81,13 +81,13 @@ async def test_outbox_send_follow_request_nest_accept(
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(remote_ap_id).mock(
|
respx_mock.get(remote_ap_id).mock(
|
||||||
return_value=httpx.Response(200,json=ra.ap_actor))
|
return_value=httpx.Response(200, json=ra.ap_actor)
|
||||||
respx_mock.post(remote_ap_id + "/inbox").mock(
|
)
|
||||||
return_value=httpx.Response(202))
|
respx_mock.post(remote_ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
from app.boxes import send_follow
|
from app.boxes import send_follow
|
||||||
|
|
||||||
async with async_db_session as db_session: #type: ignore
|
async with async_db_session as db_session: # type: ignore
|
||||||
await send_follow(db_session, remote_ap_id)
|
await send_follow(db_session, remote_ap_id)
|
||||||
|
|
||||||
# And the Follow activity was created in the outbox
|
# And the Follow activity was created in the outbox
|
||||||
|
@ -132,9 +132,9 @@ async def test_outbox_send_create_activity(
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(remote_ap_id).mock(
|
respx_mock.get(remote_ap_id).mock(
|
||||||
return_value=httpx.Response(200,json=ra.ap_actor))
|
return_value=httpx.Response(200, json=ra.ap_actor)
|
||||||
respx_mock.post(remote_ap_id + "/inbox").mock(
|
)
|
||||||
return_value=httpx.Response(202))
|
respx_mock.post(remote_ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
from app.boxes import _send_create
|
from app.boxes import _send_create
|
||||||
from app.activitypub import VisibilityEnum
|
from app.activitypub import VisibilityEnum
|
||||||
|
@ -145,12 +145,7 @@ async def test_outbox_send_create_activity(
|
||||||
content = "*Blod Text* =code Text= \n"
|
content = "*Blod Text* =code Text= \n"
|
||||||
content = to_html(content)
|
content = to_html(content)
|
||||||
|
|
||||||
await _send_create(
|
await _send_create(db_session, "Note", content, VisibilityEnum.PUBLIC)
|
||||||
db_session,
|
|
||||||
"Note",
|
|
||||||
content,
|
|
||||||
VisibilityEnum.PUBLIC
|
|
||||||
)
|
|
||||||
|
|
||||||
# And the Follow activity was created in the outbox
|
# And the Follow activity was created in the outbox
|
||||||
outbox_object = db.execute(select(models.OutboxObject)).scalar_one()
|
outbox_object = db.execute(select(models.OutboxObject)).scalar_one()
|
||||||
|
@ -171,17 +166,16 @@ async def test_outbox_send_unlisted_note(
|
||||||
|
|
||||||
# mock request
|
# mock request
|
||||||
respx_mock.get(remote_ap_id).mock(
|
respx_mock.get(remote_ap_id).mock(
|
||||||
return_value=httpx.Response(200,json=ra.ap_actor))
|
return_value=httpx.Response(200, json=ra.ap_actor)
|
||||||
respx_mock.post(remote_ap_id + "/inbox").mock(
|
)
|
||||||
return_value=httpx.Response(202))
|
respx_mock.post(remote_ap_id + "/inbox").mock(return_value=httpx.Response(202))
|
||||||
|
|
||||||
from app.activitypub import VisibilityEnum
|
from app.activitypub import VisibilityEnum
|
||||||
|
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
"/outbox",
|
"/outbox",
|
||||||
headers={"Authorization": "Basic test-token"},
|
headers={"Authorization": "Basic test-token"},
|
||||||
content='{"visibility": "unlisted","content": "note content"}'
|
content='{"visibility": "unlisted","content": "note content"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
@ -4,7 +4,6 @@ from app.main import app
|
||||||
from app.utils import precheck
|
from app.utils import precheck
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def build_remote_actor():
|
def build_remote_actor():
|
||||||
ra = factories.RemoteActorFactory(
|
ra = factories.RemoteActorFactory(
|
||||||
base_url="https://example.com",
|
base_url="https://example.com",
|
||||||
|
@ -12,7 +11,6 @@ def build_remote_actor():
|
||||||
public_key="pk",
|
public_key="pk",
|
||||||
)
|
)
|
||||||
|
|
||||||
app.dependency_overrides[precheck.inbox_prechecker] = \
|
app.dependency_overrides[precheck.inbox_prechecker] = factories.inbox_prechecker
|
||||||
factories.inbox_prechecker
|
|
||||||
|
|
||||||
return ra
|
return ra
|
||||||
|
|
Loading…
Reference in a new issue