@@ -4,12 +4,12 @@ import { Logger } from "../index.js";
44export class NewPlacesService {
55 private client : PlacesClient ;
66 private readonly defaultLanguage : string = "en" ;
7-
7+ private readonly placeFieldMask : string = [ "displayName" , "name" , "id" , "formattedAddress" , "location" , "utcOffsetMinutes" , "regularOpeningHours.periods" , "regularOpeningHours.weekdayDescriptions" , "currentOpeningHours.openNow" , "nationalPhoneNumber" , "websiteUri" , "priceLevel" , "rating" , "userRatingCount" , "reviews.rating" , "reviews.text" , "reviews.publishTime" , "reviews.authorAttribution.displayName" , "photos.heightPx" , "photos.widthPx" , "photos.name" ] . join ( "," ) ;
88 constructor ( apiKey ?: string ) {
99 this . client = new PlacesClient ( {
1010 apiKey : apiKey || process . env . GOOGLE_MAPS_API_KEY || "" ,
1111 } ) ;
12-
12+
1313 if ( ! apiKey && ! process . env . GOOGLE_MAPS_API_KEY ) {
1414 throw new Error ( "Google Maps API Key is required" ) ;
1515 }
@@ -18,11 +18,20 @@ export class NewPlacesService {
1818 async getPlaceDetails ( placeId : string ) {
1919 try {
2020 const placeName = `places/${ placeId } ` ;
21-
22- const [ place ] = await this . client . getPlace ( {
23- name : placeName ,
24- languageCode : this . defaultLanguage ,
25- } ) ;
21+
22+ const [ place ] = await this . client . getPlace (
23+ {
24+ name : placeName ,
25+ languageCode : this . defaultLanguage ,
26+ } ,
27+ {
28+ otherArgs : {
29+ headers : {
30+ "X-Goog-FieldMask" : this . placeFieldMask ,
31+ } ,
32+ } ,
33+ }
34+ ) ;
2635
2736 return this . transformPlaceResponse ( place ) ;
2837 } catch ( error : any ) {
@@ -34,7 +43,7 @@ export class NewPlacesService {
3443 private transformPlaceResponse ( place : any ) {
3544 return {
3645 name : place . displayName ?. text || place . name || "" ,
37- place_id : place . id || "" ,
46+ place_id : this . extractLegacyPlaceId ( place ) ,
3847 formatted_address : place . formattedAddress || "" ,
3948 geometry : {
4049 location : {
@@ -44,65 +53,155 @@ export class NewPlacesService {
4453 } ,
4554 rating : place . rating || 0 ,
4655 user_ratings_total : place . userRatingCount || 0 ,
47- opening_hours : place . regularOpeningHours ? {
48- open_now : this . isCurrentlyOpen ( place . regularOpeningHours ) ,
49- weekday_text : this . formatOpeningHours ( place . regularOpeningHours ) ,
50- } : undefined ,
56+ opening_hours : place . regularOpeningHours
57+ ? {
58+ open_now : this . isCurrentlyOpen ( place . regularOpeningHours , place . utcOffsetMinutes , place . currentOpeningHours ) ,
59+ weekday_text : this . formatOpeningHours ( place . regularOpeningHours ) ,
60+ }
61+ : undefined ,
5162 formatted_phone_number : place . nationalPhoneNumber || "" ,
5263 website : place . websiteUri || "" ,
5364 price_level : place . priceLevel || 0 ,
54- reviews : place . reviews ?. map ( ( review : any ) => ( {
55- rating : review . rating || 0 ,
56- text : review . text ?. text || "" ,
57- time : review . publishTime ?. seconds || 0 ,
58- author_name : review . authorAttribution ?. displayName || "" ,
59- } ) ) || [ ] ,
60- photos : place . photos ?. map ( ( photo : any ) => ( {
61- photo_reference : photo . name || "" ,
62- height : photo . heightPx || 0 ,
63- width : photo . widthPx || 0 ,
64- } ) ) || [ ] ,
65+ reviews :
66+ place . reviews ?. map ( ( review : any ) => ( {
67+ rating : review . rating || 0 ,
68+ text : review . text ?. text || "" ,
69+ time : review . publishTime ?. seconds || 0 ,
70+ author_name : review . authorAttribution ?. displayName || "" ,
71+ } ) ) || [ ] ,
72+ photos :
73+ place . photos ?. map ( ( photo : any ) => ( {
74+ photo_reference : photo . name || "" ,
75+ height : photo . heightPx || 0 ,
76+ width : photo . widthPx || 0 ,
77+ } ) ) || [ ] ,
6578 } ;
6679 }
6780
68- private isCurrentlyOpen ( openingHours : any ) : boolean {
69- if ( ! openingHours ?. weekdayDescriptions ) {
81+ private extractLegacyPlaceId ( place : any ) : string {
82+ const resourceName = place ?. name ;
83+
84+ if ( typeof resourceName === "string" && resourceName . startsWith ( "places/" ) ) {
85+ const legacyId = resourceName . substring ( "places/" . length ) ;
86+ if ( legacyId ) {
87+ return legacyId ;
88+ }
89+ }
90+
91+ return place ?. id || "" ;
92+ }
93+
94+ private isCurrentlyOpen ( openingHours : any , utcOffsetMinutes ?: number , currentOpeningHours ?: any ) : boolean {
95+ if ( typeof currentOpeningHours ?. openNow === "boolean" ) {
96+ return currentOpeningHours . openNow ;
97+ }
98+
99+ if ( typeof openingHours ?. openNow === "boolean" ) {
100+ return openingHours . openNow ;
101+ }
102+
103+ const periods = openingHours ?. periods ;
104+
105+ if ( ! Array . isArray ( periods ) || periods . length === 0 ) {
70106 return false ;
71107 }
72108
73- const now = new Date ( ) ;
74- const currentDay = now . getDay ( ) ;
75- const currentTime = now . getHours ( ) * 60 + now . getMinutes ( ) ;
109+ const minutesInDay = 24 * 60 ;
110+ const minutesInWeek = minutesInDay * 7 ;
111+
112+ const { day : localDay , minutes : localMinutes } = this . getLocalTimeComponents ( utcOffsetMinutes ) ;
113+ const localTimeValue = localDay * minutesInDay + localMinutes ;
76114
77115 const dayMapping = {
78- " SUNDAY" : 0 ,
79- " MONDAY" : 1 ,
80- " TUESDAY" : 2 ,
81- " WEDNESDAY" : 3 ,
82- " THURSDAY" : 4 ,
83- " FRIDAY" : 5 ,
84- " SATURDAY" : 6 ,
116+ SUNDAY : 0 ,
117+ MONDAY : 1 ,
118+ TUESDAY : 2 ,
119+ WEDNESDAY : 3 ,
120+ THURSDAY : 4 ,
121+ FRIDAY : 5 ,
122+ SATURDAY : 6 ,
85123 } ;
86124
87- const todayHours = openingHours . weekdayDescriptions . find ( ( desc : string ) => {
88- const dayName = Object . keys ( dayMapping ) . find ( day =>
89- desc . toUpperCase ( ) . includes ( day )
90- ) ;
91- return dayName && dayMapping [ dayName as keyof typeof dayMapping ] === currentDay ;
92- } ) ;
125+ const toDayNumber = ( value : any ) : number | undefined => {
126+ if ( typeof value === "number" && value >= 0 && value <= 6 ) {
127+ return value ;
128+ }
129+ if ( typeof value === "string" ) {
130+ const normalized = value . toUpperCase ( ) ;
131+ if ( normalized in dayMapping ) {
132+ return dayMapping [ normalized as keyof typeof dayMapping ] ;
133+ }
134+ }
135+ return undefined ;
136+ } ;
93137
94- if ( ! todayHours ) {
95- return false ;
96- }
138+ const toMinutes = ( time : any ) : number | undefined => {
139+ if ( ! time ) {
140+ return undefined ;
141+ }
97142
98- if ( todayHours . toLowerCase ( ) . includes ( "closed" ) ) {
99- return false ;
143+ const hours = typeof time . hours === "number" ? time . hours : Number ( time . hours ?? NaN ) ;
144+ const minutes = typeof time . minutes === "number" ? time . minutes : Number ( time . minutes ?? NaN ) ;
145+
146+ if ( ! Number . isFinite ( hours ) || ! Number . isFinite ( minutes ) ) {
147+ return undefined ;
148+ }
149+
150+ return hours * 60 + minutes ;
151+ } ;
152+
153+ for ( const period of periods ) {
154+ const openDay = toDayNumber ( period ?. openDay ) ;
155+ const closeDay = toDayNumber ( period ?. closeDay ?? period ?. openDay ) ;
156+ const openMinutes = toMinutes ( period ?. openTime ) ;
157+ const closeMinutes = toMinutes ( period ?. closeTime ) ;
158+
159+ if ( openDay === undefined || openMinutes === undefined ) {
160+ continue ;
161+ }
162+
163+ let start = openDay * minutesInDay + openMinutes ;
164+ let end : number ;
165+
166+ if ( closeDay === undefined || closeMinutes === undefined ) {
167+ end = start + minutesInDay ;
168+ } else {
169+ end = closeDay * minutesInDay + closeMinutes ;
170+ }
171+
172+ if ( end <= start ) {
173+ end += minutesInWeek ;
174+ }
175+
176+ let comparableLocalTime = localTimeValue ;
177+ while ( comparableLocalTime < start ) {
178+ comparableLocalTime += minutesInWeek ;
179+ }
180+
181+ if ( comparableLocalTime >= start && comparableLocalTime < end ) {
182+ return true ;
183+ }
100184 }
101- if ( todayHours . toLowerCase ( ) . includes ( "24 hours" ) ) {
102- return true ;
185+
186+ return false ;
187+ }
188+
189+ private getLocalTimeComponents ( utcOffsetMinutes ?: number ) : { day : number ; minutes : number } {
190+ const now = new Date ( ) ;
191+
192+ if ( typeof utcOffsetMinutes === "number" && Number . isFinite ( utcOffsetMinutes ) ) {
193+ const localTime = new Date ( now . getTime ( ) + utcOffsetMinutes * 60000 ) ;
194+
195+ return {
196+ day : localTime . getUTCDay ( ) ,
197+ minutes : localTime . getUTCHours ( ) * 60 + localTime . getUTCMinutes ( ) ,
198+ } ;
103199 }
104200
105- return true ;
201+ return {
202+ day : now . getDay ( ) ,
203+ minutes : now . getHours ( ) * 60 + now . getMinutes ( ) ,
204+ } ;
106205 }
107206
108207 private formatOpeningHours ( openingHours : any ) : string [ ] {
@@ -111,11 +210,11 @@ export class NewPlacesService {
111210
112211 private extractErrorMessage ( error : any ) : string {
113212 const apiError = error ?. message || error ?. details || error ?. status ;
114-
213+
115214 if ( apiError ) {
116215 return `${ apiError } ` ;
117216 }
118217
119218 return error instanceof Error ? error . message : String ( error ) ;
120219 }
121- }
220+ }
0 commit comments