@@ -4,7 +4,8 @@ import { BaseConnection } from "./BaseConnection";
44import { IConnection , IConnectionState } from "." ;
55import { Connection , InstantiateConnectionOpts , ProvisionConnectionOpts } from "./IConnection" ;
66import { CommandError } from "../errors" ;
7-
7+ import { IBridgeStorageProvider } from "../Stores/StorageProvider" ;
8+ import { Logger } from "matrix-appservice-bridge" ;
89export interface HoundConnectionState extends IConnectionState {
910 challengeId : string ;
1011}
@@ -14,20 +15,44 @@ export interface HoundPayload {
1415 challengeId : string ,
1516}
1617
18+ /**
19+ * @url https://documenter.getpostman.com/view/22349866/UzXLzJUV#0913e0b9-9cb5-440e-9d8d-bf6430285ee9
20+ */
1721export interface HoundActivity {
18- id : string ;
19- distance : number ; // in meters
20- duration : number ;
21- elevation : number ;
22- createdAt : string ;
23- activityType : string ;
24- activityName : string ;
25- user : {
26- id : string ;
27- fullname : string ;
28- fname : string ;
29- lname : string ;
30- }
22+ userId : string ,
23+ activityId : string ,
24+ participant : string ,
25+ /**
26+ * @example "07/26/2022"
27+ */
28+ date : string ,
29+ /**
30+ * @example "2022-07-26T13:49:22Z"
31+ */
32+ datetime : string ,
33+ name : string ,
34+ type : string ,
35+ /**
36+ * @example strava
37+ */
38+ app : string ,
39+ durationSeconds : number ,
40+ /**
41+ * @example "1.39"
42+ */
43+ distanceKilometers : string ,
44+ /**
45+ * @example "0.86"
46+ */
47+ distanceMiles : string ,
48+ /**
49+ * @example "0.86"
50+ */
51+ elevationMeters : string ,
52+ /**
53+ * @example "0.86"
54+ */
55+ elevationFeet : string ,
3156}
3257
3358export interface IChallenge {
@@ -76,6 +101,7 @@ function getEmojiForType(type: string) {
76101 }
77102}
78103
104+ const log = new Logger ( "HoundConnection" ) ;
79105const md = markdownit ( ) ;
80106@Connection
81107export class HoundConnection extends BaseConnection implements IConnection {
@@ -95,12 +121,12 @@ export class HoundConnection extends BaseConnection implements IConnection {
95121
96122 public static validateState ( data : Record < string , unknown > ) : HoundConnectionState {
97123 // Convert URL to ID.
98- if ( ! data . challengeId && data . url && data . url === "string" ) {
124+ if ( ! data . challengeId && data . url && typeof data . url === "string" ) {
99125 data . challengeId = this . getIdFromURL ( data . url ) ;
100126 }
101127
102128 // Test for v1 uuid.
103- if ( ! data . challengeId || typeof data . challengeId !== "string" || / ^ \w { 8 } (?: - \w { 4 } ) { 3 } - \w { 12 } $ / . test ( data . challengeId ) ) {
129+ if ( ! data . challengeId || typeof data . challengeId !== "string" || ! / ^ \w { 8 } (?: - \w { 4 } ) { 3 } - \w { 12 } $ / . test ( data . challengeId ) ) {
104130 throw Error ( 'Missing or invalid id' ) ;
105131 }
106132
@@ -109,14 +135,14 @@ export class HoundConnection extends BaseConnection implements IConnection {
109135 }
110136 }
111137
112- public static createConnectionForState ( roomId : string , event : StateEvent < Record < string , unknown > > , { config, intent} : InstantiateConnectionOpts ) {
138+ public static createConnectionForState ( roomId : string , event : StateEvent < Record < string , unknown > > , { config, intent, storage } : InstantiateConnectionOpts ) {
113139 if ( ! config . challengeHound ) {
114140 throw Error ( 'Challenge hound is not configured' ) ;
115141 }
116- return new HoundConnection ( roomId , event . stateKey , this . validateState ( event . content ) , intent ) ;
142+ return new HoundConnection ( roomId , event . stateKey , this . validateState ( event . content ) , intent , storage ) ;
117143 }
118144
119- static async provisionConnection ( roomId : string , _userId : string , data : Record < string , unknown > = { } , { intent, config} : ProvisionConnectionOpts ) {
145+ static async provisionConnection ( roomId : string , _userId : string , data : Record < string , unknown > = { } , { intent, config, storage } : ProvisionConnectionOpts ) {
120146 if ( ! config . challengeHound ) {
121147 throw Error ( 'Challenge hound is not configured' ) ;
122148 }
@@ -127,7 +153,7 @@ export class HoundConnection extends BaseConnection implements IConnection {
127153 throw new CommandError ( `Fetch failed, status ${ statusDataRequest . status } ` , "Challenge could not be found. Is it active?" ) ;
128154 }
129155 const { challengeName } = await statusDataRequest . json ( ) as { challengeName : string } ;
130- const connection = new HoundConnection ( roomId , validState . challengeId , validState , intent ) ;
156+ const connection = new HoundConnection ( roomId , validState . challengeId , validState , intent , storage ) ;
131157 await intent . underlyingClient . sendStateEvent ( roomId , HoundConnection . CanonicalEventType , validState . challengeId , validState ) ;
132158 return {
133159 connection,
@@ -140,7 +166,8 @@ export class HoundConnection extends BaseConnection implements IConnection {
140166 roomId : string ,
141167 stateKey : string ,
142168 private state : HoundConnectionState ,
143- private readonly intent : Intent ) {
169+ private readonly intent : Intent ,
170+ private readonly storage : IBridgeStorageProvider ) {
144171 super ( roomId , stateKey , HoundConnection . CanonicalEventType )
145172 }
146173
@@ -156,25 +183,41 @@ export class HoundConnection extends BaseConnection implements IConnection {
156183 return this . state . priority || super . priority ;
157184 }
158185
159- public async handleNewActivity ( payload : HoundActivity ) {
160- const distance = `${ ( payload . distance / 1000 ) . toFixed ( 2 ) } km` ;
161- const emoji = getEmojiForType ( payload . activityType ) ;
162- const body = `🎉 **${ payload . user . fullname } ** completed a ${ distance } ${ emoji } ${ payload . activityType } (${ payload . activityName } )` ;
163- const content : any = {
186+ public async handleNewActivity ( activity : HoundActivity ) {
187+ log . info ( `New activity recorded ${ activity . activityId } ` ) ;
188+ const existingActivityEventId = await this . storage . getHoundActivity ( this . challengeId , activity . activityId ) ;
189+ const distance = parseFloat ( activity . distanceKilometers ) ;
190+ const distanceUnits = `${ ( distance ) . toFixed ( 2 ) } km` ;
191+ const emoji = getEmojiForType ( activity . type ) ;
192+ const body = `🎉 **${ activity . participant } ** completed a ${ distanceUnits } ${ emoji } ${ activity . type } (${ activity . name } )` ;
193+ let content : any = {
164194 body,
165195 format : "org.matrix.custom.html" ,
166196 formatted_body : md . renderInline ( body ) ,
167197 } ;
168198 content [ "msgtype" ] = "m.notice" ;
169- content [ "uk.half-shot.matrix-challenger.activity.id" ] = payload . id ;
170- content [ "uk.half-shot.matrix-challenger.activity.distance" ] = Math . round ( payload . distance ) ;
171- content [ "uk.half-shot.matrix-challenger.activity.elevation" ] = Math . round ( payload . elevation ) ;
172- content [ "uk.half-shot.matrix-challenger.activity.duration" ] = Math . round ( payload . duration ) ;
199+ content [ "uk.half-shot.matrix-challenger.activity.id" ] = activity . activityId ;
200+ content [ "uk.half-shot.matrix-challenger.activity.distance" ] = Math . round ( distance * 1000 ) ;
201+ content [ "uk.half-shot.matrix-challenger.activity.elevation" ] = Math . round ( parseFloat ( activity . elevationMeters ) ) ;
202+ content [ "uk.half-shot.matrix-challenger.activity.duration" ] = Math . round ( activity . durationSeconds ) ;
173203 content [ "uk.half-shot.matrix-challenger.activity.user" ] = {
174- "name" : payload . user . fullname ,
175- id : payload . user . id ,
204+ "name" : activity . participant ,
205+ id : activity . userId ,
176206 } ;
177- await this . intent . underlyingClient . sendMessage ( this . roomId , content ) ;
207+ if ( existingActivityEventId ) {
208+ log . debug ( `Updating existing activity ${ activity . activityId } ${ existingActivityEventId } ` ) ;
209+ content = {
210+ body : `* ${ content . body } ` ,
211+ msgtype : "m.notice" ,
212+ "m.new_content" : content ,
213+ "m.relates_to" : {
214+ "event_id" : existingActivityEventId ,
215+ "rel_type" : "m.replace"
216+ } ,
217+ } ;
218+ }
219+ const eventId = await this . intent . underlyingClient . sendMessage ( this . roomId , content ) ;
220+ await this . storage . storeHoundActivityEvent ( this . challengeId , activity . activityId , eventId ) ;
178221 }
179222
180223 public toString ( ) {
0 commit comments