2024-08-18 05:53:14 +00:00
""" Protocol-independent code for sending and receiving DMs aka chat messages. """
import logging
2024-08-18 15:28:11 +00:00
from granary import as1
2024-08-18 05:53:14 +00:00
from oauth_dropins . webutil import util
import common
import models
2024-08-18 15:28:11 +00:00
import protocol
2024-08-18 05:53:14 +00:00
logger = logging . getLogger ( __name__ )
2024-08-19 20:55:05 +00:00
def maybe_send ( * , from_proto , to_user , text , type = None ) :
""" Sends a DM.
2024-08-18 05:53:14 +00:00
Creates a task to send the DM asynchronously .
2024-08-19 20:55:05 +00:00
If ` ` type ` ` is provided , and we ' ve already sent this user a DM of this type
from this protocol , does nothing .
2024-08-18 05:53:14 +00:00
Args :
from_proto ( protocol . Protocol )
to_user ( models . User )
text ( str ) : message content . May be HTML .
2024-08-19 20:55:05 +00:00
type ( str ) : optional , one of DM . TYPES
2024-08-18 05:53:14 +00:00
"""
2024-08-19 20:55:05 +00:00
if type :
dm = models . DM ( protocol = from_proto . LABEL , type = type )
if dm in to_user . sent_dms :
return
2024-08-18 05:53:14 +00:00
from web import Web
bot = Web . get_by_id ( from_proto . bot_user_id ( ) )
logger . info ( f ' Sending DM from { bot . key . id ( ) } to { to_user . key . id ( ) } : { text } ' )
if not to_user . obj or not to_user . obj . as1 :
logger . info ( " can ' t send DM, recipient has no profile obj " )
return
2024-08-19 20:55:05 +00:00
id = f ' { bot . profile_id ( ) } # { type or " ? " } -dm- { to_user . key . id ( ) } - { util . now ( ) . isoformat ( ) } '
2024-08-18 05:53:14 +00:00
target_uri = to_user . target_for ( to_user . obj , shared = False )
target = models . Target ( protocol = to_user . LABEL , uri = target_uri )
obj_key = models . Object ( id = id , source_protocol = ' web ' , undelivered = [ target ] ,
our_as1 = {
' objectType ' : ' activity ' ,
' verb ' : ' post ' ,
' id ' : f ' { id } -create ' ,
' actor ' : bot . key . id ( ) ,
' object ' : {
' objectType ' : ' note ' ,
' id ' : id ,
' author ' : bot . key . id ( ) ,
' content ' : text ,
' tags ' : [ {
' objectType ' : ' mention ' ,
' url ' : to_user . key . id ( ) ,
} ] ,
' to ' : [ to_user . key . id ( ) ] ,
} ,
' to ' : [ to_user . key . id ( ) ] ,
} ) . put ( )
common . create_task ( queue = ' send ' , obj = obj_key . urlsafe ( ) , protocol = to_user . LABEL ,
url = target . uri , user = bot . key . urlsafe ( ) )
2024-08-19 20:55:05 +00:00
if type :
to_user . sent_dms . append ( dm )
to_user . put ( )
2024-08-18 05:53:14 +00:00
2024-08-18 15:28:11 +00:00
def receive ( * , from_user , obj ) :
""" Handles a DM that a user sent to one of our protocol bot users.
Args :
from_user ( models . User )
obj ( Object ) : DM
Returns :
( str , int ) tuple : ( response body , HTTP status code ) Flask response
"""
recip = as1 . recipient_if_dm ( obj . as1 )
assert recip
to_proto = protocol . Protocol . for_bridgy_subdomain ( recip )
assert to_proto # already checked in check_supported call in Protocol.receive
inner_obj = ( as1 . get_object ( obj . as1 ) if as1 . object_type ( obj . as1 ) == ' post '
else obj . as1 )
logger . info ( f ' got DM from { from_user . key . id ( ) } to { to_proto . LABEL } : { inner_obj . get ( " content " ) } ' )
2024-08-19 20:26:45 +00:00
# remove @-mentions in HTML links
2024-08-18 15:28:11 +00:00
soup = util . parse_html ( inner_obj . get ( ' content ' , ' ' ) )
for link in soup . find_all ( ' a ' ) :
link . extract ( )
content = soup . get_text ( ) . strip ( ) . lower ( )
2024-08-19 20:26:45 +00:00
# parse and handle message
2024-08-18 15:28:11 +00:00
if content in ( ' yes ' , ' ok ' ) :
from_user . enable_protocol ( to_proto )
to_proto . bot_follow ( from_user )
2024-08-19 20:26:45 +00:00
return ' OK ' , 200
2024-08-18 15:28:11 +00:00
elif content == ' no ' :
to_proto . delete_user_copy ( from_user )
from_user . disable_protocol ( to_proto )
2024-08-19 20:26:45 +00:00
return ' OK ' , 200
2024-08-20 03:36:07 +00:00
# request a user
2024-08-19 20:26:45 +00:00
elif to_proto . owns_handle ( content ) is not False :
2024-08-20 03:36:07 +00:00
def error_reply ( text , type = None ) :
maybe_send ( from_proto = to_proto , to_user = from_user , text = text , type = type )
2024-08-19 21:05:01 +00:00
return ' OK ' , 200
2024-08-20 03:36:07 +00:00
if not from_user . is_enabled ( to_proto ) :
return error_reply ( f ' Please bridge your account to { to_proto . PHRASE } by following this account before requesting another user. ' )
2024-08-19 20:26:45 +00:00
if to_id := to_proto . handle_to_id ( content ) :
2024-08-20 03:36:07 +00:00
handle = content
2024-08-19 20:26:45 +00:00
if to_user := to_proto . get_or_create ( to_id ) :
2024-08-20 03:36:07 +00:00
from_proto = from_user . __class__
if not to_user . obj :
# doesn't exist
return error_reply ( f " Couldn ' t find { to_proto . PHRASE } user { handle } " )
elif to_user . is_enabled ( from_proto ) :
# already bridged
return error_reply ( f ' { to_user . user_link ( handle = True , maybe_internal_link = False ) } is already bridged into { from_proto . PHRASE } . ' )
elif ( models . DM ( protocol = from_proto . LABEL , type = ' request_bridging ' )
in to_user . sent_dms ) :
# already requested
return error_reply ( f ' That user is already bridged into { from_proto . PHRASE } : { to_user . user_link ( handle = True , maybe_internal_link = False ) } ' )
maybe_send ( from_proto = from_proto , to_user = to_user ,
2024-08-19 20:26:45 +00:00
type = ' request_bridging ' , text = f """ \
2024-08-20 03:36:07 +00:00
< p > Hi ! { obj . actor_link ( image = False ) } ( { from_user . handle_as ( to_proto ) } ) is using Bridgy Fed to bridge their account on { from_proto . PHRASE } into { to_proto . PHRASE } here , and they ' d like to follow you. To let {from_proto.PHRASE} users see and interact with you, follow this account. <a href= " https://fed.brid.gy/docs " >See the docs</a> for more information.
< p > If you do nothing , your account won ' t be bridged, and users on {from_proto.PHRASE} won ' t be able to see or interact with you .
2024-08-19 20:26:45 +00:00
< p > Bridgy Fed will only send you this message once . """ )
return ' OK ' , 200
2024-08-18 15:28:11 +00:00
2024-08-19 21:05:01 +00:00
return " Couldn ' t understand DM: foo bar " , 304