@@ -49,6 +49,8 @@ export {
4949const DEFAULT_MIN_CONFIDENCE = 0.5
5050const ACTIVITY_KEYWORD_BOOST = 0.15
5151const URL_SUGGESTION_BOOST = 0.25
52+ const BARE_WEBSITE_URL_CONFIDENCE = 0.55
53+ const REFERENCE_WEBSITE_DOMAINS = [ 'wikipedia.org' , 'wikimedia.org' ]
5254
5355/**
5456 * Check if content contains activity-related keywords.
@@ -225,15 +227,49 @@ function findBestUrl(urls: readonly string[]): BestUrl {
225227 return { type : bestType , confidence : bestConfidence }
226228}
227229
228- function shouldIncludeUrl ( firstUrl : string , content : string ) : boolean {
230+ function isBareUrlMessage ( content : string , urls : readonly string [ ] ) : boolean {
231+ let remaining = content
232+ for ( const url of urls ) {
233+ remaining = remaining . replaceAll ( url , ' ' )
234+ }
235+
236+ return remaining . replace ( / [ \s . , ; : ! ? ( ) [ \] { } ' " “ ” ‘ ’ < > - ] / g, '' ) . length === 0
237+ }
238+
239+ function isReferenceWebsiteUrl ( url : string ) : boolean {
240+ try {
241+ const hostname = new URL ( url ) . hostname . toLowerCase ( )
242+ return REFERENCE_WEBSITE_DOMAINS . some (
243+ ( domain ) => hostname === domain || hostname . endsWith ( `.${ domain } ` )
244+ )
245+ } catch {
246+ return false
247+ }
248+ }
249+
250+ function shouldIncludeUrl ( firstUrl : string , content : string , urls : readonly string [ ] ) : boolean {
229251 // Skip social media URLs - they could be anything (memes, random videos)
230252 if ( isSocialUrl ( firstUrl ) ) return false
231253 // Include activity URLs or messages with activity phrases
232- return isActivityUrl ( firstUrl ) || hasActivityPhrase ( content )
254+ return (
255+ isActivityUrl ( firstUrl ) ||
256+ hasActivityPhrase ( content ) ||
257+ ( classifyUrl ( firstUrl ) === 'website' &&
258+ ! isReferenceWebsiteUrl ( firstUrl ) &&
259+ isBareUrlMessage ( content , urls ) )
260+ )
233261}
234262
235- function applyUrlBoosts ( confidence : number , content : string ) : number {
263+ function applyUrlBoosts (
264+ confidence : number ,
265+ content : string ,
266+ urlType : string ,
267+ urls : readonly string [ ]
268+ ) : number {
236269 let result = confidence
270+ if ( urlType === 'website' && isBareUrlMessage ( content , urls ) ) {
271+ result = Math . max ( result , BARE_WEBSITE_URL_CONFIDENCE )
272+ }
237273 if ( hasActivityPhrase ( content ) ) {
238274 result = Math . min ( 1.0 , result + URL_SUGGESTION_BOOST )
239275 }
@@ -262,10 +298,10 @@ function findUrlMatches(
262298 if ( ! msg || ! msg . urls || msg . urls . length === 0 ) continue
263299
264300 const firstUrl = msg . urls [ 0 ] ?? ''
265- if ( ! shouldIncludeUrl ( firstUrl , msg . content ) ) continue
301+ if ( ! shouldIncludeUrl ( firstUrl , msg . content , msg . urls ) ) continue
266302
267303 const best = findBestUrl ( msg . urls )
268- const confidence = applyUrlBoosts ( best . confidence , msg . content )
304+ const confidence = applyUrlBoosts ( best . confidence , msg . content , best . type , msg . urls )
269305
270306 if ( confidence >= minConfidence ) {
271307 const ctx = getMessageContext ( messages , i )
@@ -276,7 +312,7 @@ function findUrlMatches(
276312 timestamp : msg . timestamp ,
277313 confidence,
278314 urlType : best . type ,
279- candidateType : 'suggestion' , // Sharing a URL = suggesting
315+ candidateType : 'suggestion' ,
280316 urls : msg . urls ,
281317 contextBefore : ctx . before ,
282318 contextAfter : ctx . after
0 commit comments