Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
Mirai
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
Mirai
Commits
ad36c99b
Commit
ad36c99b
authored
Feb 15, 2020
by
Him188
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master'
parents
2e614f65
3eab26a5
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
412 additions
and
11 deletions
+412
-11
mirai-api-http/README_CH.md
mirai-api-http/README_CH.md
+54
-0
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
...kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
+5
-1
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
...ain/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
+16
-1
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
.../net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
+10
-2
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
...ttp/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
+2
-0
mirai-console/build.gradle.kts
mirai-console/build.gradle.kts
+1
-0
mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsole.kt
...i-console/src/main/kotlin/net/mamoe/mirai/MiraiConsole.kt
+6
-7
mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsoleUI.kt
...console/src/main/kotlin/net/mamoe/mirai/MiraiConsoleUI.kt
+318
-0
No files found.
mirai-api-http/README_CH.md
View file @
ad36c99b
...
...
@@ -220,6 +220,44 @@ fun main() {
### 发送引用回复消息(仅支持群消息)
```
[POST] /sendQuoteMessage
```
使用此方法向指定的消息进行引用回复
#### 请求
```
json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 引用消息的Message Source的Uid |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码
```
json5
{
"code": 0,
"msg": "success"
}
```
### 发送图片消息(通过URL)
```
...
...
@@ -308,6 +346,9 @@ Content-Type:multipart/form-data
[{
"type": "GroupMessage", // 消息类型:GroupMessage或FriendMessage
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain",
"text": "Miral牛逼"
}],
...
...
@@ -350,6 +391,19 @@ Content-Type:multipart/form-data
+
[ ] Xml,Xml卡片消息
+
[ ] 敬请期待
#### Source
```
json5
{
"type": "Source",
"uid": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------------------------------------------------ |
| uid | Long | 消息的识别号,用于引用回复(Source类型只在群消息中返回,且永远为chain的第一个元素) |
#### At
```
json5
...
...
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt
View file @
ad36c99b
...
...
@@ -36,6 +36,9 @@ data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
// Message
@Serializable
@SerialName
(
"Source"
)
data class
MessageSourceDTO
(
val
uid
:
Long
)
:
MessageDTO
()
@Serializable
@SerialName
(
"At"
)
data class
AtDTO
(
val
target
:
Long
,
val
display
:
String
)
:
MessageDTO
()
@Serializable
...
...
@@ -85,6 +88,7 @@ fun MessageChainDTO.toMessageChain() =
@UseExperimental
(
ExperimentalUnsignedTypes
::
class
)
fun
Message
.
toDTO
()
=
when
(
this
)
{
is
MessageSource
->
MessageSourceDTO
(
messageUid
)
is
At
->
AtDTO
(
target
,
display
)
is
AtAll
->
AtAllDTO
(
0L
)
is
Face
->
FaceDTO
(
id
.
value
.
toInt
())
...
...
@@ -102,7 +106,7 @@ fun MessageDTO.toMessage() = when (this) {
is
PlainDTO
->
PlainText
(
text
)
is
ImageDTO
->
Image
(
imageId
)
is
XmlDTO
->
XMLMessage
(
xml
)
is
UnknownMessageDTO
->
PlainText
(
"assert cannot reach"
)
is
MessageSourceDTO
,
is
UnknownMessageDTO
->
PlainText
(
"assert cannot reach"
)
}
...
...
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt
View file @
ad36c99b
...
...
@@ -9,17 +9,32 @@
package
net.mamoe.mirai.api.http.queue
import
net.mamoe.mirai.message.GroupMessage
import
net.mamoe.mirai.message.MessagePacket
import
net.mamoe.mirai.message.data.MessageSource
import
java.util.concurrent.ConcurrentHashMap
import
java.util.concurrent.ConcurrentLinkedDeque
class
MessageQueue
:
ConcurrentLinkedDeque
<
MessagePacket
<
*
,
*
>>()
{
val
quoteCache
=
ConcurrentHashMap
<
Long
,
GroupMessage
>()
fun
fetch
(
size
:
Int
):
List
<
MessagePacket
<
*
,
*
>>
{
var
count
=
size
quoteCache
.
clear
()
val
ret
=
ArrayList
<
MessagePacket
<
*
,
*
>>(
count
)
while
(!
this
.
isEmpty
()
&&
count--
>
0
)
{
ret
.
add
(
this
.
pop
())
val
packet
=
pop
()
ret
.
add
(
packet
)
if
(
packet
is
GroupMessage
)
{
addCache
(
packet
)
}
}
return
ret
}
private
fun
addCache
(
msg
:
GroupMessage
)
{
quoteCache
[
msg
.
message
[
MessageSource
].
messageUid
]
=
msg
}
}
\ No newline at end of file
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt
View file @
ad36c99b
...
...
@@ -52,6 +52,12 @@ fun Application.messageModule() {
call
.
respondStateCode
(
StateCode
.
Success
)
}
miraiVerify
<
SendDTO
>(
"/quoteMessage"
)
{
it
.
session
.
messageQueue
.
quoteCache
[
it
.
target
]
?.
quoteReply
(
it
.
messageChain
.
toMessageChain
())
?:
throw
NoSuchElementException
()
call
.
respondStateCode
(
StateCode
.
Success
)
}
miraiVerify
<
SendImageDTO
>(
"sendImageMessage"
)
{
val
bot
=
it
.
session
.
bot
val
contact
=
when
{
...
...
@@ -72,12 +78,14 @@ fun Application.messageModule() {
if
(!
SessionManager
.
containSession
(
sessionKey
))
throw
IllegalSessionException
val
session
=
try
{
SessionManager
[
sessionKey
]
as
AuthedSession
}
catch
(
e
:
TypeCastException
)
{
throw
NotVerifiedSessionException
}
}
catch
(
e
:
TypeCastException
)
{
throw
NotVerifiedSessionException
}
val
type
=
parts
.
value
(
"type"
)
parts
.
file
(
"img"
)
?.
apply
{
val
image
=
streamProvider
().
use
{
when
(
type
)
{
when
(
type
)
{
"group"
->
session
.
bot
.
groups
.
toList
().
random
().
uploadImage
(
it
)
"friend"
->
session
.
bot
.
qqs
.
toList
().
random
().
uploadImage
(
it
)
else
->
null
...
...
mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt
View file @
ad36c99b
...
...
@@ -13,6 +13,7 @@ import kotlinx.serialization.*
import
kotlinx.serialization.json.Json
import
kotlinx.serialization.modules.SerializersModule
import
net.mamoe.mirai.api.http.data.common.*
import
net.mamoe.mirai.message.data.MessageSource
// 解析失败时直接返回null,由路由判断响应400状态
@UseExperimental
(
ImplicitReflectionSerializer
::
class
)
...
...
@@ -50,6 +51,7 @@ object MiraiJson {
UnKnownMessagePacketDTO
::
class
with
UnKnownMessagePacketDTO
.
serializer
()
}
polymorphic
(
MessageDTO
.
serializer
())
{
MessageSourceDTO
::
class
with
MessageSourceDTO
.
serializer
()
AtDTO
::
class
with
AtDTO
.
serializer
()
AtAllDTO
::
class
with
AtAllDTO
.
serializer
()
FaceDTO
::
class
with
FaceDTO
.
serializer
()
...
...
mirai-console/build.gradle.kts
View file @
ad36c99b
...
...
@@ -42,5 +42,6 @@ dependencies {
api
(
group
=
"com.alibaba"
,
name
=
"fastjson"
,
version
=
"1.2.62"
)
api
(
group
=
"org.yaml"
,
name
=
"snakeyaml"
,
version
=
"1.25"
)
api
(
group
=
"com.moandjiezana.toml"
,
name
=
"toml4j"
,
version
=
"0.7.2"
)
api
(
group
=
"com.googlecode.lanterna"
,
name
=
"lanterna"
,
version
=
"3.0.2"
)
// classpath is not set correctly by IDE
}
\ No newline at end of file
mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsole.kt
View file @
ad36c99b
...
...
@@ -17,6 +17,7 @@ import net.mamoe.mirai.plugins.withDefaultWriteSave
import
net.mamoe.mirai.api.http.MiraiHttpAPIServer
import
net.mamoe.mirai.api.http.generateSessionKey
import
net.mamoe.mirai.contact.sendMessage
import
net.mamoe.mirai.utils.MiraiLogger
import
java.io.File
import
kotlin.concurrent.thread
...
...
@@ -37,7 +38,7 @@ object MiraiConsole {
get
()
=
PluginManager
var
logger
:
MiraiConsoleLogger
=
DefaultLogger
UIPushLogger
()
var
path
:
String
=
System
.
getProperty
(
"user.dir"
)
...
...
@@ -254,14 +255,11 @@ object MiraiConsole {
}
}
interface
MiraiConsoleLogger
{
operator
fun
invoke
(
any
:
Any
?
=
null
)
}
object
DefaultLogger
:
MiraiConsoleLogger
{
class
UIPushLogger
(
override
val
identity
:
String
?,
override
var
follower
:
MiraiLogger
?)
:
MiraiLogger
{
override
fun
invoke
(
any
:
Any
?)
{
MiraiConsoleUI
.
start
()
if
(
any
!=
null
)
{
println
(
"[Mirai$version $build]: "
+
any
.
toString
()
)
MiraiConsoleUI
.
pushLog
(
0
,
"[Mirai$version $build]: $any"
)
}
}
}
...
...
@@ -287,6 +285,7 @@ class MiraiConsoleLoader {
Runtime
.
getRuntime
().
addShutdownHook
(
thread
(
start
=
false
)
{
MiraiConsole
.
stop
()
})
}
}
}
...
...
mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsoleUI.kt
0 → 100644
View file @
ad36c99b
package
net.mamoe.mirai
import
com.googlecode.lanterna.SGR
import
com.googlecode.lanterna.TerminalSize
import
com.googlecode.lanterna.TextColor
import
com.googlecode.lanterna.graphics.TextGraphics
import
com.googlecode.lanterna.input.KeyStroke
import
com.googlecode.lanterna.input.KeyType
import
com.googlecode.lanterna.terminal.DefaultTerminalFactory
import
com.googlecode.lanterna.terminal.Terminal
import
com.googlecode.lanterna.terminal.TerminalResizeListener
import
com.googlecode.lanterna.terminal.swing.SwingTerminal
import
com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
import
java.lang.StringBuilder
import
java.util.*
import
kotlin.concurrent.thread
object
MiraiConsoleUI
{
val
log
=
mutableMapOf
<
Long
,
LimitLinkedQueue
<
String
>>().
also
{
it
[
0L
]
=
LimitLinkedQueue
(
50
)
}
private
val
screens
=
mutableListOf
(
0L
)
private
var
currentScreenId
=
0
fun
addBotScreen
(
uin
:
Long
)
{
screens
.
add
(
uin
)
log
[
uin
]
=
LimitLinkedQueue
()
}
fun
pushLog
(
uin
:
Long
,
str
:
String
)
{
log
[
uin
]
!!
.
push
(
str
)
}
var
hasStart
=
false
fun
start
()
{
if
(
hasStart
)
{
return
}
hasStart
=
true
val
defaultTerminalFactory
=
DefaultTerminalFactory
()
var
terminal
:
Terminal
?
=
null
try
{
terminal
=
defaultTerminalFactory
.
createTerminal
()
terminal
.
enterPrivateMode
()
terminal
.
clearScreen
()
terminal
.
setCursorVisible
(
false
)
}
catch
(
e
:
Exception
)
{
terminal
=
SwingTerminalFrame
(
"Mirai Console"
)
terminal
.
enterPrivateMode
()
terminal
.
clearScreen
()
terminal
.
setCursorVisible
(
false
)
}
if
(
terminal
==
null
)
{
error
(
"can not create terminal"
)
}
val
textGraphics
:
TextGraphics
=
terminal
.
newTextGraphics
()
try
{
fun
getLeftScreenId
():
Int
{
var
newId
=
currentScreenId
-
1
if
(
newId
<
0
)
{
newId
=
screens
.
size
-
1
}
return
newId
}
fun
getRightScreenId
():
Int
{
var
newId
=
1
+
currentScreenId
if
(
newId
>=
screens
.
size
)
{
newId
=
0
}
return
newId
}
fun
getScreenName
(
id
:
Int
):
String
{
return
when
(
screens
[
id
])
{
0L
->
{
"Console Screen"
}
else
->
{
"Bot: ${screens[id]}"
}
}
}
var
inited
=
false
fun
clearRows
(
row
:
Int
)
{
textGraphics
.
putString
(
0
,
row
,
" "
.
repeat
(
terminal
.
terminalSize
.
columns
))
}
fun
drawFrame
(
title
:
String
)
{
val
width
=
terminal
.
terminalSize
.
columns
val
height
=
terminal
.
terminalSize
.
rows
terminal
.
setBackgroundColor
(
TextColor
.
ANSI
.
DEFAULT
)
if
(!
inited
)
{
val
mainTitle
=
"Mirai Console v0.01 Core v0.14"
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
WHITE
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
GREEN
textGraphics
.
putString
((
width
-
mainTitle
.
length
)
/
2
,
1
,
mainTitle
,
SGR
.
BOLD
)
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
putString
(
2
,
3
,
"-"
.
repeat
(
width
-
4
))
textGraphics
.
putString
(
2
,
5
,
"-"
.
repeat
(
width
-
4
))
textGraphics
.
putString
(
2
,
height
-
4
,
"-"
.
repeat
(
width
-
4
))
textGraphics
.
putString
(
2
,
height
-
3
,
"|>>>"
)
textGraphics
.
putString
(
width
-
3
,
height
-
3
,
"|"
)
textGraphics
.
putString
(
2
,
height
-
2
,
"-"
.
repeat
(
width
-
4
))
inited
=
true
}
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
DEFAULT
val
leftName
=
getScreenName
(
getLeftScreenId
())
clearRows
(
2
)
textGraphics
.
putString
((
width
-
title
.
length
)
/
2
-
"$leftName << "
.
length
,
2
,
"$leftName << "
)
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
WHITE
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
YELLOW
textGraphics
.
putString
((
width
-
title
.
length
)
/
2
,
2
,
title
,
SGR
.
BOLD
)
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
DEFAULT
val
rightName
=
getScreenName
(
getRightScreenId
())
textGraphics
.
putString
((
width
+
title
.
length
)
/
2
+
1
,
2
,
">> $rightName"
)
}
fun
drawMainFrame
(
onlineBotCount
:
Number
)
{
drawFrame
(
"Console Screen"
)
val
width
=
terminal
.
terminalSize
.
columns
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
DEFAULT
clearRows
(
4
)
textGraphics
.
putString
(
2
,
4
,
"|Online Bots: $onlineBotCount"
)
textGraphics
.
putString
(
width
-
2
-
"Powered By Mamoe Technologies|"
.
length
,
4
,
"Powered By Mamoe Technologies|"
)
}
fun
drawBotFrame
(
qq
:
Long
,
adminCount
:
Number
)
{
drawFrame
(
"Bot: $qq"
)
val
width
=
terminal
.
terminalSize
.
columns
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
DEFAULT
clearRows
(
4
)
textGraphics
.
putString
(
2
,
4
,
"|Admins: $adminCount"
)
textGraphics
.
putString
(
width
-
2
-
"Add admins via commands|"
.
length
,
4
,
"Add admins via commands|"
)
}
fun
drawLogs
(
values
:
List
<
String
>)
{
val
width
=
terminal
.
terminalSize
.
columns
-
6
val
heightMin
=
5
var
currentHeight
=
terminal
.
terminalSize
.
rows
-
5
for
(
index
in
heightMin
until
currentHeight
)
{
clearRows
(
index
)
}
values
.
forEach
{
if
(
currentHeight
>
heightMin
)
{
var
x
=
it
while
(
currentHeight
>
heightMin
)
{
if
(
x
.
isEmpty
()
||
x
.
isBlank
())
break
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
GREEN
textGraphics
.
backgroundColor
=
TextColor
.
ANSI
.
DEFAULT
val
towrite
=
if
(
x
.
length
>
width
)
{
x
.
substring
(
0
,
width
).
also
{
x
=
x
.
substring
(
width
)
}
}
else
{
x
.
also
{
x
=
""
}
}
textGraphics
.
putString
(
3
,
currentHeight
,
towrite
,
SGR
.
ITALIC
)
--
currentHeight
}
}
}
if
(
terminal
is
SwingTerminalFrame
)
{
terminal
.
flush
()
}
}
var
commandBuilder
=
StringBuilder
()
fun
redrawCommand
()
{
val
height
=
terminal
.
terminalSize
.
rows
val
width
=
terminal
.
terminalSize
.
columns
clearRows
(
height
-
3
)
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
DEFAULT
textGraphics
.
putString
(
2
,
height
-
3
,
"|>>>"
)
textGraphics
.
putString
(
width
-
3
,
height
-
3
,
"|"
)
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
BLUE
textGraphics
.
putString
(
7
,
height
-
3
,
commandBuilder
.
toString
())
if
(
terminal
is
SwingTerminalFrame
)
{
terminal
.
flush
()
}
}
fun
addCommandChar
(
c
:
Char
)
{
if
(
commandBuilder
.
isEmpty
()
&&
c
!=
'/'
)
{
addCommandChar
(
'/'
)
}
textGraphics
.
foregroundColor
=
TextColor
.
ANSI
.
BLUE
val
height
=
terminal
.
terminalSize
.
rows
commandBuilder
.
append
(
c
)
if
(
terminal
is
SwingTerminalFrame
)
{
redrawCommand
()
}
else
{
textGraphics
.
putString
(
6
+
commandBuilder
.
length
,
height
-
3
,
c
.
toString
())
}
}
fun
deleteCommandChar
()
{
if
(!
commandBuilder
.
isEmpty
())
{
commandBuilder
=
StringBuilder
(
commandBuilder
.
toString
().
substring
(
0
,
commandBuilder
.
length
-
1
))
}
val
height
=
terminal
.
terminalSize
.
rows
if
(
terminal
is
SwingTerminalFrame
)
{
redrawCommand
()
}
else
{
textGraphics
.
putString
(
7
+
commandBuilder
.
length
,
height
-
3
,
" "
)
}
}
fun
emptyCommand
()
{
commandBuilder
=
StringBuilder
()
redrawCommand
()
if
(
terminal
is
SwingTerminal
)
{
terminal
.
flush
()
}
}
fun
update
()
{
when
(
screens
[
currentScreenId
])
{
0L
->
{
drawMainFrame
(
screens
.
size
-
1
)
}
else
->
{
drawBotFrame
(
screens
[
currentScreenId
],
0
)
}
}
terminal
.
flush
()
}
terminal
.
addResizeListener
(
TerminalResizeListener
{
terminal1
:
Terminal
,
newSize
:
TerminalSize
->
terminal
.
clearScreen
()
inited
=
false
update
()
redrawCommand
()
})
update
()
val
charList
=
listOf
(
','
,
'.'
,
'/'
,
'@'
,
'#'
,
'$'
,
'%'
,
'^'
,
'&'
,
'*'
,
'('
,
')'
,
'_'
,
'='
,
'+'
,
'!'
,
' '
)
thread
{
while
(
true
)
{
var
keyStroke
:
KeyStroke
=
terminal
.
readInput
()
when
(
keyStroke
.
keyType
)
{
KeyType
.
ArrowLeft
->
{
currentScreenId
=
getLeftScreenId
()
update
()
}
KeyType
.
ArrowRight
->
{
currentScreenId
=
getRightScreenId
()
update
()
}
KeyType
.
Enter
->
{
emptyCommand
()
}
else
->
{
if
(
keyStroke
.
character
!=
null
)
{
if
(
keyStroke
.
character
.
toInt
()
==
8
)
{
deleteCommandChar
()
}
if
(
keyStroke
.
character
.
isLetterOrDigit
()
||
charList
.
contains
(
keyStroke
.
character
))
{
addCommandChar
(
keyStroke
.
character
)
}
}
}
}
}
}
}
catch
(
e
:
Exception
)
{
e
.
printStackTrace
()
}
}
}
class
LimitLinkedQueue
<
T
>(
val
limit
:
Int
=
50
)
:
LinkedList
<
T
>()
{
override
fun
push
(
e
:
T
)
{
if
(
size
>=
limit
)
{
pollLast
()
}
super
.
push
(
e
)
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment