Merge pull request #194 from h3poteto/iss-125

refs #125 Create misskey REST APIs
This commit is contained in:
AkiraFukushima 2020-03-08 23:32:02 +09:00 committed by GitHub
commit be4074acde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2640 additions and 52 deletions

View file

@ -79,7 +79,6 @@
"moment": "^2.24.0",
"oauth": "^0.9.15",
"socks-proxy-agent": "github:h3poteto/node-socks-proxy-agent#master",
"typescript": "3.7.5",
"ws": "^7.2.1"
},
"dependencies": {

View file

@ -0,0 +1,49 @@
import * as readline from 'readline'
import { OAuth, Misskey } from 'megalodon'
const rl: readline.ReadLine = readline.createInterface({
input: process.stdin,
output: process.stdout
})
const SCOPES: Array<string> = ['read', 'write', 'follow']
const BASE_URL: string = 'https://misskey.io'
let clientId: string
let clientSecret: string
const client = new Misskey(BASE_URL)
client
.registerApp('Test App', {
scopes: SCOPES
})
.then(appData => {
clientId = appData.clientId
clientSecret = appData.clientSecret
console.log('\napp_secret_key:')
console.log(clientSecret)
console.log('Authorization URL is generated.')
console.log(appData.url)
console.log()
return new Promise<string | null>(resolve => {
rl.question('Enter any keys after you authorize to misskey: ', _code => {
resolve(appData.session_token)
rl.close()
})
})
})
.then((session_token: string | null) => {
if (!session_token) {
throw new Error('Could not get session token')
}
return client.fetchAccessToken(clientId, clientSecret, session_token, BASE_URL)
})
.then((tokenData: OAuth.TokenData) => {
console.log('\naccess_token:')
console.log(tokenData.accessToken)
console.log('\nrefresh_token:')
console.log(tokenData.refreshToken)
console.log()
})
.catch((err: Error) => console.error(err))

View file

@ -6,7 +6,7 @@ namespace Entity {
account: Account
created_at: string
id: string
status: Status | null
type: 'mention' | 'reblog' | 'favourite' | 'follow'
status?: Status
type: 'mention' | 'reblog' | 'favourite' | 'follow' | 'poll'
}
}

View file

@ -9,6 +9,7 @@ import { ProxyConfig } from './proxy_config'
import generator, { MegalodonInterface } from './megalodon'
import Mastodon from './mastodon'
import Pleroma from './pleroma'
import Misskey from './misskey'
import Entity from './entity'
export {
@ -24,6 +25,7 @@ export {
MegalodonInterface,
Mastodon,
Pleroma,
Misskey,
Entity
}

View file

@ -205,7 +205,7 @@ export default class Mastodon implements MegalodonInterface {
}
public async updateCredentials(
discoverable?: string | null,
discoverable?: boolean | null,
bot?: boolean | null,
display_name?: string | null,
note?: string | null,
@ -220,7 +220,7 @@ export default class Mastodon implements MegalodonInterface {
fields_attributes?: Array<{ name: string; value: string }>
): Promise<Response<Entity.Account>> {
let params = {}
if (discoverable) {
if (discoverable !== null) {
params = Object.assign(params, {
discoverable: discoverable
})
@ -762,23 +762,20 @@ export default class Mastodon implements MegalodonInterface {
// ======================================
public async report(
account_id: string,
comment: string,
status_ids?: Array<string> | null,
comment?: string | null,
forward?: boolean | null
): Promise<Response<Entity.Report>> {
let params = {
account_id: account_id
account_id: account_id,
comment: comment
}
if (status_ids) {
params = Object.assign(params, {
status_ids: status_ids
})
}
if (comment) {
params = Object.assign(params, {
comment: comment
})
}
if (forward !== null) {
params = Object.assign(params, {
forward: forward

View file

@ -6,7 +6,7 @@ namespace MastodonEntity {
account: Account
created_at: string
id: string
status: Status | null
type: 'mention' | 'reblog' | 'favourite' | 'follow'
status?: Status
type: 'mention' | 'reblog' | 'favourite' | 'follow' | 'poll'
}
}

View file

@ -120,7 +120,7 @@ export interface MegalodonInterface {
* @return An account.
*/
updateCredentials(
discoverable?: string | null,
discoverable?: boolean | null,
bot?: boolean | null,
display_name?: string | null,
note?: string | null,
@ -450,12 +450,7 @@ export interface MegalodonInterface {
* @param forward If the account is remote, should the report be forwarded to the remote admin?
* @return Report
*/
report(
account_id: string,
status_ids?: Array<string> | null,
comment?: string | null,
forward?: boolean | null
): Promise<Response<Entity.Report>>
report(account_id: string, comment: string, status_ids?: Array<string> | null, forward?: boolean | null): Promise<Response<Entity.Report>>
// ======================================
// accounts/follow_requests
// ======================================
@ -581,7 +576,7 @@ export interface MegalodonInterface {
* @param id The target status id.
* @return Status
*/
deleteStatus(id: string): Promise<Response<Entity.Status>>
deleteStatus(id: string): Promise<Response<{}>>
/**
* GET /api/v1/statuses/:id/context
*
@ -694,7 +689,13 @@ export interface MegalodonInterface {
* @param focus Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0.
* @return Attachment
*/
updateMedia(id: string, file?: any, description?: string | null, focus?: string | null): Promise<Response<Entity.Attachment>>
updateMedia(
id: string,
file?: any,
description?: string | null,
focus?: string | null,
is_sensitive?: boolean | null
): Promise<Response<Entity.Attachment>>
// ======================================
// statuses/polls
// ======================================
@ -712,7 +713,7 @@ export interface MegalodonInterface {
* @param choices Array of own votes containing index for each option (starting from 0).
* @return Poll
*/
votePoll(id: string, choices: Array<number>): Promise<Response<Entity.Poll>>
votePoll(id: string, choices: Array<number>, status_id?: string | null): Promise<Response<Entity.Poll>>
// ======================================
// statuses/scheduled_statuses
// ======================================
@ -1158,6 +1159,24 @@ export class NoImplementedError extends Error {
}
}
export class ArgumentError extends Error {
constructor(err?: string) {
super(err)
this.name = new.target.name
Object.setPrototypeOf(this, new.target.prototype)
}
}
export class UnexpectedError extends Error {
constructor(err?: string) {
super(err)
this.name = new.target.name
Object.setPrototypeOf(this, new.target.prototype)
}
}
type Instance = {
title: string
uri: string

1880
src/misskey.ts Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,358 @@
import axios, { AxiosResponse, CancelTokenSource, AxiosRequestConfig } from 'axios'
import { DEFAULT_UA } from '@/default'
import proxyAgent, { ProxyConfig } from '@/proxy_config'
import Response from '@/response'
import moment from 'moment'
import { DEFAULT_UA } from '../default'
import proxyAgent, { ProxyConfig } from '../proxy_config'
import Response from '../response'
import MisskeyEntity from './entity'
import MegalodonEntity from '../entity'
namespace MisskeyAPI {
export namespace Entity {
export type App = MisskeyEntity.App
export type Blocking = MisskeyEntity.Blocking
export type Choice = MisskeyEntity.Choice
export type CreatedNote = MisskeyEntity.CreatedNote
export type Emoji = MisskeyEntity.Emoji
export type Favorite = MisskeyEntity.Favorite
export type File = MisskeyEntity.File
export type Follower = MisskeyEntity.Follower
export type Following = MisskeyEntity.Following
export type FollowRequest = MisskeyEntity.FollowRequest
export type Hashtag = MisskeyEntity.Hashtag
export type List = MisskeyEntity.List
export type Meta = MisskeyEntity.Meta
export type Mute = MisskeyEntity.Mute
export type Note = MisskeyEntity.Note
export type Notification = MisskeyEntity.Notification
export type Poll = MisskeyEntity.Poll
export type Relation = MisskeyEntity.Relation
export type User = MisskeyEntity.User
export type UserDetail = MisskeyEntity.UserDetail
export type UserKey = MisskeyEntity.UserKey
export type Session = MisskeyEntity.Session
export type Stats = MisskeyEntity.Stats
}
export namespace Converter {
export const emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => {
return {
shortcode: e.name,
static_url: e.url,
url: e.url,
visible_in_picker: true
}
}
export const user = (u: Entity.User): MegalodonEntity.Account => {
let acct = u.username
if (u.host) {
acct = `${u.username}@${u.host}`
}
return {
id: u.id,
username: u.username,
acct: acct,
display_name: u.name,
locked: false,
created_at: '',
followers_count: 0,
following_count: 0,
statuses_count: 0,
note: '',
url: '',
avatar: u.avatarUrl,
avatar_static: u.avatarColor,
header: '',
header_static: '',
emojis: u.emojis.map(e => emoji(e)),
moved: null,
fields: null,
bot: null
}
}
export const userDetail = (u: Entity.UserDetail): MegalodonEntity.Account => {
let acct = u.username
if (u.host) {
acct = `${u.username}@${u.host}`
}
return {
id: u.id,
username: u.username,
acct: acct,
display_name: u.name,
locked: u.isLocked,
created_at: u.createdAt,
followers_count: u.followersCount,
following_count: u.followingCount,
statuses_count: u.notesCount,
note: u.description,
url: acct,
avatar: u.avatarUrl,
avatar_static: u.avatarColor,
header: u.bannerUrl,
header_static: u.bannerColor,
emojis: u.emojis.map(e => emoji(e)),
moved: null,
fields: null,
bot: u.isBot
}
}
export const visibility = (v: 'public' | 'home' | 'followers' | 'specified'): 'public' | 'unlisted' | 'private' | 'direct' => {
switch (v) {
case 'public':
return v
case 'home':
return 'unlisted'
case 'followers':
return 'private'
case 'specified':
return 'direct'
}
}
export const encodeVisibility = (v: 'public' | 'unlisted' | 'private' | 'direct'): 'public' | 'home' | 'followers' | 'specified' => {
switch (v) {
case 'public':
return v
case 'unlisted':
return 'home'
case 'private':
return 'followers'
case 'direct':
return 'specified'
}
}
export const file = (f: Entity.File): MegalodonEntity.Attachment => {
return {
id: f.id,
type: 'image',
url: f.url,
remote_url: f.url,
preview_url: f.thumbnailUrl,
text_url: f.url,
meta: null,
description: null
}
}
export const follower = (f: Entity.Follower): MegalodonEntity.Account => {
return user(f.follower)
}
export const following = (f: Entity.Following): MegalodonEntity.Account => {
return user(f.followee)
}
export const relation = (r: Entity.Relation): MegalodonEntity.Relationship => {
return {
id: r.id,
following: r.isFollowing,
followed_by: r.isFollowed,
blocking: r.isBlocking,
muting: r.isMuted,
muting_notifications: false,
requested: r.hasPendingFollowRequestFromYou,
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
}
export const choice = (c: Entity.Choice): MegalodonEntity.PollOption => {
return {
title: c.text,
votes_count: c.votes
}
}
export const poll = (p: Entity.Poll): MegalodonEntity.Poll => {
const now = moment()
const expire = moment(p.expiresAt)
const count = p.choices.reduce((sum, choice) => sum + choice.votes, 0)
return {
id: '',
expires_at: p.expiresAt,
expired: now.isAfter(expire),
multiple: p.multiple,
votes_count: count,
options: p.choices.map(c => choice(c)),
voted: p.choices.some(c => c.isVoted)
}
}
export const note = (n: Entity.Note): MegalodonEntity.Status => {
return {
id: n.id,
uri: '',
url: '',
account: user(n.user),
in_reply_to_id: n.replyId,
in_reply_to_account_id: null,
reblog: n.renote ? note(n.renote) : null,
content: n.text,
created_at: n.createdAt,
emojis: n.emojis.map(e => emoji(e)),
replies_count: n.repliesCount,
reblogs_count: n.renoteCount,
favourites_count: 0,
reblogged: false,
favourited: false,
muted: false,
sensitive: n.files ? n.files.some(f => f.isSensitive) : false,
spoiler_text: n.cw ? n.cw : '',
visibility: visibility(n.visibility),
media_attachments: n.files ? n.files.map(f => file(f)) : [],
mentions: [],
tags: [],
card: null,
poll: n.poll ? poll(n.poll) : null,
application: null,
language: null,
pinned: null
}
}
export const noteToConversation = (n: Entity.Note): MegalodonEntity.Conversation => {
const accounts: Array<MegalodonEntity.Account> = [user(n.user)]
if (n.reply) {
accounts.push(user(n.reply.user))
}
return {
id: n.id,
accounts: accounts,
last_status: note(n),
unread: false
}
}
export const list = (l: Entity.List): MegalodonEntity.List => ({
id: l.id,
title: l.name
})
export const encodeNotificationType = (
e: 'follow' | 'favourite' | 'reblog' | 'mention' | 'poll'
): 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollVote' => {
switch (e) {
case 'follow':
return e
case 'mention':
return 'reply'
case 'favourite':
return 'reaction'
case 'reblog':
return 'renote'
case 'poll':
return 'pollVote'
}
}
export const decodeNotificationType = (
e:
| 'follow'
| 'mention'
| 'reply'
| 'renote'
| 'quote'
| 'reaction'
| 'pollVote'
| 'receiveFollowRequest'
| 'followRequestAccepted'
| 'groupInvited'
): 'follow' | 'favourite' | 'reblog' | 'mention' | 'poll' => {
switch (e) {
case 'follow':
return e
case 'mention':
case 'reply':
return 'mention'
case 'renote':
return 'reblog'
case 'reaction':
return 'favourite'
case 'pollVote':
return 'poll'
default:
return 'follow'
}
}
export const notification = (n: Entity.Notification): MegalodonEntity.Notification => {
let notification = {
id: n.id,
account: user(n.user),
created_at: n.createdAt,
type: decodeNotificationType(n.type)
}
if (n.note) {
notification = Object.assign(notification, {
status: note(n.note)
})
}
return notification
}
export const stats = (s: Entity.Stats): MegalodonEntity.Stats => {
return {
user_count: s.usersCount,
status_count: s.notesCount,
domain_count: s.instances
}
}
export const meta = (m: Entity.Meta, s: Entity.Stats): MegalodonEntity.Instance => {
const wss = m.uri.replace(/^https:\/\//, 'wss://')
return {
uri: m.uri,
title: m.name,
description: m.description,
email: m.maintainerEmail,
version: m.version,
thumbnail: m.bannerUrl,
urls: {
streaming_api: `${wss}/streaming`
},
stats: stats(s),
languages: m.langs,
contact_account: null,
max_toot_chars: m.maxNoteTextLength,
registrations: !m.disableRegistration
}
}
export const hashtag = (h: Entity.Hashtag): MegalodonEntity.Tag => {
return {
name: h.tag,
url: h.tag,
history: null
}
}
}
export const DEFAULT_SCOPE = [
'read:account',
'write:account',
'read:blocks',
'write:blocks',
'read:favourites',
'write:favourites',
'read:following',
'write:following',
'read:messaging',
'write:messaging',
'read:mute',
'write:mute',
'write:note',
'read:notifications',
'write:notifications',
'read:reactions',
'write:reactions',
'write:votes'
]
/**
* Interface
*/
@ -44,7 +393,6 @@ namespace MisskeyAPI {
* @param baseUrl base URL of the target
* @param proxyConfig Proxy setting, or set false if don't use proxy.
*/
public static async post<T>(
path: string,
params = {},
@ -99,6 +447,14 @@ namespace MisskeyAPI {
return res
})
}
/**
* Cancel all requests in this instance.
* @returns void
*/
public cancel(): void {
return this.cancelTokenSource.cancel('Request is canceled by user')
}
}
}

View file

@ -0,0 +1,9 @@
namespace MisskeyEntity {
export type App = {
id: string
name: string
callbackUrl: string
permission: Array<string>
secret: string
}
}

View file

@ -0,0 +1,10 @@
/// <reference path="userDetail.ts" />
namespace MisskeyEntity {
export type Blocking = {
id: string
createdAt: string
blockeeId: string
blockee: UserDetail
}
}

View file

@ -0,0 +1,7 @@
/// <reference path="note.ts" />
namespace MisskeyEntity {
export type CreatedNote = {
createdNote: Note
}
}

View file

@ -0,0 +1,8 @@
namespace MisskeyEntity {
export type Emoji = {
name: string
host: string | null
url: string
aliases: Array<string>
}
}

View file

@ -0,0 +1,10 @@
/// <reference path="note.ts" />
namespace MisskeyEntity {
export type Favorite = {
id: string
createdAt: string
noteId: string
note: Note
}
}

View file

@ -0,0 +1,18 @@
namespace MisskeyEntity {
export type File = {
id: string
createdAt: string
name: string
type: string
md5: string
size: number
isSensitive: boolean
properties: {
width: number
height: number
avgColor: string
}
url: string
thumbnailUrl: string
}
}

View file

@ -0,0 +1,9 @@
/// <reference path="user.ts" />
namespace MisskeyEntity {
export type FollowRequest = {
id: string
follower: User
followee: User
}
}

View file

@ -0,0 +1,11 @@
/// <reference path="userDetail.ts" />
namespace MisskeyEntity {
export type Follower = {
id: string
createdAt: string
followeeId: string
followerId: string
follower: UserDetail
}
}

View file

@ -0,0 +1,11 @@
/// <reference path="userDetail.ts" />
namespace MisskeyEntity {
export type Following = {
id: string
createdAt: string
followeeId: string
followerId: string
followee: UserDetail
}
}

View file

@ -0,0 +1,7 @@
namespace MisskeyEntity {
export type Hashtag = {
tag: string
chart: Array<number>
usersCount: number
}
}

View file

@ -0,0 +1,8 @@
namespace MisskeyEntity {
export type List = {
id: string
createdAt: string
name: string
userIds: Array<string>
}
}

View file

@ -0,0 +1,18 @@
/// <reference path="emoji.ts" />
namespace MisskeyEntity {
export type Meta = {
maintainerName: string
maintainerEmail: string
name: string
version: string
uri: string
description: string
langs: Array<string>
disableRegistration: boolean
disableLocalTimeline: boolean
bannerUrl: string
maxNoteTextLength: 300
emojis: Array<Emoji>
}
}

View file

@ -0,0 +1,10 @@
/// <reference path="userDetail.ts" />
namespace MisskeyEntity {
export type Mute = {
id: string
createdAt: string
muteeId: string
mutee: UserDetail
}
}

View file

@ -0,0 +1,31 @@
/// <reference path="user.ts" />
/// <reference path="emoji.ts" />
/// <reference path="file.ts" />
/// <reference path="poll.ts" />
namespace MisskeyEntity {
export type Note = {
id: string
createdAt: string
userId: string
user: User
text: string
cw: string | null
visibility: 'public' | 'home' | 'followers' | 'specified'
renoteCount: number
repliesCount: number
reactions: { [key: string]: number }
emojis: Array<Emoji>
fileIds: Array<string>
files: Array<File>
replyId: string | null
renoteId: string | null
uri?: string
reply?: Note
renote?: Note
viaMobile?: boolean
tags?: Array<string>
poll?: Poll
mentions?: Array<string>
}
}

View file

@ -0,0 +1,25 @@
/// <reference path="user.ts" />
/// <reference path="note.ts" />
namespace MisskeyEntity {
export type Notification = {
id: string
createdAt: string
// https://github.com/syuilo/misskey/blob/056942391aee135eb6c77aaa63f6ed5741d701a6/src/models/entities/notification.ts#L50-L62
type:
| 'follow'
| 'mention'
| 'reply'
| 'renote'
| 'quote'
| 'reaction'
| 'pollVote'
| 'receiveFollowRequest'
| 'followRequestAccepted'
| 'groupInvited'
userId: string
user: User
note?: Note
reaction?: string
}
}

View file

@ -0,0 +1,13 @@
namespace MisskeyEntity {
export type Choice = {
text: string
votes: number
isVoted: boolean
}
export type Poll = {
multiple: boolean
expiresAt: string
choices: Array<Choice>
}
}

View file

@ -0,0 +1,12 @@
namespace MisskeyEntity {
export type Relation = {
id: string
isFollowing: boolean
hasPendingFollowRequestFromYou: boolean
hasPendingFollowRequestToYou: boolean
isFollowed: boolean
isBlocking: boolean
isBlocked: boolean
isMuted: boolean
}
}

View file

@ -0,0 +1,6 @@
namespace MisskeyEntity {
export type Session = {
token: string
url: string
}
}

View file

@ -0,0 +1,9 @@
namespace MisskeyEntity {
export type Stats = {
notesCount: number
originalNotesCount: number
usersCount: number
originalUsersCount: number
instances: number
}
}

View file

@ -0,0 +1,13 @@
/// <reference path="emoji.ts" />
namespace MisskeyEntity {
export type User = {
id: string
name: string
username: string
host: string | null
avatarUrl: string
avatarColor: string
emojis: Array<Emoji>
}
}

View file

@ -0,0 +1,29 @@
/// <reference path="emoji.ts" />
namespace MisskeyEntity {
export type UserDetail = {
id: string
name: string
username: string
host: string | null
avatarUrl: string
avatarColor: string
isAdmin: boolean
isModerator: boolean
isBot: boolean
isCat: boolean
emojis: Array<Emoji>
createdAt: string
bannerUrl: string
bannerColor: string
isLocked: boolean
isSilenced: boolean
isSuspended: boolean
description: string
followersCount: number
followingCount: number
notesCount: number
avatarId: string
bannerId: string
}
}

View file

@ -0,0 +1,8 @@
/// <reference path="user.ts" />
namespace MisskeyEntity {
export type UserKey = {
accessToken: string
user: User
}
}

24
src/misskey/entity.ts Normal file
View file

@ -0,0 +1,24 @@
/// <reference path="entities/app.ts" />
/// <reference path="entities/blocking.ts" />
/// <reference path="entities/createdNote.ts" />
/// <reference path="entities/emoji.ts" />
/// <reference path="entities/favorite.ts" />
/// <reference path="entities/file.ts" />
/// <reference path="entities/follower.ts" />
/// <reference path="entities/following.ts" />
/// <reference path="entities/followRequest.ts" />
/// <reference path="entities/hashtag.ts" />
/// <reference path="entities/list.ts" />
/// <reference path="entities/meta.ts" />
/// <reference path="entities/mute.ts" />
/// <reference path="entities/note.ts" />
/// <reference path="entities/notification.ts" />
/// <reference path="entities/poll.ts" />
/// <reference path="entities/relation.ts" />
/// <reference path="entities/user.ts" />
/// <reference path="entities/userDetail.ts" />
/// <reference path="entities/userkey.ts" />
/// <reference path="entities/session.ts" />
/// <reference path="entities/stats.ts" />
export default MisskeyEntity

View file

@ -4,7 +4,7 @@
**/
namespace OAuth {
export type AppDataFromServer = {
id: number
id: string
name: string
website: string | null
redirect_uri: string
@ -23,8 +23,9 @@ namespace OAuth {
export class AppData {
public url: string | null
public session_token: string | null
constructor(
public id: number,
public id: string,
public name: string,
public website: string | null,
public redirect_uri: string,
@ -32,6 +33,7 @@ namespace OAuth {
public client_secret: string
) {
this.url = null
this.session_token = null
}
/**

View file

@ -587,15 +587,6 @@
regexpp "^3.0.0"
tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@2.19.2":
version "2.19.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.2.tgz#4611d44cf0f0cb460c26aa7676fc0a787281e233"
integrity sha512-B88QuwT1wMJR750YvTJBNjMZwmiPpbmKYLm1yI7PCc3x0NariqPwqaPsoJRwU9DmUi0cd9dkhz1IqEnwfD+P1A==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.19.2"
eslint-scope "^5.0.0"
"@typescript-eslint/experimental-utils@2.21.0":
version "2.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.21.0.tgz#71de390a3ec00b280b69138d80733406e6e86bfa"
@ -615,19 +606,6 @@
"@typescript-eslint/typescript-estree" "2.21.0"
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/typescript-estree@2.19.2":
version "2.19.2"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.2.tgz#67485b00172f400474d243c6c0be27581a579350"
integrity sha512-Xu/qa0MDk6upQWqE4Qy2X16Xg8Vi32tQS2PR0AvnT/ZYS4YGDvtn2MStOh5y8Zy2mg4NuL06KUHlvCh95j9C6Q==
dependencies:
debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
semver "^6.3.0"
tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@2.21.0":
version "2.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.21.0.tgz#7e4be29f2e338195a2e8c818949ed0ff727cc943"