├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── cmd
└── main.go
├── config.example.yaml
├── docker-compose.yml
├── go.mod
├── go.sum
├── internal
├── handlers
│ ├── card_common_action.go
│ ├── card_role_action.go
│ ├── common.go
│ ├── event_audio_action.go
│ ├── event_common_action.go
│ ├── event_msg_action.go
│ ├── handlers.go
│ ├── inti.go
│ └── msg.go
├── initialization
│ ├── config.go
│ ├── gin.go
│ ├── lark_client.go
│ └── roles_load.go
└── services
│ └── services.go
├── pkg
├── loadbalancer
│ └── loadbalancer.go
├── openai
│ ├── audio.go
│ ├── billing.go
│ ├── common.go
│ ├── gpt3.go
│ ├── gpt3_test.go
│ ├── picture.go
│ └── test_file
│ │ ├── img.png
│ │ ├── test.jpg
│ │ └── test.wav
├── qdrantkit
│ ├── collection.go
│ ├── collection_test.go
│ ├── common.go
│ ├── point.go
│ └── qdrant.go
├── services
│ ├── msgCache.go
│ └── sessionCache.go
└── utils
│ ├── api2d
│ └── api2d.go
│ ├── audio
│ ├── ogg.go
│ └── wav.go
│ ├── strings.go
│ ├── strings_test.go
│ └── texttospeech
│ └── transform.go
└── role_list.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Go template
2 | # If you prefer the allow list template instead of the deny list, see community template:
3 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
4 | #
5 | # Binaries for programs and plugins
6 | *.exe
7 | *.exe~
8 | *.dll
9 | *.so
10 | *.dylib
11 |
12 | # Test binary, built with `go test -c`
13 | *.test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | *.out
17 |
18 | # Dependency directories (remove the comment below to include it)
19 | # vendor/
20 |
21 | # Go workspace file
22 | go.work
23 | ./code/target
24 | .idea
25 | .vscode
26 | .s
27 |
28 | config.yaml
29 |
30 | /code/target/
31 | oral-friend
32 | __debug_bin
33 | !/cmd/oral-friend
34 | .env
35 |
36 | docker.md
37 | # Mac OS
38 | .DS_Store
39 | **/.DS_Store
40 | *.pem
41 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.18 as golang
2 |
3 | ENV GO111MODULE=on \
4 | CGO_ENABLED=1 \
5 | GOPROXY=https://goproxy.cn,direct
6 |
7 | WORKDIR /build
8 | ADD /code /build
9 |
10 | RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -o feishu_chatgpt
11 |
12 | FROM alpine:latest
13 |
14 | WORKDIR /app
15 |
16 | RUN apk add --no-cache bash
17 | COPY --from=golang /build/feishu_chatgpt /app
18 | COPY --from=golang /build/role_list.yaml /app
19 | EXPOSE 9000
20 | ENTRYPOINT ["/app/feishu_chatgpt"]
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Feishu-Oral-Friend
2 |
3 | 🚀 我们希望在发布正式 Alpha 版本之前完成的功能列表:
4 |
5 | ✨ 可以添加更多你们能想到的或你们想要的功能
6 | - [ ] 调研现有语音识别工具(如飞书)、现有翻译工具并整理为文档(如google)、现有AI语音(如azure)
7 | - [ ] 实现语言互译
8 | - [ ] 为使用者进行英语语句纠错
9 | - [ ] 为使用者提供具备上下文的对话式聊天
10 | - [ ] 为使用者提供双语交流(可配置),比如使用者使用英文与我们的bot对话,bot会回复给他英文以及对应的中文
11 |
12 | ## 赞助感谢
13 |
14 | 友情感谢 'Find My A' 提供的部分经费赞助!
15 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "oral-friend/internal/handlers"
7 | "oral-friend/internal/initialization"
8 | "oral-friend/pkg/openai"
9 | "oral-friend/pkg/utils/api2d"
10 |
11 | "github.com/gin-gonic/gin"
12 | sdkginext "github.com/larksuite/oapi-sdk-gin"
13 | larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
14 | "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
15 | larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
16 | "github.com/spf13/pflag"
17 | )
18 |
19 | var (
20 | cfg = pflag.StringP("config", "c", "config.yaml", "api server config file path.")
21 | )
22 |
23 | func main() {
24 | api2d.TextToSpeech()
25 | initialization.InitRoleList()
26 | config := initialization.LoadConfig(*cfg)
27 | initialization.LoadLarkClient(*config)
28 | gpt := openai.NewChatGPT(*config)
29 | handlers.InitHandlers(gpt, *config)
30 |
31 | eventHandler := dispatcher.NewEventDispatcher(
32 | config.FeishuAppVerificationToken, config.FeishuAppEncryptKey).
33 | OnP2MessageReceiveV1(handlers.Handler).
34 | OnP2MessageReadV1(func(ctx context.Context, event *larkim.P2MessageReadV1) error {
35 | return handlers.ReadHandler(ctx, event)
36 | })
37 |
38 | cardHandler := larkcard.NewCardActionHandler(
39 | config.FeishuAppVerificationToken, config.FeishuAppEncryptKey,
40 | handlers.CardHandler())
41 |
42 | r := gin.Default()
43 | r.GET("/ping", func(c *gin.Context) {
44 | c.JSON(200, gin.H{
45 | "message": "pong",
46 | })
47 | })
48 | r.POST("/webhook/event",
49 | sdkginext.NewEventHandlerFunc(eventHandler))
50 | r.POST("/webhook/card",
51 | sdkginext.NewCardActionHandlerFunc(
52 | cardHandler))
53 |
54 | err := initialization.StartServer(*config, r)
55 | if err != nil {
56 | log.Fatalf("failed to start server: %v", err)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/config.example.yaml:
--------------------------------------------------------------------------------
1 | # 飞书
2 | APP_ID: cli_axxx
3 | APP_SECRET: xxx
4 | APP_ENCRYPT_KEY: xxx
5 | APP_VERIFICATION_TOKEN: xxx
6 | # 请确保和飞书应用管理平台中的设置一致
7 | BOT_NAME: chatGpt
8 | # openAI key 支持负载均衡 可以填写多个key 用逗号分隔
9 | OPENAI_KEY: sk-xxx,sk-xxx,sk-xxx
10 | # 服务器配置
11 | HTTP_PORT: 9000
12 | HTTPS_PORT: 9001
13 | USE_HTTPS: false
14 | CERT_FILE: cert.pem
15 | KEY_FILE: key.pem
16 | # openai 地址, 一般不需要修改, 除非你有自己的反向代理
17 | API_URL: https://api.openai.com
18 | # 代理设置, 例如 "http://127.0.0.1:7890", ""代表不使用代理
19 | HTTP_PROXY: ""
20 | QDRANT_COLLECTION: oral-friend
21 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 | services:
3 | feishu-chatgpt:
4 | container_name: feishu-chatgpt
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | ports:
9 | - "9000:9000/tcp"
10 | # volumes:
11 | # - ./code/config.yaml:/app/config.yaml:ro
12 | environment:
13 | - APP_ID=cli_axxx
14 | - APP_SECRET=xxx
15 | - APP_ENCRYPT_KEY=xxx
16 | - APP_VERIFICATION_TOKEN=xxx
17 | # 请确保和飞书应用管理平台中的设置一致
18 | - BOT_NAME=chatGpt
19 | # OpenAI API Key 支持负载均衡, 可以填写多个 Key 用逗号分隔
20 | - OPENAI_KEY=sk-xxx,sk-xxx,sk-xxx
21 | # 服务器配置
22 | - HTTP_PORT=9000
23 | - HTTPS_PORT=9001
24 | - USE_HTTPS=false
25 | - CERT_FILE=cert.pem
26 | - KEY_FILE=key.pem
27 | # OpenAI 地址, 一般不需要修改, 除非你有自己的反向代理
28 | - API_URL=https://api.openai.com
29 | # 代理设置, 例如 - HTTP_PROXY=http://127.0.0.1:7890, 默认代表不使用代理
30 | - HTTP_PROXY
31 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module oral-friend
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/duke-git/lancet/v2 v2.1.19
7 | github.com/gin-gonic/gin v1.9.0
8 | github.com/google/uuid v1.3.0
9 | github.com/larksuite/oapi-sdk-gin v1.0.0
10 | github.com/larksuite/oapi-sdk-go/v3 v3.0.19
11 | github.com/mitchellh/mapstructure v1.5.0
12 | github.com/pandodao/tokenizer-go v0.2.0
13 | github.com/patrickmn/go-cache v2.1.0+incompatible
14 | github.com/pion/opus v0.0.0-20230408025906-6520e40488b7
15 | github.com/spf13/pflag v1.0.5
16 | github.com/spf13/viper v1.15.0
17 | gopkg.in/yaml.v2 v2.4.0
18 | )
19 |
20 | require (
21 | github.com/bytedance/sonic v1.8.0 // indirect
22 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
23 | github.com/dlclark/regexp2 v1.8.1 // indirect
24 | github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c // indirect
25 | github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f // indirect
26 | github.com/fsnotify/fsnotify v1.6.0 // indirect
27 | github.com/gin-contrib/sse v0.1.0 // indirect
28 | github.com/go-playground/locales v0.14.1 // indirect
29 | github.com/go-playground/universal-translator v0.18.1 // indirect
30 | github.com/go-playground/validator/v10 v10.11.2 // indirect
31 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
32 | github.com/goccy/go-json v0.10.0 // indirect
33 | github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
34 | github.com/hashicorp/hcl v1.0.0 // indirect
35 | github.com/json-iterator/go v1.1.12 // indirect
36 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect
37 | github.com/leodido/go-urn v1.2.1 // indirect
38 | github.com/magiconair/properties v1.8.7 // indirect
39 | github.com/mattn/go-isatty v0.0.17 // indirect
40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
41 | github.com/modern-go/reflect2 v1.0.2 // indirect
42 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect
43 | github.com/spf13/afero v1.9.3 // indirect
44 | github.com/spf13/cast v1.5.0 // indirect
45 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
46 | github.com/subosito/gotenv v1.4.2 // indirect
47 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
48 | github.com/ugorji/go/codec v1.2.9 // indirect
49 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
50 | golang.org/x/crypto v0.5.0 // indirect
51 | golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
52 | golang.org/x/net v0.7.0 // indirect
53 | golang.org/x/sys v0.5.0 // indirect
54 | golang.org/x/text v0.8.0 // indirect
55 | google.golang.org/protobuf v1.28.1 // indirect
56 | gopkg.in/ini.v1 v1.67.0 // indirect
57 | gopkg.in/yaml.v3 v3.0.1 // indirect
58 | )
59 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
42 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
43 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
44 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
45 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
46 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
47 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
48 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
49 | github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
50 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
51 | github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
52 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
53 | github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
54 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
55 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
56 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
57 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
58 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
59 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
60 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
61 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
62 | github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
63 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
64 | github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
65 | github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
66 | github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
67 | github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
68 | github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c h1:/utv6nmTctV6OVgfk5+O6lEMEWL+6KJy4h9NZ5fnkQQ=
69 | github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
70 | github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
71 | github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
72 | github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f h1:mmnNidRg3cMfcgyeNtIBSDZgjf/85lA/2pplccwSxYg=
73 | github.com/dop251/goja_nodejs v0.0.0-20230226152057-060fa99b809f/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
74 | github.com/duke-git/lancet/v2 v2.1.19 h1:dbRB1m6wOMV1I0ax/3S6ngop8SYM6I7sr+7D9IXjS2E=
75 | github.com/duke-git/lancet/v2 v2.1.19/go.mod h1:hNcc06mV7qr+crH/0nP+rlC3TB0Q9g5OrVnO8/TGD4c=
76 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
77 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
78 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
79 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
80 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
81 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
82 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
83 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
84 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
85 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
86 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
87 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
88 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
89 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
90 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
91 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
92 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
93 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
94 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
95 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
96 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
97 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
98 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
99 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
100 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
101 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
102 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
103 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
104 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
105 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
106 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
107 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
108 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
109 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
110 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
111 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
112 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
113 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
114 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
115 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
116 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
117 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
118 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
119 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
120 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
121 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
122 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
123 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
124 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
125 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
126 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
127 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
128 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
129 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
130 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
131 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
132 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
133 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
134 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
135 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
136 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
137 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
138 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
139 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
140 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
141 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
142 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
143 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
144 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
145 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
146 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
147 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
148 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
149 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
150 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
151 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
152 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
153 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
154 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
155 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
156 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
157 | github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ=
158 | github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
159 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
160 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
161 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
162 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
163 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
164 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
165 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
166 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
167 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
168 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
169 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
170 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
171 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
172 | github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
173 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
174 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
175 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
176 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
177 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
178 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
179 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
180 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
181 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
182 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
183 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
184 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
185 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
186 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
187 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
188 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
189 | github.com/larksuite/oapi-sdk-gin v1.0.0 h1:pf2JyCSECZ2ra16JoQEgbl7PPaaOpoAEBno+Y91HFO0=
190 | github.com/larksuite/oapi-sdk-gin v1.0.0/go.mod h1:17QKeJMEkIYBUOrUoP0HBVErfzdu7cuJ9XiXitUwe/s=
191 | github.com/larksuite/oapi-sdk-go/v3 v3.0.19 h1:fmMURh9+APZ+a9VX75TTGFfEvmghMHfUBSrivK6pyAc=
192 | github.com/larksuite/oapi-sdk-go/v3 v3.0.19/go.mod h1:FKi8vBgtkBt/xNRQUwdWvoDmsPh7/wP75Sn5IBIBQLk=
193 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
194 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
195 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
196 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
197 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
198 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
199 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
200 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
201 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
202 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
203 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
204 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
205 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
206 | github.com/pandodao/tokenizer-go v0.2.0 h1:NhfI8fGvQkDld2cZCag6NEU3pJ/ugU9zoY1R/zi9YCs=
207 | github.com/pandodao/tokenizer-go v0.2.0/go.mod h1:t6qFbaleKxbv0KNio2XUN/mfGM5WKv4haPXDQWVDG00=
208 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
209 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
210 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
211 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
212 | github.com/pion/opus v0.0.0-20230408025906-6520e40488b7 h1:GJafmxQvfcOwYIcXpLLPIj+5f2VNAtfzezjvaDM9iDo=
213 | github.com/pion/opus v0.0.0-20230408025906-6520e40488b7/go.mod h1:m8ODxkLrcNvLY6BPvOj7yLxK1wMQWA+2jqKcsrZ293U=
214 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
215 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
216 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
217 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
218 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
219 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
220 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
221 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
222 | github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
223 | github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
224 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
225 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
226 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
227 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
228 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
229 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
230 | github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
231 | github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
232 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
233 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
234 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
235 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
236 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
237 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
238 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
239 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
240 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
241 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
242 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
243 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
244 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
245 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
246 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
247 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
248 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
249 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
250 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
251 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
252 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
253 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
254 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
255 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
256 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
257 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
258 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
259 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
260 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
261 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
262 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
263 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
264 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
265 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
266 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
267 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
268 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
269 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
270 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
271 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
272 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
273 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
274 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
275 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
276 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
277 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
278 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
279 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
280 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
281 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
282 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
283 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
284 | golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
285 | golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
286 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
287 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
288 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
289 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
290 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
291 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
292 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
293 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
294 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
295 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
296 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
297 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
298 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
299 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
300 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
301 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
302 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
303 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
304 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
305 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
306 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
307 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
308 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
309 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
310 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
311 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
312 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
313 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
314 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
315 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
316 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
317 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
318 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
319 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
320 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
321 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
322 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
323 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
324 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
325 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
326 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
327 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
328 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
329 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
330 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
331 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
332 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
333 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
334 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
335 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
336 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
337 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
338 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
339 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
340 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
341 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
342 | golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
343 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
344 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
345 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
346 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
347 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
348 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
349 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
350 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
351 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
352 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
353 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
354 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
355 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
356 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
357 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
358 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
359 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
360 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
361 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
362 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
363 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
364 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
365 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
366 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
367 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
368 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
369 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
370 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
371 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
372 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
373 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
374 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
375 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
376 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
377 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
378 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
379 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
380 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
381 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
382 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
383 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
384 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
385 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
386 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
387 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
388 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
389 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
390 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
391 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
392 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
393 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
394 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
395 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
396 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
397 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
398 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
399 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
400 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
401 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
402 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
403 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
404 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
405 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
406 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
407 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
408 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
409 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
410 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
411 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
412 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
413 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
414 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
415 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
416 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
417 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
418 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
419 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
420 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
421 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
422 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
423 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
424 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
425 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
426 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
427 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
428 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
429 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
430 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
431 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
432 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
433 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
434 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
435 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
436 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
437 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
438 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
439 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
440 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
441 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
442 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
443 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
444 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
445 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
446 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
447 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
448 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
449 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
450 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
451 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
452 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
453 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
454 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
455 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
456 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
457 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
458 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
459 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
460 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
461 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
462 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
463 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
464 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
465 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
466 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
467 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
468 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
469 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
470 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
471 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
472 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
473 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
474 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
475 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
476 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
477 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
478 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
479 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
480 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
481 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
482 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
483 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
484 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
485 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
486 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
487 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
488 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
489 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
490 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
491 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
492 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
493 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
494 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
495 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
496 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
497 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
498 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
499 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
500 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
501 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
502 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
503 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
504 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
505 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
506 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
507 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
508 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
509 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
510 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
511 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
512 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
513 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
514 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
515 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
516 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
517 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
518 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
519 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
520 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
521 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
522 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
523 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
524 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
525 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
526 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
527 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
528 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
529 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
530 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
531 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
532 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
533 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
534 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
535 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
536 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
537 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
538 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
539 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
540 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
541 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
542 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
543 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
544 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
545 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
546 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
547 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
548 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
549 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
550 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
551 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
552 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
553 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
554 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
555 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
556 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
557 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
558 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
559 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
560 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
561 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
562 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
563 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
564 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
565 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
566 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
567 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
568 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
569 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
570 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
571 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
572 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
573 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
574 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
575 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
576 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
577 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
578 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
579 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
580 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
581 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
582 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
583 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
584 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
585 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
586 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
587 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
588 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
589 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
590 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
591 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
592 |
--------------------------------------------------------------------------------
/internal/handlers/card_common_action.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 |
8 | larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
9 | )
10 |
11 | type CardHandlerMeta func(cardMsg CardMsg, m MessageHandler) CardHandlerFunc
12 |
13 | type CardHandlerFunc func(ctx context.Context, cardAction *larkcard.CardAction) (
14 | interface{}, error)
15 |
16 | var ErrNextHandler = fmt.Errorf("next handler")
17 |
18 | func NewCardHandler(m MessageHandler) CardHandlerFunc {
19 | handlers := []CardHandlerMeta{
20 | NewRoleTagCardHandler,
21 | NewRoleCardHandler,
22 | }
23 |
24 | return func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
25 | var cardMsg CardMsg
26 | actionValue := cardAction.Action.Value
27 | actionValueJson, _ := json.Marshal(actionValue)
28 | json.Unmarshal(actionValueJson, &cardMsg)
29 | //pp.Println(cardMsg)
30 | for _, handler := range handlers {
31 | h := handler(cardMsg, m)
32 | i, err := h(ctx, cardAction)
33 | if err == ErrNextHandler {
34 | continue
35 | }
36 | return i, err
37 | }
38 | return nil, nil
39 | }
40 | }
--------------------------------------------------------------------------------
/internal/handlers/card_role_action.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "oral-friend/internal/initialization"
6 | "oral-friend/pkg/openai"
7 | "oral-friend/pkg/services"
8 |
9 | larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
10 | )
11 |
12 | func NewRoleTagCardHandler(cardMsg CardMsg,
13 | m MessageHandler) CardHandlerFunc {
14 | return func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
15 |
16 | if cardMsg.Kind == RoleTagsChooseKind {
17 | newCard, err, done := CommonProcessRoleTag(cardMsg, cardAction,
18 | m.sessionCache)
19 | if done {
20 | return newCard, err
21 | }
22 | return nil, nil
23 | }
24 | return nil, ErrNextHandler
25 | }
26 | }
27 |
28 | func NewRoleCardHandler(cardMsg CardMsg,
29 | m MessageHandler) CardHandlerFunc {
30 | return func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
31 |
32 | if cardMsg.Kind == RoleChooseKind {
33 | newCard, err, done := CommonProcessRole(cardMsg, cardAction,
34 | m.sessionCache)
35 | if done {
36 | return newCard, err
37 | }
38 | return nil, nil
39 | }
40 | return nil, ErrNextHandler
41 | }
42 | }
43 |
44 | func CommonProcessRoleTag(msg CardMsg, cardAction *larkcard.CardAction,
45 | cache services.SessionServiceCacheInterface) (interface{},
46 | error, bool) {
47 | option := cardAction.Action.Option
48 | //replyMsg(context.Background(), "已选择tag:"+option,
49 | // &msg.MsgId)
50 | roles := initialization.GetTitleListByTag(option)
51 | //fmt.Printf("roles: %s", roles)
52 | SendRoleListCard(context.Background(), &msg.SessionId,
53 | &msg.MsgId, option, *roles)
54 | return nil, nil, true
55 | }
56 |
57 | func CommonProcessRole(msg CardMsg, cardAction *larkcard.CardAction,
58 | cache services.SessionServiceCacheInterface) (interface{},
59 | error, bool) {
60 | option := cardAction.Action.Option
61 | contentByTitle, error := initialization.GetFirstRoleContentByTitle(option)
62 | if error != nil {
63 | return nil, error, true
64 | }
65 | cache.Clear(msg.SessionId)
66 | systemMsg := append([]openai.Messages{}, openai.Messages{
67 | Role: "system", Content: contentByTitle,
68 | })
69 | cache.SetMsg(msg.SessionId, systemMsg)
70 | //pp.Println("systemMsg: ", systemMsg)
71 | sendSystemInstructionCard(context.Background(), &msg.SessionId,
72 | &msg.MsgId, contentByTitle)
73 | //replyMsg(context.Background(), "已选择角色:"+contentByTitle,
74 | // &msg.MsgId)
75 | return nil, nil, true
76 | }
77 |
--------------------------------------------------------------------------------
/internal/handlers/common.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "regexp"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | // 处理消息
12 | func processMessage(msg interface{}) (string, error) {
13 | msg = strings.TrimSpace(msg.(string))
14 | msgB, err := json.Marshal(msg)
15 | if err != nil {
16 | return "", err
17 | }
18 |
19 | msgStr := string(msgB)
20 |
21 | if len(msgStr) >= 2 {
22 | msgStr = msgStr[1 : len(msgStr)-1]
23 | }
24 | return msgStr, nil
25 | }
26 |
27 | func processNewLine(msg string) string {
28 | return strings.Replace(msg, "\\n", `
29 | `, -1)
30 | }
31 |
32 | func processQuote(msg string) string {
33 | return strings.Replace(msg, "\\\"", "\"", -1)
34 | }
35 |
36 | // 将字符中 \u003c 替换为 < 等等
37 | func processUnicode(msg string) string {
38 | regex := regexp.MustCompile(`\\u[0-9a-fA-F]{4}`)
39 | return regex.ReplaceAllStringFunc(msg, func(s string) string {
40 | r, _ := regexp.Compile(`\\u`)
41 | s = r.ReplaceAllString(s, "")
42 | i, _ := strconv.ParseInt(s, 16, 32)
43 | return string(rune(i))
44 | })
45 | }
46 | // func sendCard
47 | func msgFilter(msg string) string {
48 | //replace @到下一个非空的字段 为 ''
49 | regex := regexp.MustCompile(`@[^ ]*`)
50 | return regex.ReplaceAllString(msg, "")
51 |
52 | }
53 |
54 | func cleanTextBlock(msg string) string {
55 | msg = processNewLine(msg)
56 | msg = processUnicode(msg)
57 | msg = processQuote(msg)
58 | return msg
59 | }
60 |
61 |
62 | func parseContent(content string) string {
63 | //"{\"text\":\"@_user_1 hahaha\"}",
64 | //only get text content hahaha
65 | var contentMap map[string]interface{}
66 | err := json.Unmarshal([]byte(content), &contentMap)
67 | if err != nil {
68 | fmt.Println(err)
69 | }
70 | if contentMap["text"] == nil {
71 | return ""
72 | }
73 | text := contentMap["text"].(string)
74 | return msgFilter(text)
75 | }
76 |
77 | func parseFileKey(content string) string {
78 | var contentMap map[string]interface{}
79 | err := json.Unmarshal([]byte(content), &contentMap)
80 | if err != nil {
81 | fmt.Println(err)
82 | return ""
83 | }
84 | if contentMap["file_key"] == nil {
85 | return ""
86 | }
87 | fileKey := contentMap["file_key"].(string)
88 | return fileKey
89 | }
90 |
91 | func parseImageKey(content string) string {
92 | var contentMap map[string]interface{}
93 | err := json.Unmarshal([]byte(content), &contentMap)
94 | if err != nil {
95 | fmt.Println(err)
96 | return ""
97 | }
98 | if contentMap["image_key"] == nil {
99 | return ""
100 | }
101 | imageKey := contentMap["image_key"].(string)
102 | return imageKey
103 | }
--------------------------------------------------------------------------------
/internal/handlers/event_audio_action.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "oral-friend/internal/initialization"
7 | "oral-friend/pkg/utils/audio"
8 | "os"
9 |
10 | larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
11 | )
12 |
13 | type AudioAction struct { /*语音*/
14 | }
15 |
16 | func (*AudioAction) Execute(a *ActionInfo) bool {
17 | check := AzureModeCheck(a)
18 | if !check {
19 | return true
20 | }
21 |
22 | // 只有私聊才解析语音,其他不解析
23 | if a.info.handlerType != UserHandler {
24 | return true
25 | }
26 |
27 | //判断是否是语音
28 | if a.info.msgType == "audio" {
29 | fileKey := a.info.fileKey
30 | //fmt.Printf("fileKey: %s \n", fileKey)
31 | msgId := a.info.msgId
32 | //fmt.Println("msgId: ", *msgId)
33 | req := larkim.NewGetMessageResourceReqBuilder().MessageId(
34 | *msgId).FileKey(fileKey).Type("file").Build()
35 | resp, err := initialization.GetLarkClient().Im.MessageResource.Get(context.Background(), req)
36 | //fmt.Println(resp, err)
37 | if err != nil {
38 | fmt.Println(err)
39 | return true
40 | }
41 | f := fmt.Sprintf("%s.ogg", fileKey)
42 | resp.WriteFile(f)
43 | defer os.Remove(f)
44 |
45 | //fmt.Println("f: ", f)
46 | output := fmt.Sprintf("%s.mp3", fileKey)
47 | // 等待转换完成
48 | audio.OggToWavByPath(f, output)
49 | defer os.Remove(output)
50 | //fmt.Println("output: ", output)
51 |
52 | text, err := a.handler.gpt.AudioToText(output)
53 | if err != nil {
54 | fmt.Println(err)
55 |
56 | sendMsg(*a.ctx, fmt.Sprintf("🤖️:语音转换失败,请稍后再试~\n错误信息: %v", err), a.info.msgId)
57 | return false
58 | }
59 |
60 | replyMsg(*a.ctx, fmt.Sprintf("🤖️:%s", text), a.info.msgId)
61 | //fmt.Println("text: ", text)
62 | a.info.qParsed = text
63 | return true
64 | }
65 |
66 | return true
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/internal/handlers/event_common_action.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "oral-friend/internal/initialization"
7 | "oral-friend/pkg/openai"
8 | "oral-friend/pkg/utils"
9 |
10 | larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
11 | )
12 |
13 | type MsgInfo struct {
14 | handlerType HandlerType
15 | msgType string
16 | msgId *string
17 | chatId *string
18 | qParsed string
19 | fileKey string
20 | sessionId *string
21 | mention []*larkim.MentionEvent
22 | }
23 | type ActionInfo struct {
24 | handler *MessageHandler
25 | ctx *context.Context
26 | info *MsgInfo
27 | }
28 |
29 | type Action interface {
30 | Execute(a *ActionInfo) bool
31 | }
32 |
33 | type ProcessedUniqueAction struct { //消息唯一性
34 | }
35 |
36 | func (*ProcessedUniqueAction) Execute(a *ActionInfo) bool {
37 | if a.handler.msgCache.IfProcessed(*a.info.msgId) {
38 | return false
39 | }
40 | a.handler.msgCache.TagProcessed(*a.info.msgId)
41 | return true
42 | }
43 |
44 | type ProcessMentionAction struct { //是否机器人应该处理
45 | }
46 |
47 | func (*ProcessMentionAction) Execute(a *ActionInfo) bool {
48 | // 私聊直接过
49 | if a.info.handlerType == UserHandler {
50 | return true
51 | }
52 | // 群聊判断是否提到机器人
53 | if a.info.handlerType == GroupHandler {
54 | if a.handler.judgeIfMentionMe(a.info.mention) {
55 | return true
56 | }
57 | return false
58 | }
59 | return false
60 | }
61 |
62 | type EmptyAction struct { /*空消息*/
63 | }
64 |
65 | func (*EmptyAction) Execute(a *ActionInfo) bool {
66 | if len(a.info.qParsed) == 0 {
67 | sendMsg(*a.ctx, "🤖️:你想知道什么呢~", a.info.chatId)
68 | fmt.Println("msgId", *a.info.msgId,
69 | "message.text is empty")
70 | return false
71 | }
72 | return true
73 | }
74 |
75 | type ClearAction struct { /*清除消息*/
76 | }
77 |
78 | func (*ClearAction) Execute(a *ActionInfo) bool {
79 | if _, foundClear := utils.EitherTrimEqual(a.info.qParsed,
80 | "/clear", "清除"); foundClear {
81 | sendClearCacheCheckCard(*a.ctx, a.info.sessionId,
82 | a.info.msgId)
83 | return false
84 | }
85 | return true
86 | }
87 |
88 | type RolePlayAction struct { /*角色扮演*/
89 | }
90 |
91 | func (*RolePlayAction) Execute(a *ActionInfo) bool {
92 | if system, foundSystem := utils.EitherCutPrefix(a.info.qParsed,
93 | "/system ", "角色扮演 "); foundSystem {
94 | a.handler.sessionCache.Clear(*a.info.sessionId)
95 | systemMsg := append([]openai.Messages{}, openai.Messages{
96 | Role: "system", Content: system,
97 | })
98 | a.handler.sessionCache.SetMsg(*a.info.sessionId, systemMsg)
99 | sendSystemInstructionCard(*a.ctx, a.info.sessionId,
100 | a.info.msgId, system)
101 | return false
102 | }
103 | return true
104 | }
105 |
106 | type HelpAction struct { /*帮助*/
107 | }
108 |
109 | func (*HelpAction) Execute(a *ActionInfo) bool {
110 | if _, foundHelp := utils.EitherTrimEqual(a.info.qParsed, "/help",
111 | "帮助"); foundHelp {
112 | sendHelpCard(*a.ctx, a.info.sessionId, a.info.msgId)
113 | return false
114 | }
115 | return true
116 | }
117 |
118 | type BalanceAction struct { /*余额*/
119 | }
120 |
121 | func (*BalanceAction) Execute(a *ActionInfo) bool {
122 | if _, foundBalance := utils.EitherTrimEqual(a.info.qParsed,
123 | "/balance", "余额"); foundBalance {
124 | balanceResp, err := a.handler.gpt.GetBalance()
125 | if err != nil {
126 | replyMsg(*a.ctx, "查询余额失败,请稍后再试", a.info.msgId)
127 | return false
128 | }
129 | sendBalanceCard(*a.ctx, a.info.sessionId, *balanceResp)
130 | return false
131 | }
132 | return true
133 | }
134 |
135 | type RoleListAction struct { /*角色列表*/
136 | }
137 |
138 | func (*RoleListAction) Execute(a *ActionInfo) bool {
139 | if _, foundSystem := utils.EitherTrimEqual(a.info.qParsed,
140 | "/roles", "角色列表"); foundSystem {
141 | //a.handler.sessionCache.Clear(*a.info.sessionId)
142 | //systemMsg := append([]openai.Messages{}, openai.Messages{
143 | // Role: "system", Content: system,
144 | //})
145 | //a.handler.sessionCache.SetMsg(*a.info.sessionId, systemMsg)
146 | //sendSystemInstructionCard(*a.ctx, a.info.sessionId,
147 | // a.info.msgId, system)
148 | tags := initialization.GetAllUniqueTags()
149 | SendRoleTagsCard(*a.ctx, a.info.sessionId, a.info.msgId, *tags)
150 | return false
151 | }
152 | return true
153 | }
154 |
155 | type AIModeAction struct { /*AI模式*/
156 | }
157 | func (*AIModeAction) Execute(a *ActionInfo) bool {
158 | if _, foundMode := utils.EitherCutPrefix(a.info.qParsed,
159 | "/ai_mode", "AI模式"); foundMode {
160 | SendAIModeListsCard(*a.ctx, a.info.sessionId, a.info.msgId, openai.AIModeStrs)
161 | return false
162 | }
163 | return true
164 | }
--------------------------------------------------------------------------------
/internal/handlers/event_msg_action.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "fmt"
5 | "oral-friend/pkg/openai"
6 | "oral-friend/pkg/utils/texttospeech"
7 | )
8 |
9 | type MessageAction struct { /*消息*/
10 | chatgpt *openai.ChatGPT
11 | }
12 |
13 | func (*MessageAction) Execute(a *ActionInfo) bool {
14 | msg := a.handler.sessionCache.GetMsg(*a.info.sessionId)
15 | msg = append(msg, openai.Messages{
16 | Role: "user", Content: a.info.qParsed,
17 | })
18 | // get ai mode as temperature
19 | aiMode := a.handler.sessionCache.GetAIMode(*a.info.sessionId)
20 | completions, err := a.handler.gpt.Completions(msg, aiMode)
21 | if err != nil {
22 | replyMsg(*a.ctx, fmt.Sprintf(
23 | "🤖️:消息机器人摆烂了,请稍后再试~\n错误信息: %v", err), a.info.msgId)
24 | return false
25 | }
26 | msg = append(msg, completions)
27 | a.handler.sessionCache.SetMsg(*a.info.sessionId, msg)
28 | //if new topic
29 | if len(msg) == 2 {
30 | //fmt.Println("new topic", msg[1].Content)
31 | sendNewTopicCard(*a.ctx, a.info.sessionId, a.info.msgId,
32 | completions.Content)
33 | return false
34 | }
35 | err = replyMsg(*a.ctx, completions.Content, a.info.msgId)
36 | texttospeech.Transform(completions.Content)
37 | if err != nil {
38 | replyMsg(*a.ctx, fmt.Sprintf(
39 | "🤖️:消息机器人摆烂了,请稍后再试~\n错误信息: %v", err), a.info.msgId)
40 | return false
41 | }
42 | return true
43 | }
--------------------------------------------------------------------------------
/internal/handlers/handlers.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "oral-friend/internal/initialization"
7 | "oral-friend/pkg/openai"
8 | "oral-friend/pkg/services"
9 | "strings"
10 |
11 | larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
12 | larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
13 | )
14 | type MessageHandler struct {
15 | sessionCache services.SessionServiceCacheInterface
16 | msgCache services.MsgCacheInterface
17 | gpt *openai.ChatGPT
18 | config initialization.Config
19 | }
20 | // 责任链
21 | func chain(data *ActionInfo, actions ...Action) bool {
22 | for _, v := range actions {
23 | if !v.Execute(data) {
24 | return false
25 | }
26 | }
27 | return true
28 | }
29 |
30 | func judgeMsgType(event *larkim.P2MessageReceiveV1) (string, error) {
31 | msgType := event.Event.Message.MessageType
32 |
33 | switch *msgType {
34 | case "text","audio":
35 | return *msgType, nil
36 | default:
37 | return "", fmt.Errorf("unknown message type: %v", *msgType)
38 | }
39 |
40 | }
41 |
42 | func (m MessageHandler) cardHandler(ctx context.Context,
43 | cardAction *larkcard.CardAction) (interface{}, error) {
44 | messageHandler := NewCardHandler(m)
45 | return messageHandler(ctx, cardAction)
46 | }
47 |
48 |
49 | func (m MessageHandler) msgReceivedHandler(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
50 | handlerType := judgeChatType(event)
51 | if handlerType == "otherChat" {
52 | fmt.Println("unknown chat type")
53 | return nil
54 | }
55 | //fmt.Println(larkcore.Prettify(event.Event.Message))
56 |
57 | msgType, err := judgeMsgType(event)
58 | if err != nil {
59 | fmt.Printf("error getting message type: %v\n", err)
60 | return nil
61 | }
62 |
63 | content := event.Event.Message.Content
64 | msgId := event.Event.Message.MessageId
65 | rootId := event.Event.Message.RootId
66 | chatId := event.Event.Message.ChatId
67 | mention := event.Event.Message.Mentions
68 |
69 | sessionId := rootId
70 | if sessionId == nil || *sessionId == "" {
71 | sessionId = msgId
72 | }
73 | msgInfo := MsgInfo{
74 | handlerType: handlerType,
75 | msgType: msgType,
76 | msgId: msgId,
77 | chatId: chatId,
78 | qParsed: strings.Trim(parseContent(*content), " "),
79 | fileKey: parseFileKey(*content),
80 | sessionId: sessionId,
81 | mention: mention,
82 | }
83 | data := &ActionInfo{
84 | ctx: &ctx,
85 | handler: &m,
86 | info: &msgInfo,
87 | }
88 | actions := []Action{
89 | &ProcessedUniqueAction{}, //避免重复处理
90 | &ProcessMentionAction{}, //判断机器人是否应该被调用
91 | &AudioAction{}, //语音处理
92 | &EmptyAction{}, //空消息处理
93 | &ClearAction{}, //清除消息处理
94 | &AIModeAction{}, //模式切换处理
95 | &RoleListAction{}, //角色列表处理
96 | &HelpAction{}, //帮助处理
97 | &BalanceAction{}, //余额处理
98 | &RolePlayAction{}, //角色扮演处理
99 | &MessageAction{}, //消息处理
100 |
101 | }
102 | chain(data, actions...)
103 | return nil
104 | }
105 |
106 | var _ MessageHandlerInterface = (*MessageHandler)(nil)
107 |
108 | func NewMessageHandler(gpt *openai.ChatGPT,
109 | config initialization.Config) MessageHandlerInterface {
110 | return &MessageHandler{
111 | sessionCache: services.GetSessionCache(),
112 | msgCache: services.GetMsgCache(),
113 | gpt: gpt,
114 | config: config,
115 | }
116 | }
117 |
118 | func (m MessageHandler) judgeIfMentionMe(mention []*larkim.
119 | MentionEvent) bool {
120 | if len(mention) != 1 {
121 | return false
122 | }
123 | return *mention[0].Name == m.config.FeishuBotName
124 | }
125 |
126 | func AzureModeCheck(a *ActionInfo) bool {
127 | if a.handler.config.AzureOn {
128 | //sendMsg(*a.ctx, "Azure Openai 接口下,暂不支持此功能", a.info.chatId)
129 | return false
130 | }
131 | return true
132 | }
--------------------------------------------------------------------------------
/internal/handlers/inti.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "oral-friend/internal/initialization"
6 | "oral-friend/pkg/openai"
7 |
8 | larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
9 | larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
10 | )
11 |
12 | type HandlerType string
13 |
14 | type MessageHandlerInterface interface {
15 | msgReceivedHandler(ctx context.Context, event *larkim.P2MessageReceiveV1) error
16 | cardHandler(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error)
17 | }
18 |
19 | const (
20 | GroupHandler = "group"
21 | UserHandler = "personal"
22 | )
23 |
24 | // handlers 所有消息类型类型的处理器
25 | var handlers MessageHandlerInterface
26 |
27 | func InitHandlers(gpt *openai.ChatGPT, config initialization.Config){
28 | handlers = NewMessageHandler(gpt, config)
29 | }
30 |
31 | func Handler(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
32 | return handlers.msgReceivedHandler(ctx, event)
33 | }
34 |
35 | func ReadHandler(ctx context.Context, event *larkim.P2MessageReadV1) error {
36 | _ = event.Event.Reader.ReaderId.OpenId
37 | //fmt.Printf("msg is read by : %v \n", *readerId)
38 | return nil
39 | }
40 |
41 | func CardHandler() func(ctx context.Context,
42 | cardAction *larkcard.CardAction) (interface{}, error) {
43 | return func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
44 | //handlerType := judgeCardType(cardAction)
45 | return handlers.cardHandler(ctx, cardAction)
46 | }
47 | }
48 |
49 | func judgeChatType(event *larkim.P2MessageReceiveV1) HandlerType {
50 | chatType := event.Event.Message.ChatType
51 | if *chatType == "group" {
52 | return GroupHandler
53 | }
54 | if *chatType == "p2p" {
55 | return UserHandler
56 | }
57 | return "otherChat"
58 | }
--------------------------------------------------------------------------------
/internal/handlers/msg.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "oral-friend/internal/initialization"
8 | "oral-friend/pkg/openai"
9 |
10 | "github.com/google/uuid"
11 | larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
12 | larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
13 | )
14 |
15 |
16 | type CardKind string
17 | type CardChatType string
18 |
19 |
20 | var (
21 | ClearCardKind = CardKind("clear") // 清空上下文
22 | PicModeChangeKind = CardKind("pic_mode_change") // 切换图片创作模式
23 | PicResolutionKind = CardKind("pic_resolution") // 图片分辨率调整
24 | PicTextMoreKind = CardKind("pic_text_more") // 重新根据文本生成图片
25 | PicVarMoreKind = CardKind("pic_var_more") // 变量图片
26 | RoleTagsChooseKind = CardKind("role_tags_choose") // 内置角色所属标签选择
27 | RoleChooseKind = CardKind("role_choose") // 内置角色选择
28 | AIModeChooseKind = CardKind("ai_mode_choose") // AI模式选择
29 | )
30 | type CardMsg struct {
31 | Kind CardKind
32 | ChatType CardChatType
33 | Value interface{}
34 | SessionId string
35 | MsgId string
36 | }
37 |
38 | type MenuOption struct {
39 | value string
40 | label string
41 | }
42 |
43 | var (
44 | GroupChatType = CardChatType("group")
45 | UserChatType = CardChatType("personal")
46 | )
47 |
48 |
49 | // withSplitLine 用于生成分割线
50 | func withSplitLine() larkcard.MessageCardElement {
51 | splitLine := larkcard.NewMessageCardHr().
52 | Build()
53 | return splitLine
54 | }
55 |
56 |
57 | func newSendCard(header *larkcard.MessageCardHeader, elements ...larkcard.MessageCardElement) (string, error) {
58 | config := larkcard.NewMessageCardConfig().
59 | WideScreenMode(false).
60 | EnableForward(true).
61 | UpdateMulti(true).
62 | Build()
63 | var aElementPool []larkcard.MessageCardElement
64 | for _, element := range elements {
65 | aElementPool = append(aElementPool, element)
66 | }
67 | // 卡片消息体
68 | cardContent, err := larkcard.NewMessageCard().
69 | Config(config).
70 | Header(header).
71 | Elements(
72 | aElementPool,
73 | ).
74 | String()
75 | return cardContent, err
76 | }
77 | func newMenu(
78 | placeHolder string,
79 | value map[string]interface{},
80 | options ...MenuOption,
81 | ) *larkcard.
82 | MessageCardEmbedSelectMenuStatic {
83 | var aOptionPool []*larkcard.MessageCardEmbedSelectOption
84 | for _, option := range options {
85 | aOption := larkcard.NewMessageCardEmbedSelectOption().
86 | Value(option.value).
87 | Text(larkcard.NewMessageCardPlainText().
88 | Content(option.label).
89 | Build())
90 | aOptionPool = append(aOptionPool, aOption)
91 |
92 | }
93 | btn := larkcard.NewMessageCardEmbedSelectMenuStatic().
94 | MessageCardEmbedSelectMenuStatic(larkcard.NewMessageCardEmbedSelectMenuBase().
95 | Options(aOptionPool).
96 | Placeholder(larkcard.NewMessageCardPlainText().
97 | Content(placeHolder).
98 | Build()).
99 | Value(value).
100 | Build()).
101 | Build()
102 | return btn
103 | }
104 |
105 | // 生成带有角色标签 btn
106 | func withRoleTagsBtn(sessionID *string, tags ...string) larkcard.
107 | MessageCardElement {
108 | var menuOptions []MenuOption
109 |
110 | for _, tag := range tags {
111 | menuOptions = append(menuOptions, MenuOption{
112 | label: tag,
113 | value: tag,
114 | })
115 | }
116 | cancelMenu := newMenu("选择角色分类",
117 | map[string]interface{}{
118 | "value": "0",
119 | "kind": RoleTagsChooseKind,
120 | "sessionId": *sessionID,
121 | "msgId": *sessionID,
122 | },
123 | menuOptions...,
124 | )
125 |
126 | actions := larkcard.NewMessageCardAction().
127 | Actions([]larkcard.MessageCardActionElement{cancelMenu}).
128 | Layout(larkcard.MessageCardActionLayoutFlow.Ptr()).
129 | Build()
130 | return actions
131 | }
132 |
133 | // 使用 AI 模式 Btn
134 | func withAIModeBtn(sessionID *string, aiModeStrs []string) larkcard.MessageCardElement {
135 | var menuOptions []MenuOption
136 | for _, label := range aiModeStrs {
137 | menuOptions = append(menuOptions, MenuOption{
138 | label: label,
139 | value: label,
140 | })
141 | }
142 |
143 | cancelMenu := newMenu("选择模式",
144 | map[string]interface{}{
145 | "value": "0",
146 | "kind": AIModeChooseKind,
147 | "sessionId": *sessionID,
148 | "msgId": *sessionID,
149 | },
150 | menuOptions...,
151 | )
152 |
153 | actions := larkcard.NewMessageCardAction().
154 | Actions([]larkcard.MessageCardActionElement{cancelMenu}).
155 | Layout(larkcard.MessageCardActionLayoutFlow.Ptr()).
156 | Build()
157 | return actions
158 | }
159 |
160 | // withMdAndExtraBtn 用于生成带有额外按钮的消息体
161 | func withMdAndExtraBtn(msg string, btn *larkcard.
162 | MessageCardEmbedButton) larkcard.MessageCardElement {
163 | msg, i := processMessage(msg)
164 | msg = processNewLine(msg)
165 | if i != nil {
166 | return nil
167 | }
168 | mainElement := larkcard.NewMessageCardDiv().
169 | Fields(
170 | []*larkcard.MessageCardField{
171 | larkcard.NewMessageCardField().
172 | Text(larkcard.NewMessageCardLarkMd().
173 | Content(msg).
174 | Build()).
175 | IsShort(true).
176 | Build()}).
177 | Extra(btn).
178 | Build()
179 | return mainElement
180 | }
181 |
182 |
183 | // withHeader 用于生成消息头
184 | func withHeader(title string, color string) *larkcard.
185 | MessageCardHeader {
186 | if title == "" {
187 | title = "🤖️机器人提醒"
188 | }
189 | header := larkcard.NewMessageCardHeader().
190 | Template(color).
191 | Title(larkcard.NewMessageCardPlainText().
192 | Content(title).
193 | Build()).
194 | Build()
195 | return header
196 | }
197 |
198 | // withNote 用于生成纯文本脚注
199 | func withNote(note string) larkcard.MessageCardElement {
200 | noteElement := larkcard.NewMessageCardNote().
201 | Elements([]larkcard.MessageCardNoteElement{larkcard.NewMessageCardPlainText().
202 | Content(note).
203 | Build()}).
204 | Build()
205 | return noteElement
206 | }
207 |
208 | func replyCard(ctx context.Context,
209 | msgId *string,
210 | cardContent string,
211 | ) error {
212 | client := initialization.GetLarkClient()
213 | // resp, err := client.Im.Message.Reply(ctx, larkim.NewReplyMessageReqBuilder().
214 | // MessageId(*msgId).
215 | // Body(larkim.NewReplyMessageReqBodyBuilder().
216 | // MsgType(larkim.MsgTypeInteractive).
217 | // Uuid(uuid.New().String()).
218 | // Content(cardContent).
219 | // Build()).
220 | // Build())
221 |
222 | resp, err := client.Im.Message.Reply(ctx, larkim.NewReplyMessageReqBuilder().
223 | MessageId(*msgId).
224 | Body(larkim.NewReplyMessageReqBodyBuilder().
225 | MsgType(larkim.MsgTypeInteractive).
226 | Uuid(uuid.New().String()).
227 | Content(cardContent).
228 | Build()).
229 | Build())
230 |
231 |
232 | // 处理错误
233 | if err != nil {
234 | fmt.Println(err)
235 | return err
236 | }
237 |
238 | // 服务端错误处理
239 | if !resp.Success() {
240 | fmt.Println(resp.Code, resp.Msg, resp.RequestId())
241 | return errors.New(resp.Msg)
242 | }
243 | return nil
244 | }
245 |
246 |
247 | func withRoleBtn(sessionID *string, titles ...string) larkcard.
248 | MessageCardElement {
249 | var menuOptions []MenuOption
250 |
251 | for _, tag := range titles {
252 | menuOptions = append(menuOptions, MenuOption{
253 | label: tag,
254 | value: tag,
255 | })
256 | }
257 | cancelMenu := newMenu("查看内置角色",
258 | map[string]interface{}{
259 | "value": "0",
260 | "kind": RoleChooseKind,
261 | "sessionId": *sessionID,
262 | "msgId": *sessionID,
263 | },
264 | menuOptions...,
265 | )
266 |
267 | actions := larkcard.NewMessageCardAction().
268 | Actions([]larkcard.MessageCardActionElement{cancelMenu}).
269 | Layout(larkcard.MessageCardActionLayoutFlow.Ptr()).
270 | Build()
271 | return actions
272 | }
273 |
274 | // withMainText 用于生成纯文本消息体
275 | func withMainText(msg string) larkcard.MessageCardElement {
276 | msg, i := processMessage(msg)
277 | msg = cleanTextBlock(msg)
278 | if i != nil {
279 | return nil
280 | }
281 | mainElement := larkcard.NewMessageCardDiv().
282 | Fields([]*larkcard.MessageCardField{larkcard.NewMessageCardField().
283 | Text(larkcard.NewMessageCardPlainText().
284 | Content(msg).
285 | Build()).
286 | IsShort(false).
287 | Build()}).
288 | Build()
289 | return mainElement
290 | }
291 |
292 | func SendRoleListCard(ctx context.Context,
293 | sessionId *string, msgId *string, roleTag string, roleList []string) {
294 | newCard, _ := newSendCard(
295 | withHeader("🛖 角色列表"+" - "+roleTag, larkcard.TemplateIndigo),
296 | withRoleBtn(sessionId, roleList...),
297 | withNote("提醒:选择内置场景,快速进入角色扮演模式。"))
298 | replyCard(ctx, msgId, newCard)
299 | }
300 |
301 |
302 | func replyCardWithBackId(ctx context.Context,
303 | msgId *string,
304 | cardContent string,
305 | ) (*string, error) {
306 | client := initialization.GetLarkClient()
307 | resp, err := client.Im.Message.Reply(ctx, larkim.NewReplyMessageReqBuilder().
308 | MessageId(*msgId).
309 | Body(larkim.NewReplyMessageReqBodyBuilder().
310 | MsgType(larkim.MsgTypeInteractive).
311 | Uuid(uuid.New().String()).
312 | Content(cardContent).
313 | Build()).
314 | Build())
315 |
316 | // 处理错误
317 | if err != nil {
318 | fmt.Println(err)
319 | return nil, err
320 | }
321 |
322 | // 服务端错误处理
323 | if !resp.Success() {
324 | fmt.Println(resp.Code, resp.Msg, resp.RequestId())
325 | return nil, errors.New(resp.Msg)
326 | }
327 |
328 | //ctx = context.WithValue(ctx, "SendMsgId", *resp.Data.MessageId)
329 | //SendMsgId := ctx.Value("SendMsgId")
330 | //pp.Println(SendMsgId)
331 | return resp.Data.MessageId, nil
332 | }
333 |
334 |
335 | func sendMsg(ctx context.Context, msg string, chatId *string) error {
336 | //fmt.Println("sendMsg", msg, chatId)
337 | msg, i := processMessage(msg)
338 | if i != nil {
339 | return i
340 | }
341 | client := initialization.GetLarkClient()
342 | content := larkim.NewTextMsgBuilder().
343 | Text(msg).
344 | Build()
345 |
346 | //fmt.Println("content", content)
347 |
348 | resp, err := client.Im.Message.Create(ctx, larkim.NewCreateMessageReqBuilder().
349 | ReceiveIdType(larkim.ReceiveIdTypeChatId).
350 | Body(larkim.NewCreateMessageReqBodyBuilder().
351 | MsgType(larkim.MsgTypeText).
352 | ReceiveId(*chatId).
353 | Content(content).
354 | Build()).
355 | Build())
356 |
357 | // 处理错误
358 | if err != nil {
359 | fmt.Println(err)
360 | return err
361 | }
362 |
363 | // 服务端错误处理
364 | if !resp.Success() {
365 | fmt.Println(resp.Code, resp.Msg, resp.RequestId())
366 | return errors.New(resp.Msg)
367 | }
368 | return nil
369 | }
370 |
371 | func newSendCardWithOutHeader(
372 | elements ...larkcard.MessageCardElement) (string, error) {
373 | config := larkcard.NewMessageCardConfig().
374 | WideScreenMode(false).
375 | EnableForward(true).
376 | UpdateMulti(true).
377 | Build()
378 | var aElementPool []larkcard.MessageCardElement
379 | for _, element := range elements {
380 | aElementPool = append(aElementPool, element)
381 | }
382 | // 卡片消息体
383 | cardContent, err := larkcard.NewMessageCard().
384 | Config(config).
385 | Elements(
386 | aElementPool,
387 | ).
388 | String()
389 | return cardContent, err
390 | }
391 | // withMainMd 用于生成markdown消息体
392 | func withMainMd(msg string) larkcard.MessageCardElement {
393 | msg, i := processMessage(msg)
394 | msg = processNewLine(msg)
395 | if i != nil {
396 | return nil
397 | }
398 | mainElement := larkcard.NewMessageCardDiv().
399 | Fields([]*larkcard.MessageCardField{larkcard.NewMessageCardField().
400 | Text(larkcard.NewMessageCardLarkMd().
401 | Content(msg).
402 | Build()).
403 | IsShort(true).
404 | Build()}).
405 | Build()
406 | return mainElement
407 | }
408 |
409 | func newBtn(content string, value map[string]interface{},
410 | typename larkcard.MessageCardButtonType) *larkcard.
411 | MessageCardEmbedButton {
412 | btn := larkcard.NewMessageCardEmbedButton().
413 | Type(typename).
414 | Value(value).
415 | Text(larkcard.NewMessageCardPlainText().
416 | Content(content).
417 | Build())
418 | return btn
419 | }
420 |
421 | // 清除卡片按钮
422 | func withClearDoubleCheckBtn(sessionID *string) larkcard.MessageCardElement {
423 | confirmBtn := newBtn("确认清除", map[string]interface{}{
424 | "value": "1",
425 | "kind": ClearCardKind,
426 | "chatType": UserChatType,
427 | "sessionId": *sessionID,
428 | }, larkcard.MessageCardButtonTypeDanger,
429 | )
430 | cancelBtn := newBtn("我再想想", map[string]interface{}{
431 | "value": "0",
432 | "kind": ClearCardKind,
433 | "sessionId": *sessionID,
434 | "chatType": UserChatType,
435 | },
436 | larkcard.MessageCardButtonTypeDefault)
437 |
438 | actions := larkcard.NewMessageCardAction().
439 | Actions([]larkcard.MessageCardActionElement{confirmBtn, cancelBtn}).
440 | Layout(larkcard.MessageCardActionLayoutBisected.Ptr()).
441 | Build()
442 |
443 | return actions
444 | }
445 |
446 | func sendClearCacheCheckCard(ctx context.Context,
447 | sessionId *string, msgId *string) {
448 | newCard, _ := newSendCard(
449 | withHeader("🆑 机器人提醒", larkcard.TemplateBlue),
450 | withMainMd("您确定要清除对话上下文吗?"),
451 | withNote("请注意,这将开始一个全新的对话,您将无法利用之前话题的历史信息"),
452 | withClearDoubleCheckBtn(sessionId))
453 | replyCard(ctx, msgId, newCard)
454 | }
455 |
456 | func sendSystemInstructionCard(ctx context.Context,
457 | sessionId *string, msgId *string, content string) {
458 | newCard, _ := newSendCard(
459 | withHeader("🥷 已进入角色扮演模式", larkcard.TemplateIndigo),
460 | withMainText(content),
461 | withNote("请注意,这将开始一个全新的对话,您将无法利用之前话题的历史信息"))
462 | replyCard(ctx, msgId, newCard)
463 | }
464 |
465 | func sendOnProcessCard(ctx context.Context,
466 | sessionId *string, msgId *string) (*string, error) {
467 | newCard, _ := newSendCardWithOutHeader(
468 | withNote("正在思考,请稍等..."))
469 | id, err := replyCardWithBackId(ctx, msgId, newCard)
470 | if err != nil {
471 | return nil, err
472 | }
473 | return id, nil
474 | }
475 |
476 | func sendHelpCard(ctx context.Context,
477 | sessionId *string, msgId *string) {
478 | newCard, _ := newSendCard(
479 | withHeader("🎒需要帮助吗?", larkcard.TemplateBlue),
480 | withMainMd("**我是具备打字机效果的聊天机器人!**"),
481 | withSplitLine(),
482 | withMdAndExtraBtn(
483 | "** 🆑 清除话题上下文**\n文本回复 *清除* 或 */clear*",
484 | newBtn("立刻清除", map[string]interface{}{
485 | "value": "1",
486 | "kind": ClearCardKind,
487 | "chatType": UserChatType,
488 | "sessionId": *sessionId,
489 | }, larkcard.MessageCardButtonTypeDanger)),
490 | withMainMd("🛖 **内置角色列表** \n"+" 文本回复 *角色列表* 或 */roles*"),
491 | withMainMd("🥷 **角色扮演模式**\n文本回复*角色扮演* 或 */system*+空格+角色信息"),
492 | withSplitLine(),
493 | withMainMd("🎒 **需要更多帮助**\n文本回复 *帮助* 或 */help*"),
494 | )
495 | replyCard(ctx, msgId, newCard)
496 | }
497 |
498 | func replyMsg(ctx context.Context, msg string, msgId *string) error {
499 | msg, i := processMessage(msg)
500 | if i != nil {
501 | return i
502 | }
503 | client := initialization.GetLarkClient()
504 | content := larkim.NewTextMsgBuilder().
505 | Text(msg).
506 | Build()
507 |
508 | resp, err := client.Im.Message.Reply(ctx, larkim.NewReplyMessageReqBuilder().
509 | MessageId(*msgId).
510 | Body(larkim.NewReplyMessageReqBodyBuilder().
511 | MsgType(larkim.MsgTypeText).
512 | Uuid(uuid.New().String()).
513 | Content(content).
514 | Build()).
515 | Build())
516 |
517 | // 处理错误
518 | if err != nil {
519 | fmt.Println(err)
520 | return err
521 | }
522 |
523 | // 服务端错误处理
524 | if !resp.Success() {
525 | fmt.Println(resp.Code, resp.Msg, resp.RequestId())
526 | return errors.New(resp.Msg)
527 | }
528 | return nil
529 | }
530 |
531 | func sendBalanceCard(ctx context.Context, msgId *string,
532 | balance openai.BalanceResponse) {
533 | newCard, _ := newSendCard(
534 | withHeader("🎰️ 余额查询", larkcard.TemplateBlue),
535 | withMainMd(fmt.Sprintf("总额度: %.2f$", balance.TotalGranted)),
536 | withMainMd(fmt.Sprintf("已用额度: %.2f$", balance.TotalUsed)),
537 | withMainMd(fmt.Sprintf("可用额度: %.2f$",
538 | balance.TotalAvailable)),
539 | withNote(fmt.Sprintf("有效期: %s - %s",
540 | balance.EffectiveAt.Format("2006-01-02 15:04:05"),
541 | balance.ExpiresAt.Format("2006-01-02 15:04:05"))),
542 | )
543 | replyCard(ctx, msgId, newCard)
544 | }
545 |
546 | func SendRoleTagsCard(ctx context.Context,
547 | sessionId *string, msgId *string, roleTags []string) {
548 | newCard, _ := newSendCard(
549 | withHeader("🛖 请选择角色类别", larkcard.TemplateIndigo),
550 | withRoleTagsBtn(sessionId, roleTags...),
551 | withNote("提醒:选择角色所属分类,以便我们为您推荐更多相关角色。"))
552 | replyCard(ctx, msgId, newCard)
553 | }
554 |
555 | func SendAIModeListsCard(ctx context.Context,
556 | sessionId *string, msgId *string, aiModeStrs []string) {
557 | newCard, _ := newSendCard(
558 | withHeader("🤖 AI模式选择", larkcard.TemplateIndigo),
559 | withAIModeBtn(sessionId, aiModeStrs),
560 | withNote("提醒:选择内置模式,让AI更好的理解您的需求。"))
561 | replyCard(ctx, msgId, newCard)
562 | }
563 |
564 | func sendNewTopicCard(ctx context.Context,
565 | sessionId *string, msgId *string, content string) {
566 | newCard, _ := newSendCard(
567 | withHeader("👻️ 已开启新的话题", larkcard.TemplateBlue),
568 | withMainText(content),
569 | withNote("提醒:点击对话框参与回复,可保持话题连贯"))
570 | replyCard(ctx, msgId, newCard)
571 | }
--------------------------------------------------------------------------------
/internal/initialization/config.go:
--------------------------------------------------------------------------------
1 | package initialization
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/spf13/viper"
10 | )
11 |
12 | type Config struct {
13 | FeishuAppId string
14 | FeishuAppSecret string
15 | FeishuAppEncryptKey string
16 | FeishuAppVerificationToken string
17 | FeishuBotName string
18 | OpenaiApiKeys []string
19 | HttpPort int
20 | HttpsPort int
21 | UseHttps bool
22 | CertFile string
23 | KeyFile string
24 | OpenaiApiUrl string
25 | HttpProxy string
26 | AzureOn bool
27 | AzureApiVersion string
28 | AzureDeploymentName string
29 | AzureResourceName string
30 | AzureOpenaiToken string
31 | }
32 |
33 | func LoadConfig(cfg string) *Config {
34 | viper.SetConfigFile(cfg)
35 | viper.ReadInConfig()
36 | viper.AutomaticEnv()
37 |
38 | config := &Config{
39 | FeishuAppId: getViperStringValue("APP_ID", ""),
40 | FeishuAppSecret: getViperStringValue("APP_SECRET", ""),
41 | FeishuAppEncryptKey: getViperStringValue("APP_ENCRYPT_KEY", ""),
42 | FeishuAppVerificationToken: getViperStringValue("APP_VERIFICATION_TOKEN", ""),
43 | FeishuBotName: getViperStringValue("BOT_NAME", ""),
44 | OpenaiApiKeys: getViperStringArray("OPENAI_KEY", nil),
45 | HttpPort: getViperIntValue("HTTP_PORT", 9000),
46 | HttpsPort: getViperIntValue("HTTPS_PORT", 9001),
47 | UseHttps: getViperBoolValue("USE_HTTPS", false),
48 | CertFile: getViperStringValue("CERT_FILE", "cert.pem"),
49 | KeyFile: getViperStringValue("KEY_FILE", "key.pem"),
50 | OpenaiApiUrl: getViperStringValue("API_URL", "https://api.openai.com"),
51 | HttpProxy: getViperStringValue("HTTP_PROXY", ""),
52 | }
53 |
54 | return config
55 | }
56 |
57 | func getViperStringValue(key string, defaultValue string) string {
58 | value := viper.GetString(key)
59 | if value == "" {
60 | return defaultValue
61 | }
62 | return value
63 | }
64 |
65 | // OPENAI_KEY: sk-xxx,sk-xxx,sk-xxx
66 | // result:[sk-xxx sk-xxx sk-xxx]
67 | func getViperStringArray(key string, defaultValue []string) []string {
68 | value := viper.GetString(key)
69 | if value == "" {
70 | return defaultValue
71 | }
72 | raw := strings.Split(value, ",")
73 | return filterFormatKey(raw)
74 | }
75 |
76 | func getViperIntValue(key string, defaultValue int) int {
77 | value := viper.GetString(key)
78 | if value == "" {
79 | return defaultValue
80 | }
81 | intValue, err := strconv.Atoi(value)
82 | if err != nil {
83 | fmt.Printf("Invalid value for %s, using default value %d\n", key, defaultValue)
84 | return defaultValue
85 | }
86 | return intValue
87 | }
88 |
89 | func getViperBoolValue(key string, defaultValue bool) bool {
90 | value := viper.GetString(key)
91 | if value == "" {
92 | return defaultValue
93 | }
94 | boolValue, err := strconv.ParseBool(value)
95 | if err != nil {
96 | fmt.Printf("Invalid value for %s, using default value %v\n", key, defaultValue)
97 | return defaultValue
98 | }
99 | return boolValue
100 | }
101 |
102 | func (config *Config) GetCertFile() string {
103 | if config.CertFile == "" {
104 | return "cert.pem"
105 | }
106 | if _, err := os.Stat(config.CertFile); err != nil {
107 | fmt.Printf("Certificate file %s does not exist, using default file cert.pem\n", config.CertFile)
108 | return "cert.pem"
109 | }
110 | return config.CertFile
111 | }
112 |
113 | func (config *Config) GetKeyFile() string {
114 | if config.KeyFile == "" {
115 | return "key.pem"
116 | }
117 | if _, err := os.Stat(config.KeyFile); err != nil {
118 | fmt.Printf("Key file %s does not exist, using default file key.pem\n", config.KeyFile)
119 | return "key.pem"
120 | }
121 | return config.KeyFile
122 | }
123 |
124 | // 过滤出 "sk-" 开头的 key
125 | func filterFormatKey(keys []string) []string {
126 | var result []string
127 | for _, key := range keys {
128 | if strings.HasPrefix(key, "sk-") {
129 | result = append(result, key)
130 | }
131 | }
132 | return result
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/internal/initialization/gin.go:
--------------------------------------------------------------------------------
1 | package initialization
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "fmt"
7 | "log"
8 | "net/http"
9 | "time"
10 |
11 | "github.com/gin-gonic/gin"
12 | )
13 |
14 | func loadCertificate(config Config) (cert tls.Certificate, err error) {
15 | cert, err = tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
16 | if err != nil {
17 | return cert, fmt.Errorf("failed to load certificate: %v", err)
18 | }
19 |
20 | // check certificate expiry
21 | if len(cert.Certificate) == 0 {
22 | return cert, fmt.Errorf("no certificates found in %s", config.CertFile)
23 | }
24 | parsedCert, err := x509.ParseCertificate(cert.Certificate[0])
25 | if err != nil {
26 | return cert, fmt.Errorf("failed to parse certificate: %v", err)
27 | }
28 | cert.Leaf = parsedCert
29 | certExpiry := cert.Leaf.NotAfter
30 | if certExpiry.Before(time.Now()) {
31 | return cert, fmt.Errorf("certificate expired on %v", certExpiry)
32 | }
33 |
34 | return cert, nil
35 | }
36 |
37 | func startHTTPServer(config Config, r *gin.Engine) (err error) {
38 | log.Printf("http server started: http://localhost:%d/webhook/event\n", config.HttpPort)
39 | err = r.Run(fmt.Sprintf(":%d", config.HttpPort))
40 | if err != nil {
41 | return fmt.Errorf("failed to start http server: %v", err)
42 | }
43 | return nil
44 | }
45 | func startHTTPSServer(config Config, r *gin.Engine) (err error) {
46 | cert, err := loadCertificate(config)
47 | if err != nil {
48 | return fmt.Errorf("failed to load certificate: %v", err)
49 | }
50 | server := &http.Server{
51 | Addr: fmt.Sprintf(":%d", config.HttpsPort),
52 | Handler: r,
53 | TLSConfig: &tls.Config{
54 | Certificates: []tls.Certificate{cert},
55 | },
56 | }
57 | fmt.Printf("https server started: https://localhost:%d/webhook/event\n", config.HttpsPort)
58 | err = server.ListenAndServeTLS("", "")
59 | if err != nil {
60 | return fmt.Errorf("failed to start https server: %v", err)
61 | }
62 | return nil
63 | }
64 | func StartServer(config Config, r *gin.Engine) (err error) {
65 | if config.UseHttps {
66 | err = startHTTPSServer(config, r)
67 | } else {
68 | err = startHTTPServer(config, r)
69 | }
70 | return err
71 | }
72 |
--------------------------------------------------------------------------------
/internal/initialization/lark_client.go:
--------------------------------------------------------------------------------
1 | package initialization
2 |
3 | import (
4 | lark "github.com/larksuite/oapi-sdk-go/v3"
5 | )
6 |
7 | var larkClient *lark.Client
8 |
9 | func LoadLarkClient(config Config) {
10 | larkClient = lark.NewClient(config.FeishuAppId, config.FeishuAppSecret)
11 | }
12 |
13 | func GetLarkClient() *lark.Client {
14 | return larkClient
15 | }
16 |
--------------------------------------------------------------------------------
/internal/initialization/roles_load.go:
--------------------------------------------------------------------------------
1 | package initialization
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "os"
9 |
10 | "github.com/duke-git/lancet/v2/slice"
11 | "github.com/duke-git/lancet/v2/validator"
12 | "gopkg.in/yaml.v2"
13 | )
14 |
15 | type Role struct {
16 | Title string `yaml:"title"`
17 | Content string `yaml:"content"`
18 | Tags []string `yaml:"tags"`
19 | }
20 |
21 | var RoleList *[]Role
22 |
23 | // InitRoleList 加载Prompt
24 | func InitRoleList() *[]Role {
25 | wd, err := os.Getwd()
26 | fmt.Println(wd)
27 | data, err := ioutil.ReadFile("role_list.yaml")
28 | if err != nil {
29 | log.Fatal(err)
30 | }
31 |
32 | err = yaml.Unmarshal(data, &RoleList)
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 | return RoleList
37 | }
38 |
39 | func GetTitleListByTag(tags string) *[]string {
40 | roles := make([]string, 0)
41 | //pp.Println(RoleList)
42 | for _, role := range *RoleList {
43 | for _, roleTag := range role.Tags {
44 | if roleTag == tags && !validator.IsEmptyString(role.
45 | Title) {
46 | roles = append(roles, role.Title)
47 | }
48 | }
49 | }
50 | return &roles
51 | }
52 |
53 | func GetFirstRoleContentByTitle(title string) (string, error) {
54 | for _, role := range *RoleList {
55 | if role.Title == title {
56 | return role.Content, nil
57 | }
58 | }
59 | return "", errors.New("role not found")
60 | }
61 |
62 | func GetAllUniqueTags() *[]string {
63 | tags := make([]string, 0)
64 | for _, role := range *RoleList {
65 | tags = append(tags, role.Tags...)
66 | }
67 | result := slice.Union(tags)
68 | return &result
69 | }
70 |
71 | func GetRoleByTitle(title string) *Role {
72 | for _, role := range *RoleList {
73 | if role.Title == title {
74 | return &role
75 | }
76 | }
77 | return nil
78 | }
--------------------------------------------------------------------------------
/internal/services/services.go:
--------------------------------------------------------------------------------
1 | package services
2 |
--------------------------------------------------------------------------------
/pkg/loadbalancer/loadbalancer.go:
--------------------------------------------------------------------------------
1 | package loadbalancer
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "sync"
7 | "time"
8 | )
9 |
10 | type API struct {
11 | Key string
12 | Times uint32
13 | Available bool
14 | }
15 |
16 | type LoadBalancer struct {
17 | apis []*API
18 | mu sync.RWMutex
19 | }
20 |
21 | func NewLoadBalancer(keys []string) *LoadBalancer {
22 | lb := &LoadBalancer{}
23 | for _, key := range keys {
24 | lb.apis = append(lb.apis, &API{Key: key})
25 | }
26 | //SetAvailabilityForAll true
27 | lb.SetAvailabilityForAll(true)
28 | return lb
29 | }
30 |
31 | func (lb *LoadBalancer) GetAPI() *API {
32 | lb.mu.RLock()
33 | defer lb.mu.RUnlock()
34 |
35 | var availableAPIs []*API
36 | for _, api := range lb.apis {
37 | if api.Available {
38 | availableAPIs = append(availableAPIs, api)
39 | }
40 | }
41 | if len(availableAPIs) == 0 {
42 | //随机复活一个
43 | fmt.Printf("No available API, revive one randomly\n")
44 | rand.Seed(time.Now().UnixNano())
45 | index := rand.Intn(len(lb.apis))
46 | lb.apis[index].Available = true
47 | return lb.apis[index]
48 | }
49 |
50 | selectedAPI := availableAPIs[0]
51 | minTimes := selectedAPI.Times
52 | for _, api := range availableAPIs {
53 | if api.Times < minTimes {
54 | selectedAPI = api
55 | minTimes = api.Times
56 | }
57 | }
58 | selectedAPI.Times++
59 | //fmt.Printf("API Availability:\n")
60 | //for _, api := range lb.apis {
61 | // fmt.Printf("%s: %v\n", api.Key, api.Available)
62 | // fmt.Printf("%s: %d\n", api.Key, api.Times)
63 | //}
64 |
65 | return selectedAPI
66 | }
67 | func (lb *LoadBalancer) SetAvailability(key string, available bool) {
68 | lb.mu.Lock()
69 | defer lb.mu.Unlock()
70 |
71 | for _, api := range lb.apis {
72 | if api.Key == key {
73 | api.Available = available
74 | return
75 | }
76 | }
77 | }
78 |
79 | func (lb *LoadBalancer) RegisterAPI(key string) {
80 | lb.mu.Lock()
81 | defer lb.mu.Unlock()
82 |
83 | if lb.apis == nil {
84 | lb.apis = make([]*API, 0)
85 | }
86 |
87 | lb.apis = append(lb.apis, &API{Key: key})
88 | }
89 |
90 | func (lb *LoadBalancer) SetAvailabilityForAll(available bool) {
91 | lb.mu.Lock()
92 | defer lb.mu.Unlock()
93 |
94 | for _, api := range lb.apis {
95 | api.Available = available
96 | }
97 | }
98 |
99 | func (lb *LoadBalancer) GetAPIs() []*API {
100 | lb.mu.RLock()
101 | defer lb.mu.RUnlock()
102 |
103 | apis := make([]*API, len(lb.apis))
104 | copy(apis, lb.apis)
105 | return apis
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/openai/audio.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "mime/multipart"
8 | "os"
9 | )
10 |
11 | type AudioToTextRequestBody struct {
12 | File string `json:"file"`
13 | Model string `json:"model"`
14 | ResponseFormat string `json:"response_format"`
15 | }
16 |
17 | type AudioToTextResponseBody struct {
18 | Text string `json:"text"`
19 | }
20 |
21 | func audioMultipartForm(request AudioToTextRequestBody, w *multipart.Writer) error {
22 | f, err := os.Open(request.File)
23 | if err != nil {
24 | return fmt.Errorf("opening audio file: %w", err)
25 | }
26 |
27 | fw, err := w.CreateFormFile("file", f.Name())
28 | if err != nil {
29 | return fmt.Errorf("creating form file: %w", err)
30 | }
31 |
32 | if _, err = io.Copy(fw, f); err != nil {
33 | return fmt.Errorf("reading from opened audio file: %w", err)
34 | }
35 |
36 | fw, err = w.CreateFormField("model")
37 | if err != nil {
38 | return fmt.Errorf("creating form field: %w", err)
39 | }
40 |
41 | modelName := bytes.NewReader([]byte(request.Model))
42 | if _, err = io.Copy(fw, modelName); err != nil {
43 | return fmt.Errorf("writing model name: %w", err)
44 | }
45 | w.Close()
46 |
47 | return nil
48 | }
49 |
50 | func (gpt *ChatGPT) AudioToText(audio string) (string, error) {
51 | requestBody := AudioToTextRequestBody{
52 | File: audio,
53 | Model: "whisper-1",
54 | ResponseFormat: "text",
55 | }
56 | audioToTextResponseBody := &AudioToTextResponseBody{}
57 | err := gpt.sendRequestWithBodyType(gpt.ApiUrl+"/v1/audio/transcriptions",
58 | "POST", formVoiceDataBody, requestBody, audioToTextResponseBody)
59 | //fmt.Println(audioToTextResponseBody)
60 | if err != nil {
61 | //fmt.Println(err)
62 | return "", err
63 | }
64 |
65 | return audioToTextResponseBody.Text, nil
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/openai/billing.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "time"
7 | )
8 |
9 | type BillingSubScrip struct {
10 | HardLimitUsd float64 `json:"hard_limit_usd"`
11 | AccessUntil float64 `json:"access_until"`
12 | }
13 | type BillingUsage struct {
14 | TotalUsage float64 `json:"total_usage"`
15 | }
16 |
17 | type BalanceResponse struct {
18 | TotalGranted float64 `json:"total_granted"`
19 | TotalUsed float64 `json:"total_used"`
20 | TotalAvailable float64 `json:"total_available"`
21 | EffectiveAt time.Time `json:"effective_at"`
22 | ExpiresAt time.Time `json:"expires_at"`
23 | }
24 |
25 | func (gpt *ChatGPT) GetBalance() (*BalanceResponse, error) {
26 | fmt.Println("进入")
27 | var data1 BillingSubScrip
28 | err := gpt.sendRequestWithBodyType(
29 | gpt.ApiUrl+"/v1/dashboard/billing/subscription",
30 | http.MethodGet,
31 | nilBody,
32 | nil,
33 | &data1,
34 | )
35 | fmt.Println("出错1", err)
36 | if err != nil {
37 | return nil, fmt.Errorf("failed to get billing subscription: %v", err)
38 | }
39 | nowdate := time.Now()
40 | enddate := nowdate.Format("2006-01-02")
41 | startdate := nowdate.AddDate(0, 0, -100).Format("2006-01-02")
42 | var data2 BillingUsage
43 | err = gpt.sendRequestWithBodyType(
44 | gpt.ApiUrl+fmt.Sprintf("/v1/dashboard/billing/usage?start_date=%s&end_date=%s", startdate, enddate),
45 | http.MethodGet,
46 | nilBody,
47 | nil,
48 | &data2,
49 | )
50 | fmt.Println(data2)
51 | fmt.Println("出错2", err)
52 | if err != nil {
53 | return nil, fmt.Errorf("failed to get billing subscription: %v", err)
54 | }
55 |
56 | balance := &BalanceResponse{
57 | TotalGranted: data1.HardLimitUsd,
58 | TotalUsed: data2.TotalUsage / 100,
59 | TotalAvailable: data1.HardLimitUsd - data2.TotalUsage/100,
60 | ExpiresAt: time.Now(),
61 | EffectiveAt: time.Now(),
62 | }
63 |
64 | if data1.AccessUntil > 0 {
65 | balance.EffectiveAt = time.Now()
66 | balance.ExpiresAt = time.Unix(int64(data1.AccessUntil), 0)
67 | }
68 |
69 | return balance, nil
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/openai/common.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "mime/multipart"
10 | "net/http"
11 | "net/url"
12 | "oral-friend/internal/initialization"
13 | "oral-friend/pkg/loadbalancer"
14 | "strings"
15 | "time"
16 | )
17 |
18 | type PlatForm string
19 |
20 | const (
21 | AzureApiUrlV1 = "openai.azure.com/openai/deployments/"
22 | )
23 | const (
24 | OpenAI PlatForm = "openai"
25 | Azure PlatForm = "azure"
26 | )
27 |
28 | type AzureConfig struct {
29 | BaseURL string
30 | ResourceName string
31 | DeploymentName string
32 | ApiVersion string
33 | ApiToken string
34 | }
35 |
36 | type ChatGPT struct {
37 | Lb *loadbalancer.LoadBalancer
38 | ApiKey []string
39 | ApiUrl string
40 | HttpProxy string
41 | Platform PlatForm
42 | AzureConfig AzureConfig
43 | }
44 | type requestBodyType int
45 |
46 | const (
47 | jsonBody requestBodyType = iota
48 | formVoiceDataBody
49 | formPictureDataBody
50 |
51 | nilBody
52 | )
53 |
54 | func (gpt *ChatGPT) doAPIRequestWithRetry(url, method string,
55 | bodyType requestBodyType,
56 | requestBody interface{}, responseBody interface{}, client *http.Client, maxRetries int) error {
57 | var api *loadbalancer.API
58 | var requestBodyData []byte
59 | var err error
60 | var writer *multipart.Writer
61 | api = gpt.Lb.GetAPI()
62 |
63 | switch bodyType {
64 | case jsonBody:
65 | requestBodyData, err = json.Marshal(requestBody)
66 | if err != nil {
67 | return err
68 | }
69 | case formVoiceDataBody:
70 | formBody := &bytes.Buffer{}
71 | writer = multipart.NewWriter(formBody)
72 | err = audioMultipartForm(requestBody.(AudioToTextRequestBody), writer)
73 | if err != nil {
74 | return err
75 | }
76 | err = writer.Close()
77 | if err != nil {
78 | return err
79 | }
80 | requestBodyData = formBody.Bytes()
81 | case formPictureDataBody:
82 | formBody := &bytes.Buffer{}
83 | writer = multipart.NewWriter(formBody)
84 | err = pictureMultipartForm(requestBody.(ImageVariantRequestBody), writer)
85 | if err != nil {
86 | return err
87 | }
88 | err = writer.Close()
89 | if err != nil {
90 | return err
91 | }
92 | requestBodyData = formBody.Bytes()
93 | case nilBody:
94 | requestBodyData = nil
95 |
96 | default:
97 | return errors.New("unknown request body type")
98 | }
99 |
100 | if api == nil {
101 | return errors.New("no available API")
102 | }
103 |
104 | req, err := http.NewRequest(method, url, bytes.NewReader(requestBodyData))
105 | if err != nil {
106 | return err
107 | }
108 |
109 | req.Header.Set("Content-Type", "application/json")
110 | if bodyType == formVoiceDataBody || bodyType == formPictureDataBody {
111 | req.Header.Set("Content-Type", writer.FormDataContentType())
112 | }
113 | if gpt.Platform == OpenAI {
114 | req.Header.Set("Authorization", "Bearer "+api.Key)
115 | } else {
116 | req.Header.Set("api-key", gpt.AzureConfig.ApiToken)
117 | }
118 |
119 | var response *http.Response
120 | var retry int
121 | for retry = 0; retry <= maxRetries; retry++ {
122 | response, err = client.Do(req)
123 | //fmt.Println("--------------------")
124 | //fmt.Println("req", req.Header)
125 | //fmt.Printf("response: %v", response)
126 | // read body
127 | if err != nil || response.StatusCode < 200 || response.StatusCode >= 300 {
128 |
129 | body, _ := ioutil.ReadAll(response.Body)
130 | fmt.Println("body", string(body))
131 |
132 | gpt.Lb.SetAvailability(api.Key, false)
133 | if retry == maxRetries {
134 | break
135 | }
136 | time.Sleep(time.Duration(retry+1) * time.Second)
137 | } else {
138 | break
139 | }
140 | }
141 | if response != nil {
142 | defer response.Body.Close()
143 | }
144 |
145 | if response == nil || response.StatusCode < 200 || response.StatusCode >= 300 {
146 | return fmt.Errorf("%s api failed after %d retries", strings.ToUpper(method), retry)
147 | }
148 |
149 | body, err := ioutil.ReadAll(response.Body)
150 | if err != nil {
151 | return err
152 | }
153 |
154 | err = json.Unmarshal(body, responseBody)
155 | if err != nil {
156 | return err
157 | }
158 |
159 | gpt.Lb.SetAvailability(api.Key, true)
160 | return nil
161 | }
162 |
163 | func (gpt *ChatGPT) sendRequestWithBodyType(link, method string,
164 | bodyType requestBodyType,
165 | requestBody interface{}, responseBody interface{}) error {
166 | var err error
167 | client := &http.Client{Timeout: 110 * time.Second}
168 | if gpt.HttpProxy == "" {
169 | err = gpt.doAPIRequestWithRetry(link, method, bodyType,
170 | requestBody, responseBody, client, 3)
171 | } else {
172 | proxyUrl, err := url.Parse(gpt.HttpProxy)
173 | if err != nil {
174 | return err
175 | }
176 | transport := &http.Transport{
177 | Proxy: http.ProxyURL(proxyUrl),
178 | }
179 | proxyClient := &http.Client{
180 | Transport: transport,
181 | Timeout: 110 * time.Second,
182 | }
183 | err = gpt.doAPIRequestWithRetry(link, method, bodyType,
184 | requestBody, responseBody, proxyClient, 3)
185 | }
186 |
187 | return err
188 | }
189 |
190 | func NewChatGPT(config initialization.Config) *ChatGPT {
191 | var lb *loadbalancer.LoadBalancer
192 | if config.AzureOn {
193 | keys := []string{config.AzureOpenaiToken}
194 | lb = loadbalancer.NewLoadBalancer(keys)
195 | } else {
196 | lb = loadbalancer.NewLoadBalancer(config.OpenaiApiKeys)
197 | }
198 | platform := OpenAI
199 |
200 | if config.AzureOn {
201 | platform = Azure
202 | }
203 |
204 | return &ChatGPT{
205 | Lb: lb,
206 | ApiKey: config.OpenaiApiKeys,
207 | ApiUrl: config.OpenaiApiUrl,
208 | HttpProxy: config.HttpProxy,
209 | Platform: platform,
210 | AzureConfig: AzureConfig{
211 | BaseURL: AzureApiUrlV1,
212 | ResourceName: config.AzureResourceName,
213 | DeploymentName: config.AzureDeploymentName,
214 | ApiVersion: config.AzureApiVersion,
215 | ApiToken: config.AzureOpenaiToken,
216 | },
217 | }
218 | }
219 |
220 | func (gpt *ChatGPT) FullUrl(suffix string) string {
221 | var url string
222 | switch gpt.Platform {
223 | case Azure:
224 | url = fmt.Sprintf("https://%s.%s%s/%s?api-version=%s",
225 | gpt.AzureConfig.ResourceName, gpt.AzureConfig.BaseURL,
226 | gpt.AzureConfig.DeploymentName, suffix, gpt.AzureConfig.ApiVersion)
227 | case OpenAI:
228 | url = fmt.Sprintf("%s/v1/%s", gpt.ApiUrl, suffix)
229 | }
230 | return url
231 | }
232 |
--------------------------------------------------------------------------------
/pkg/openai/gpt3.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "errors"
5 | "strings"
6 |
7 | "github.com/pandodao/tokenizer-go"
8 | )
9 |
10 | type AIMode float64
11 |
12 | const (
13 | Fresh AIMode = 0.1
14 | Warmth AIMode = 0.4
15 | Balance AIMode = 0.7
16 | Creativity AIMode = 1.0
17 | )
18 |
19 | var AIModeMap = map[string]AIMode{
20 | "清新": Fresh,
21 | "温暖": Warmth,
22 | "平衡": Balance,
23 | "创意": Creativity,
24 | }
25 |
26 | var AIModeStrs = []string{
27 | "清新",
28 | "温暖",
29 | "平衡",
30 | "创意",
31 | }
32 |
33 | const (
34 | maxTokens = 2000
35 | engine = "gpt-3.5-turbo"
36 | )
37 |
38 | type Messages struct {
39 | Role string `json:"role"`
40 | Content string `json:"content"`
41 | }
42 |
43 | // ChatGPTResponseBody 请求体
44 | type ChatGPTResponseBody struct {
45 | ID string `json:"id"`
46 | Object string `json:"object"`
47 | Created int `json:"created"`
48 | Model string `json:"model"`
49 | Choices []ChatGPTChoiceItem `json:"choices"`
50 | Usage map[string]interface{} `json:"usage"`
51 | }
52 |
53 | type ChatGPTChoiceItem struct {
54 | Message Messages `json:"message"`
55 | Index int `json:"index"`
56 | FinishReason string `json:"finish_reason"`
57 | }
58 |
59 | // ChatGPTRequestBody 响应体
60 | type ChatGPTRequestBody struct {
61 | Model string `json:"model"`
62 | Messages []Messages `json:"messages"`
63 | MaxTokens int `json:"max_tokens"`
64 | Temperature AIMode `json:"temperature"`
65 | TopP int `json:"top_p"`
66 | FrequencyPenalty int `json:"frequency_penalty"`
67 | PresencePenalty int `json:"presence_penalty"`
68 | }
69 |
70 | func (msg *Messages) CalculateTokenLength() int {
71 | text := strings.TrimSpace(msg.Content)
72 | return tokenizer.MustCalToken(text)
73 | }
74 |
75 | func (gpt *ChatGPT) Completions(msg []Messages, aiMode AIMode) (resp Messages,
76 | err error) {
77 | requestBody := ChatGPTRequestBody{
78 | Model: engine,
79 | Messages: msg,
80 | MaxTokens: maxTokens,
81 | Temperature: aiMode,
82 | TopP: 1,
83 | FrequencyPenalty: 0,
84 | PresencePenalty: 0,
85 | }
86 | gptResponseBody := &ChatGPTResponseBody{}
87 | url := gpt.FullUrl("chat/completions")
88 | //fmt.Println(url)
89 | if url == "" {
90 | return resp, errors.New("无法获取openai请求地址")
91 | }
92 | err = gpt.sendRequestWithBodyType(url, "POST", jsonBody, requestBody, gptResponseBody)
93 | if err == nil && len(gptResponseBody.Choices) > 0 {
94 | resp = gptResponseBody.Choices[0].Message
95 | } else {
96 | resp = Messages{}
97 | err = errors.New("openai 请求失败")
98 | }
99 | return resp, err
100 | }
101 |
--------------------------------------------------------------------------------
/pkg/openai/gpt3_test.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "fmt"
5 | "oral-friend/internal/initialization"
6 | "testing"
7 | )
8 |
9 | func TestCompletions(t *testing.T) {
10 | config := initialization.LoadConfig("../../config.yaml")
11 |
12 | msgs := []Messages{
13 | {Role: "system", Content: "你是一个专业的翻译官,负责中英文翻译。"},
14 | {Role: "user", Content: "翻译这段话: The assistant messages help store prior responses. They can also be written by a developer to help give examples of desired behavior."},
15 | }
16 |
17 | gpt := NewChatGPT(*config)
18 |
19 | resp, err := gpt.Completions(msgs, Balance)
20 | if err != nil {
21 | t.Errorf("TestCompletions failed with error: %v", err)
22 | }
23 |
24 | fmt.Println(resp.Content, resp.Role)
25 | }
26 |
27 | func TestGenerateOneImage(t *testing.T) {
28 | config := initialization.LoadConfig("../../config.yaml")
29 | gpt := NewChatGPT(*config)
30 | prompt := "a red apple"
31 | size := "256x256"
32 | imageURL, err := gpt.GenerateOneImage(prompt, size)
33 | if err != nil {
34 | t.Errorf("TestGenerateOneImage failed with error: %v", err)
35 | }
36 | if imageURL == "" {
37 | t.Errorf("TestGenerateOneImage returned empty imageURL")
38 | }
39 | }
40 |
41 | func TestAudioToText(t *testing.T) {
42 | config := initialization.LoadConfig("../../config.yaml")
43 | gpt := NewChatGPT(*config)
44 | audio := "./test_file/test.wav"
45 | text, err := gpt.AudioToText(audio)
46 | if err != nil {
47 | t.Errorf("TestAudioToText failed with error: %v", err)
48 | }
49 | fmt.Printf("TestAudioToText returned text: %s \n", text)
50 | if text == "" {
51 | t.Errorf("TestAudioToText returned empty text")
52 | }
53 |
54 | }
55 |
56 | func TestVariateOneImage(t *testing.T) {
57 | config := initialization.LoadConfig("../../config.yaml")
58 | gpt := NewChatGPT(*config)
59 | image := "./test_file/img.png"
60 | size := "256x256"
61 | //compressionType, err := GetImageCompressionType(image)
62 | //if err != nil {
63 | // return
64 | //}
65 | //fmt.Println("compressionType: ", compressionType)
66 | ConvertToRGBA(image, image)
67 | err := VerifyPngs([]string{image})
68 | if err != nil {
69 | t.Errorf("TestVariateOneImage failed with error: %v", err)
70 | return
71 | }
72 |
73 | imageBs64, err := gpt.GenerateOneImageVariation(image, size)
74 | if err != nil {
75 | t.Errorf("TestVariateOneImage failed with error: %v", err)
76 | }
77 | //fmt.Printf("TestVariateOneImage returned imageBs64: %s \n", imageBs64)
78 | if imageBs64 == "" {
79 | t.Errorf("TestVariateOneImage returned empty imageURL")
80 | }
81 | }
82 |
83 | func TestVariateOneImageWithJpg(t *testing.T) {
84 | config := initialization.LoadConfig("../../config.yaml")
85 | gpt := NewChatGPT(*config)
86 | image := "./test_file/test.jpg"
87 | size := "256x256"
88 | compressionType, err := GetImageCompressionType(image)
89 | if err != nil {
90 | return
91 | }
92 | fmt.Println("compressionType: ", compressionType)
93 | //ConvertJPGtoPNG(image)
94 | ConvertToRGBA(image, image)
95 | err = VerifyPngs([]string{image})
96 | if err != nil {
97 | t.Errorf("TestVariateOneImage failed with error: %v", err)
98 | return
99 | }
100 |
101 | imageBs64, err := gpt.GenerateOneImageVariation(image, size)
102 | if err != nil {
103 | t.Errorf("TestVariateOneImage failed with error: %v", err)
104 | }
105 | fmt.Printf("TestVariateOneImage returned imageBs64: %s \n", imageBs64)
106 | if imageBs64 == "" {
107 | t.Errorf("TestVariateOneImage returned empty imageURL")
108 | }
109 | }
110 |
111 | // 余额接口已经被废弃
112 | func TestChatGPT_GetBalance(t *testing.T) {
113 | config := initialization.LoadConfig("../../config.yaml")
114 | gpt := NewChatGPT(*config)
115 | balance, err := gpt.GetBalance()
116 | if err != nil {
117 | t.Errorf("TestChatGPT_GetBalance failed with error: %v", err)
118 | }
119 | fmt.Println("balance: ", balance)
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/openai/picture.go:
--------------------------------------------------------------------------------
1 | package openai
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "image"
7 | "image/jpeg"
8 | "image/png"
9 | "io"
10 | "mime/multipart"
11 | "os"
12 | )
13 |
14 | type ImageGenerationRequestBody struct {
15 | Prompt string `json:"prompt"`
16 | N int `json:"n"`
17 | Size string `json:"size"`
18 | ResponseFormat string `json:"response_format"`
19 | }
20 |
21 | type ImageResponseBody struct {
22 | Created int64 `json:"created"`
23 | Data []struct {
24 | Base64Json string `json:"b64_json"`
25 | } `json:"data"`
26 | }
27 |
28 | type ImageVariantRequestBody struct {
29 | Image string `json:"image"`
30 | N int `json:"n"`
31 | Size string `json:"size"`
32 | ResponseFormat string `json:"response_format"`
33 | }
34 |
35 | func (gpt *ChatGPT) GenerateImage(prompt string, size string,
36 | n int) ([]string, error) {
37 | requestBody := ImageGenerationRequestBody{
38 | Prompt: prompt,
39 | N: n,
40 | Size: size,
41 | ResponseFormat: "b64_json",
42 | }
43 |
44 | imageResponseBody := &ImageResponseBody{}
45 | err := gpt.sendRequestWithBodyType(gpt.ApiUrl+"/v1/images/generations",
46 | "POST", jsonBody, requestBody, imageResponseBody)
47 |
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | var b64Pool []string
53 | for _, data := range imageResponseBody.Data {
54 | b64Pool = append(b64Pool, data.Base64Json)
55 | }
56 | return b64Pool, nil
57 | }
58 |
59 | func (gpt *ChatGPT) GenerateOneImage(prompt string,
60 | size string) (string, error) {
61 | b64s, err := gpt.GenerateImage(prompt, size, 1)
62 | if err != nil {
63 | return "", err
64 | }
65 | return b64s[0], nil
66 | }
67 |
68 | func (gpt *ChatGPT) GenerateOneImageWithDefaultSize(
69 | prompt string) (string, error) {
70 | return gpt.GenerateOneImage(prompt, "512x512")
71 | }
72 |
73 | func (gpt *ChatGPT) GenerateImageVariation(images string,
74 | size string, n int) ([]string, error) {
75 | requestBody := ImageVariantRequestBody{
76 | Image: images,
77 | N: n,
78 | Size: size,
79 | ResponseFormat: "b64_json",
80 | }
81 |
82 | imageResponseBody := &ImageResponseBody{}
83 | err := gpt.sendRequestWithBodyType(gpt.ApiUrl+"/v1/images/variations",
84 | "POST", formPictureDataBody, requestBody, imageResponseBody)
85 |
86 | if err != nil {
87 | return nil, err
88 | }
89 |
90 | var b64Pool []string
91 | for _, data := range imageResponseBody.Data {
92 | b64Pool = append(b64Pool, data.Base64Json)
93 | }
94 | return b64Pool, nil
95 | }
96 |
97 | func (gpt *ChatGPT) GenerateOneImageVariation(images string,
98 | size string) (string, error) {
99 | b64s, err := gpt.GenerateImageVariation(images, size, 1)
100 | if err != nil {
101 | return "", err
102 | }
103 | return b64s[0], nil
104 | }
105 |
106 | func pictureMultipartForm(request ImageVariantRequestBody,
107 | w *multipart.Writer) error {
108 |
109 | f, err := os.Open(request.Image)
110 | if err != nil {
111 | return fmt.Errorf("opening audio file: %w", err)
112 | }
113 | fw, err := w.CreateFormFile("image", f.Name())
114 | if err != nil {
115 | return fmt.Errorf("creating form file: %w", err)
116 | }
117 | if _, err = io.Copy(fw, f); err != nil {
118 | return fmt.Errorf("reading from opened audio file: %w", err)
119 | }
120 |
121 | err = w.WriteField("size", request.Size)
122 | if err != nil {
123 | return fmt.Errorf("writing size: %w", err)
124 | }
125 |
126 | err = w.WriteField("n", fmt.Sprintf("%d", request.N))
127 | if err != nil {
128 | return fmt.Errorf("writing n: %w", err)
129 | }
130 |
131 | err = w.WriteField("response_format", request.ResponseFormat)
132 | if err != nil {
133 | return fmt.Errorf("writing response_format: %w", err)
134 | }
135 |
136 | //err = w.WriteField("user", "user123456")
137 |
138 | //fw, err = w.CreateFormField("model")
139 | //if err != nil {
140 | // return fmt.Errorf("creating form field: %w", err)
141 | //}
142 | //modelName := bytes.NewReader([]byte(request.Model))
143 | //if _, err = io.Copy(fw, modelName); err != nil {
144 | // return fmt.Errorf("writing model name: %w", err)
145 | //}
146 |
147 | //fmt.Printf("w.FormDataContentType(): %s ", w.FormDataContentType())
148 |
149 | w.Close()
150 |
151 | return nil
152 | }
153 |
154 | func VerifyPngs(pngPaths []string) error {
155 | foundPng := false
156 | var expectedWidth, expectedHeight int
157 |
158 | for _, pngPath := range pngPaths {
159 | f, err := os.Open(pngPath)
160 | if err != nil {
161 | return fmt.Errorf("os.Open: %v", err)
162 | }
163 |
164 | fi, err := f.Stat()
165 | if err != nil {
166 | return fmt.Errorf("f.Stat: %v", err)
167 | }
168 | if fi.Size() > 4*1024*1024 {
169 | return fmt.Errorf("image size too large, "+
170 | "must be under %d MB", 4)
171 | }
172 |
173 | image, err := png.Decode(f)
174 | if err != nil {
175 | return fmt.Errorf("image must be valid png, got error: %v", err)
176 | }
177 | width := image.Bounds().Dx()
178 | height := image.Bounds().Dy()
179 | if width != height {
180 | return fmt.Errorf("found non-square image with dimensions %dx%d", width, height)
181 | }
182 |
183 | if !foundPng {
184 | foundPng = true
185 | expectedWidth = width
186 | expectedHeight = height
187 | } else {
188 | if width != expectedWidth || height != expectedHeight {
189 | return fmt.Errorf("dimensions of all images must match, got both (%dx%d) and (%dx%d)", width, height, expectedWidth, expectedHeight)
190 | }
191 | }
192 | }
193 |
194 | return nil
195 | }
196 |
197 | func ConvertToRGBA(inputFilePath string, outputFilePath string) error {
198 | // 打开输入文件
199 | inputFile, err := os.Open(inputFilePath)
200 | if err != nil {
201 | return fmt.Errorf("打开文件时出错:%w", err)
202 | }
203 | defer inputFile.Close()
204 |
205 | // 解码图像
206 | img, _, err := image.Decode(inputFile)
207 | if err != nil {
208 | return fmt.Errorf("解码图像时出错:%w", err)
209 | }
210 |
211 | // 将图像转换为RGBA模式
212 | rgba := image.NewRGBA(img.Bounds())
213 | for x := 0; x < img.Bounds().Max.X; x++ {
214 | for y := 0; y < img.Bounds().Max.Y; y++ {
215 | rgba.Set(x, y, img.At(x, y))
216 | }
217 | }
218 |
219 | // 创建输出文件
220 | outputFile, err := os.Create(outputFilePath)
221 | if err != nil {
222 | return fmt.Errorf("创建输出文件时出错:%w", err)
223 | }
224 | defer outputFile.Close()
225 |
226 | // 编码图像为 PNG 格式并写入输出文件
227 | if err := png.Encode(outputFile, rgba); err != nil {
228 | return fmt.Errorf("编码图像时出错:%w", err)
229 | }
230 |
231 | return nil
232 | }
233 |
234 | func ConvertJpegToPNG(jpgPath string) error {
235 | // Open the JPEG file for reading
236 | f, err := os.Open(jpgPath)
237 | if err != nil {
238 | return err
239 | }
240 | defer f.Close()
241 |
242 | // Check if the file is a JPEG image
243 | _, err = jpeg.Decode(f)
244 | if err != nil {
245 | // The file is not a JPEG image, no need to convert it
246 | return fmt.Errorf("file %s is not a JPEG image", jpgPath)
247 | }
248 |
249 | // Reset the file pointer to the beginning of the file
250 | _, err = f.Seek(0, 0)
251 | if err != nil {
252 | return err
253 | }
254 |
255 | // Create a new PNG file for writing
256 | pngPath := jpgPath[:len(jpgPath)-4] + ".png" // replace .jpg extension with .png
257 | out, err := os.Create(pngPath)
258 | if err != nil {
259 | return err
260 | }
261 | defer out.Close()
262 |
263 | // Decode the JPEG image and encode it as PNG
264 | img, err := jpeg.Decode(f)
265 | if err != nil {
266 | return err
267 | }
268 | err = png.Encode(out, img)
269 | if err != nil {
270 | return err
271 | }
272 |
273 | return nil
274 | }
275 |
276 | func GetImageCompressionType(path string) (string, error) {
277 | // 打开文件
278 | file, err := os.Open(path)
279 | if err != nil {
280 | return "", err
281 | }
282 | defer file.Close()
283 |
284 | // 创建 bufio.Reader
285 | reader := bufio.NewReader(file)
286 |
287 | // 解码图像
288 | _, format, err := image.DecodeConfig(reader)
289 | if err != nil {
290 | fmt.Println("err: ", err)
291 | return "", err
292 | }
293 |
294 | fmt.Println("format: ", format)
295 | // 返回压缩类型
296 | return format, nil
297 | }
298 |
--------------------------------------------------------------------------------
/pkg/openai/test_file/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConnectAI-E/Feishu-Oral-Friend/e35fe98bdeed56f855df823e2add96bdb63f7690/pkg/openai/test_file/img.png
--------------------------------------------------------------------------------
/pkg/openai/test_file/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConnectAI-E/Feishu-Oral-Friend/e35fe98bdeed56f855df823e2add96bdb63f7690/pkg/openai/test_file/test.jpg
--------------------------------------------------------------------------------
/pkg/openai/test_file/test.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConnectAI-E/Feishu-Oral-Friend/e35fe98bdeed56f855df823e2add96bdb63f7690/pkg/openai/test_file/test.wav
--------------------------------------------------------------------------------
/pkg/qdrantkit/collection.go:
--------------------------------------------------------------------------------
1 | package qdrantkit
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "log"
7 | "net/http"
8 | )
9 |
10 | const (
11 | collectionApi = "/collections"
12 | )
13 |
14 | type CreateCollectionRequest struct {
15 | Vectors Vectors `json:"vectors"`
16 | }
17 | type Vectors struct {
18 | Size int `json:"size"`
19 | Distance string `json:"distance"`
20 | }
21 |
22 | func (q Qdrant) init() {
23 | err := q.GetCollection(q.CollectionName)
24 | if err == nil {
25 | return
26 | }
27 | createCollectionRequest := CreateCollectionRequest{Vectors: Vectors{
28 | Size: 1536,
29 | Distance: "Cosine",
30 | }}
31 | err = q.CreateCollection(q.CollectionName, createCollectionRequest)
32 | if err != nil {
33 | panic("init error:" + err.Error())
34 | }
35 | }
36 |
37 | // CreateCollection creates a collection in qdrant
38 | func (q Qdrant) CreateCollection(name string, createCollectionRequest CreateCollectionRequest) (err error) {
39 | response := &CommonResponse{}
40 | var reqBytes []byte
41 | reqBytes, err = json.Marshal(createCollectionRequest)
42 | if err != nil {
43 | log.Println(err.Error())
44 | return
45 | }
46 |
47 | body, err := q.Send(http.MethodPut, collectionApi+"/"+name, reqBytes)
48 | if err != nil {
49 | log.Println(err.Error())
50 | return
51 | }
52 |
53 | err = json.Unmarshal(body, &response)
54 | if err != nil {
55 | return
56 | }
57 | if response.Result == nil {
58 | return errors.New(response.Status.(map[string]interface{})["error"].(string))
59 | }
60 | return
61 |
62 | }
63 |
64 | func (q Qdrant) GetCollection(name string) (err error) {
65 | response := &CommonResponse{}
66 |
67 | body, err := q.Send(http.MethodGet, collectionApi+"/"+name, nil)
68 | if err != nil {
69 | log.Println(err.Error())
70 | return
71 | }
72 |
73 | err = json.Unmarshal(body, &response)
74 | if err != nil {
75 | return
76 | }
77 | if response.Result == nil {
78 | return errors.New(response.Status.(map[string]interface{})["error"].(string))
79 | }
80 |
81 | return
82 | }
83 |
--------------------------------------------------------------------------------
/pkg/qdrantkit/collection_test.go:
--------------------------------------------------------------------------------
1 | package qdrantkit
2 |
3 | import "testing"
4 |
5 | func TestCreateCollection(t *testing.T) {
6 | type args struct {
7 | name string
8 | createCollectionRequest CreateCollectionRequest
9 | }
10 | tests := []struct {
11 | name string
12 | args args
13 | wantErr bool
14 | }{
15 | {
16 | name: "1",
17 | args: args{name: "xxx", createCollectionRequest: CreateCollectionRequest{Vectors: Vectors{
18 | Size: 111,
19 | Distance: "Cosine",
20 | }}},
21 | wantErr: false,
22 | },
23 | }
24 | client := New("http://localhost:6333", "test")
25 | for _, tt := range tests {
26 | t.Run(tt.name, func(t *testing.T) {
27 | if err := client.CreateCollection(tt.args.name, tt.args.createCollectionRequest); (err != nil) != tt.wantErr {
28 | t.Errorf("CreateCollection() error = %v, wantErr %v", err, tt.wantErr)
29 | }
30 | })
31 | }
32 | }
33 |
34 | func TestGetCollection(t *testing.T) {
35 | type args struct {
36 | name string
37 | }
38 | tests := []struct {
39 | name string
40 | args args
41 | wantErr bool
42 | }{
43 | {
44 | name: "1",
45 | args: args{name: "xxx"},
46 | wantErr: false,
47 | },
48 | }
49 | client := New("http://localhost:6333", "test")
50 | for _, tt := range tests {
51 | t.Run(tt.name, func(t *testing.T) {
52 | if err := client.GetCollection(tt.args.name); (err != nil) != tt.wantErr {
53 | t.Errorf("GetCollection() error = %v, wantErr %v", err, tt.wantErr)
54 | }
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/qdrantkit/common.go:
--------------------------------------------------------------------------------
1 | package qdrantkit
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "net/http"
7 | )
8 |
9 | func (q Qdrant) Send(httpMethod string, suffix string, reqBytes []byte) (body []byte, err error) {
10 | req, err := http.NewRequest(httpMethod, q.Host+suffix, bytes.NewBuffer(reqBytes))
11 | if err != nil {
12 | return
13 | }
14 |
15 | req.Header.Add("Content-Type", "application/json")
16 |
17 | client := &http.Client{}
18 | resp, err := client.Do(req)
19 | if err != nil {
20 | return
21 | }
22 | defer resp.Body.Close()
23 |
24 | body, err = io.ReadAll(resp.Body)
25 | return
26 | }
27 |
28 | type CommonResponse struct {
29 | Result interface{} `json:"result"`
30 | Status interface{} `json:"status"`
31 | Time float64 `json:"time"`
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/qdrantkit/point.go:
--------------------------------------------------------------------------------
1 | package qdrantkit
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "log"
7 | "net/http"
8 |
9 | "github.com/mitchellh/mapstructure"
10 | )
11 |
12 | const (
13 | pointsApi = "/points"
14 | searchApi = "/search"
15 | )
16 |
17 | type PointRequest struct {
18 | Points []Point `json:"points"`
19 | }
20 |
21 | type Point struct {
22 | ID string `json:"id"`
23 | Payload interface{} `json:"payload"`
24 | Vector []float64 `json:"vector"`
25 | }
26 |
27 | type PointResponse struct {
28 | Result interface{} `json:"result"`
29 | Status interface{} `json:"status"`
30 | Time float64 `json:"time"`
31 | }
32 |
33 | type PointSearchRequest struct {
34 | Params map[string]interface{} `json:"params"`
35 | Vector []float64 `json:"vector"`
36 | Limit int `json:"limit"`
37 | WithPayload bool `json:"with_payload"`
38 | WithVector bool `json:"with_vector"`
39 | }
40 | type Match struct {
41 | Value string `json:"value"`
42 | }
43 | type Must struct {
44 | Key string `json:"key"`
45 | Match Match `json:"match"`
46 | }
47 |
48 | type SearchResult struct {
49 | ID string `json:"id"`
50 | Version int `json:"version"`
51 | Score float64 `json:"score"`
52 | Payload interface{} `json:"payload"`
53 | Vector []float64 `json:"vector,omitempty"`
54 | }
55 |
56 | func (q Qdrant) CreatePoints(collectionName string, pointRequest PointRequest) (err error) {
57 | response := &CommonResponse{}
58 | var reqBytes []byte
59 | reqBytes, err = json.Marshal(pointRequest)
60 | if err != nil {
61 | log.Println(err)
62 | return
63 | }
64 |
65 | body, err := q.Send(http.MethodPut, collectionApi+"/"+collectionName+pointsApi+"?wait=true", reqBytes)
66 | if err != nil {
67 | log.Println(err)
68 | return
69 | }
70 |
71 | err = json.Unmarshal(body, &response)
72 | if err != nil {
73 | log.Println(err)
74 | return
75 | }
76 | if response.Result == nil {
77 | log.Println(response.Status)
78 | return errors.New(response.Status.(map[string]interface{})["error"].(string))
79 | }
80 | return
81 |
82 | }
83 |
84 | func (q Qdrant) SearchPoints(pointSearchRequest PointSearchRequest) (res []SearchResult, err error) {
85 | response := &CommonResponse{}
86 | var reqBytes []byte
87 | reqBytes, err = json.Marshal(pointSearchRequest)
88 | if err != nil {
89 | log.Println(err)
90 | return
91 | }
92 |
93 | body, err := q.Send(http.MethodPost, collectionApi+"/"+q.CollectionName+pointsApi+searchApi, reqBytes)
94 | if err != nil {
95 | log.Println(err)
96 | return
97 | }
98 |
99 | err = json.Unmarshal(body, &response)
100 | if err != nil {
101 | log.Println(err)
102 | return
103 | }
104 | if response.Result == nil {
105 | return res, errors.New(response.Status.(map[string]interface{})["error"].(string))
106 | }
107 | list := response.Result.([]interface{})
108 | for _, v := range list {
109 | sr := SearchResult{}
110 | err = mapstructure.Decode(v, &sr)
111 | if err != nil {
112 | log.Println(err)
113 | return
114 | }
115 | res = append(res, sr)
116 | }
117 | return
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/pkg/qdrantkit/qdrant.go:
--------------------------------------------------------------------------------
1 | package qdrantkit
2 |
3 | type Qdrant struct {
4 | Host string
5 | CollectionName string
6 | }
7 |
8 | func New(host, cn string) *Qdrant {
9 | q := &Qdrant{
10 | Host: host,
11 | CollectionName: cn,
12 | }
13 | q.init()
14 | return q
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/services/msgCache.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/patrickmn/go-cache"
7 | )
8 |
9 | type MsgService struct {
10 | cache *cache.Cache
11 | }
12 | type MsgCacheInterface interface {
13 | IfProcessed(msgId string) bool
14 | TagProcessed(msgId string)
15 | Clear(userId string) bool
16 | }
17 |
18 | var msgService *MsgService
19 |
20 | func (u MsgService) IfProcessed(msgId string) bool {
21 | _, found := u.cache.Get(msgId)
22 | return found
23 | }
24 | func (u MsgService) TagProcessed(msgId string) {
25 | u.cache.Set(msgId, true, time.Minute*30)
26 | }
27 |
28 | func (u MsgService) Clear(userId string) bool {
29 | u.cache.Delete(userId)
30 | return true
31 | }
32 |
33 | func GetMsgCache() MsgCacheInterface {
34 | if msgService == nil {
35 | msgService = &MsgService{cache: cache.New(30*time.Minute, 30*time.Minute)}
36 | }
37 | return msgService
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/services/sessionCache.go:
--------------------------------------------------------------------------------
1 | package services
2 |
3 | import (
4 | "oral-friend/pkg/openai"
5 | "time"
6 |
7 | "github.com/patrickmn/go-cache"
8 | )
9 |
10 | type SessionMode string
11 | type SessionService struct {
12 | cache *cache.Cache
13 | }
14 | type PicSetting struct {
15 | resolution Resolution
16 | }
17 | type Resolution string
18 |
19 | type SessionMeta struct {
20 | Mode SessionMode `json:"mode"`
21 | Msg []openai.Messages `json:"msg,omitempty"`
22 | PicSetting PicSetting `json:"pic_setting,omitempty"`
23 | AIMode openai.AIMode `json:"ai_mode,omitempty"`
24 | }
25 |
26 | const (
27 | Resolution256 Resolution = "256x256"
28 | Resolution512 Resolution = "512x512"
29 | Resolution1024 Resolution = "1024x1024"
30 | )
31 | const (
32 | ModePicCreate SessionMode = "pic_create"
33 | ModePicVary SessionMode = "pic_vary"
34 | ModeGPT SessionMode = "gpt"
35 | )
36 |
37 | type SessionServiceCacheInterface interface {
38 | Get(sessionId string) *SessionMeta
39 | Set(sessionId string, sessionMeta *SessionMeta)
40 | GetMsg(sessionId string) []openai.Messages
41 | SetMsg(sessionId string, msg []openai.Messages)
42 | SetMode(sessionId string, mode SessionMode)
43 | GetMode(sessionId string) SessionMode
44 | GetAIMode(sessionId string) openai.AIMode
45 | SetAIMode(sessionId string, aiMode openai.AIMode)
46 | SetPicResolution(sessionId string, resolution Resolution)
47 | GetPicResolution(sessionId string) string
48 | Clear(sessionId string)
49 | }
50 |
51 | var sessionServices *SessionService
52 |
53 | // implement Get interface
54 | func (s *SessionService) Get(sessionId string) *SessionMeta {
55 | sessionContext, ok := s.cache.Get(sessionId)
56 | if !ok {
57 | return nil
58 | }
59 | sessionMeta := sessionContext.(*SessionMeta)
60 | return sessionMeta
61 | }
62 |
63 | // implement Set interface
64 | func (s *SessionService) Set(sessionId string, sessionMeta *SessionMeta) {
65 | maxCacheTime := time.Hour * 12
66 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
67 | }
68 |
69 | func (s *SessionService) GetMode(sessionId string) SessionMode {
70 | // Get the session mode from the cache.
71 | sessionContext, ok := s.cache.Get(sessionId)
72 | if !ok {
73 | return ModeGPT
74 | }
75 | sessionMeta := sessionContext.(*SessionMeta)
76 | return sessionMeta.Mode
77 | }
78 |
79 | func (s *SessionService) SetMode(sessionId string, mode SessionMode) {
80 | maxCacheTime := time.Hour * 12
81 | sessionContext, ok := s.cache.Get(sessionId)
82 | if !ok {
83 | sessionMeta := &SessionMeta{Mode: mode}
84 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
85 | return
86 | }
87 | sessionMeta := sessionContext.(*SessionMeta)
88 | sessionMeta.Mode = mode
89 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
90 | }
91 |
92 | func (s *SessionService) GetAIMode(sessionId string) openai.AIMode {
93 | sessionContext, ok := s.cache.Get(sessionId)
94 | if !ok {
95 | return openai.Balance
96 | }
97 | sessionMeta := sessionContext.(*SessionMeta)
98 | return sessionMeta.AIMode
99 | }
100 |
101 | // SetAIMode set the ai mode for the session.
102 | func (s *SessionService) SetAIMode(sessionId string, aiMode openai.AIMode) {
103 | maxCacheTime := time.Hour * 12
104 | sessionContext, ok := s.cache.Get(sessionId)
105 | if !ok {
106 | sessionMeta := &SessionMeta{AIMode: aiMode}
107 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
108 | return
109 | }
110 | sessionMeta := sessionContext.(*SessionMeta)
111 | sessionMeta.AIMode = aiMode
112 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
113 | }
114 |
115 | func (s *SessionService) GetMsg(sessionId string) (msg []openai.Messages) {
116 | sessionContext, ok := s.cache.Get(sessionId)
117 | if !ok {
118 | return nil
119 | }
120 | sessionMeta := sessionContext.(*SessionMeta)
121 | return sessionMeta.Msg
122 | }
123 |
124 | func (s *SessionService) SetMsg(sessionId string, msg []openai.Messages) {
125 | maxLength := 4096
126 | maxCacheTime := time.Hour * 12
127 |
128 | //限制对话上下文长度
129 | for getStrPoolTotalLength(msg) > maxLength {
130 | msg = append(msg[:1], msg[2:]...)
131 | }
132 |
133 | sessionContext, ok := s.cache.Get(sessionId)
134 | if !ok {
135 | sessionMeta := &SessionMeta{Msg: msg}
136 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
137 | return
138 | }
139 | sessionMeta := sessionContext.(*SessionMeta)
140 | sessionMeta.Msg = msg
141 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
142 | }
143 |
144 | func (s *SessionService) SetPicResolution(sessionId string,
145 | resolution Resolution) {
146 | maxCacheTime := time.Hour * 12
147 |
148 | //if not in [Resolution256, Resolution512, Resolution1024] then set
149 | //to Resolution256
150 | switch resolution {
151 | case Resolution256, Resolution512, Resolution1024:
152 | default:
153 | resolution = Resolution256
154 | }
155 |
156 | sessionContext, ok := s.cache.Get(sessionId)
157 | if !ok {
158 | sessionMeta := &SessionMeta{PicSetting: PicSetting{resolution: resolution}}
159 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
160 | return
161 | }
162 | sessionMeta := sessionContext.(*SessionMeta)
163 | sessionMeta.PicSetting.resolution = resolution
164 | s.cache.Set(sessionId, sessionMeta, maxCacheTime)
165 | }
166 |
167 | func (s *SessionService) GetPicResolution(sessionId string) string {
168 | sessionContext, ok := s.cache.Get(sessionId)
169 | if !ok {
170 | return string(Resolution256)
171 | }
172 | sessionMeta := sessionContext.(*SessionMeta)
173 | return string(sessionMeta.PicSetting.resolution)
174 |
175 | }
176 |
177 | func (s *SessionService) Clear(sessionId string) {
178 | // Delete the session context from the cache.
179 | s.cache.Delete(sessionId)
180 | }
181 |
182 | func GetSessionCache() SessionServiceCacheInterface {
183 | if sessionServices == nil {
184 | sessionServices = &SessionService{cache: cache.New(time.Hour*12, time.Hour*1)}
185 | }
186 | return sessionServices
187 | }
188 |
189 | func getStrPoolTotalLength(strPool []openai.Messages) int {
190 | var total int
191 | for _, v := range strPool {
192 | total += v.CalculateTokenLength()
193 | }
194 | return total
195 | }
196 |
--------------------------------------------------------------------------------
/pkg/utils/api2d/api2d.go:
--------------------------------------------------------------------------------
1 | package api2d
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "net/http"
8 | )
9 |
10 | type Message struct {
11 | Role string `json:"role"`
12 | Content string `json:"content"`
13 | }
14 |
15 | type RequestBody struct {
16 | Model string `json:"model"`
17 | Messages []Message `json:"messages"`
18 | }
19 |
20 | func TextToSpeech() {
21 | url := "https://openai.api2d.net/v1/chat/completions"
22 | requestBody := RequestBody{
23 | Model: "gpt-3.5-turbo",
24 | Messages: []Message{
25 | {
26 | Role: "user",
27 | Content: "你好!给我讲个笑话。",
28 | },
29 | },
30 | }
31 | requestBodyBytes, err := json.Marshal(requestBody)
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBodyBytes))
37 | if err != nil {
38 | panic(err)
39 | }
40 | req.Header.Set("Content-Type", "application/json")
41 | req.Header.Set("Authorization", "fk186009-gCYVPTkf6aMycD4o2ZM9fRsDwp52ONdz|ck43-632713d")
42 |
43 | client := &http.Client{}
44 | resp, err := client.Do(req)
45 | if err != nil {
46 | panic(err)
47 | }
48 | defer resp.Body.Close()
49 |
50 | fmt.Println("status:", resp.Status)
51 | var responseMap map[string]interface{}
52 | if err := json.NewDecoder(resp.Body).Decode(&responseMap); err != nil {
53 | panic(err)
54 | }
55 | fmt.Println("response:", responseMap)
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/utils/audio/ogg.go:
--------------------------------------------------------------------------------
1 | package audio
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "io"
7 | "os"
8 |
9 | "github.com/pion/opus"
10 | "github.com/pion/opus/pkg/oggreader"
11 | )
12 |
13 | func OggToWavByPath(ogg string, wav string) error {
14 | input, err := os.Open(ogg)
15 | if err != nil {
16 | return err
17 | }
18 | defer input.Close()
19 |
20 | output, err := os.Create(wav)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | defer output.Close()
26 | return OggToWav(input, output)
27 | }
28 |
29 | func OggToWav(input io.Reader, output io.WriteSeeker) error {
30 | ogg, _, err := oggreader.NewWith(input)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | out := make([]byte, 1920)
36 |
37 | decoder := opus.NewDecoder()
38 | encoder := NewEncoder(output, 44100, 16)
39 |
40 | for {
41 | segments, _, err := ogg.ParseNextPage()
42 | if errors.Is(err, io.EOF) {
43 | break
44 | } else if bytes.HasPrefix(segments[0], []byte("OpusTags")) {
45 | continue
46 | }
47 |
48 | if err != nil {
49 | panic(err)
50 | }
51 |
52 | for i := range segments {
53 | if _, _, err = decoder.Decode(segments[i], out); err != nil {
54 | panic(err)
55 | }
56 | encoder.Write(out)
57 | }
58 | }
59 | encoder.Close()
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/utils/audio/wav.go:
--------------------------------------------------------------------------------
1 | package audio
2 |
3 | import (
4 | "encoding/binary"
5 | "io"
6 | )
7 |
8 | type Encoder struct {
9 | Output io.WriteSeeker
10 | SampleRate int
11 | BitDepth int
12 | totalBytes uint32
13 | isHeaderWritten bool
14 | }
15 |
16 | func (e *Encoder) WriteHeader() error {
17 | if err := writeLe(e.Output, []byte("RIFF")); err != nil {
18 | return err
19 | }
20 |
21 | if err := writeLe(e.Output, uint32(0)); err != nil { // Placeholder for file size
22 | return err
23 | }
24 |
25 | if err := writeLe(e.Output, []byte("WAVE")); err != nil {
26 | return err
27 | }
28 |
29 | if err := writeLe(e.Output, []byte("fmt ")); err != nil {
30 | return err
31 | }
32 | if err := writeLe(e.Output, uint32(16)); err != nil {
33 | return err
34 | }
35 |
36 | if err := writeLe(e.Output, uint16(1)); err != nil { // Audio format: PCM
37 | return err
38 | }
39 | if err := writeLe(e.Output, uint16(1)); err != nil { // Number of channels: 1 (mono)
40 | return err
41 | }
42 | if err := writeLe(e.Output, uint32(e.SampleRate)); err != nil {
43 | return err
44 | }
45 |
46 | if err := writeLe(e.Output, uint32(e.SampleRate*e.BitDepth/8)); err != nil {
47 | return err
48 | }
49 |
50 | if err := writeLe(e.Output, uint16(e.BitDepth/8)); err != nil {
51 | return err
52 | }
53 | if err := writeLe(e.Output, uint16(e.BitDepth)); err != nil {
54 | return err
55 | }
56 |
57 | if err := writeLe(e.Output, []byte("data")); err != nil {
58 | return err
59 | }
60 |
61 | if err := writeLe(e.Output, uint32(0)); err != nil { //Placeholder for data size
62 | return err
63 | }
64 | e.isHeaderWritten = true
65 | return nil
66 | }
67 |
68 | func writeLe[T []byte | uint32 | uint16 | uint8](w io.Writer, data T) error {
69 | return binary.Write(w, binary.LittleEndian, data)
70 | }
71 |
72 | func (e *Encoder) Write(data []byte) error {
73 | if !e.isHeaderWritten {
74 | e.WriteHeader()
75 | }
76 | n, err := e.Output.Write(data)
77 | if err != nil {
78 | return err
79 | }
80 | e.totalBytes += uint32(n)
81 | return nil
82 | }
83 |
84 | func (e *Encoder) Close() error {
85 | if _, err := e.Output.Seek(4, io.SeekStart); err != nil {
86 | return err
87 | }
88 | if err := binary.Write(e.Output, binary.LittleEndian, uint32(36+e.totalBytes)); err != nil {
89 | return err
90 | }
91 | if _, err := e.Output.Seek(40, io.SeekStart); err != nil {
92 | return err
93 | }
94 | if err := binary.Write(e.Output, binary.LittleEndian, e.totalBytes); err != nil {
95 | return err
96 | }
97 | return nil
98 | }
99 |
100 | func NewEncoder(w io.WriteSeeker, sampleRate int, bitDepth int) *Encoder {
101 | return &Encoder{
102 | SampleRate: sampleRate,
103 | Output: w,
104 | BitDepth: bitDepth,
105 | isHeaderWritten: false,
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/pkg/utils/strings.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "strings"
4 |
5 | func CutPrefix(s, prefix string) (string, bool) {
6 | if strings.HasPrefix(s, prefix) {
7 | return strings.TrimPrefix(s, prefix), true
8 | }
9 | return s, false
10 | }
11 |
12 | func EitherCutPrefix(s string, prefix ...string) (string, bool) {
13 | // 任一前缀匹配则返回剩余部分
14 | for _, p := range prefix {
15 | if strings.HasPrefix(s, p) {
16 | return strings.TrimPrefix(s, p), true
17 | }
18 | }
19 | return s, false
20 | }
21 |
22 | // trim space and equal
23 | func TrimEqual(s, prefix string) (string, bool) {
24 | if strings.TrimSpace(s) == prefix {
25 | return "", true
26 | }
27 | return s, false
28 | }
29 |
30 | func EitherTrimEqual(s string, prefix ...string) (string, bool) {
31 | // 任一前缀匹配则返回剩余部分
32 | for _, p := range prefix {
33 | if strings.TrimSpace(s) == p {
34 | return "", true
35 | }
36 | }
37 | return s, false
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/utils/strings_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "testing"
4 |
5 | func TestEitherCutPrefix(t *testing.T) {
6 | type args struct {
7 | s string
8 | prefix []string
9 | }
10 | tests := []struct {
11 | name string
12 | args args
13 | want string
14 | want1 bool
15 | }{
16 | {
17 | name: "Prefix match",
18 | args: args{
19 | s: "/system bar",
20 | prefix: []string{"/system "},
21 | },
22 | want: "bar",
23 | want1: true,
24 | },
25 |
26 | {
27 | name: "Prefix match",
28 | args: args{
29 | s: "扮演 bar",
30 | prefix: []string{"扮演 "},
31 | },
32 | want: "bar",
33 | want1: true,
34 | },
35 | }
36 | for _, tt := range tests {
37 | t.Run(tt.name, func(t *testing.T) {
38 | got, got1 := EitherCutPrefix(tt.args.s, tt.args.prefix...)
39 | if got != tt.want {
40 | t.Errorf("EitherCutPrefix() got = %v, want %v", got, tt.want)
41 | }
42 | if got1 != tt.want1 {
43 | t.Errorf("EitherCutPrefix() got1 = %v, want %v", got1, tt.want1)
44 | }
45 | })
46 | }
47 | }
48 |
49 | func TestEitherTrimEqual(t *testing.T) {
50 | type args struct {
51 | s string
52 | prefix []string
53 | }
54 | tests := []struct {
55 | name string
56 | args args
57 | want string
58 | want1 bool
59 | }{
60 | {
61 | name: "Prefix match",
62 | args: args{
63 | s: "清除",
64 | prefix: []string{"清除"},
65 | },
66 | want: "",
67 | want1: true,
68 | },
69 | {
70 | name: "Prefix match",
71 | args: args{
72 | s: " /clear ",
73 | prefix: []string{"清除", "/clear"},
74 | },
75 | want: "",
76 | want1: true,
77 | },
78 | {
79 | name: "Prefix match",
80 | args: args{
81 | s: " 清除 ",
82 | prefix: []string{"清除", "/clear"},
83 | },
84 | want: "",
85 | want1: true,
86 | },
87 | {
88 | name: "Prefix match",
89 | args: args{
90 | s: " clear ",
91 | prefix: []string{"清除", "/clear"},
92 | },
93 | want: " clear ",
94 | want1: false,
95 | },
96 | }
97 |
98 | for _, tt := range tests {
99 | t.Run(tt.name, func(t *testing.T) {
100 | got, got1 := EitherTrimEqual(tt.args.s, tt.args.prefix...)
101 | if got != tt.want {
102 | t.Errorf("EitherTrimEqual() got = %v, want %v", got, tt.want)
103 | }
104 | if got1 != tt.want1 {
105 | t.Errorf("EitherTrimEqual() got1 = %v, want %v", got1, tt.want1)
106 | }
107 | })
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/pkg/utils/texttospeech/transform.go:
--------------------------------------------------------------------------------
1 | package texttospeech
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/go-tts/tts/pkg/speech"
7 | )
8 |
9 | func Transform(q string) {
10 | audioIn,_ := speech.FromText("text", speech.LangEn)
11 | fmt.Println(audioIn)
12 | }
--------------------------------------------------------------------------------
/role_list.yaml:
--------------------------------------------------------------------------------
1 | # 可在此处提交你认为不错的角色预设, 注意保持格式一致。
2 | # PR时的tag暂时集中在 [ "日常办公", "生活助手" ,"代码专家", "文案撰写"]
3 | # 更多点子可参考我另一个参与的项目: https://open-gpt.app/
4 |
5 | - title: 周报生成
6 | content: 请帮我把以下的工作内容填充为一篇完整的周报,用 markdown 格式以分点叙述的形式输出:
7 | example: 重新优化设计稿,和前端再次沟通UI细节,确保落地
8 | author: 二丫讲梵
9 | tags:
10 | - 日常办公
11 |
12 | - title: 产品经理
13 | content: 请确认我的以下请求。请您作为产品经理回复我。我将会提供一个主题,您将帮助我编写一份包括以下章节标题的 PRD 文档:主题、简介、问题陈述、目标与目的、用户故事、技术要求、收益、KPI 指标、开发风险以及结论。在我要求具体主题、功能或开发的 PRD 之前,请不要先写任何一份 PRD 文档。
14 | example: 我想要一个可以在手机上使用的应用程序,可以帮助我在旅行中找到最好的餐厅。
15 | author: 二丫讲梵
16 | tags:
17 | - 日常办公
18 |
19 | - title: 公文写作大师
20 | content: 你是某机关单位办公室秘书,你熟悉各类公文写作格式,你喜欢撰写文字材料,请你文采过人地,条理清晰地跟我对话
21 | example: 你好,我是某某某,我想要你帮我写一份公文,内容是:团结一致,共同抗击疫情,全力以赴,共克时艰。
22 | author: 小叉Ray
23 | tags:
24 | - 日常办公
25 | - 文案撰写
26 |
27 | - title: 招聘HR
28 | content: 我想让你担任招聘人员。我将提供一些关于职位空缺的信息,而你的工作是制定寻找合格申请人的策略。这可能包括通过社交媒体、社交活动甚至参加招聘会接触潜在候选人,以便为每个职位找到最合适的人选。我的第一个请求是:
29 | example: 我需要一名有经验的前端开发工程师,他应该有 3 年以上的工作经验,熟悉 React 和 Vue,熟悉前端工程化。
30 | author: 二丫讲梵
31 | tags:
32 | - 日常办公
33 |
34 |
35 | - title: 创意总监
36 | content: 你是一位擅长头脑风暴的创意大师,你有很多好的主意,请你围绕这些内容提出好的设想和方法
37 | example: 我想要一个可以在手机上使用的应用程序,可以帮助我在旅行中找到最好的餐厅。
38 | author: 小叉Ray
39 | tags:
40 | - 日常办公
41 |
42 | - title: 拒绝同事
43 | content: 以一种礼貌和表达得体的方式拒绝别人,同时保持积极的关系和情感连接
44 | example: 你好,我很抱歉,我现在没有时间帮你做这件事情
45 | author: 小叉Ray
46 | tags:
47 | - 日常办公
48 | - 文案撰写
49 |
50 |
51 | - title: 回复老板
52 | content: 请用 5 种委婉的借口向领导表达后面的内容
53 | example: 不想加班
54 | author: 小叉Ray
55 | tags:
56 | - 日常办公
57 | - 文案撰写
58 |
59 | - title: 邮件回复
60 | content: Generate a set of email responses that are professional, concise, and appropriate for communication with leaders and clients in a variety of industries. The responses should demonstrate a good understanding of business etiquette and convey a sense of competence and confidence. Please ensure that the responses are tailored to specific scenarios and contexts,using Chinese as the language of output
61 | example: 产品的细节很不完善,需要沟通一下
62 | author: 小叉Ray
63 | tags:
64 | - 日常办公
65 | - 文案撰写
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | - title: 三菜一汤
75 | content: 根据用户输入的金额单位是人民币,帮用户推荐在该金额合计下能够做的菜,要求三个菜和一个汤。要把每一道菜的金额都写出来,以及他的简单做法,还要有总结
76 | example: 我有 100 元,我想做湖北菜
77 | author: 小叉Ray
78 | tags:
79 | - 生活助手
80 |
81 | - title: 解梦大师
82 | content: 我要你充当解梦师。我会给你描述我的梦,你会根据梦中出现的符号和主题提供解释。不要提供关于梦者的个人意见或假设。仅根据所提供的信息提供事实解释。我的第一个梦是:
83 | example: 遇见了一只大灰狼,它在我面前转了一圈,然后就消失了
84 | author: 二丫讲梵
85 | tags:
86 | - 生活助手
87 |
88 | - title: 佛祖
89 | content: 你是一个如来佛祖,你需要回答提问者的佛学问题,因此你要学会很多佛教专业术语,你的回答尽量简短,富有佛教哲理。你要称自己为老衲,称提问者为施主。如果遭遇对方不合理的请求,请直接回复:施主请自重,我佛慈悲。你的每一句话结尾都要加上 阿弥陀佛。你的回答尽量简短,不允许超过100字。禁止回答与问题无关的话题
90 | example: 佛祖,我想问你,为什么我总是很沮丧,生活没有意义
91 | author: 小叉Ray
92 | tags:
93 | - 生活助手
94 |
95 |
96 |
97 |
98 | - title: 小红书文案
99 | content: 小红书的风格是:很吸引眼球的标题,每个段落都加 emoji, 最后加一些 tag。请用小红书风格
100 | example: 今天我去了一家很好吃的餐厅,我吃了一份很好吃的饭菜,我很喜欢,推荐给大家
101 | author: 二丫讲梵
102 | tags:
103 | - 文案撰写
104 |
105 | - title: 知乎段子手
106 | content: 微博的风格是:用"谢邀"开头,用很多学术语言,引用很多名言,做大道理的论述,提到自己很厉害的教育背景并且经验丰富,最后还要引用一些论文。请用微博风格
107 | example: 今天我去了一家很好吃的餐厅,我吃了一份很好吃的饭菜,我很喜欢,推荐给大家
108 | author: 二丫讲梵
109 | tags:
110 | - 文案撰写
111 |
112 | - title: 专业道歉信
113 | content: 请写一份真挚的道歉信,为后面的内容表达歉意
114 | example: 我很抱歉,我没有按时完成你的工作
115 | author: 小叉Ray
116 | tags:
117 | - 文案撰写
118 |
119 | - title: 古文专家
120 | content: 你是一个文言文大师,请把后面的内容翻译成文言文
121 | example: 记得早点回来哦,我做好饭菜等你回家
122 | author: 小叉Ray
123 | tags:
124 | - 文案撰写
125 |
126 |
127 | - title: 川端康城的笔
128 | content: 请以川端康城的写作风格,描写下面的句字
129 | example: 他不慌不忙的走出教室, 找到那个女孩
130 | author: 小叉Ray
131 | tags:
132 | - 文案撰写
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | - title: 网络安全
143 | content: 我想让你充当网络安全专家。我将提供一些关于如何存储和共享数据的具体信息,而你的工作就是想出保护这些数据免受恶意行为者攻击的策略。这可能包括建议加密方法、创建防火墙或实施将某些活动标记为可疑的策略。我的第一个请求是:
144 | author: 二丫讲梵
145 | tags:
146 | - 代码专家
147 |
148 | - title: 正则生成器
149 | content: 我希望你充当正则表达式生成器。您的角色是生成匹配文本中特定模式的正则表达式。您应该以一种可以轻松复制并粘贴到支持正则表达式的文本编辑器或编程语言中的格式提供正则表达式。不要写正则表达式如何工作的解释或例子;只需提供正则表达式本身。我的第一个提示是:
150 | author: 二丫讲梵
151 | tags:
152 | - 代码专家
153 |
154 | - title: 前端专家
155 | content: 我想让你充当前端开发专家。我将提供一些关于如何在网页上显示信息的具体信息,而你的工作就是想出为我解决问题的策略。这可能包括建议代码、代码逻辑思路策略。我的第一个请求是:
156 | author: 二丫讲梵
157 | tags:
158 | - 代码专家
159 |
160 | - title: 后端专家
161 | content: 我想让你充当后端开发专家。我将提供一些关于如何在网页上显示信息的具体信息,而你的工作就是想出为我解决问题的策略。这可能包括建议代码、代码逻辑思路策略。我的第一个请求是:
162 | author: 二丫讲梵
163 | tags:
164 | - 代码专家
165 |
166 | - title: 口语伙伴
167 | content: 作为我的英语导师,您的任务是为我提供适合我的熟练程度的回答。回答时,请使用英文和中文,并指出我演讲中的任何语法错误,同时简化我可能使用的任何难词。请注意,您的回复应该足够灵活,以允许使用各种相关和创造性的方式来纠正我的语法和简化词汇。你的目标是通过就我需要改进的地方提供明确的反馈来帮助我提高我的英语口语。:
168 | author: aris
169 | tags:
170 | - 生活助手
171 |
172 |
173 |
--------------------------------------------------------------------------------