Skip to content

Commit

Permalink
Merge #581
Browse files Browse the repository at this point in the history
581: 578 consider to add geo service r=myConsciousness a=myConsciousness

# 1. Description

<!-- Provide a description of what this PR is doing.
If you're modifying existing behavior, describe the existing behavior, how this PR is changing it,
and what motivated the change. If this is a breaking change, specify explicitly which APIs have been
changed. -->

## 1.1. Checklist

<!-- Before you create this PR confirm that it meets all requirements listed below by checking the
relevant checkboxes (`[x]`). This will ensure a smooth and quick review process. -->

- [x] The title of my PR starts with a [Conventional Commit] prefix (`fix:`, `feat:`, `docs:` etc).
- [x] I have read the [Contributor Guide] and followed the process outlined for submitting PRs.
- [x] I have updated/added tests for ALL new/updated/fixed functionality.
- [x] I have updated/added relevant documentation in `docs` and added dartdoc comments with `///`.
- [x] I have updated/added relevant examples in `examples`.

## 1.2. Breaking Change

<!-- Does your PR require users to manually update their apps to accommodate your change?

If the PR is a breaking change this should be indicated with suffix "!"  (for example, `feat!:`, `fix!:`). See [Conventional Commit] for details.
-->

- [ ] Yes, this is a breaking change.
- [x] No, this is _not_ a breaking change.

## 1.3. Related Issues

<!-- Provide a list of issues related to this PR from the [issue database].
Indicate which of these issues are resolved or fixed by this PR, i.e. Fixes #xxxx* !-->

<!-- Links -->

[issue database]: https://github.com/twitter-dart/twitter-api-v2/issues
[contributor guide]: https://github.com/twitter-dart/twitter-api-v2/blob/main/CONTRIBUTING.md
[style guide]: https://github.com/twitter-dart/twitter-api-v2/blob/main/STYLEGUIDE.md
[conventional commit]: https://conventionalcommits.org


Co-authored-by: myConsciousness <[email protected]>
  • Loading branch information
bors[bot] and myConsciousness authored Nov 28, 2022
2 parents a8f4df6 + b817efa commit fb61628
Show file tree
Hide file tree
Showing 50 changed files with 2,174 additions and 115 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"DDTHH",
"Decahose",
"Elon",
"geocode",
"hashtagged",
"hmac",
"Kato",
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Note

## v4.5.0

- Supported `Geo` service. ([#578](https://github.com/twitter-dart/twitter-api-v2/issues/578))
- `GET /1.1/geo/search.json`
- `GET /1.1/geo/id/$placeId.json`

## v4.4.4

- Supported additional language codes. ([#570](https://github.com/twitter-dart/twitter-api-v2/issues/570))
Expand Down
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@
- [1.3.6. Direct Messages Service](#136-direct-messages-service)
- [1.3.6.1. Lookup Event](#1361-lookup-event)
- [1.3.6.2. Manage Event](#1362-manage-event)
- [1.3.7. Compliance Service](#137-compliance-service)
- [1.3.7.1. Batch Compliance](#1371-batch-compliance)
- [1.3.7. Geo Service](#137-geo-service)
- [1.3.7.1. Lookup Place](#1371-lookup-place)
- [1.3.7.2. Search Locations](#1372-search-locations)
- [1.3.8. Compliance Service](#138-compliance-service)
- [1.3.8.1. Batch Compliance](#1381-batch-compliance)
- [1.4. Tips 🏄](#14-tips-)
- [1.4.1. Method Names](#141-method-names)
- [1.4.2. Generate App-Only Bearer Token](#142-generate-app-only-bearer-token)
Expand Down Expand Up @@ -569,9 +572,26 @@ Future<void> main() async {
| [POST /2/dm_conversations](https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations) | [createGroupConversation](https://pub.dev/documentation/twitter_api_v2/latest/twitter_api_v2/DirectMessagesService/createGroupConversation.html) |
| [POST /2/dm_conversations/:dm_conversation_id/messages](https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-dm_conversation_id-messages) | [createMessage](https://pub.dev/documentation/twitter_api_v2/latest/twitter_api_v2/DirectMessagesService/createMessage.html) |

### 1.3.7. Compliance Service
### 1.3.7. Geo Service

#### 1.3.7.1. Batch Compliance
> **Note**</br>
> Twitter API v1.1 endpoint is used because Twitter Official does not yet release the Geo endpoint for Twitter API v2.0. Therefore, this service may be changed in the future.
#### 1.3.7.1. Lookup Place

| Endpoint | Method Name |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
| GET /1.1/geo/id/:placeId.json | [lookupById](https://pub.dev/documentation/twitter_api_v2/latest/twitter_api_v2/GeoService/lookupById.html) |

#### 1.3.7.2. Search Locations

| Endpoint | Method Name |
| -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| [GET /1.1/geo/search.json](https://developer.twitter.com/en/docs/twitter-api/v1/geo/places-near-location/api-reference/get-geo-search) | [searchLocations](https://pub.dev/documentation/twitter_api_v2/latest/twitter_api_v2/GeoService/searchLocations.html) |

### 1.3.8. Compliance Service

#### 1.3.8.1. Batch Compliance

| Endpoint | Method Name |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
Expand Down
2 changes: 1 addition & 1 deletion lib/src/service/base_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ abstract class BaseService implements _Service {
rateLimitConverter.convert(response.headers),
),
data: jsonBody[ResponseField.data.value]
.map<D>((tweet) => dataBuilder(tweet))
.map<D>((json) => dataBuilder(json))
.toList(),
includes: jsonBody.containsKey(ResponseField.includes.value)
? Includes.fromJson(jsonBody[ResponseField.includes.value])
Expand Down
2 changes: 1 addition & 1 deletion lib/src/service/common/includes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import 'package:freezed_annotation/freezed_annotation.dart';

// Project imports:
import '../geo/place_data.dart';
import '../media/media_data.dart';
import '../places/place_data.dart';
import '../polls/poll_data.dart';
import '../spaces/topic_data.dart';
import '../tweets/tweet_data.dart';
Expand Down
2 changes: 2 additions & 0 deletions lib/src/service/dms/dm_event_meta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided the conditions.

// ignore_for_file: invalid_annotation_target

// Package imports:
import 'package:freezed_annotation/freezed_annotation.dart';

Expand Down
File renamed without changes.
File renamed without changes.
18 changes: 18 additions & 0 deletions lib/src/service/geo/geo_granularity.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2022 Kato Shinya. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided the conditions.

// Package imports:
import 'package:twitter_api_core/twitter_api_core.dart';

enum GeoGranularity implements Serializable {
neighborhood,
city,
admin,
country;

@override
String get value => name;

const GeoGranularity();
}
222 changes: 222 additions & 0 deletions lib/src/service/geo/geo_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// Copyright 2022 Kato Shinya. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided the conditions.

// Package imports:
import 'dart:convert';

import 'package:twitter_api_core/twitter_api_core.dart' as core;

// Project imports:
import '../../../twitter_api_v2.dart';
import '../base_service.dart';

/// This class provides methods to easily access endpoints based on Geo.
abstract class GeoService {
/// Returns the new instance of [GeoService].
factory GeoService({required core.ClientContext context}) =>
_GeoService(context: context);

/// Returns place data associated with a specific place ID.
///
/// ## Parameters
///
/// - [placeId]: An ID indicating a specific place.
///
/// ## Endpoint Url
///
/// - https://api.twitter.com/1.1/geo/id/:placeId.json
///
/// ## Authentication Methods
///
/// - OAuth 1.0a
///
/// ## Rate Limits
///
/// - **User rate limit (OAuth 1.0a)**:
/// 75 requests per 15-minute window per each authenticated user
Future<TwitterResponse<PlaceData, void>> lookupById({
required String placeId,
});

/// Search for places that can be attached to a Tweet.
///
/// Given a latitude and a longitude pair, an IP address, or a name,
/// this request will return a list of all the valid places that
/// can be used as the `placeId` when updating a status.
///
/// Conceptually, a query can be made from the user's location,
/// retrieve a list of places, have the user validate the location they
/// are at, and then send the ID of this location.
///
/// This is the recommended method to use find places that can be
/// attached to tweet.
///
/// Unlike `GET /1.1/geo/reverse_geocode` which provides raw data access,
/// this endpoint can potentially re-order places with regards to the user
/// who is authenticated. This approach is also preferred for interactive
/// place matching with the user.
///
/// Some parameters in this method are only required based on the
/// existence of other parameters. For instance, [latitude] is required if
/// [longitude] is provided, and vice-versa. Authentication is recommended,
/// but not required with this method.
///
/// ## Parameters
///
/// - [query]: Free-form text to match against while executing
/// a geo-based query, best suited for finding nearby locations
/// by name. Remember to URL encode the query.
///
/// - [granularity]: This is the minimal granularity of place types to
/// return and must be one of: [GeoGranularity.neighborhood],
/// [GeoGranularity.city], [GeoGranularity.admin] or
/// [GeoGranularity.country]. If no granularity is provided
/// for the request [GeoGranularity.neighborhood] is assumed.
/// Setting this to [GeoGranularity.city], for example,
/// will find places which have a type of
/// [GeoGranularity.city], [GeoGranularity.admin] or
/// [GeoGranularity.country].
///
/// - [maxResults]: A hint as to the number of results to return.
/// This does not guarantee that the number of results
/// returned will equal max_results, but instead informs how
/// many "nearby" results to return. Ideally, only pass in
/// the number of places you intend to display to the user
/// here.
///
/// - [latitude]: The `latitude` to search around.
/// This parameter will be ignored unless it is
/// inside the `range -90.0 to +90.0` (North is positive)
/// inclusive. It will also be ignored if there isn't a
/// corresponding long parameter.
///
/// - [longitude]: The `longitude` to search around.
/// The valid ranges for longitude are `-180.0 to +180.0`
/// (East is positive) inclusive. This parameter will be
/// ignored if outside that range, if it is not a number,
/// if geo_enabled is turned off, or if there not a
/// corresponding lat parameter.
///
/// - [ipAddress]: An IP address.
/// Used when attempting to fix geo location based off of
/// the user's IP address.
///
/// ## Endpoint Url
///
/// - https://api.twitter.com/1.1/geo/search.json
///
/// ## Authentication Methods
///
/// - OAuth 1.0a
///
/// ## Rate Limits
///
/// - **User rate limit (OAuth 1.0a)**:
/// 75 requests per 15-minute window per each authenticated user
///
/// ## Reference
///
/// - https://developer.twitter.com/en/docs/twitter-api/v1/geo/places-near-location/api-reference/get-geo-search
Future<TwitterResponse<List<PlaceData>, void>> searchLocations({
String? query,
GeoGranularity? granularity,
int? maxResults,
double? latitude,
double? longitude,
String? ipAddress,
});
}

class _GeoService extends BaseService implements GeoService {
/// Returns the new instance of [_GeoService].
_GeoService({required super.context});

@override
Future<TwitterResponse<PlaceData, void>> lookupById({
required String placeId,
}) async {
final response = await super.get(
core.UserContext.oauth1Only,
'/1.1/geo/id/$placeId.json',
);

final place = _checkResponse(response);

return TwitterResponse(
rateLimit: RateLimit.fromJson(
rateLimitConverter.convert(response.headers),
),
data: PlaceData.fromJson(_toV2Format([place]).first),
);
}

@override
Future<TwitterResponse<List<PlaceData>, void>> searchLocations({
String? query,
GeoGranularity? granularity,
int? maxResults,
double? latitude,
double? longitude,
String? ipAddress,
}) async {
final response = await super.get(
core.UserContext.oauth1Only,
'/1.1/geo/search.json',
queryParameters: {
'query': query,
'granularity': granularity,
'max_results': maxResults,
'lat': latitude,
'long': longitude,
'ip': ipAddress,
},
);

final places = _checkResponse(response)['result']['places'];

return TwitterResponse(
rateLimit: RateLimit.fromJson(
rateLimitConverter.convert(response.headers),
),
data: _toV2Format(places)
.map<PlaceData>((json) => PlaceData.fromJson(json))
.toList(),
);
}

dynamic _checkResponse(final core.Response response) {
final json = jsonDecode(response.body);

if (json is Map<String, dynamic>) {
if (json.containsKey('errors')) {
throw DataNotFoundException(
'No data exists in response.',
response,
);
}
}

return json;
}

List<dynamic> _toV2Format(
final List<dynamic> places,
) {
final json = <dynamic>[];
for (final place in places) {
final $place = Map<String, dynamic>.from(place);

final containedWithinIdentifiers = <String>[];
for (final containedWithinPlace in place['contained_within']) {
containedWithinIdentifiers.add(containedWithinPlace['id']);
}

$place['contained_within'] = containedWithinIdentifiers;

json.add($place);
}

return json;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided the conditions.

// ignore_for_file: invalid_annotation_target

// Package imports:
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:twitter_api_core/twitter_api_core.dart';

// Project imports:
import '../common/data.dart';
import 'place_geo.dart';
import 'place_type.dart';

part 'place_data.freezed.dart';
part 'place_data.g.dart';
Expand All @@ -20,7 +25,7 @@ part 'place_data.g.dart';
/// Use the expansion with the field parameter: place.fields when requesting
/// additional fields to complete the object.
@freezed
class PlaceData with _$PlaceData {
class PlaceData with _$PlaceData implements Data {
@JsonSerializable(includeIfNull: false)
const factory PlaceData({
/// The unique identifier of the expanded place, if this is a point of
Expand Down Expand Up @@ -51,21 +56,21 @@ class PlaceData with _$PlaceData {
/// ## How It Can Be Used
///
/// - Classify a Tweet by a specific type of place.
String? placeType,
PlaceType? placeType,

/// The full-length name of the country this place belongs to.
///
/// ## How It Can Be Used
///
/// - Classify a Tweet by country name.
String? country,
@JsonKey(name: 'country') String? countryName,

/// The ISO Alpha-2 country code this place belongs to.
///
/// ## How It Can Be Used
///
/// - Classify a Tweet by country code.
String? countryCode,
@JsonKey(name: 'country_code') Country? country,

/// Contains place details in GeoJSON format.
PlaceGeo? geo,
Expand Down
Loading

0 comments on commit fb61628

Please sign in to comment.