diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..4d20d93
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,54 @@
+# https://releases.llvm.org/6.0.0/tools/clang/docs/ClangFormatStyleOptions.html
+
+# LLVM 스타일을 기반으로 한 C++ 코딩 스타일.
+BasedOnStyle: LLVM
+Language: Cpp
+
+# 공백 설정
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+
+# 들여쓰기 설정
+IndentCaseLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+TabWidth: 4
+UseTab: false
+
+# 코드 정렬 설정
+AlignAfterOpenBracket: Align
+# AlignConsecutiveAssignments: false
+# AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignTrailingComments: true
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+# BinPackArguments: false
+# BinPackParameters: false
+# BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+SortIncludes: true
+SortUsingDeclarations: true
+
+# 포인터 변수 스타일 설정
+DerivePointerAlignment: false
+PointerAlignment: Right
+
+# 나머지 스타일 설정
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+ColumnLimit: 0
+MaxEmptyLinesToKeep: 1
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6379ee5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+# 로그 파일
+*.log
+
+# 백업 파일과 설정 파일
+*.bak
+*.manifest
+
+# 오브젝트 파일
+*.o
+
+# 정적 라이브러리 파일
+*.a
+*.lib
+
+# 빌드 결과물이 저장된 폴더
+**/.vs/
+**/bin/
+**/build/
+**/**/Debug/
+**/**/Release/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3877ae0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..aba9bae
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,80 @@
+#
+# Copyright (c) 2022 jdeokkim
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+.PHONY: all clean
+
+_COLOR_BEGIN := $(shell tput setaf 13)
+_COLOR_END := $(shell tput sgr0)
+
+PROJECT_NAME := saerom
+PROJECT_FULL_NAME := jdeokkim/saerom
+
+PROJECT_PREFIX := $(_COLOR_BEGIN)$(PROJECT_FULL_NAME):$(_COLOR_END)
+
+BINARY_PATH := bin
+INCLUDE_PATH := include
+RESOURCE_PATH := res
+SOURCE_PATH := src
+
+INCLUDE_PATH += $(SOURCE_PATH)/external
+
+SOURCES := \
+ $(SOURCE_PATH)/bot.c \
+ $(SOURCE_PATH)/info.c \
+ $(SOURCE_PATH)/json.c \
+ $(SOURCE_PATH)/krdict.c \
+ $(SOURCE_PATH)/papago.c \
+ $(SOURCE_PATH)/utils.c \
+ $(SOURCE_PATH)/yxml.c \
+ $(SOURCE_PATH)/main.c
+
+OBJECTS := $(SOURCES:.c=.o)
+
+TARGETS := $(BINARY_PATH)/$(PROJECT_NAME)
+
+CC := gcc
+CFLAGS := -D_DEFAULT_SOURCE -g $(INCLUDE_PATH:%=-I%) -O2 -std=gnu99
+LDLIBS := -ldl -lcurl -ldiscord -lpthread -lsigar
+
+all: pre-build build post-build
+
+pre-build:
+ @echo "$(PROJECT_PREFIX) Using: '$(CC)' to build this project."
+
+build: $(TARGETS)
+
+$(SOURCE_PATH)/%.o: $(SOURCE_PATH)/%.c
+ @echo "$(PROJECT_PREFIX) Compiling: $@ (from $<)"
+ @$(CC) -c $< -o $@ $(CFLAGS)
+
+$(TARGETS): $(OBJECTS)
+ @mkdir -p $(BINARY_PATH)
+ @echo "$(PROJECT_PREFIX) Linking: $(TARGETS)"
+ @$(CC) $(OBJECTS) -o $(TARGETS) $(CFLAGS) $(LDFLAGS) $(LDLIBS) $(WEBFLAGS)
+
+post-build:
+ @echo "$(PROJECT_PREFIX) Build complete."
+
+clean:
+ @echo "$(PROJECT_PREFIX) Cleaning up."
+ @rm -rf $(BINARY_PATH)/*
+ @rm -rf $(SOURCE_PATH)/*.o
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c559a69
--- /dev/null
+++ b/README.md
@@ -0,0 +1,94 @@
+# saerom
+
+
+[![version badge](https://img.shields.io/github/v/release/jdeokkim/saerom?color=orange&include_prereleases)](https://github.com/jdeokkim/saerom/releases)
+[![code-size badge](https://img.shields.io/github/languages/code-size/jdeokkim/saerom?color=green)](https://github.com/jdeokkim/saerom)
+[![license badge](https://img.shields.io/github/license/jdeokkim/saerom?color=brightgreen)](https://github.com/jdeokkim/saerom/blob/main/LICENSE)
+
+A C99 Discord bot for Korean learning servers.
+
+## Commands
+
+| Name | Description |
+| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------|
+| `/info` | Show information about this bot |
+| `/krd` | Search the given text in the [Basic Korean Dictionary](https://krdict.korean.go.kr/), published by the National Institute of Korean Language |
+| `/ppg` | Translate the given text between two languages using [NAVER™ Papago NMT API](https://developers.naver.com/docs/papago/README.md) |
+
+## Screenshots
+
+### `/info`
+
+
+ Screenshot
+
+
+
+
+### `/krd`
+
+
+ Screenshot
+
+
+
+
+### `/ppg`
+
+
+ Screenshot
+
+
+
+
+## Prerequisites
+
+- GCC version 9.4.0+
+- GNU Make version 4.1+
+- CMake version 3.22.0+
+- Git version 2.17.1+
+- libcurl4 version 7.58.0+ (with OpenSSL flavor)
+
+```console
+$ sudo apt install build-essential cmake git libcurl4-openssl-dev
+```
+
+## Building
+
+This project uses [GNU Make](https://www.gnu.org/software/make) as the build system.
+
+1. Install [concord v2.0.1+](https://github.com/Cogmasters/concord).
+
+```console
+$ git clone https://github.com/Cogmasters/concord && cd concord
+$ make -j`nproc`
+$ sudo make install
+```
+
+2. Then, install the latest version of [boundary/sigar](https://github.com/boundary/sigar).
+
+```console
+$ git clone https://github.com/boundary/sigar && cd sigar
+$ mkdir build && cd build
+$ cmake .. && make -j`nproc`
+$ sudo make install
+$ ldconfig
+```
+
+3. Build this project with the following commands.
+
+```console
+$ git clone https://github.com/jdeokkim/saerom && cd saerom
+$ make
+```
+
+4. Configure and run the bot.
+
+```console
+$ vim res/config.json
+$ ./bin/saerom
+```
+
+## License
+
+GNU General Public License, version 3
\ No newline at end of file
diff --git a/include/saerom.h b/include/saerom.h
new file mode 100644
index 0000000..60008c2
--- /dev/null
+++ b/include/saerom.h
@@ -0,0 +1,139 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef SAEROM_H
+#define SAEROM_H
+
+#include
+
+#include
+#include
+
+/* | 매크로 정의... | */
+
+#define APPLICATION_NAME "jdeokkim/saerom"
+#define APPLICATION_VERSION "v0.1.0"
+#define APPLICATION_DESCRIPTION "A C99 Discord bot for Korean learning servers."
+#define APPLICATION_PROJECT_URL "https://github.com/jdeokkim/saerom"
+#define APPLICATION_ID 986954825176596498
+
+#define DEVELOPER_NAME "Jaedeok Kim"
+#define DEVELOPER_ICON_URL "https://avatars.githubusercontent.com/u/28700668"
+
+#define MAX_FILE_SIZE 1024
+#define MAX_STRING_SIZE 256
+
+/* | 자료형 정의... | */
+
+/* Discord 봇의 환경 설정을 나타내는 구조체. */
+struct sr_config {
+ unsigned char flags;
+ struct {
+ char api_key[MAX_STRING_SIZE];
+ } krdict;
+ struct {
+ char client_id[MAX_STRING_SIZE];
+ char client_secret[MAX_STRING_SIZE];
+ } papago;
+};
+
+/* Discord 봇의 환경 설정 플래그를 나타내는 열거형. */
+enum sr_flag {
+ SR_FLAG_KRDICT = (1 << 0),
+ SR_FLAG_PAPAGO = (1 << 1)
+};
+
+/* | `bot` 모듈 함수... | */
+
+/* Discord 봇을 초기화한다. */
+void init_bot(int argc, char *argv[]);
+
+/* Discord 봇을 실행한다. */
+void run_bot(void);
+
+/* Discord 봇에 할당된 메모리를 해제한다. */
+void deinit_bot(void);
+
+/* `CURLV` 인터페이스를 반환한다. */
+void *get_curlv(void);
+
+/* Discord 봇의 환경 설정 정보를 반환한다. */
+struct sr_config *get_sr_config(void);
+
+/* Discord 봇의 CPU 사용량 (단위: 퍼센트)을 반환한다. */
+double get_cpu_usage(void);
+
+/* Discord 봇의 메모리 사용량 (단위: 메가바이트)을 반환한다. */
+double get_ram_usage(void);
+
+/* Discord 봇의 작동 시간 (단위: 밀리초)을 반환한다. */
+uint64_t get_uptime(void);
+
+/* | `info` 모듈 함수... | */
+
+/* `info` 모듈 명령어를 생성한다. */
+void create_info_command(struct discord *client);
+
+/* `info` 모듈 명령어에 할당된 메모리를 해제한다. */
+void release_info_command(struct discord *client);
+
+/* `info` 모듈 명령어를 실행한다. */
+void run_info_command(
+ struct discord *client,
+ const struct discord_interaction *event
+);
+
+/* | `krdict` 모듈 함수... | */
+
+/* `krdict` 모듈 명령어를 생성한다. */
+void create_krdict_command(struct discord *client);
+
+/* `krdict` 모듈 명령어에 할당된 메모리를 해제한다. */
+void release_krdict_command(struct discord *client);
+
+/* `krdict` 모듈 명령어를 실행한다. */
+void run_krdict_command(
+ struct discord *client,
+ const struct discord_interaction *event
+);
+
+/* | `papago` 모듈 함수... | */
+
+/* `papago` 모듈 명령어를 생성한다. */
+void create_papago_command(struct discord *client);
+
+/* `papago` 모듈 명령어에 할당된 메모리를 해제한다. */
+void release_papago_command(struct discord *client);
+
+/* `papago` 모듈 명령어를 실행한다. */
+void run_papago_command(
+ struct discord *client,
+ const struct discord_interaction *event
+);
+
+/* | `utils` 모듈 함수... | */
+
+/* 주어진 사용자의 프로필 사진 URL을 반환한다. */
+const char *get_avatar_url(const struct discord_user *user);
+
+/* 주어진 경로에 위치한 파일의 내용을 반환한다. */
+char *get_file_contents(const char *path);
+
+/* 두 문자열의 내용이 서로 같은지 확인한다. */
+bool string_equals(const char *s1, const char *s2);
+
+#endif
\ No newline at end of file
diff --git a/res/config.json b/res/config.json
new file mode 100644
index 0000000..76b14ec
--- /dev/null
+++ b/res/config.json
@@ -0,0 +1,32 @@
+{
+ "logging": {
+ "level": "info",
+ "filename": "bot.log",
+ "quiet": false,
+ "overwrite": true,
+ "use_color": true,
+ "http": {
+ "enable": true,
+ "filename": "http.log"
+ },
+ "disable_modules": ["WEBSOCKETS", "USER_AGENT"]
+ },
+ "discord": {
+ "token": "YOUR-BOT-TOKEN",
+ "default_prefix": {
+ "enable": false,
+ "prefix": "^"
+ }
+ },
+ "saerom": {
+ "krdict": {
+ "enable": true,
+ "api_key": "YOUR-API-KEY"
+ },
+ "papago": {
+ "enable": true,
+ "client_id": "YOUR-CLIENT-ID",
+ "client_secret": "YOUR-CLIENT-SECRET"
+ }
+ }
+}
\ No newline at end of file
diff --git a/res/images/.gitkeep b/res/images/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/res/images/avatar.png b/res/images/avatar.png
new file mode 100644
index 0000000..2b7d516
Binary files /dev/null and b/res/images/avatar.png differ
diff --git a/res/images/avatar.xcf b/res/images/avatar.xcf
new file mode 100644
index 0000000..3410cc0
Binary files /dev/null and b/res/images/avatar.xcf differ
diff --git a/res/images/screenshot-info.png b/res/images/screenshot-info.png
new file mode 100644
index 0000000..0cd9256
Binary files /dev/null and b/res/images/screenshot-info.png differ
diff --git a/res/images/screenshot-krd.png b/res/images/screenshot-krd.png
new file mode 100644
index 0000000..ac608c1
Binary files /dev/null and b/res/images/screenshot-krd.png differ
diff --git a/res/images/screenshot-ppg.png b/res/images/screenshot-ppg.png
new file mode 100644
index 0000000..c0c2c64
Binary files /dev/null and b/res/images/screenshot-ppg.png differ
diff --git a/src/bot.c b/src/bot.c
new file mode 100644
index 0000000..01dc0ea
--- /dev/null
+++ b/src/bot.c
@@ -0,0 +1,370 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+
+#define CURLV_IMPLEMENTATION
+#include
+
+#include "saerom.h"
+
+/* | `bot` 모듈 매크로 정의... | */
+
+#define CONFIG_PATH "res/config.json"
+
+/* | `bot` 모듈 자료형 정의... | */
+
+/* Discord 봇의 명령어를 나타내는 구조체. */
+struct sr_command {
+ char *name;
+ void (*on_create)(struct discord *client);
+ void (*on_release)(struct discord *client);
+ void (*on_run)(
+ struct discord *client,
+ const struct discord_interaction *event
+ );
+};
+
+/* Discord 봇의 명령어 목록. */
+static const struct sr_command slash_commands[] = {
+ {
+ .name = "info",
+ .on_create = create_info_command,
+ .on_release = release_info_command,
+ .on_run = run_info_command
+ },
+ {
+ .name = "krd",
+ .on_create = create_krdict_command,
+ .on_release = release_krdict_command,
+ .on_run = run_krdict_command
+ },
+ {
+ .name = "ppg",
+ .on_create = create_papago_command,
+ .on_release = release_papago_command,
+ .on_run = run_papago_command
+ }
+};
+
+/* | `bot` 모듈 상수 및 변수... | */
+
+/* `CURLV` 인터페이스. */
+static CURLV *curlv;
+
+/* `sigar` 인터페이스. */
+static sigar_t *sigar;
+
+/* Discord 봇의 클라이언트 객체. */
+static struct discord *client;
+
+/* Discord 봇의 환경 설정. */
+static struct sr_config config;
+
+/* Discord 봇의 시작 시간. */
+static uint64_t old_timestamp;
+
+/* | `bot` 모듈 함수... | */
+
+/* Discord 봇이 실행 중일 때 주기적으로 호출된다. */
+static void on_idle(struct discord *client);
+
+/* Discord 봇의 클라이언트가 준비되었을 때 호출된다. */
+static void on_ready(
+ struct discord *client,
+ const struct discord_ready *event
+);
+
+/* Discord 봇이 명령어 처리를 요청받았을 때 호출된다. */
+static void on_interaction_create(
+ struct discord *client,
+ const struct discord_interaction *event
+);
+
+/* Discord 봇의 추가 정보를 초기화한다. */
+static void sr_config_init(void);
+
+/* Discord 봇의 추가 정보에 할당된 메모리를 해제한다. */
+static void sr_config_cleanup(void);
+
+/* Discord 봇의 명령어들을 생성한다. */
+static void create_slash_commands(struct discord *client);
+
+/* Discord 봇의 명령어들에 할당된 메모리를 해제한다. */
+static void release_slash_commands(struct discord *client);
+
+/* Discord 봇을 초기화한다. */
+void init_bot(int argc, char *argv[]) {
+ ccord_global_init();
+
+ client = discord_config_init((argc > 1) ? argv[1] : CONFIG_PATH);
+
+ sr_config_init();
+
+ discord_set_on_idle(client, on_idle);
+ discord_set_on_ready(client, on_ready);
+ discord_set_on_interaction_create(client, on_interaction_create);
+
+ create_slash_commands(client);
+}
+
+/* Discord 봇을 실행한다. */
+void run_bot(void) {
+ discord_run(client);
+}
+
+/* Discord 봇에 할당된 메모리를 해제한다. */
+void deinit_bot(void) {
+ release_slash_commands(client);
+
+ sr_config_cleanup();
+
+ discord_cleanup(client);
+
+ ccord_global_cleanup();
+}
+
+/* `CURLV` 인터페이스를 반환한다. */
+void *get_curlv(void) {
+ return (void *) curlv;
+}
+
+/* Discord 봇의 환경 설정 정보를 반환한다. */
+struct sr_config *get_sr_config(void) {
+ return &config;
+}
+
+/* Discord 봇의 CPU 사용량 (단위: 퍼센트)을 반환한다. */
+double get_cpu_usage(void) {
+ sigar_proc_cpu_t cpu;
+
+ sigar_proc_cpu_get(
+ sigar,
+ sigar_pid_get(sigar),
+ &cpu
+ );
+
+ return cpu.percent;
+}
+
+/* Discord 봇의 메모리 사용량 (단위: 메가바이트)을 반환한다. */
+double get_ram_usage(void) {
+ static const double DENOMINATOR = 1.0 / (1024.0 * 1024.0);
+
+ sigar_proc_mem_t mem;
+
+ sigar_proc_mem_get(
+ sigar,
+ sigar_pid_get(sigar),
+ &mem
+ );
+
+ return mem.resident * DENOMINATOR;
+}
+
+/* Discord 봇의 작동 시간 (단위: 밀리초)을 반환한다. */
+uint64_t get_uptime(void) {
+ return discord_timestamp(client) - old_timestamp;
+}
+
+/* Discord 봇이 실행 중일 때 주기적으로 호출된다. */
+static void on_idle(struct discord *client) {
+ curlv_read_requests(curlv);
+
+ // CPU 사용량 최적화
+ cog_sleep_us(1);
+}
+
+/* Discord 봇의 클라이언트가 준비되었을 때 호출된다. */
+static void on_ready(
+ struct discord *client,
+ const struct discord_ready *event
+) {
+ log_info(
+ "[SAEROM] Connected to Discord as %s#%s (in %d server(s))",
+ event->user->username,
+ event->user->discriminator,
+ event->guilds->size
+ );
+
+ /*
+ "Bots are only able to send `name`, `type`, and optionally `url`."
+
+ - https://discord.com/developers/docs/topics/gateway#activity-object-activity-structure
+ */
+
+ struct discord_activity activities[] = {
+ {
+ .name = "/info (" APPLICATION_VERSION ")",
+ .type = DISCORD_ACTIVITY_GAME,
+ },
+ };
+
+ old_timestamp = discord_timestamp(client);
+
+ struct discord_presence_update status = {
+ .activities = &(struct discord_activities) {
+ .size = sizeof(activities) / sizeof(*activities),
+ .array = activities,
+ },
+ .status = "online",
+ .afk = false,
+ .since = old_timestamp
+ };
+
+ discord_update_presence(client, &status);
+}
+
+/* Discord 봇이 명령어 처리를 요청받았을 때 호출된다. */
+static void on_interaction_create(
+ struct discord *client,
+ const struct discord_interaction *event
+) {
+ /*
+ "An Interaction is the message that your application receives when a user
+ uses an application command or a message component. For Slash Commands,
+ it includes the values that the user submitted."
+
+ - https://discord.com/developers/docs/interactions/receiving-and-responding
+ */
+
+ if (event->data == NULL) return;
+
+ const struct discord_user *user = (event->member != NULL)
+ ? event->member->user
+ : event->user;
+
+ const char *context = NULL;
+
+ switch (event->type) {
+ case DISCORD_INTERACTION_APPLICATION_COMMAND:
+ context = event->data->name;
+
+ log_info(
+ "[SAEROM] Handling command `/%s` executed by %s#%s",
+ context,
+ user->username,
+ user->discriminator
+ );
+
+ for (int i = 0; i < sizeof(slash_commands) / sizeof(*slash_commands); i++)
+ if (string_equals(context, slash_commands[i].name))
+ slash_commands[i].on_run(client, event);
+
+ break;
+
+ case DISCORD_INTERACTION_MESSAGE_COMPONENT:
+ context = event->data->custom_id;
+
+ log_info(
+ "[SAEROM] Handling component interaction `#%s` invoked by %s#%s",
+ context,
+ user->username,
+ user->discriminator
+ );
+
+ for (int i = 0; i < sizeof(slash_commands) / sizeof(*slash_commands); i++) {
+ const char *name = slash_commands[i].name;
+
+ if (strncmp(context, name, strlen(name)) == 0)
+ slash_commands[i].on_run(client, event);
+ }
+
+ break;
+ }
+}
+
+/* Discord 봇의 추가 정보를 초기화한다. */
+static void sr_config_init(void) {
+ if (client == NULL) return;
+
+ curlv = curlv_init();
+
+ sigar_open(&sigar);
+
+ struct ccord_szbuf_readonly field = discord_config_get_field(
+ client, (char *[3]) { "saerom", "krdict", "enable" }, 3
+ );
+
+ if (strncmp("true", field.start, field.size) == 0) {
+ config.flags |= SR_FLAG_KRDICT;
+
+ field = discord_config_get_field(
+ client, (char *[3]) { "saerom", "krdict", "api_key" }, 3
+ );
+
+ strncpy(config.krdict.api_key, field.start, field.size);
+ }
+
+ field = discord_config_get_field(
+ client, (char *[3]) { "saerom", "papago", "enable" }, 3
+ );
+
+ if (strncmp("true", field.start, field.size) == 0) {
+ config.flags |= SR_FLAG_PAPAGO;
+
+ field = discord_config_get_field(
+ client, (char *[3]) { "saerom", "papago", "client_id" }, 3
+ );
+
+ strncpy(config.papago.client_id, field.start, field.size);
+
+ field = discord_config_get_field(
+ client, (char *[3]) { "saerom", "papago", "client_secret" }, 3
+ );
+
+ strncpy(config.papago.client_secret, field.start, field.size);
+ }
+}
+
+/* Discord 봇의 추가 정보에 할당된 메모리를 해제한다. */
+static void sr_config_cleanup(void) {
+ curlv_cleanup(curlv);
+
+ sigar_close(sigar);
+}
+
+/* Discord 봇의 명령어들을 생성한다. */
+static void create_slash_commands(struct discord *client) {
+ for (int i = 0; i < sizeof(slash_commands) / sizeof(*slash_commands); i++) {
+ if (string_equals(slash_commands[i].name, "krd")
+ && !(config.flags & SR_FLAG_KRDICT)) continue;
+
+ if (string_equals(slash_commands[i].name, "ppg")
+ && !(config.flags & SR_FLAG_PAPAGO)) continue;
+
+ log_info("[SAEROM] Creating global application command `/%s`", slash_commands[i].name);
+
+ slash_commands[i].on_create(client);
+ }
+}
+
+/* Discord 봇의 명령어들에 할당된 메모리를 해제한다. */
+static void release_slash_commands(struct discord *client) {
+ for (int i = 0; i < sizeof(slash_commands) / sizeof(*slash_commands); i++) {
+ if (string_equals(slash_commands[i].name, "krd")
+ && !(config.flags & SR_FLAG_KRDICT)) continue;
+
+ if (string_equals(slash_commands[i].name, "ppg")
+ && !(config.flags & SR_FLAG_PAPAGO)) continue;
+
+ log_info("[SAEROM] Releasing global application command `/%s`", slash_commands[i].name);
+
+ slash_commands[i].on_release(client);
+ }
+}
\ No newline at end of file
diff --git a/src/external/curlv.h b/src/external/curlv.h
new file mode 100644
index 0000000..b7cc537
--- /dev/null
+++ b/src/external/curlv.h
@@ -0,0 +1,314 @@
+/*
+ Copyright (c) 2013, Ben Noordhuis
+ Copyright (c) 2022, Jaedeok Kim
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#ifndef CURLV_H
+#define CURLV_H
+
+#include
+
+#include
+
+/* | 자료형 정의... | */
+
+/* 문자열을 나타내는 구조체. */
+typedef struct CURLV_STR {
+ char *str; // 문자열의 메모리 주소.
+ size_t len; // 문자열의 길이.
+} CURLV_STR;
+
+/* 사용자 요청의 처리가 끝났을 때 호출될 함수.*/
+typedef void (*curlv_read_callback)(CURLV_STR res, void *user_data);
+
+/* 사용자의 요청을 나타내는 구조체. */
+typedef struct CURLV_REQ {
+ CURL *easy; // 요청에 사용할 핸들.
+ struct curl_slist *header; // HTTP 요청 헤더.
+ curlv_read_callback callback; // 응답을 받았을 때 호출될 함수.
+ void *user_data; // 사용자 정의 데이터.
+} CURLV_REQ;
+
+/* `CURLV` 인터페이스를 나타내는 구조체. */
+typedef struct CURLV CURLV;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* | 라이브러리 함수... | */
+
+/* `CURLV` 인터페이스를 초기화한다. */
+CURLV *curlv_init(void);
+
+/* `CURLV` 인터페이스에 할당된 메모리를 해제한다. */
+void curlv_cleanup(CURLV *cv);
+
+/* `CURLV` 인터페이스에 새로운 요청을 추가한다. */
+void curlv_create_request(CURLV *cv, const CURLV_REQ *req);
+
+/* `CURLV` 인터페이스에서 가장 먼저 추가된 요청을 제거한다. */
+void curlv_remove_request(CURLV *cv);
+
+/* `CURLV` 인터페이스에 추가된 모든 요청들을 처리한다. */
+void curlv_read_requests(CURLV *cv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // `CURLV_H`
+
+#ifdef CURLV_IMPLEMENTATION
+
+#include
+#include
+
+#include
+
+/* | 매크로 정의... | */
+
+typedef void *QUEUE[2];
+
+#define QUEUE(type) QUEUE
+
+#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0]))
+#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1]))
+#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q)))
+#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q)))
+
+#define QUEUE_DATA(ptr, type, field) \
+ ((type *) ((char *) (ptr) - offsetof(type, field)))
+
+#define QUEUE_FOREACH(q, h) \
+ for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))
+
+#define QUEUE_EMPTY(q) \
+ ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))
+
+#define QUEUE_HEAD(q) \
+ (QUEUE_NEXT(q))
+
+#define QUEUE_INIT(q) \
+ do { \
+ QUEUE_NEXT(q) = (q); \
+ QUEUE_PREV(q) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_TAIL(h, q) \
+ do { \
+ QUEUE_NEXT(q) = (h); \
+ QUEUE_PREV(q) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(q) = (q); \
+ QUEUE_PREV(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_REMOVE(q) \
+ do { \
+ QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \
+ QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \
+ } \
+ while (0)
+
+/* | 자료형 정의... | */
+
+/* 요청 큐의 요소를 나타내는 구조체. */
+typedef struct CURLV_QE {
+ CURLV_REQ request;
+ CURLV_STR response;
+ QUEUE(_) entry;
+} CURLV_QE;
+
+/* `CURLV` 인터페이스를 나타내는 구조체. */
+struct CURLV {
+ CURLM *multi;
+ QUEUE(CURLV_QE) requests;
+ pthread_mutex_t lock;
+};
+
+/* | 라이브러리 함수... | */
+
+/* `CURLV` 인터페이스의 응답 처리 시에 호출되는 함수. */
+static size_t curlv_write_callback(char *ptr, size_t size, size_t num, void *write_data);
+
+/* 요청 큐의 요소를 초기화한다. */
+static CURLV_QE *curlv_qe_init(const CURLV_REQ *req);
+
+/* 요청 큐의 요소에 할당된 메모리를 해제한다. */
+static void curlv_qe_cleanup(CURLV_QE *qe);
+
+/* `CURLV` 인터페이스를 초기화한다. */
+CURLV *curlv_init(void) {
+ CURLV *result = calloc(1, sizeof(CURLV));
+
+ QUEUE_INIT(&result->requests);
+
+ result->multi = curl_multi_init();
+
+ pthread_mutex_init(&result->lock, NULL);
+
+ return result;
+}
+
+/* `CURLV` 인터페이스에 할당된 메모리를 해제한다. */
+void curlv_cleanup(CURLV *cv) {
+ if (cv != NULL) {
+ while (!QUEUE_EMPTY(&cv->requests))
+ curlv_remove_request(cv);
+
+ curl_multi_cleanup(cv->multi);
+
+ pthread_mutex_destroy(&cv->lock);
+ }
+
+ free(cv);
+}
+
+/* `CURLV` 인터페이스에 새로운 요청을 추가한다. */
+void curlv_create_request(CURLV *cv, const CURLV_REQ *req) {
+ if (cv == NULL || req == NULL) return;
+
+ CURLV_QE *qe = curlv_qe_init(req);
+
+ curl_easy_setopt(req->easy, CURLOPT_WRITEFUNCTION, curlv_write_callback);
+ curl_easy_setopt(req->easy, CURLOPT_WRITEDATA, (void *) &qe->response);
+ curl_easy_setopt(req->easy, CURLOPT_HTTPHEADER, req->header);
+ curl_easy_setopt(req->easy, CURLOPT_PRIVATE, (void *) qe);
+
+ pthread_mutex_lock(&cv->lock);
+
+ curl_multi_add_handle(cv->multi, req->easy);
+
+ QUEUE_INSERT_TAIL(&cv->requests, &qe->entry);
+
+ pthread_mutex_unlock(&cv->lock);
+}
+
+/* `CURLV` 인터페이스에서 가장 먼저 추가된 요청을 제거한다. */
+void curlv_remove_request(CURLV *cv) {
+ if (cv == NULL) return;
+
+ pthread_mutex_lock(&cv->lock);
+
+ QUEUE(CURLV_QE) *head = QUEUE_HEAD(&cv->requests);
+
+ QUEUE_REMOVE(head);
+
+ CURLV_QE *qe = QUEUE_DATA(head, CURLV_QE, entry);
+
+ curl_multi_remove_handle(cv->multi, qe->request.easy);
+
+ curlv_qe_cleanup(qe);
+
+ pthread_mutex_unlock(&cv->lock);
+}
+
+/* `CURLV` 인터페이스에 추가된 모든 요청들을 처리한다. */
+void curlv_read_requests(CURLV *cv) {
+ if (cv == NULL) return;
+
+ /* https://curl.se/libcurl/c/threadsafe.html */
+
+ pthread_mutex_lock(&cv->lock);
+
+ int still_running;
+
+ do {
+ CURLMcode result = curl_multi_perform(cv->multi, &still_running);
+
+ if (result != CURLM_OK) break;
+ } while (still_running);
+
+ struct CURLMsg *msg;
+
+ for (;;) {
+ int msgs_in_queue;
+
+ msg = curl_multi_info_read(cv->multi, &msgs_in_queue);
+
+ if (msg == NULL) break;
+
+ if (msg->msg == CURLMSG_DONE) {
+ CURLV_QE *qe;
+
+ CURLcode result = curl_easy_getinfo(
+ msg->easy_handle,
+ CURLINFO_PRIVATE,
+ &qe
+ );
+
+ if (qe->request.callback != NULL)
+ qe->request.callback(qe->response, qe->request.user_data);
+
+ QUEUE_REMOVE(&qe->entry);
+
+ curl_multi_remove_handle(cv->multi, qe->request.easy);
+ curlv_qe_cleanup(qe);
+ }
+ }
+
+ pthread_mutex_unlock(&cv->lock);
+}
+
+/* `CURLV` 인터페이스의 응답 처리 시에 호출되는 함수. */
+static size_t curlv_write_callback(char *ptr, size_t size, size_t num, void *write_data) {
+ size_t new_size = size * num;
+
+ CURLV_STR *response = (CURLV_STR *) write_data;
+
+ char *new_ptr = realloc(response->str, response->len + new_size + 1);
+
+ if (new_ptr == NULL) return 0;
+
+ response->str = new_ptr;
+
+ memcpy(&(response->str[response->len]), ptr, new_size);
+
+ response->len += new_size;
+ response->str[response->len] = 0;
+
+ return new_size;
+}
+
+/* 요청 큐의 요소를 초기화한다. */
+static CURLV_QE *curlv_qe_init(const CURLV_REQ *req) {
+ CURLV_QE *result = calloc(1, sizeof(CURLV_QE));
+
+ result->request = (*req);
+
+ return result;
+}
+
+/* 요청 큐의 요소에 할당된 메모리를 해제한다. */
+static void curlv_qe_cleanup(CURLV_QE *qe) {
+ if (qe != NULL) {
+ curl_easy_cleanup(qe->request.easy);
+ curl_slist_free_all(qe->request.header);
+
+ free(qe->response.str);
+ }
+
+ free(qe);
+}
+
+#endif // `CURLV_IMPLEMENTATION`
\ No newline at end of file
diff --git a/src/external/json.h b/src/external/json.h
new file mode 100644
index 0000000..dccb58a
--- /dev/null
+++ b/src/external/json.h
@@ -0,0 +1,117 @@
+/*
+ Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
+ All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#ifndef CCAN_JSON_H
+#define CCAN_JSON_H
+
+#include
+#include
+
+typedef enum {
+ JSON_NULL,
+ JSON_BOOL,
+ JSON_STRING,
+ JSON_NUMBER,
+ JSON_ARRAY,
+ JSON_OBJECT,
+} JsonTag;
+
+typedef struct JsonNode JsonNode;
+
+struct JsonNode
+{
+ /* only if parent is an object or array (NULL otherwise) */
+ JsonNode *parent;
+ JsonNode *prev, *next;
+
+ /* only if parent is an object (NULL otherwise) */
+ char *key; /* Must be valid UTF-8. */
+
+ JsonTag tag;
+ union {
+ /* JSON_BOOL */
+ bool bool_;
+
+ /* JSON_STRING */
+ char *string_; /* Must be valid UTF-8. */
+
+ /* JSON_NUMBER */
+ double number_;
+
+ /* JSON_ARRAY */
+ /* JSON_OBJECT */
+ struct {
+ JsonNode *head, *tail;
+ } children;
+ };
+};
+
+/*** Encoding, decoding, and validation ***/
+
+JsonNode *json_decode (const char *json);
+char *json_encode (const JsonNode *node);
+char *json_encode_string (const char *str);
+char *json_stringify (const JsonNode *node, const char *space);
+void json_delete (JsonNode *node);
+
+bool json_validate (const char *json);
+
+/*** Lookup and traversal ***/
+
+JsonNode *json_find_element (JsonNode *array, int index);
+JsonNode *json_find_member (JsonNode *object, const char *name);
+
+JsonNode *json_first_child (const JsonNode *node);
+
+#define json_foreach(i, object_or_array) \
+ for ((i) = json_first_child(object_or_array); \
+ (i) != NULL; \
+ (i) = (i)->next)
+
+/*** Construction and manipulation ***/
+
+JsonNode *json_mknull(void);
+JsonNode *json_mkbool(bool b);
+JsonNode *json_mkstring(const char *s);
+JsonNode *json_mknumber(double n);
+JsonNode *json_mkarray(void);
+JsonNode *json_mkobject(void);
+
+void json_append_element(JsonNode *array, JsonNode *element);
+void json_prepend_element(JsonNode *array, JsonNode *element);
+void json_append_member(JsonNode *object, const char *key, JsonNode *value);
+void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
+
+void json_remove_from_parent(JsonNode *node);
+
+/*** Debugging ***/
+
+/*
+ * Look for structure and encoding problems in a JsonNode or its descendents.
+ *
+ * If a problem is detected, return false, writing a description of the problem
+ * to errmsg (unless errmsg is NULL).
+ */
+bool json_check(const JsonNode *node, char errmsg[256]);
+
+#endif
diff --git a/src/external/yxml.h b/src/external/yxml.h
new file mode 100644
index 0000000..a0cc5f9
--- /dev/null
+++ b/src/external/yxml.h
@@ -0,0 +1,162 @@
+/* Copyright (c) 2013-2014 Yoran Heling
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef YXML_H
+#define YXML_H
+
+#include
+#include
+
+#if defined(_MSC_VER) && !defined(__cplusplus) && !defined(inline)
+#define inline __inline
+#endif
+
+/* Full API documentation for this library can be found in the "yxml.md" file
+ * in the yxml git repository, or online at http://dev.yorhel.nl/yxml/man */
+
+typedef enum {
+ YXML_EEOF = -5, /* Unexpected EOF */
+ YXML_EREF = -4, /* Invalid character or entity reference (&whatever;) */
+ YXML_ECLOSE = -3, /* Close tag does not match open tag ( .. ) */
+ YXML_ESTACK = -2, /* Stack overflow (too deeply nested tags or too long element/attribute name) */
+ YXML_ESYN = -1, /* Syntax error (unexpected byte) */
+ YXML_OK = 0, /* Character consumed, no new token present */
+ YXML_ELEMSTART = 1, /* Start of an element: '' or '' */
+ YXML_ATTRSTART = 4, /* Attribute: 'Name=..' */
+ YXML_ATTRVAL = 5, /* Attribute value */
+ YXML_ATTREND = 6, /* End of attribute '.."' */
+ YXML_PISTART = 7, /* Start of a processing instruction */
+ YXML_PICONTENT = 8, /* Content of a PI */
+ YXML_PIEND = 9 /* End of a processing instruction */
+} yxml_ret_t;
+
+/* When, exactly, are tokens returned?
+ *
+ * ' ELEMSTART
+ * '/' ELEMSTART, '>' ELEMEND
+ * ' ' ELEMSTART
+ * '>'
+ * '/', '>' ELEMEND
+ * Attr
+ * '=' ATTRSTART
+ * "X ATTRVAL
+ * 'Y' ATTRVAL
+ * 'Z' ATTRVAL
+ * '"' ATTREND
+ * '>'
+ * '/', '>' ELEMEND
+ *
+ * ' ELEMEND
+ */
+
+
+typedef struct {
+ /* PUBLIC (read-only) */
+
+ /* Name of the current element, zero-length if not in any element. Changed
+ * after YXML_ELEMSTART. The pointer will remain valid up to and including
+ * the next non-YXML_ATTR* token, the pointed-to buffer will remain valid
+ * up to and including the YXML_ELEMEND for the corresponding element. */
+ char *elem;
+
+ /* The last read character(s) of an attribute value (YXML_ATTRVAL), element
+ * data (YXML_CONTENT), or processing instruction (YXML_PICONTENT). Changed
+ * after one of the respective YXML_ values is returned, and only valid
+ * until the next yxml_parse() call. Usually, this string only consists of
+ * a single byte, but multiple bytes are returned in the following cases:
+ * - "": The two characters "?x"
+ * - "": The two characters "]x"
+ * - "": The three characters "]]x"
+ * - "N;" and "N;", where dec(n) > 127. The referenced Unicode
+ * character is then encoded in multiple UTF-8 bytes.
+ */
+ char data[8];
+
+ /* Name of the current attribute. Changed after YXML_ATTRSTART, valid up to
+ * and including the next YXML_ATTREND. */
+ char *attr;
+
+ /* Name/target of the current processing instruction, zero-length if not in
+ * a PI. Changed after YXML_PISTART, valid up to (but excluding)
+ * the next YXML_PIEND. */
+ char *pi;
+
+ /* Line number, byte offset within that line, and total bytes read. These
+ * values refer to the position _after_ the last byte given to
+ * yxml_parse(). These are useful for debugging and error reporting. */
+ uint64_t byte;
+ uint64_t total;
+ uint32_t line;
+
+
+ /* PRIVATE */
+ int state;
+ unsigned char *stack; /* Stack of element names + attribute/PI name, separated by \0. Also starts with a \0. */
+ size_t stacksize, stacklen;
+ unsigned reflen;
+ unsigned quote;
+ int nextstate; /* Used for '@' state remembering and for the "string" consuming state */
+ unsigned ignore;
+ unsigned char *string;
+} yxml_t;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void yxml_init(yxml_t *, void *, size_t);
+
+
+yxml_ret_t yxml_parse(yxml_t *, int);
+
+
+/* May be called after the last character has been given to yxml_parse().
+ * Returns YXML_OK if the XML document is valid, YXML_EEOF otherwise. Using
+ * this function isn't really necessary, but can be used to detect documents
+ * that don't end correctly. In particular, an error is returned when the XML
+ * document did not contain a (complete) root element, or when the document
+ * ended while in a comment or processing instruction. */
+yxml_ret_t yxml_eof(yxml_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+/* Returns the length of the element name (x->elem), attribute name (x->attr),
+ * or PI name (x->pi). This function should ONLY be used directly after the
+ * YXML_ELEMSTART, YXML_ATTRSTART or YXML_PISTART (respectively) tokens have
+ * been returned by yxml_parse(), calling this at any other time may not give
+ * the correct results. This function should also NOT be used on strings other
+ * than x->elem, x->attr or x->pi. */
+static inline size_t yxml_symlen(yxml_t *x, const char *s) {
+ return (x->stack + x->stacklen) - (const unsigned char*)s;
+}
+
+#endif
+
+/* vim: set noet sw=4 ts=4: */
diff --git a/src/info.c b/src/info.c
new file mode 100644
index 0000000..ac612c4
--- /dev/null
+++ b/src/info.c
@@ -0,0 +1,153 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+
+#include "saerom.h"
+
+/* | `info` 모듈 상수 및 변수... | */
+
+/* `info` 모듈 명령어의 매개 변수. */
+static struct discord_application_command_option options[] = { };
+
+/* `info` 모듈 명령어에 대한 정보. */
+static struct discord_create_global_application_command params = {
+ .name = "info",
+ .description = "Show information about this bot",
+ .default_permission = true,
+ .options = &(struct discord_application_command_options) {
+ .size = sizeof(options) / sizeof(*options),
+ .array = options
+ }
+};
+
+/* | `info` 모듈 함수... | */
+
+/* `info` 모듈 명령어를 생성한다. */
+void create_info_command(struct discord *client) {
+ discord_create_global_application_command(
+ client,
+ APPLICATION_ID,
+ ¶ms,
+ NULL
+ );
+}
+
+/* `info` 모듈 명령어에 할당된 메모리를 해제한다. */
+void release_info_command(struct discord *client) {
+ /* no-op */
+}
+
+/* `info` 모듈 명령어를 실행한다. */
+void run_info_command(
+ struct discord *client,
+ const struct discord_interaction *event
+) {
+ if (event->type == DISCORD_INTERACTION_MESSAGE_COMPONENT) return;
+
+ char cpu_usage_str[MAX_STRING_SIZE];
+ char ram_usage_str[MAX_STRING_SIZE];
+
+ char uptime_str[MAX_STRING_SIZE];
+ char ping_str[MAX_STRING_SIZE];
+ char flags_str[MAX_STRING_SIZE];
+
+ struct sr_config *config = get_sr_config();
+
+ time_t uptime_in_seconds = get_uptime() * 0.001f;
+
+ snprintf(cpu_usage_str, sizeof(cpu_usage_str), "%.1f%%", get_cpu_usage());
+ snprintf(ram_usage_str, sizeof(ram_usage_str), "%.1fMB", get_ram_usage());
+
+ strftime(uptime_str, sizeof(uptime_str), "%T", gmtime(&uptime_in_seconds));
+ snprintf(ping_str, sizeof(ping_str), "%dms", discord_get_ping(client));
+ snprintf(flags_str, sizeof(flags_str), "0x%02hhX", config->flags);
+
+ struct discord_embed_field fields[] = {
+ {
+ .name = "Version",
+ .value = APPLICATION_VERSION,
+ .Inline = true
+ },
+ {
+ .name = "Uptime",
+ .value = uptime_str,
+ .Inline = true
+ },
+ {
+ .name = "Latency",
+ .value = ping_str,
+ .Inline = true
+ },
+ {
+ .name = "CPU",
+ .value = cpu_usage_str,
+ .Inline = true
+ },
+ {
+ .name = "RAM",
+ .value = ram_usage_str,
+ .Inline = true
+ },
+ {
+ .name = "FLAGS",
+ .value = flags_str,
+ .Inline = true
+ },
+ };
+
+ struct discord_embed embeds[] = {
+ {
+ .title = APPLICATION_NAME,
+ .description = APPLICATION_DESCRIPTION,
+ .url = APPLICATION_PROJECT_URL,
+ .timestamp = discord_timestamp(client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "✨"
+ },
+ .thumbnail = &(struct discord_embed_thumbnail) {
+ .url = (char *) get_avatar_url(discord_get_self(client))
+ },
+ .author = &(struct discord_embed_author) {
+ .name = DEVELOPER_NAME,
+ .icon_url = DEVELOPER_ICON_URL
+ },
+ .fields = &(struct discord_embed_fields) {
+ .size = sizeof(fields) / sizeof(*fields),
+ .array = fields
+ }
+ }
+ };
+
+ struct discord_interaction_response params = {
+ .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
+ .data = &(struct discord_interaction_callback_data) {
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ }
+ };
+
+ discord_create_interaction_response(
+ client,
+ event->id,
+ event->token,
+ ¶ms,
+ NULL
+ );
+}
\ No newline at end of file
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..2f0452a
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,1381 @@
+/*
+ Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
+ All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#include "json.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#define out_of_memory() do { \
+ fprintf(stderr, "Out of memory.\n"); \
+ exit(EXIT_FAILURE); \
+ } while (0)
+
+/* Sadly, strdup is not portable. */
+static char *json_strdup(const char *str)
+{
+ char *ret = (char*) malloc(strlen(str) + 1);
+ if (ret == NULL)
+ out_of_memory();
+ strcpy(ret, str);
+ return ret;
+}
+
+/* String buffer */
+
+typedef struct
+{
+ char *cur;
+ char *end;
+ char *start;
+} SB;
+
+static void sb_init(SB *sb)
+{
+ sb->start = (char*) malloc(17);
+ if (sb->start == NULL)
+ out_of_memory();
+ sb->cur = sb->start;
+ sb->end = sb->start + 16;
+}
+
+/* sb and need may be evaluated multiple times. */
+#define sb_need(sb, need) do { \
+ if ((sb)->end - (sb)->cur < (need)) \
+ sb_grow(sb, need); \
+ } while (0)
+
+static void sb_grow(SB *sb, int need)
+{
+ size_t length = sb->cur - sb->start;
+ size_t alloc = sb->end - sb->start;
+
+ do {
+ alloc *= 2;
+ } while (alloc < length + need);
+
+ sb->start = (char*) realloc(sb->start, alloc + 1);
+ if (sb->start == NULL)
+ out_of_memory();
+ sb->cur = sb->start + length;
+ sb->end = sb->start + alloc;
+}
+
+static void sb_put(SB *sb, const char *bytes, int count)
+{
+ sb_need(sb, count);
+ memcpy(sb->cur, bytes, count);
+ sb->cur += count;
+}
+
+#define sb_putc(sb, c) do { \
+ if ((sb)->cur >= (sb)->end) \
+ sb_grow(sb, 1); \
+ *(sb)->cur++ = (c); \
+ } while (0)
+
+static void sb_puts(SB *sb, const char *str)
+{
+ sb_put(sb, str, strlen(str));
+}
+
+static char *sb_finish(SB *sb)
+{
+ *sb->cur = 0;
+ assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start));
+ return sb->start;
+}
+
+static void sb_free(SB *sb)
+{
+ free(sb->start);
+}
+
+/*
+ * Unicode helper functions
+ *
+ * These are taken from the ccan/charset module and customized a bit.
+ * Putting them here means the compiler can (choose to) inline them,
+ * and it keeps ccan/json from having a dependency.
+ */
+
+/*
+ * Type for Unicode codepoints.
+ * We need our own because wchar_t might be 16 bits.
+ */
+typedef uint32_t uchar_t;
+
+/*
+ * Validate a single UTF-8 character starting at @s.
+ * The string must be null-terminated.
+ *
+ * If it's valid, return its length (1 thru 4).
+ * If it's invalid or clipped, return 0.
+ *
+ * This function implements the syntax given in RFC3629, which is
+ * the same as that given in The Unicode Standard, Version 6.0.
+ *
+ * It has the following properties:
+ *
+ * * All codepoints U+0000..U+10FFFF may be encoded,
+ * except for U+D800..U+DFFF, which are reserved
+ * for UTF-16 surrogate pair encoding.
+ * * UTF-8 byte sequences longer than 4 bytes are not permitted,
+ * as they exceed the range of Unicode.
+ * * The sixty-six Unicode "non-characters" are permitted
+ * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF).
+ */
+static int utf8_validate_cz(const char *s)
+{
+ unsigned char c = *s++;
+
+ if (c <= 0x7F) { /* 00..7F */
+ return 1;
+ } else if (c <= 0xC1) { /* 80..C1 */
+ /* Disallow overlong 2-byte sequence. */
+ return 0;
+ } else if (c <= 0xDF) { /* C2..DF */
+ /* Make sure subsequent byte is in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+
+ return 2;
+ } else if (c <= 0xEF) { /* E0..EF */
+ /* Disallow overlong 3-byte sequence. */
+ if (c == 0xE0 && (unsigned char)*s < 0xA0)
+ return 0;
+
+ /* Disallow U+D800..U+DFFF. */
+ if (c == 0xED && (unsigned char)*s > 0x9F)
+ return 0;
+
+ /* Make sure subsequent bytes are in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+
+ return 3;
+ } else if (c <= 0xF4) { /* F0..F4 */
+ /* Disallow overlong 4-byte sequence. */
+ if (c == 0xF0 && (unsigned char)*s < 0x90)
+ return 0;
+
+ /* Disallow codepoints beyond U+10FFFF. */
+ if (c == 0xF4 && (unsigned char)*s > 0x8F)
+ return 0;
+
+ /* Make sure subsequent bytes are in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80)
+ return 0;
+
+ return 4;
+ } else { /* F5..FF */
+ return 0;
+ }
+}
+
+/* Validate a null-terminated UTF-8 string. */
+static bool utf8_validate(const char *s)
+{
+ int len;
+
+ for (; *s != 0; s += len) {
+ len = utf8_validate_cz(s);
+ if (len == 0)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Read a single UTF-8 character starting at @s,
+ * returning the length, in bytes, of the character read.
+ *
+ * This function assumes input is valid UTF-8,
+ * and that there are enough characters in front of @s.
+ */
+static int utf8_read_char(const char *s, uchar_t *out)
+{
+ const unsigned char *c = (const unsigned char*) s;
+
+ assert(utf8_validate_cz(s));
+
+ if (c[0] <= 0x7F) {
+ /* 00..7F */
+ *out = c[0];
+ return 1;
+ } else if (c[0] <= 0xDF) {
+ /* C2..DF (unless input is invalid) */
+ *out = ((uchar_t)c[0] & 0x1F) << 6 |
+ ((uchar_t)c[1] & 0x3F);
+ return 2;
+ } else if (c[0] <= 0xEF) {
+ /* E0..EF */
+ *out = ((uchar_t)c[0] & 0xF) << 12 |
+ ((uchar_t)c[1] & 0x3F) << 6 |
+ ((uchar_t)c[2] & 0x3F);
+ return 3;
+ } else {
+ /* F0..F4 (unless input is invalid) */
+ *out = ((uchar_t)c[0] & 0x7) << 18 |
+ ((uchar_t)c[1] & 0x3F) << 12 |
+ ((uchar_t)c[2] & 0x3F) << 6 |
+ ((uchar_t)c[3] & 0x3F);
+ return 4;
+ }
+}
+
+/*
+ * Write a single UTF-8 character to @s,
+ * returning the length, in bytes, of the character written.
+ *
+ * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF.
+ *
+ * This function will write up to 4 bytes to @out.
+ */
+static int utf8_write_char(uchar_t unicode, char *out)
+{
+ unsigned char *o = (unsigned char*) out;
+
+ assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF));
+
+ if (unicode <= 0x7F) {
+ /* U+0000..U+007F */
+ *o++ = unicode;
+ return 1;
+ } else if (unicode <= 0x7FF) {
+ /* U+0080..U+07FF */
+ *o++ = 0xC0 | unicode >> 6;
+ *o++ = 0x80 | (unicode & 0x3F);
+ return 2;
+ } else if (unicode <= 0xFFFF) {
+ /* U+0800..U+FFFF */
+ *o++ = 0xE0 | unicode >> 12;
+ *o++ = 0x80 | (unicode >> 6 & 0x3F);
+ *o++ = 0x80 | (unicode & 0x3F);
+ return 3;
+ } else {
+ /* U+10000..U+10FFFF */
+ *o++ = 0xF0 | unicode >> 18;
+ *o++ = 0x80 | (unicode >> 12 & 0x3F);
+ *o++ = 0x80 | (unicode >> 6 & 0x3F);
+ *o++ = 0x80 | (unicode & 0x3F);
+ return 4;
+ }
+}
+
+/*
+ * Compute the Unicode codepoint of a UTF-16 surrogate pair.
+ *
+ * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF.
+ * If they aren't, this function returns false.
+ */
+static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uchar_t *unicode)
+{
+ if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) {
+ *unicode = 0x10000 + ((((uchar_t)uc & 0x3FF) << 10) | (lc & 0x3FF));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*
+ * Construct a UTF-16 surrogate pair given a Unicode codepoint.
+ *
+ * @unicode must be U+10000..U+10FFFF.
+ */
+static void to_surrogate_pair(uchar_t unicode, uint16_t *uc, uint16_t *lc)
+{
+ uchar_t n;
+
+ assert(unicode >= 0x10000 && unicode <= 0x10FFFF);
+
+ n = unicode - 0x10000;
+ *uc = ((n >> 10) & 0x3FF) | 0xD800;
+ *lc = (n & 0x3FF) | 0xDC00;
+}
+
+#define is_space(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == ' ')
+#define is_digit(c) ((c) >= '0' && (c) <= '9')
+
+static bool parse_value (const char **sp, JsonNode **out);
+static bool parse_string (const char **sp, char **out);
+static bool parse_number (const char **sp, double *out);
+static bool parse_array (const char **sp, JsonNode **out);
+static bool parse_object (const char **sp, JsonNode **out);
+static bool parse_hex16 (const char **sp, uint16_t *out);
+
+static bool expect_literal (const char **sp, const char *str);
+static void skip_space (const char **sp);
+
+static void emit_value (SB *out, const JsonNode *node);
+static void emit_value_indented (SB *out, const JsonNode *node, const char *space, int indent_level);
+static void emit_string (SB *out, const char *str);
+static void emit_number (SB *out, double num);
+static void emit_array (SB *out, const JsonNode *array);
+static void emit_array_indented (SB *out, const JsonNode *array, const char *space, int indent_level);
+static void emit_object (SB *out, const JsonNode *object);
+static void emit_object_indented (SB *out, const JsonNode *object, const char *space, int indent_level);
+
+static int write_hex16(char *out, uint16_t val);
+
+static JsonNode *mknode(JsonTag tag);
+static void append_node(JsonNode *parent, JsonNode *child);
+static void prepend_node(JsonNode *parent, JsonNode *child);
+static void append_member(JsonNode *object, char *key, JsonNode *value);
+
+/* Assertion-friendly validity checks */
+static bool tag_is_valid(unsigned int tag);
+static bool number_is_valid(const char *num);
+
+JsonNode *json_decode(const char *json)
+{
+ const char *s = json;
+ JsonNode *ret;
+
+ skip_space(&s);
+ if (!parse_value(&s, &ret))
+ return NULL;
+
+ skip_space(&s);
+ if (*s != 0) {
+ json_delete(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+char *json_encode(const JsonNode *node)
+{
+ return json_stringify(node, NULL);
+}
+
+char *json_encode_string(const char *str)
+{
+ SB sb;
+ sb_init(&sb);
+
+ emit_string(&sb, str);
+
+ return sb_finish(&sb);
+}
+
+char *json_stringify(const JsonNode *node, const char *space)
+{
+ SB sb;
+ sb_init(&sb);
+
+ if (space != NULL)
+ emit_value_indented(&sb, node, space, 0);
+ else
+ emit_value(&sb, node);
+
+ return sb_finish(&sb);
+}
+
+void json_delete(JsonNode *node)
+{
+ if (node != NULL) {
+ json_remove_from_parent(node);
+
+ switch (node->tag) {
+ case JSON_STRING:
+ free(node->string_);
+ break;
+ case JSON_ARRAY:
+ case JSON_OBJECT:
+ {
+ JsonNode *child, *next;
+ for (child = node->children.head; child != NULL; child = next) {
+ next = child->next;
+ json_delete(child);
+ }
+ break;
+ }
+ default:;
+ }
+
+ free(node);
+ }
+}
+
+bool json_validate(const char *json)
+{
+ const char *s = json;
+
+ skip_space(&s);
+ if (!parse_value(&s, NULL))
+ return false;
+
+ skip_space(&s);
+ if (*s != 0)
+ return false;
+
+ return true;
+}
+
+JsonNode *json_find_element(JsonNode *array, int index)
+{
+ JsonNode *element;
+ int i = 0;
+
+ if (array == NULL || array->tag != JSON_ARRAY)
+ return NULL;
+
+ json_foreach(element, array) {
+ if (i == index)
+ return element;
+ i++;
+ }
+
+ return NULL;
+}
+
+JsonNode *json_find_member(JsonNode *object, const char *name)
+{
+ JsonNode *member;
+
+ if (object == NULL || object->tag != JSON_OBJECT)
+ return NULL;
+
+ json_foreach(member, object)
+ if (strcmp(member->key, name) == 0)
+ return member;
+
+ return NULL;
+}
+
+JsonNode *json_first_child(const JsonNode *node)
+{
+ if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT))
+ return node->children.head;
+ return NULL;
+}
+
+static JsonNode *mknode(JsonTag tag)
+{
+ JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode));
+ if (ret == NULL)
+ out_of_memory();
+ ret->tag = tag;
+ return ret;
+}
+
+JsonNode *json_mknull(void)
+{
+ return mknode(JSON_NULL);
+}
+
+JsonNode *json_mkbool(bool b)
+{
+ JsonNode *ret = mknode(JSON_BOOL);
+ ret->bool_ = b;
+ return ret;
+}
+
+static JsonNode *mkstring(char *s)
+{
+ JsonNode *ret = mknode(JSON_STRING);
+ ret->string_ = s;
+ return ret;
+}
+
+JsonNode *json_mkstring(const char *s)
+{
+ return mkstring(json_strdup(s));
+}
+
+JsonNode *json_mknumber(double n)
+{
+ JsonNode *node = mknode(JSON_NUMBER);
+ node->number_ = n;
+ return node;
+}
+
+JsonNode *json_mkarray(void)
+{
+ return mknode(JSON_ARRAY);
+}
+
+JsonNode *json_mkobject(void)
+{
+ return mknode(JSON_OBJECT);
+}
+
+static void append_node(JsonNode *parent, JsonNode *child)
+{
+ child->parent = parent;
+ child->prev = parent->children.tail;
+ child->next = NULL;
+
+ if (parent->children.tail != NULL)
+ parent->children.tail->next = child;
+ else
+ parent->children.head = child;
+ parent->children.tail = child;
+}
+
+static void prepend_node(JsonNode *parent, JsonNode *child)
+{
+ child->parent = parent;
+ child->prev = NULL;
+ child->next = parent->children.head;
+
+ if (parent->children.head != NULL)
+ parent->children.head->prev = child;
+ else
+ parent->children.tail = child;
+ parent->children.head = child;
+}
+
+static void append_member(JsonNode *object, char *key, JsonNode *value)
+{
+ value->key = key;
+ append_node(object, value);
+}
+
+void json_append_element(JsonNode *array, JsonNode *element)
+{
+ assert(array->tag == JSON_ARRAY);
+ assert(element->parent == NULL);
+
+ append_node(array, element);
+}
+
+void json_prepend_element(JsonNode *array, JsonNode *element)
+{
+ assert(array->tag == JSON_ARRAY);
+ assert(element->parent == NULL);
+
+ prepend_node(array, element);
+}
+
+void json_append_member(JsonNode *object, const char *key, JsonNode *value)
+{
+ assert(object->tag == JSON_OBJECT);
+ assert(value->parent == NULL);
+
+ append_member(object, json_strdup(key), value);
+}
+
+void json_prepend_member(JsonNode *object, const char *key, JsonNode *value)
+{
+ assert(object->tag == JSON_OBJECT);
+ assert(value->parent == NULL);
+
+ value->key = json_strdup(key);
+ prepend_node(object, value);
+}
+
+void json_remove_from_parent(JsonNode *node)
+{
+ JsonNode *parent = node->parent;
+
+ if (parent != NULL) {
+ if (node->prev != NULL)
+ node->prev->next = node->next;
+ else
+ parent->children.head = node->next;
+ if (node->next != NULL)
+ node->next->prev = node->prev;
+ else
+ parent->children.tail = node->prev;
+
+ free(node->key);
+
+ node->parent = NULL;
+ node->prev = node->next = NULL;
+ node->key = NULL;
+ }
+}
+
+static bool parse_value(const char **sp, JsonNode **out)
+{
+ const char *s = *sp;
+
+ switch (*s) {
+ case 'n':
+ if (expect_literal(&s, "null")) {
+ if (out)
+ *out = json_mknull();
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case 'f':
+ if (expect_literal(&s, "false")) {
+ if (out)
+ *out = json_mkbool(false);
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case 't':
+ if (expect_literal(&s, "true")) {
+ if (out)
+ *out = json_mkbool(true);
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case '"': {
+ char *str;
+ if (parse_string(&s, out ? &str : NULL)) {
+ if (out)
+ *out = mkstring(str);
+ *sp = s;
+ return true;
+ }
+ return false;
+ }
+
+ case '[':
+ if (parse_array(&s, out)) {
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ case '{':
+ if (parse_object(&s, out)) {
+ *sp = s;
+ return true;
+ }
+ return false;
+
+ default: {
+ double num;
+ if (parse_number(&s, out ? &num : NULL)) {
+ if (out)
+ *out = json_mknumber(num);
+ *sp = s;
+ return true;
+ }
+ return false;
+ }
+ }
+}
+
+static bool parse_array(const char **sp, JsonNode **out)
+{
+ const char *s = *sp;
+ JsonNode *ret = out ? json_mkarray() : NULL;
+ JsonNode *element;
+
+ if (*s++ != '[')
+ goto failure;
+ skip_space(&s);
+
+ if (*s == ']') {
+ s++;
+ goto success;
+ }
+
+ for (;;) {
+ if (!parse_value(&s, out ? &element : NULL))
+ goto failure;
+ skip_space(&s);
+
+ if (out)
+ json_append_element(ret, element);
+
+ if (*s == ']') {
+ s++;
+ goto success;
+ }
+
+ if (*s++ != ',')
+ goto failure;
+ skip_space(&s);
+ }
+
+success:
+ *sp = s;
+ if (out)
+ *out = ret;
+ return true;
+
+failure:
+ json_delete(ret);
+ return false;
+}
+
+static bool parse_object(const char **sp, JsonNode **out)
+{
+ const char *s = *sp;
+ JsonNode *ret = out ? json_mkobject() : NULL;
+ char *key;
+ JsonNode *value;
+
+ if (*s++ != '{')
+ goto failure;
+ skip_space(&s);
+
+ if (*s == '}') {
+ s++;
+ goto success;
+ }
+
+ for (;;) {
+ if (!parse_string(&s, out ? &key : NULL))
+ goto failure;
+ skip_space(&s);
+
+ if (*s++ != ':')
+ goto failure_free_key;
+ skip_space(&s);
+
+ if (!parse_value(&s, out ? &value : NULL))
+ goto failure_free_key;
+ skip_space(&s);
+
+ if (out)
+ append_member(ret, key, value);
+
+ if (*s == '}') {
+ s++;
+ goto success;
+ }
+
+ if (*s++ != ',')
+ goto failure;
+ skip_space(&s);
+ }
+
+success:
+ *sp = s;
+ if (out)
+ *out = ret;
+ return true;
+
+failure_free_key:
+ if (out)
+ free(key);
+failure:
+ json_delete(ret);
+ return false;
+}
+
+bool parse_string(const char **sp, char **out)
+{
+ const char *s = *sp;
+ SB sb;
+ char throwaway_buffer[4];
+ /* enough space for a UTF-8 character */
+ char *b;
+
+ if (*s++ != '"')
+ return false;
+
+ if (out) {
+ sb_init(&sb);
+ sb_need(&sb, 4);
+ b = sb.cur;
+ } else {
+ b = throwaway_buffer;
+ }
+
+ while (*s != '"') {
+ unsigned char c = *s++;
+
+ /* Parse next character, and write it to b. */
+ if (c == '\\') {
+ c = *s++;
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ *b++ = c;
+ break;
+ case 'b':
+ *b++ = '\b';
+ break;
+ case 'f':
+ *b++ = '\f';
+ break;
+ case 'n':
+ *b++ = '\n';
+ break;
+ case 'r':
+ *b++ = '\r';
+ break;
+ case 't':
+ *b++ = '\t';
+ break;
+ case 'u':
+ {
+ uint16_t uc, lc;
+ uchar_t unicode;
+
+ if (!parse_hex16(&s, &uc))
+ goto failed;
+
+ if (uc >= 0xD800 && uc <= 0xDFFF) {
+ /* Handle UTF-16 surrogate pair. */
+ if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc))
+ goto failed; /* Incomplete surrogate pair. */
+ if (!from_surrogate_pair(uc, lc, &unicode))
+ goto failed; /* Invalid surrogate pair. */
+ } else if (uc == 0) {
+ /* Disallow "\u0000". */
+ goto failed;
+ } else {
+ unicode = uc;
+ }
+
+ b += utf8_write_char(unicode, b);
+ break;
+ }
+ default:
+ /* Invalid escape */
+ goto failed;
+ }
+ } else if (c <= 0x1F) {
+ /* Control characters are not allowed in string literals. */
+ goto failed;
+ } else {
+ /* Validate and echo a UTF-8 character. */
+ int len;
+
+ s--;
+ len = utf8_validate_cz(s);
+ if (len == 0)
+ goto failed; /* Invalid UTF-8 character. */
+
+ while (len--)
+ *b++ = *s++;
+ }
+
+ /*
+ * Update sb to know about the new bytes,
+ * and set up b to write another character.
+ */
+ if (out) {
+ sb.cur = b;
+ sb_need(&sb, 4);
+ b = sb.cur;
+ } else {
+ b = throwaway_buffer;
+ }
+ }
+ s++;
+
+ if (out)
+ *out = sb_finish(&sb);
+ *sp = s;
+ return true;
+
+failed:
+ if (out)
+ sb_free(&sb);
+ return false;
+}
+
+/*
+ * The JSON spec says that a number shall follow this precise pattern
+ * (spaces and quotes added for readability):
+ * '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)?
+ *
+ * However, some JSON parsers are more liberal. For instance, PHP accepts
+ * '.5' and '1.'. JSON.parse accepts '+3'.
+ *
+ * This function takes the strict approach.
+ */
+bool parse_number(const char **sp, double *out)
+{
+ const char *s = *sp;
+
+ /* '-'? */
+ if (*s == '-')
+ s++;
+
+ /* (0 | [1-9][0-9]*) */
+ if (*s == '0') {
+ s++;
+ } else {
+ if (!is_digit(*s))
+ return false;
+ do {
+ s++;
+ } while (is_digit(*s));
+ }
+
+ /* ('.' [0-9]+)? */
+ if (*s == '.') {
+ s++;
+ if (!is_digit(*s))
+ return false;
+ do {
+ s++;
+ } while (is_digit(*s));
+ }
+
+ /* ([Ee] [+-]? [0-9]+)? */
+ if (*s == 'E' || *s == 'e') {
+ s++;
+ if (*s == '+' || *s == '-')
+ s++;
+ if (!is_digit(*s))
+ return false;
+ do {
+ s++;
+ } while (is_digit(*s));
+ }
+
+ if (out)
+ *out = strtod(*sp, NULL);
+
+ *sp = s;
+ return true;
+}
+
+static void skip_space(const char **sp)
+{
+ const char *s = *sp;
+ while (is_space(*s))
+ s++;
+ *sp = s;
+}
+
+static void emit_value(SB *out, const JsonNode *node)
+{
+ assert(tag_is_valid(node->tag));
+ switch (node->tag) {
+ case JSON_NULL:
+ sb_puts(out, "null");
+ break;
+ case JSON_BOOL:
+ sb_puts(out, node->bool_ ? "true" : "false");
+ break;
+ case JSON_STRING:
+ emit_string(out, node->string_);
+ break;
+ case JSON_NUMBER:
+ emit_number(out, node->number_);
+ break;
+ case JSON_ARRAY:
+ emit_array(out, node);
+ break;
+ case JSON_OBJECT:
+ emit_object(out, node);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level)
+{
+ assert(tag_is_valid(node->tag));
+ switch (node->tag) {
+ case JSON_NULL:
+ sb_puts(out, "null");
+ break;
+ case JSON_BOOL:
+ sb_puts(out, node->bool_ ? "true" : "false");
+ break;
+ case JSON_STRING:
+ emit_string(out, node->string_);
+ break;
+ case JSON_NUMBER:
+ emit_number(out, node->number_);
+ break;
+ case JSON_ARRAY:
+ emit_array_indented(out, node, space, indent_level);
+ break;
+ case JSON_OBJECT:
+ emit_object_indented(out, node, space, indent_level);
+ break;
+ default:
+ assert(false);
+ }
+}
+
+static void emit_array(SB *out, const JsonNode *array)
+{
+ const JsonNode *element;
+
+ sb_putc(out, '[');
+ json_foreach(element, array) {
+ emit_value(out, element);
+ if (element->next != NULL)
+ sb_putc(out, ',');
+ }
+ sb_putc(out, ']');
+}
+
+static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level)
+{
+ const JsonNode *element = array->children.head;
+ int i;
+
+ if (element == NULL) {
+ sb_puts(out, "[]");
+ return;
+ }
+
+ sb_puts(out, "[\n");
+ while (element != NULL) {
+ for (i = 0; i < indent_level + 1; i++)
+ sb_puts(out, space);
+ emit_value_indented(out, element, space, indent_level + 1);
+
+ element = element->next;
+ sb_puts(out, element != NULL ? ",\n" : "\n");
+ }
+ for (i = 0; i < indent_level; i++)
+ sb_puts(out, space);
+ sb_putc(out, ']');
+}
+
+static void emit_object(SB *out, const JsonNode *object)
+{
+ const JsonNode *member;
+
+ sb_putc(out, '{');
+ json_foreach(member, object) {
+ emit_string(out, member->key);
+ sb_putc(out, ':');
+ emit_value(out, member);
+ if (member->next != NULL)
+ sb_putc(out, ',');
+ }
+ sb_putc(out, '}');
+}
+
+static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level)
+{
+ const JsonNode *member = object->children.head;
+ int i;
+
+ if (member == NULL) {
+ sb_puts(out, "{}");
+ return;
+ }
+
+ sb_puts(out, "{\n");
+ while (member != NULL) {
+ for (i = 0; i < indent_level + 1; i++)
+ sb_puts(out, space);
+ emit_string(out, member->key);
+ sb_puts(out, ": ");
+ emit_value_indented(out, member, space, indent_level + 1);
+
+ member = member->next;
+ sb_puts(out, member != NULL ? ",\n" : "\n");
+ }
+ for (i = 0; i < indent_level; i++)
+ sb_puts(out, space);
+ sb_putc(out, '}');
+}
+
+void emit_string(SB *out, const char *str)
+{
+ bool escape_unicode = false;
+ const char *s = str;
+ char *b;
+
+ assert(utf8_validate(str));
+
+ /*
+ * 14 bytes is enough space to write up to two
+ * \uXXXX escapes and two quotation marks.
+ */
+ sb_need(out, 14);
+ b = out->cur;
+
+ *b++ = '"';
+ while (*s != 0) {
+ unsigned char c = *s++;
+
+ /* Encode the next character, and write it to b. */
+ switch (c) {
+ case '"':
+ *b++ = '\\';
+ *b++ = '"';
+ break;
+ case '\\':
+ *b++ = '\\';
+ *b++ = '\\';
+ break;
+ case '\b':
+ *b++ = '\\';
+ *b++ = 'b';
+ break;
+ case '\f':
+ *b++ = '\\';
+ *b++ = 'f';
+ break;
+ case '\n':
+ *b++ = '\\';
+ *b++ = 'n';
+ break;
+ case '\r':
+ *b++ = '\\';
+ *b++ = 'r';
+ break;
+ case '\t':
+ *b++ = '\\';
+ *b++ = 't';
+ break;
+ default: {
+ int len;
+
+ s--;
+ len = utf8_validate_cz(s);
+
+ if (len == 0) {
+ /*
+ * Handle invalid UTF-8 character gracefully in production
+ * by writing a replacement character (U+FFFD)
+ * and skipping a single byte.
+ *
+ * This should never happen when assertions are enabled
+ * due to the assertion at the beginning of this function.
+ */
+ assert(false);
+ if (escape_unicode) {
+ strcpy(b, "\\uFFFD");
+ b += 6;
+ } else {
+ *b++ = 0xEF;
+ *b++ = 0xBF;
+ *b++ = 0xBD;
+ }
+ s++;
+ } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) {
+ /* Encode using \u.... */
+ uint32_t unicode;
+
+ s += utf8_read_char(s, &unicode);
+
+ if (unicode <= 0xFFFF) {
+ *b++ = '\\';
+ *b++ = 'u';
+ b += write_hex16(b, unicode);
+ } else {
+ /* Produce a surrogate pair. */
+ uint16_t uc, lc;
+ assert(unicode <= 0x10FFFF);
+ to_surrogate_pair(unicode, &uc, &lc);
+ *b++ = '\\';
+ *b++ = 'u';
+ b += write_hex16(b, uc);
+ *b++ = '\\';
+ *b++ = 'u';
+ b += write_hex16(b, lc);
+ }
+ } else {
+ /* Write the character directly. */
+ while (len--)
+ *b++ = *s++;
+ }
+
+ break;
+ }
+ }
+
+ /*
+ * Update *out to know about the new bytes,
+ * and set up b to write another encoded character.
+ */
+ out->cur = b;
+ sb_need(out, 14);
+ b = out->cur;
+ }
+ *b++ = '"';
+
+ out->cur = b;
+}
+
+static void emit_number(SB *out, double num)
+{
+ /*
+ * This isn't exactly how JavaScript renders numbers,
+ * but it should produce valid JSON for reasonable numbers
+ * preserve precision well enough, and avoid some oddities
+ * like 0.3 -> 0.299999999999999988898 .
+ */
+ char buf[64];
+ sprintf(buf, "%.16g", num);
+
+ if (number_is_valid(buf))
+ sb_puts(out, buf);
+ else
+ sb_puts(out, "null");
+}
+
+static bool tag_is_valid(unsigned int tag)
+{
+ return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT);
+}
+
+static bool number_is_valid(const char *num)
+{
+ return (parse_number(&num, NULL) && *num == '\0');
+}
+
+static bool expect_literal(const char **sp, const char *str)
+{
+ const char *s = *sp;
+
+ while (*str != '\0')
+ if (*s++ != *str++)
+ return false;
+
+ *sp = s;
+ return true;
+}
+
+/*
+ * Parses exactly 4 hex characters (capital or lowercase).
+ * Fails if any input chars are not [0-9A-Fa-f].
+ */
+static bool parse_hex16(const char **sp, uint16_t *out)
+{
+ const char *s = *sp;
+ uint16_t ret = 0;
+ uint16_t i;
+ uint16_t tmp;
+ char c;
+
+ for (i = 0; i < 4; i++) {
+ c = *s++;
+ if (c >= '0' && c <= '9')
+ tmp = c - '0';
+ else if (c >= 'A' && c <= 'F')
+ tmp = c - 'A' + 10;
+ else if (c >= 'a' && c <= 'f')
+ tmp = c - 'a' + 10;
+ else
+ return false;
+
+ ret <<= 4;
+ ret += tmp;
+ }
+
+ if (out)
+ *out = ret;
+ *sp = s;
+ return true;
+}
+
+/*
+ * Encodes a 16-bit number into hexadecimal,
+ * writing exactly 4 hex chars.
+ */
+static int write_hex16(char *out, uint16_t val)
+{
+ const char *hex = "0123456789ABCDEF";
+
+ *out++ = hex[(val >> 12) & 0xF];
+ *out++ = hex[(val >> 8) & 0xF];
+ *out++ = hex[(val >> 4) & 0xF];
+ *out++ = hex[ val & 0xF];
+
+ return 4;
+}
+
+bool json_check(const JsonNode *node, char errmsg[256])
+{
+ #define problem(...) do { \
+ if (errmsg != NULL) \
+ snprintf(errmsg, 256, __VA_ARGS__); \
+ return false; \
+ } while (0)
+
+ if (node->key != NULL && !utf8_validate(node->key))
+ problem("key contains invalid UTF-8");
+
+ if (!tag_is_valid(node->tag))
+ problem("tag is invalid (%u)", node->tag);
+
+ if (node->tag == JSON_BOOL) {
+ if (node->bool_ != false && node->bool_ != true)
+ problem("bool_ is neither false (%d) nor true (%d)", (int)false, (int)true);
+ } else if (node->tag == JSON_STRING) {
+ if (node->string_ == NULL)
+ problem("string_ is NULL");
+ if (!utf8_validate(node->string_))
+ problem("string_ contains invalid UTF-8");
+ } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) {
+ JsonNode *head = node->children.head;
+ JsonNode *tail = node->children.tail;
+
+ if (head == NULL || tail == NULL) {
+ if (head != NULL)
+ problem("tail is NULL, but head is not");
+ if (tail != NULL)
+ problem("head is NULL, but tail is not");
+ } else {
+ JsonNode *child;
+ JsonNode *last = NULL;
+
+ if (head->prev != NULL)
+ problem("First child's prev pointer is not NULL");
+
+ for (child = head; child != NULL; last = child, child = child->next) {
+ if (child == node)
+ problem("node is its own child");
+ if (child->next == child)
+ problem("child->next == child (cycle)");
+ if (child->next == head)
+ problem("child->next == head (cycle)");
+
+ if (child->parent != node)
+ problem("child does not point back to parent");
+ if (child->next != NULL && child->next->prev != child)
+ problem("child->next does not point back to child");
+
+ if (node->tag == JSON_ARRAY && child->key != NULL)
+ problem("Array element's key is not NULL");
+ if (node->tag == JSON_OBJECT && child->key == NULL)
+ problem("Object member's key is NULL");
+
+ if (!json_check(child, errmsg))
+ return false;
+ }
+
+ if (last != tail)
+ problem("tail does not match pointer found by starting at head and following next links");
+ }
+ }
+
+ return true;
+
+ #undef problem
+}
diff --git a/src/krdict.c b/src/krdict.c
new file mode 100644
index 0000000..9199c07
--- /dev/null
+++ b/src/krdict.c
@@ -0,0 +1,589 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+
+#include
+#include
+
+#include "saerom.h"
+
+#define KRDICT_REQUEST_URL "https://krdict.korean.go.kr/api/search"
+
+#define KRDICT_MAX_ORDER_COUNT 8
+
+/* | `krdict` 모듈 자료형 정의... | */
+
+/* `krdict` 모듈 명령어 실행 정보를 나타내는 구조체. */
+struct krdict_context {
+ struct discord *client;
+ struct {
+ u64snowflake id;
+ char *token;
+ } event;
+};
+
+/* `krdict` 모듈 명령어의 개별 검색 결과를 나타내는 구조체. */
+struct krdict_item {
+ char word[MAX_STRING_SIZE];
+ char origin[MAX_STRING_SIZE];
+ char pos[MAX_STRING_SIZE];
+ char link[MAX_STRING_SIZE];
+ char entry[DISCORD_EMBED_DESCRIPTION_LEN];
+};
+
+/* | `krdict` 모듈 상수 및 변수... | */
+
+/* `krdict` 모듈 명령어의 매개 변수. */
+static struct discord_application_command_option options[] = {
+ {
+ .type = DISCORD_APPLICATION_OPTION_STRING,
+ .name = "query",
+ .description = "The text you're looking up",
+ .required = true
+ },
+ {
+ .type = DISCORD_APPLICATION_OPTION_BOOLEAN,
+ .name = "translated",
+ .description = "Whether to translate the results to English or not"
+ }
+};
+
+/* `krdict` 모듈 명령어에 대한 정보. */
+static struct discord_create_global_application_command params = {
+ .name = "krd",
+ .description = "Search the given text in the Basic Korean Dictionary by the National Institute of Korean Language",
+ .default_permission = true,
+ .options = &(struct discord_application_command_options) {
+ .size = sizeof(options) / sizeof(*options),
+ .array = options
+ }
+};
+
+/* | `krdict` 모듈 함수... | */
+
+/* 개인 메시지 전송에 성공했을 때 호출되는 함수. */
+static void on_direct_message_success(
+ struct discord *client,
+ struct discord_response *resp,
+ const struct discord_message *msg
+);
+
+/* 개인 메시지 전송에 실패했을 때 호출되는 함수. */
+static void on_direct_message_failure(
+ struct discord *client,
+ struct discord_response *resp
+);
+
+/* 컴포넌트와의 상호 작용 시에 호출되는 함수. */
+static void on_interaction(
+ struct discord *client,
+ const struct discord_interaction *event
+);
+
+/* 컴포넌트와의 상호 작용이 끝났을 때 호출되는 함수. */
+static void on_interaction_complete(
+ struct discord *client,
+ void *data
+);
+
+/* 요청 URL에서 응답을 받았을 때 호출되는 함수. */
+static void on_response(CURLV_STR res, void *user_data);
+
+/* 응답 결과로 받은 오류 메시지를 처리한다. */
+static void handle_error(struct krdict_context *context, const char *code);
+
+/* `krdict` 모듈 명령어를 생성한다. */
+void create_krdict_command(struct discord *client) {
+ discord_create_global_application_command(
+ client,
+ APPLICATION_ID,
+ ¶ms,
+ NULL
+ );
+}
+
+/* `krdict` 모듈 명령어에 할당된 메모리를 해제한다. */
+void release_krdict_command(struct discord *client) {
+ /* TODO: ... */
+}
+
+/* `krdict` 모듈 명령어를 실행한다. */
+void run_krdict_command(
+ struct discord *client,
+ const struct discord_interaction *event
+) {
+ if (event->type == DISCORD_INTERACTION_MESSAGE_COMPONENT) {
+ on_interaction(client, event);
+
+ return;
+ }
+
+ char *query = "", *translated = "true";
+
+ for (int i = 0; i < event->data->options->size; i++) {
+ char *name = event->data->options->array[i].name;
+ char *value = event->data->options->array[i].value;
+
+ if (string_equals(name, "query")) query = value;
+ else if (string_equals(name, "translated")) translated = value;
+ }
+
+ CURLV_REQ request = { .callback = on_response };
+
+ request.easy = curl_easy_init();
+
+ char buffer[DISCORD_MAX_MESSAGE_LEN] = "";
+
+ struct sr_config *config = get_sr_config();
+
+ snprintf(
+ buffer,
+ DISCORD_MAX_MESSAGE_LEN,
+ string_equals(translated, "true")
+ ? "key=%s&q=%s&advanced=y&translated=y&trans_lang=1"
+ : "key=%s&q=%s&advanced=y",
+ config->krdict.api_key,
+ query
+ );
+
+ curl_easy_setopt(request.easy, CURLOPT_URL, KRDICT_REQUEST_URL);
+ curl_easy_setopt(request.easy, CURLOPT_POSTFIELDSIZE, strlen(buffer));
+ curl_easy_setopt(request.easy, CURLOPT_COPYPOSTFIELDS, buffer);
+ curl_easy_setopt(request.easy, CURLOPT_SSL_VERIFYPEER, false);
+ curl_easy_setopt(request.easy, CURLOPT_POST, 1);
+
+ struct krdict_context *context = malloc(sizeof(struct krdict_context));
+
+ context->client = client;
+ context->event.id = event->id;
+ context->event.token = malloc(strlen(event->token) + 1);
+
+ strcpy(context->event.token, event->token);
+
+ request.user_data = context;
+
+ curlv_create_request(get_curlv(), &request);
+
+ discord_create_interaction_response(
+ context->client,
+ context->event.id,
+ context->event.token,
+ &(struct discord_interaction_response) {
+ .type = DISCORD_INTERACTION_DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
+ },
+ NULL
+ );
+}
+
+/* 개인 메시지 전송에 성공했을 때 호출되는 함수. */
+static void on_direct_message_success(
+ struct discord *client,
+ struct discord_response *resp,
+ const struct discord_message *msg
+) {
+ if (resp->data == NULL) return;
+
+ struct discord_interaction *event = resp->data;
+
+ struct discord_embed embeds[] = {
+ {
+ .title = "Results",
+ .description = "Sent you a direct message!",
+ .timestamp = discord_timestamp(client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "🗒️"
+ }
+ }
+ };
+
+ struct discord_interaction_response params = {
+ .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
+ .data = &(struct discord_interaction_callback_data) {
+ .flags = DISCORD_MESSAGE_EPHEMERAL,
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ }
+ };
+
+ discord_create_interaction_response(
+ client,
+ event->id,
+ event->token,
+ ¶ms,
+ NULL
+ );
+}
+
+/* 개인 메시지 전송에 실패했을 때 호출되는 함수. */
+static void on_direct_message_failure(
+ struct discord *client,
+ struct discord_response *resp
+) {
+ if (resp->data == NULL) return;
+
+ struct discord_interaction *event = resp->data;
+
+ struct discord_embed embeds[] = {
+ {
+ .title = "Results",
+ .description = "Failed to send you a direct message. "
+ "Please check your privacy settings!",
+ .timestamp = discord_timestamp(client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "🗒️"
+ }
+ }
+ };
+
+ struct discord_interaction_response params = {
+ .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
+ .data = &(struct discord_interaction_callback_data) {
+ .flags = DISCORD_MESSAGE_EPHEMERAL,
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ }
+ };
+
+ discord_create_interaction_response(
+ client,
+ event->id,
+ event->token,
+ ¶ms,
+ NULL
+ );
+}
+
+/* 컴포넌트와의 상호 작용 시에 호출되는 함수. */
+static void on_interaction(
+ struct discord *client,
+ const struct discord_interaction *event
+) {
+ struct discord_channel ret_channel = { .id = 0 };
+
+ const struct discord_user *user = (event->member != NULL)
+ ? event->member->user
+ : event->user;
+
+ CCORDcode result = discord_create_dm(
+ client,
+ &(struct discord_create_dm) {
+ .recipient_id = user->id
+ },
+ &(struct discord_ret_channel) {
+ .sync = &ret_channel
+ }
+ );
+
+ if (result != CCORD_OK) return;
+
+ discord_channel_cleanup(&ret_channel);
+
+ log_info(
+ "[SAEROM] Attempting to send a direct message to %s#%s",
+ user->username,
+ user->discriminator
+ );
+
+ struct discord_interaction *event_clone = malloc(
+ sizeof(struct discord_interaction)
+ );
+
+ event_clone->id = event->id;
+ event_clone->token = malloc(strlen(event->token) + 1);
+
+ strcpy(event_clone->token, event->token);
+
+ discord_create_message(
+ client,
+ ret_channel.id,
+ &(struct discord_create_message) {
+ .embeds = event->message->embeds
+ },
+ &(struct discord_ret_message) {
+ .data = event_clone,
+ .cleanup = on_interaction_complete,
+ .done = on_direct_message_success,
+ .fail = on_direct_message_failure
+ }
+ );
+}
+
+/* 컴포넌트와의 상호 작용이 끝났을 때 호출되는 함수. */
+static void on_interaction_complete(
+ struct discord *client,
+ void *data
+) {
+ struct discord_interaction *event = data;
+
+ free(event->token);
+ free(event);
+}
+
+/* 요청 URL에서 응답을 받았을 때 호출되는 함수. */
+static void on_response(CURLV_STR res, void *user_data) {
+ if (res.str == NULL || user_data == NULL) return;
+
+ log_info("[SAEROM] Received %ld bytes from \"%s\"", res.len, KRDICT_REQUEST_URL);
+
+ yxml_t *parser = malloc(sizeof(yxml_t) + res.len);
+
+ yxml_init(parser, parser + 1, res.len);
+
+ struct krdict_context *context = (struct krdict_context *) user_data;
+ struct krdict_item item;
+
+ char buffer[DISCORD_EMBED_DESCRIPTION_LEN] = "";
+ char content[DISCORD_MAX_MESSAGE_LEN] = "";
+
+ char *elem, *temp, *ptr;
+
+ int len, num, order, total = 1;
+
+ for (char *doc = res.str; *doc; doc++) {
+ yxml_ret_t result = yxml_parse(parser, *doc);
+
+ if (result < 0) {
+ handle_error(context, "-1");
+
+ free(parser);
+
+ return;
+ }
+
+ switch (result) {
+ case YXML_ELEMSTART:
+ elem = parser->elem;
+ ptr = content;
+
+ break;
+
+ case YXML_CONTENT:
+ temp = parser->data;
+
+ while (*temp != 0) {
+ if (*temp == '\n' || *temp == '\t') {
+ temp++;
+
+ continue;
+ }
+
+ *(ptr++) = *(temp++);
+ }
+
+ break;
+
+ case YXML_ELEMEND:
+ *ptr = 0;
+
+ if (string_equals(parser->elem, "error")) {
+ handle_error(context, content);
+
+ free(parser);
+
+ return;
+ }
+
+ if (string_equals(parser->elem, "channel")) {
+ if (string_equals(elem, "total")) {
+ total = atoi(content);
+
+ if (total <= 0) break;
+ } else if (string_equals(elem, "num")) {
+ num = atoi(content);
+
+ if (total > num) total = num;
+ } else {
+ continue;
+ }
+ }
+
+ if (string_equals(elem, "word")) {
+ strncpy(item.word, content, sizeof(item.word));
+ } else if (string_equals(elem, "origin")) {
+ strncpy(item.origin, content, sizeof(item.origin));
+ } else if (string_equals(elem, "pos")) {
+ strncpy(item.pos, content, sizeof(item.pos));
+ } else if (string_equals(elem, "link")) {
+ strncpy(item.link, content, sizeof(item.link));
+
+ if (strlen(item.origin) > 0) {
+ snprintf(
+ item.entry,
+ sizeof(item.entry),
+ "[**%s (%s) 「%s」**](%s)\n\n",
+ item.word,
+ item.origin,
+ item.pos,
+ item.link
+ );
+ } else {
+ snprintf(
+ item.entry,
+ sizeof(item.entry),
+ "[**%s 「%s」**](%s)\n\n",
+ item.word,
+ item.pos,
+ item.link
+ );
+ }
+
+ strcat(buffer, item.entry);
+ } else if (string_equals(elem, "sense_order")) {
+ order = atoi(content);
+
+ if (order >= KRDICT_MAX_ORDER_COUNT) break;
+
+ len = strlen(buffer);
+
+ snprintf(
+ buffer + len,
+ sizeof(buffer) - len,
+ "**%s. ",
+ content
+ );
+ } else if (string_equals(elem, "trans_word")) {
+ if (order >= KRDICT_MAX_ORDER_COUNT) break;
+
+ len = strlen(buffer);
+
+ snprintf(
+ buffer + len,
+ sizeof(buffer) - len,
+ "%s**\n- ",
+ content
+ );
+ } else if (string_equals(elem, "trans_dfn")) {
+ if (order >= KRDICT_MAX_ORDER_COUNT) break;
+
+ len = strlen(buffer);
+
+ snprintf(
+ buffer + len,
+ sizeof(buffer) - len,
+ "%s\n\n",
+ content
+ );
+ }
+
+ elem = parser->elem;
+
+ break;
+ }
+
+ if (total <= 0) break;
+ }
+
+ struct discord_embed embeds[] = {
+ {
+ .title = "Results",
+ .description = "No results found.",
+ .timestamp = discord_timestamp(context->client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "🗒️"
+ }
+ }
+ };
+
+ if (total > 0) embeds[0].description = buffer;
+
+ struct discord_component buttons[] = {
+ {
+ .type = DISCORD_COMPONENT_BUTTON,
+ .style = DISCORD_BUTTON_SECONDARY,
+ .label = "🔖 Bookmark",
+ .custom_id = "krd_btn_uwu"
+ }
+ };
+
+ struct discord_component action_rows[] = {
+ {
+ .type = DISCORD_COMPONENT_ACTION_ROW,
+ .components = &(struct discord_components){
+ .size = sizeof(buttons) / sizeof(*buttons),
+ .array = buttons
+ }
+ },
+ };
+
+ discord_edit_original_interaction_response(
+ context->client,
+ APPLICATION_ID,
+ context->event.token,
+ &(struct discord_edit_original_interaction_response) {
+ .components = &(struct discord_components){
+ .size = (total > 0)
+ ? sizeof(action_rows) / sizeof(*action_rows)
+ : 0,
+ .array = action_rows
+ },
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ },
+ NULL
+ );
+
+ free(context->event.token);
+ free(context);
+
+ free(parser);
+}
+
+/* 응답 결과로 받은 오류 메시지를 처리한다. */
+static void handle_error(struct krdict_context *context, const char *code) {
+ if (context == NULL || code == NULL) return;
+
+ log_warn(
+ "[SAEROM] An error (%s) has occured while processing the request",
+ code
+ );
+
+ struct discord_embed embeds[] = {
+ {
+ .title = "Results",
+ .description = "An unknown error has occured while processing "
+ "your request.",
+ .timestamp = discord_timestamp(context->client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "🗒️"
+ }
+ }
+ };
+
+ discord_edit_original_interaction_response(
+ context->client,
+ APPLICATION_ID,
+ context->event.token,
+ &(struct discord_edit_original_interaction_response) {
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ },
+ NULL
+ );
+
+ free(context->event.token);
+ free(context);
+}
\ No newline at end of file
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..6652537
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,28 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "saerom.h"
+
+int main(int argc, char *argv[]) {
+ init_bot(argc, argv);
+
+ run_bot();
+
+ deinit_bot();
+
+ return 0;
+}
\ No newline at end of file
diff --git a/src/papago.c b/src/papago.c
new file mode 100644
index 0000000..b3f7792
--- /dev/null
+++ b/src/papago.c
@@ -0,0 +1,337 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+
+#include
+#include
+
+#include "saerom.h"
+
+#define PAPAGO_REQUEST_URL "https://openapi.naver.com/v1/papago/n2mt"
+
+/* | `papago` 모듈 자료형 정의... | */
+
+/* `papago` 모듈 명령어 실행 정보를 나타내는 구조체. */
+struct papago_context {
+ struct discord *client;
+ struct {
+ u64snowflake id;
+ char *token;
+ } event;
+ char *text;
+};
+
+/* | `papago` 모듈 상수 및 변수... | */
+
+/* `papago` 모듈 명령어의 원본 언어 및 목적 언어 목록.*/
+static struct discord_application_command_option_choice languages[] = {
+ { .name = "Chinese (Simplified)", .value = "\"zh-CN\"" },
+ { .name = "Chinese (Traditional)", .value = "\"zh-TW\"" },
+ { .name = "English", .value = "\"en\"" },
+ { .name = "French", .value = "\"fr\"" },
+ { .name = "German", .value = "\"de\"" },
+ { .name = "Indonesian", .value = "\"id\"" },
+ { .name = "Italian", .value = "\"it\"" },
+ { .name = "Japanese", .value = "\"ja\"" },
+ { .name = "Korean", .value = "\"ko\"" },
+ { .name = "Russian", .value = "\"ru\"" },
+ { .name = "Spanish", .value = "\"es\"" },
+ { .name = "Thai", .value = "\"th\"" },
+ { .name = "Vietnamese", .value = "\"vi\"" }
+};
+
+/* `papago` 모듈 명령어의 매개 변수. */
+static struct discord_application_command_option options[] = {
+ {
+ .type = DISCORD_APPLICATION_OPTION_STRING,
+ .name = "text",
+ .description = "The text to be translated",
+ .required = true
+ },
+ {
+ .type = DISCORD_APPLICATION_OPTION_STRING,
+ .name = "source",
+ .description = "The language to translate the text from",
+ .choices = &(struct discord_application_command_option_choices) {
+ .size = sizeof(languages) / sizeof(*languages),
+ .array = languages
+ }
+ },
+ {
+ .type = DISCORD_APPLICATION_OPTION_STRING,
+ .name = "target",
+ .description = "The language to translate the text to",
+ .choices = &(struct discord_application_command_option_choices) {
+ .size = sizeof(languages) / sizeof(*languages),
+ .array = languages
+ }
+ }
+};
+
+/* `papago` 모듈 명령어에 대한 정보. */
+static struct discord_create_global_application_command params = {
+ .name = "ppg",
+ .description = "Translate the given text between two languages using NAVER™ Papago NMT API",
+ .default_permission = true,
+ .options = &(struct discord_application_command_options) {
+ .size = sizeof(options) / sizeof(*options),
+ .array = options
+ }
+};
+
+/* | `papago` 모듈 함수... | */
+
+/* 요청 URL에서 응답을 받았을 때 호출되는 함수. */
+static void on_response(CURLV_STR res, void *user_data);
+
+/* 응답 결과로 받은 오류 메시지를 처리한다. */
+static void handle_error(struct papago_context *context, const char *code);
+
+/* `papago` 모듈 명령어를 생성한다. */
+void create_papago_command(struct discord *client) {
+ discord_create_global_application_command(
+ client,
+ APPLICATION_ID,
+ ¶ms,
+ NULL
+ );
+}
+
+/* `papago` 모듈 명령어에 할당된 메모리를 해제한다. */
+void release_papago_command(struct discord *client) {
+ /* no-op */
+}
+
+/* `papago` 모듈 명령어를 실행한다. */
+void run_papago_command(
+ struct discord *client,
+ const struct discord_interaction *event
+) {
+ if (event->type == DISCORD_INTERACTION_MESSAGE_COMPONENT) return;
+
+ char *text = "", *source = "ko", *target = "en";
+
+ for (int i = 0; i < event->data->options->size; i++) {
+ char *name = event->data->options->array[i].name;
+ char *value = event->data->options->array[i].value;
+
+ if (string_equals(name, "text")) text = value;
+ else if (string_equals(name, "source")) source = value;
+ else if (string_equals(name, "target")) target = value;
+ }
+
+ CURLV_REQ request = { .callback = on_response };
+
+ request.easy = curl_easy_init();
+
+ char buffer[DISCORD_MAX_MESSAGE_LEN] = "";
+
+ snprintf(
+ buffer,
+ DISCORD_MAX_MESSAGE_LEN,
+ "source=%s&target=%s&text=%s",
+ source,
+ target,
+ text
+ );
+
+ curl_easy_setopt(request.easy, CURLOPT_URL, PAPAGO_REQUEST_URL);
+ curl_easy_setopt(request.easy, CURLOPT_POSTFIELDSIZE, strlen(buffer));
+ curl_easy_setopt(request.easy, CURLOPT_COPYPOSTFIELDS, buffer);
+ curl_easy_setopt(request.easy, CURLOPT_POST, 1);
+
+ struct sr_config *config = get_sr_config();
+
+ request.header = curl_slist_append(
+ request.header,
+ "Content-Type: application/x-www-form-urlencoded; charset=UTF-8"
+ );
+
+ snprintf(
+ buffer,
+ DISCORD_MAX_MESSAGE_LEN,
+ "X-Naver-Client-Id: %s",
+ config->papago.client_id
+ );
+
+ request.header = curl_slist_append(request.header, buffer);
+
+ snprintf(
+ buffer,
+ DISCORD_MAX_MESSAGE_LEN,
+ "X-Naver-Client-Secret: %s",
+ config->papago.client_secret
+ );
+
+ request.header = curl_slist_append(request.header, buffer);
+
+ struct papago_context *context = malloc(sizeof(struct papago_context));
+
+ context->client = client;
+ context->event.id = event->id;
+ context->event.token = malloc(strlen(event->token) + 1);
+ context->text = malloc(strlen(text) + 1);
+
+ strcpy(context->event.token, event->token);
+ strcpy(context->text, text);
+
+ request.user_data = context;
+
+ curlv_create_request(get_curlv(), &request);
+
+ discord_create_interaction_response(
+ context->client,
+ context->event.id,
+ context->event.token,
+ &(struct discord_interaction_response) {
+ .type = DISCORD_INTERACTION_DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
+ },
+ NULL
+ );
+}
+
+/* 요청 URL에서 응답을 받았을 때 호출되는 함수. */
+static void on_response(CURLV_STR res, void *user_data) {
+ if (res.str == NULL || user_data == NULL) return;
+
+ log_info("[SAEROM] Received %ld bytes from \"%s\"", res.len, PAPAGO_REQUEST_URL);
+
+ JsonNode *root = json_decode(res.str);
+
+ JsonNode *node = json_find_member(root, "errorCode");
+
+ struct papago_context *context = (struct papago_context *) user_data;
+
+ if (node != NULL) {
+ handle_error(context, node->string_);
+
+ json_delete(root);
+
+ return;
+ }
+
+ node = json_find_member(root, "message");
+ node = json_find_member(node, "result");
+
+ struct discord_embed_field fields[2] = {
+ [0] = { .value = context->text }
+ };
+
+ char source_field_name[MAX_STRING_SIZE] = "";
+ char target_field_name[MAX_STRING_SIZE] = "";
+
+ JsonNode *i = NULL;
+
+ json_foreach(i, node) {
+ if (string_equals(i->key, "srcLangType")) {
+ snprintf(source_field_name, MAX_STRING_SIZE, "Source (%s)", i->string_);
+
+ fields[0].name = source_field_name;
+ } else if (string_equals(i->key, "tarLangType")) {
+ snprintf(target_field_name, MAX_STRING_SIZE, "Target (%s)", i->string_);
+
+ fields[1].name = target_field_name;
+ } else if (string_equals(i->key, "translatedText")) {
+ fields[1].value = i->string_;
+ }
+ }
+
+ struct discord_embed embeds[] = {
+ {
+ .title = "Translation",
+ .timestamp = discord_timestamp(context->client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "🌏"
+ },
+ .fields = &(struct discord_embed_fields) {
+ .size = sizeof(fields) / sizeof(*fields),
+ .array = fields
+ }
+ }
+ };
+
+ discord_edit_original_interaction_response(
+ context->client,
+ APPLICATION_ID,
+ context->event.token,
+ &(struct discord_edit_original_interaction_response) {
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ },
+ NULL
+ );
+
+ free(context->event.token);
+ free(context->text);
+ free(context);
+
+ json_delete(root);
+}
+
+/* 응답 결과로 받은 오류 메시지를 처리한다. */
+static void handle_error(struct papago_context *context, const char *code) {
+ if (context == NULL || code == NULL) return;
+
+ log_warn(
+ "[SAEROM] An error (%s) has occured while processing the request",
+ code
+ );
+
+ struct discord_embed embeds[] = {
+ {
+ .title = "Translation",
+ .timestamp = discord_timestamp(context->client),
+ .footer = &(struct discord_embed_footer) {
+ .text = "🌏"
+ }
+ }
+ };
+
+ if (string_equals(code, "024"))
+ embeds[0].description = "Invalid client id or client secret given, "
+ "please check your configuration file.";
+ else if (string_equals(code, "N2MT05"))
+ embeds[0].description = "Target language must not be the same as the "
+ "source language.";
+ else if (string_equals(code, "N2MT08"))
+ embeds[0].description = "The length of the `text` parameter is too long.";
+ else
+ embeds[0].description = "An unknown error has occured while processing "
+ "your request.";
+
+ discord_edit_original_interaction_response(
+ context->client,
+ APPLICATION_ID,
+ context->event.token,
+ &(struct discord_edit_original_interaction_response) {
+ .embeds = &(struct discord_embeds) {
+ .size = sizeof(embeds) / sizeof(*embeds),
+ .array = embeds
+ }
+ },
+ NULL
+ );
+
+ free(context->event.token);
+ free(context->text);
+
+ free(context);
+}
\ No newline at end of file
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..96fa982
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,92 @@
+/*
+ Copyright (c) 2022 jdeokkim
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+#include
+
+#include
+
+#include "saerom.h"
+
+/* | `utils` 모듈 매크로 정의... | */
+
+#define DISCORD_MAGIC_NUMBER 5
+
+/* | `utils` 모듈 함수... | */
+
+/* 주어진 사용자의 프로필 사진 URL을 반환한다. */
+const char *get_avatar_url(const struct discord_user *user) {
+ static char buffer[MAX_STRING_SIZE];
+
+ if (user->avatar != NULL) {
+ snprintf(
+ buffer,
+ MAX_STRING_SIZE,
+ "https://cdn.discordapp.com/avatars/%"SCNu64"/%s.webp",
+ user->id,
+ user->avatar
+ );
+ } else {
+ snprintf(
+ buffer,
+ MAX_STRING_SIZE,
+ "https://cdn.discordapp.com/embed/avatars/%"SCNu64".png",
+ strtoul(user->discriminator, NULL, 10) % DISCORD_MAGIC_NUMBER
+ );
+ }
+
+ return buffer;
+}
+
+/* 주어진 경로에 위치한 파일의 내용을 반환한다. */
+char *get_file_contents(const char *path) {
+ FILE *fp = fopen(path, "rb");
+
+ if (fp == NULL) {
+ log_warn("[SAEROM] Unable to open file \"%s\"", path);
+
+ return NULL;
+ }
+
+ char *buffer = malloc(MAX_FILE_SIZE);
+
+ if (buffer == NULL) {
+ log_warn("[SAEROM] Unable to allocate memory for file \"%s\"", path);
+
+ return NULL;
+ }
+
+ size_t file_size = fread(buffer, sizeof(*buffer), MAX_FILE_SIZE, fp);
+
+ char *new_buffer = realloc(buffer, file_size);
+
+ if (new_buffer != NULL) {
+ new_buffer[file_size] = 0;
+
+ buffer = new_buffer;
+ }
+
+ fclose(fp);
+
+ return buffer;
+}
+
+/* 두 문자열의 내용이 서로 같은지 확인한다. */
+bool string_equals(const char *s1, const char *s2) {
+ return strncmp(s1, s2, MAX_STRING_SIZE) == 0;
+}
\ No newline at end of file
diff --git a/src/yxml.c b/src/yxml.c
new file mode 100644
index 0000000..23d04a9
--- /dev/null
+++ b/src/yxml.c
@@ -0,0 +1,1060 @@
+/* This file is generated by yxml-gen.pl using yxml-states and yxml.c.in as input files.
+ * It is preferable to edit those files instead of this one if you want to make a change.
+ * The source files can be found through the homepage: https://dev.yorhel.nl/yxml */
+
+/* Copyright (c) 2013-2014 Yoran Heling
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include
+#include
+
+typedef enum {
+ YXMLS_string,
+ YXMLS_attr0,
+ YXMLS_attr1,
+ YXMLS_attr2,
+ YXMLS_attr3,
+ YXMLS_attr4,
+ YXMLS_cd0,
+ YXMLS_cd1,
+ YXMLS_cd2,
+ YXMLS_comment0,
+ YXMLS_comment1,
+ YXMLS_comment2,
+ YXMLS_comment3,
+ YXMLS_comment4,
+ YXMLS_dt0,
+ YXMLS_dt1,
+ YXMLS_dt2,
+ YXMLS_dt3,
+ YXMLS_dt4,
+ YXMLS_elem0,
+ YXMLS_elem1,
+ YXMLS_elem2,
+ YXMLS_elem3,
+ YXMLS_enc0,
+ YXMLS_enc1,
+ YXMLS_enc2,
+ YXMLS_enc3,
+ YXMLS_etag0,
+ YXMLS_etag1,
+ YXMLS_etag2,
+ YXMLS_init,
+ YXMLS_le0,
+ YXMLS_le1,
+ YXMLS_le2,
+ YXMLS_le3,
+ YXMLS_lee1,
+ YXMLS_lee2,
+ YXMLS_leq0,
+ YXMLS_misc0,
+ YXMLS_misc1,
+ YXMLS_misc2,
+ YXMLS_misc2a,
+ YXMLS_misc3,
+ YXMLS_pi0,
+ YXMLS_pi1,
+ YXMLS_pi2,
+ YXMLS_pi3,
+ YXMLS_pi4,
+ YXMLS_std0,
+ YXMLS_std1,
+ YXMLS_std2,
+ YXMLS_std3,
+ YXMLS_ver0,
+ YXMLS_ver1,
+ YXMLS_ver2,
+ YXMLS_ver3,
+ YXMLS_xmldecl0,
+ YXMLS_xmldecl1,
+ YXMLS_xmldecl2,
+ YXMLS_xmldecl3,
+ YXMLS_xmldecl4,
+ YXMLS_xmldecl5,
+ YXMLS_xmldecl6,
+ YXMLS_xmldecl7,
+ YXMLS_xmldecl8,
+ YXMLS_xmldecl9
+} yxml_state_t;
+
+
+#define yxml_isChar(c) 1
+/* 0xd should be part of SP, too, but yxml_parse() already normalizes that into 0xa */
+#define yxml_isSP(c) (c == 0x20 || c == 0x09 || c == 0x0a)
+#define yxml_isAlpha(c) ((c|32)-'a' < 26)
+#define yxml_isNum(c) (c-'0' < 10)
+#define yxml_isHex(c) (yxml_isNum(c) || (c|32)-'a' < 6)
+#define yxml_isEncName(c) (yxml_isAlpha(c) || yxml_isNum(c) || c == '.' || c == '_' || c == '-')
+#define yxml_isNameStart(c) (yxml_isAlpha(c) || c == ':' || c == '_' || c >= 128)
+#define yxml_isName(c) (yxml_isNameStart(c) || yxml_isNum(c) || c == '-' || c == '.')
+/* XXX: The valid characters are dependent on the quote char, hence the access to x->quote */
+#define yxml_isAttValue(c) (yxml_isChar(c) && c != x->quote && c != '<' && c != '&')
+/* Anything between '&' and ';', the yxml_ref* functions will do further
+ * validation. Strictly speaking, this is "yxml_isName(c) || c == '#'", but
+ * this parser doesn't understand entities with '.', ':', etc, anwyay. */
+#define yxml_isRef(c) (yxml_isNum(c) || yxml_isAlpha(c) || c == '#')
+
+#define INTFROM5CHARS(a, b, c, d, e) ((((uint64_t)(a))<<32) | (((uint64_t)(b))<<24) | (((uint64_t)(c))<<16) | (((uint64_t)(d))<<8) | (uint64_t)(e))
+
+
+/* Set the given char value to ch (0<=ch<=255). */
+static inline void yxml_setchar(char *dest, unsigned ch) {
+ *(unsigned char *)dest = ch;
+}
+
+
+/* Similar to yxml_setchar(), but will convert ch (any valid unicode point) to
+ * UTF-8 and appends a '\0'. dest must have room for at least 5 bytes. */
+static void yxml_setutf8(char *dest, unsigned ch) {
+ if(ch <= 0x007F)
+ yxml_setchar(dest++, ch);
+ else if(ch <= 0x07FF) {
+ yxml_setchar(dest++, 0xC0 | (ch>>6));
+ yxml_setchar(dest++, 0x80 | (ch & 0x3F));
+ } else if(ch <= 0xFFFF) {
+ yxml_setchar(dest++, 0xE0 | (ch>>12));
+ yxml_setchar(dest++, 0x80 | ((ch>>6) & 0x3F));
+ yxml_setchar(dest++, 0x80 | (ch & 0x3F));
+ } else {
+ yxml_setchar(dest++, 0xF0 | (ch>>18));
+ yxml_setchar(dest++, 0x80 | ((ch>>12) & 0x3F));
+ yxml_setchar(dest++, 0x80 | ((ch>>6) & 0x3F));
+ yxml_setchar(dest++, 0x80 | (ch & 0x3F));
+ }
+ *dest = 0;
+}
+
+
+static inline yxml_ret_t yxml_datacontent(yxml_t *x, unsigned ch) {
+ yxml_setchar(x->data, ch);
+ x->data[1] = 0;
+ return YXML_CONTENT;
+}
+
+
+static inline yxml_ret_t yxml_datapi1(yxml_t *x, unsigned ch) {
+ yxml_setchar(x->data, ch);
+ x->data[1] = 0;
+ return YXML_PICONTENT;
+}
+
+
+static inline yxml_ret_t yxml_datapi2(yxml_t *x, unsigned ch) {
+ x->data[0] = '?';
+ yxml_setchar(x->data+1, ch);
+ x->data[2] = 0;
+ return YXML_PICONTENT;
+}
+
+
+static inline yxml_ret_t yxml_datacd1(yxml_t *x, unsigned ch) {
+ x->data[0] = ']';
+ yxml_setchar(x->data+1, ch);
+ x->data[2] = 0;
+ return YXML_CONTENT;
+}
+
+
+static inline yxml_ret_t yxml_datacd2(yxml_t *x, unsigned ch) {
+ x->data[0] = ']';
+ x->data[1] = ']';
+ yxml_setchar(x->data+2, ch);
+ x->data[3] = 0;
+ return YXML_CONTENT;
+}
+
+
+static inline yxml_ret_t yxml_dataattr(yxml_t *x, unsigned ch) {
+ /* Normalize attribute values according to the XML spec section 3.3.3. */
+ yxml_setchar(x->data, ch == 0x9 || ch == 0xa ? 0x20 : ch);
+ x->data[1] = 0;
+ return YXML_ATTRVAL;
+}
+
+
+static yxml_ret_t yxml_pushstack(yxml_t *x, char **res, unsigned ch) {
+ if(x->stacklen+2 >= x->stacksize)
+ return YXML_ESTACK;
+ x->stacklen++;
+ *res = (char *)x->stack+x->stacklen;
+ x->stack[x->stacklen] = ch;
+ x->stacklen++;
+ x->stack[x->stacklen] = 0;
+ return YXML_OK;
+}
+
+
+static yxml_ret_t yxml_pushstackc(yxml_t *x, unsigned ch) {
+ if(x->stacklen+1 >= x->stacksize)
+ return YXML_ESTACK;
+ x->stack[x->stacklen] = ch;
+ x->stacklen++;
+ x->stack[x->stacklen] = 0;
+ return YXML_OK;
+}
+
+
+static void yxml_popstack(yxml_t *x) {
+ do
+ x->stacklen--;
+ while(x->stack[x->stacklen]);
+}
+
+
+static inline yxml_ret_t yxml_elemstart (yxml_t *x, unsigned ch) { return yxml_pushstack(x, &x->elem, ch); }
+static inline yxml_ret_t yxml_elemname (yxml_t *x, unsigned ch) { return yxml_pushstackc(x, ch); }
+static inline yxml_ret_t yxml_elemnameend(yxml_t *x, unsigned ch) { return YXML_ELEMSTART; }
+
+
+/* Also used in yxml_elemcloseend(), since this function just removes the last
+ * element from the stack and returns ELEMEND. */
+static yxml_ret_t yxml_selfclose(yxml_t *x, unsigned ch) {
+ yxml_popstack(x);
+ if(x->stacklen) {
+ x->elem = (char *)x->stack+x->stacklen-1;
+ while(*(x->elem-1))
+ x->elem--;
+ return YXML_ELEMEND;
+ }
+ x->elem = (char *)x->stack;
+ x->state = YXMLS_misc3;
+ return YXML_ELEMEND;
+}
+
+
+static inline yxml_ret_t yxml_elemclose(yxml_t *x, unsigned ch) {
+ if(*((unsigned char *)x->elem) != ch)
+ return YXML_ECLOSE;
+ x->elem++;
+ return YXML_OK;
+}
+
+
+static inline yxml_ret_t yxml_elemcloseend(yxml_t *x, unsigned ch) {
+ if(*x->elem)
+ return YXML_ECLOSE;
+ return yxml_selfclose(x, ch);
+}
+
+
+static inline yxml_ret_t yxml_attrstart (yxml_t *x, unsigned ch) { return yxml_pushstack(x, &x->attr, ch); }
+static inline yxml_ret_t yxml_attrname (yxml_t *x, unsigned ch) { return yxml_pushstackc(x, ch); }
+static inline yxml_ret_t yxml_attrnameend(yxml_t *x, unsigned ch) { return YXML_ATTRSTART; }
+static inline yxml_ret_t yxml_attrvalend (yxml_t *x, unsigned ch) { yxml_popstack(x); return YXML_ATTREND; }
+
+
+static inline yxml_ret_t yxml_pistart (yxml_t *x, unsigned ch) { return yxml_pushstack(x, &x->pi, ch); }
+static inline yxml_ret_t yxml_piname (yxml_t *x, unsigned ch) { return yxml_pushstackc(x, ch); }
+static inline yxml_ret_t yxml_piabort (yxml_t *x, unsigned ch) { yxml_popstack(x); return YXML_OK; }
+static inline yxml_ret_t yxml_pinameend(yxml_t *x, unsigned ch) {
+ return (x->pi[0]|32) == 'x' && (x->pi[1]|32) == 'm' && (x->pi[2]|32) == 'l' && !x->pi[3] ? YXML_ESYN : YXML_PISTART;
+}
+static inline yxml_ret_t yxml_pivalend (yxml_t *x, unsigned ch) { yxml_popstack(x); x->pi = (char *)x->stack; return YXML_PIEND; }
+
+
+static inline yxml_ret_t yxml_refstart(yxml_t *x, unsigned ch) {
+ memset(x->data, 0, sizeof(x->data));
+ x->reflen = 0;
+ return YXML_OK;
+}
+
+
+static yxml_ret_t yxml_ref(yxml_t *x, unsigned ch) {
+ if(x->reflen >= sizeof(x->data)-1)
+ return YXML_EREF;
+ yxml_setchar(x->data+x->reflen, ch);
+ x->reflen++;
+ return YXML_OK;
+}
+
+
+static yxml_ret_t yxml_refend(yxml_t *x, yxml_ret_t ret) {
+ unsigned char *r = (unsigned char *)x->data;
+ unsigned ch = 0;
+ if(*r == '#') {
+ if(r[1] == 'x')
+ for(r += 2; yxml_isHex((unsigned)*r); r++)
+ ch = (ch<<4) + (*r <= '9' ? *r-'0' : (*r|32)-'a' + 10);
+ else
+ for(r++; yxml_isNum((unsigned)*r); r++)
+ ch = (ch*10) + (*r-'0');
+ if(*r)
+ ch = 0;
+ } else {
+ uint64_t i = INTFROM5CHARS(r[0], r[1], r[2], r[3], r[4]);
+ ch =
+ i == INTFROM5CHARS('l','t', 0, 0, 0) ? '<' :
+ i == INTFROM5CHARS('g','t', 0, 0, 0) ? '>' :
+ i == INTFROM5CHARS('a','m','p', 0, 0) ? '&' :
+ i == INTFROM5CHARS('a','p','o','s',0) ? '\'':
+ i == INTFROM5CHARS('q','u','o','t',0) ? '"' : 0;
+ }
+
+ /* Codepoints not allowed in the XML 1.1 definition of a Char */
+ if(!ch || ch > 0x10FFFF || ch == 0xFFFE || ch == 0xFFFF || (ch-0xDFFF) < 0x7FF)
+ return YXML_EREF;
+ yxml_setutf8(x->data, ch);
+ return ret;
+}
+
+
+static inline yxml_ret_t yxml_refcontent(yxml_t *x, unsigned ch) { return yxml_refend(x, YXML_CONTENT); }
+static inline yxml_ret_t yxml_refattrval(yxml_t *x, unsigned ch) { return yxml_refend(x, YXML_ATTRVAL); }
+
+
+void yxml_init(yxml_t *x, void *stack, size_t stacksize) {
+ memset(x, 0, sizeof(*x));
+ x->line = 1;
+ x->stack = (unsigned char*)stack;
+ x->stacksize = stacksize;
+ *x->stack = 0;
+ x->elem = x->pi = x->attr = (char *)x->stack;
+ x->state = YXMLS_init;
+}
+
+
+yxml_ret_t yxml_parse(yxml_t *x, int _ch) {
+ /* Ensure that characters are in the range of 0..255 rather than -126..125.
+ * All character comparisons are done with positive integers. */
+ unsigned ch = (unsigned)(_ch+256) & 0xff;
+ if(!ch)
+ return YXML_ESYN;
+ x->total++;
+
+ /* End-of-Line normalization, "\rX", "\r\n" and "\n" are recognized and
+ * normalized to a single '\n' as per XML 1.0 section 2.11. XML 1.1 adds
+ * some non-ASCII character sequences to this list, but we can only handle
+ * ASCII here without making assumptions about the input encoding. */
+ if(x->ignore == ch) {
+ x->ignore = 0;
+ return YXML_OK;
+ }
+ x->ignore = (ch == 0xd) * 0xa;
+ if(ch == 0xa || ch == 0xd) {
+ ch = 0xa;
+ x->line++;
+ x->byte = 0;
+ }
+ x->byte++;
+
+ switch((yxml_state_t)x->state) {
+ case YXMLS_string:
+ if(ch == *x->string) {
+ x->string++;
+ if(!*x->string)
+ x->state = x->nextstate;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_attr0:
+ if(yxml_isName(ch))
+ return yxml_attrname(x, ch);
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_attr1;
+ return yxml_attrnameend(x, ch);
+ }
+ if(ch == (unsigned char)'=') {
+ x->state = YXMLS_attr2;
+ return yxml_attrnameend(x, ch);
+ }
+ break;
+ case YXMLS_attr1:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'=') {
+ x->state = YXMLS_attr2;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_attr2:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') {
+ x->state = YXMLS_attr3;
+ x->quote = ch;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_attr3:
+ if(yxml_isAttValue(ch))
+ return yxml_dataattr(x, ch);
+ if(ch == (unsigned char)'&') {
+ x->state = YXMLS_attr4;
+ return yxml_refstart(x, ch);
+ }
+ if(x->quote == ch) {
+ x->state = YXMLS_elem2;
+ return yxml_attrvalend(x, ch);
+ }
+ break;
+ case YXMLS_attr4:
+ if(yxml_isRef(ch))
+ return yxml_ref(x, ch);
+ if(ch == (unsigned char)'\x3b') {
+ x->state = YXMLS_attr3;
+ return yxml_refattrval(x, ch);
+ }
+ break;
+ case YXMLS_cd0:
+ if(ch == (unsigned char)']') {
+ x->state = YXMLS_cd1;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch))
+ return yxml_datacontent(x, ch);
+ break;
+ case YXMLS_cd1:
+ if(ch == (unsigned char)']') {
+ x->state = YXMLS_cd2;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch)) {
+ x->state = YXMLS_cd0;
+ return yxml_datacd1(x, ch);
+ }
+ break;
+ case YXMLS_cd2:
+ if(ch == (unsigned char)']')
+ return yxml_datacontent(x, ch);
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch)) {
+ x->state = YXMLS_cd0;
+ return yxml_datacd2(x, ch);
+ }
+ break;
+ case YXMLS_comment0:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment1;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_comment1:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment2;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_comment2:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment3;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch))
+ return YXML_OK;
+ break;
+ case YXMLS_comment3:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment4;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch)) {
+ x->state = YXMLS_comment2;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_comment4:
+ if(ch == (unsigned char)'>') {
+ x->state = x->nextstate;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_dt0:
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc1;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') {
+ x->state = YXMLS_dt1;
+ x->quote = ch;
+ x->nextstate = YXMLS_dt0;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'<') {
+ x->state = YXMLS_dt2;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch))
+ return YXML_OK;
+ break;
+ case YXMLS_dt1:
+ if(x->quote == ch) {
+ x->state = x->nextstate;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch))
+ return YXML_OK;
+ break;
+ case YXMLS_dt2:
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi0;
+ x->nextstate = YXMLS_dt0;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'!') {
+ x->state = YXMLS_dt3;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_dt3:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment1;
+ x->nextstate = YXMLS_dt0;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch)) {
+ x->state = YXMLS_dt4;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_dt4:
+ if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') {
+ x->state = YXMLS_dt1;
+ x->quote = ch;
+ x->nextstate = YXMLS_dt4;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_dt0;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch))
+ return YXML_OK;
+ break;
+ case YXMLS_elem0:
+ if(yxml_isName(ch))
+ return yxml_elemname(x, ch);
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_elem1;
+ return yxml_elemnameend(x, ch);
+ }
+ if(ch == (unsigned char)'/') {
+ x->state = YXMLS_elem3;
+ return yxml_elemnameend(x, ch);
+ }
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return yxml_elemnameend(x, ch);
+ }
+ break;
+ case YXMLS_elem1:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'/') {
+ x->state = YXMLS_elem3;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return YXML_OK;
+ }
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_attr0;
+ return yxml_attrstart(x, ch);
+ }
+ break;
+ case YXMLS_elem2:
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_elem1;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'/') {
+ x->state = YXMLS_elem3;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_elem3:
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return yxml_selfclose(x, ch);
+ }
+ break;
+ case YXMLS_enc0:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'=') {
+ x->state = YXMLS_enc1;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_enc1:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') {
+ x->state = YXMLS_enc2;
+ x->quote = ch;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_enc2:
+ if(yxml_isAlpha(ch)) {
+ x->state = YXMLS_enc3;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_enc3:
+ if(yxml_isEncName(ch))
+ return YXML_OK;
+ if(x->quote == ch) {
+ x->state = YXMLS_xmldecl6;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_etag0:
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_etag1;
+ return yxml_elemclose(x, ch);
+ }
+ break;
+ case YXMLS_etag1:
+ if(yxml_isName(ch))
+ return yxml_elemclose(x, ch);
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_etag2;
+ return yxml_elemcloseend(x, ch);
+ }
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return yxml_elemcloseend(x, ch);
+ }
+ break;
+ case YXMLS_etag2:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc2;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_init:
+ if(ch == (unsigned char)'\xef') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_misc0;
+ x->string = (unsigned char *)"\xbb\xbf";
+ return YXML_OK;
+ }
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_misc0;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'<') {
+ x->state = YXMLS_le0;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_le0:
+ if(ch == (unsigned char)'!') {
+ x->state = YXMLS_lee1;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_leq0;
+ return YXML_OK;
+ }
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_elem0;
+ return yxml_elemstart(x, ch);
+ }
+ break;
+ case YXMLS_le1:
+ if(ch == (unsigned char)'!') {
+ x->state = YXMLS_lee1;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi0;
+ x->nextstate = YXMLS_misc1;
+ return YXML_OK;
+ }
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_elem0;
+ return yxml_elemstart(x, ch);
+ }
+ break;
+ case YXMLS_le2:
+ if(ch == (unsigned char)'!') {
+ x->state = YXMLS_lee2;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi0;
+ x->nextstate = YXMLS_misc2;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'/') {
+ x->state = YXMLS_etag0;
+ return YXML_OK;
+ }
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_elem0;
+ return yxml_elemstart(x, ch);
+ }
+ break;
+ case YXMLS_le3:
+ if(ch == (unsigned char)'!') {
+ x->state = YXMLS_comment0;
+ x->nextstate = YXMLS_misc3;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi0;
+ x->nextstate = YXMLS_misc3;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_lee1:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment1;
+ x->nextstate = YXMLS_misc1;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'D') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_dt0;
+ x->string = (unsigned char *)"OCTYPE";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_lee2:
+ if(ch == (unsigned char)'-') {
+ x->state = YXMLS_comment1;
+ x->nextstate = YXMLS_misc2;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'[') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_cd0;
+ x->string = (unsigned char *)"CDATA[";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_leq0:
+ if(ch == (unsigned char)'x') {
+ x->state = YXMLS_xmldecl0;
+ x->nextstate = YXMLS_misc1;
+ return yxml_pistart(x, ch);
+ }
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_pi1;
+ x->nextstate = YXMLS_misc1;
+ return yxml_pistart(x, ch);
+ }
+ break;
+ case YXMLS_misc0:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'<') {
+ x->state = YXMLS_le0;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_misc1:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'<') {
+ x->state = YXMLS_le1;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_misc2:
+ if(ch == (unsigned char)'<') {
+ x->state = YXMLS_le2;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'&') {
+ x->state = YXMLS_misc2a;
+ return yxml_refstart(x, ch);
+ }
+ if(yxml_isChar(ch))
+ return yxml_datacontent(x, ch);
+ break;
+ case YXMLS_misc2a:
+ if(yxml_isRef(ch))
+ return yxml_ref(x, ch);
+ if(ch == (unsigned char)'\x3b') {
+ x->state = YXMLS_misc2;
+ return yxml_refcontent(x, ch);
+ }
+ break;
+ case YXMLS_misc3:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'<') {
+ x->state = YXMLS_le3;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_pi0:
+ if(yxml_isNameStart(ch)) {
+ x->state = YXMLS_pi1;
+ return yxml_pistart(x, ch);
+ }
+ break;
+ case YXMLS_pi1:
+ if(yxml_isName(ch))
+ return yxml_piname(x, ch);
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi4;
+ return yxml_pinameend(x, ch);
+ }
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_pi2;
+ return yxml_pinameend(x, ch);
+ }
+ break;
+ case YXMLS_pi2:
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi3;
+ return YXML_OK;
+ }
+ if(yxml_isChar(ch))
+ return yxml_datapi1(x, ch);
+ break;
+ case YXMLS_pi3:
+ if(ch == (unsigned char)'>') {
+ x->state = x->nextstate;
+ return yxml_pivalend(x, ch);
+ }
+ if(yxml_isChar(ch)) {
+ x->state = YXMLS_pi2;
+ return yxml_datapi2(x, ch);
+ }
+ break;
+ case YXMLS_pi4:
+ if(ch == (unsigned char)'>') {
+ x->state = x->nextstate;
+ return yxml_pivalend(x, ch);
+ }
+ break;
+ case YXMLS_std0:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'=') {
+ x->state = YXMLS_std1;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_std1:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') {
+ x->state = YXMLS_std2;
+ x->quote = ch;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_std2:
+ if(ch == (unsigned char)'y') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_std3;
+ x->string = (unsigned char *)"es";
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'n') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_std3;
+ x->string = (unsigned char *)"o";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_std3:
+ if(x->quote == ch) {
+ x->state = YXMLS_xmldecl8;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_ver0:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'=') {
+ x->state = YXMLS_ver1;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_ver1:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'\'' || ch == (unsigned char)'"') {
+ x->state = YXMLS_string;
+ x->quote = ch;
+ x->nextstate = YXMLS_ver2;
+ x->string = (unsigned char *)"1.";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_ver2:
+ if(yxml_isNum(ch)) {
+ x->state = YXMLS_ver3;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_ver3:
+ if(yxml_isNum(ch))
+ return YXML_OK;
+ if(x->quote == ch) {
+ x->state = YXMLS_xmldecl4;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl0:
+ if(ch == (unsigned char)'m') {
+ x->state = YXMLS_xmldecl1;
+ return yxml_piname(x, ch);
+ }
+ if(yxml_isName(ch)) {
+ x->state = YXMLS_pi1;
+ return yxml_piname(x, ch);
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi4;
+ return yxml_pinameend(x, ch);
+ }
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_pi2;
+ return yxml_pinameend(x, ch);
+ }
+ break;
+ case YXMLS_xmldecl1:
+ if(ch == (unsigned char)'l') {
+ x->state = YXMLS_xmldecl2;
+ return yxml_piname(x, ch);
+ }
+ if(yxml_isName(ch)) {
+ x->state = YXMLS_pi1;
+ return yxml_piname(x, ch);
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_pi4;
+ return yxml_pinameend(x, ch);
+ }
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_pi2;
+ return yxml_pinameend(x, ch);
+ }
+ break;
+ case YXMLS_xmldecl2:
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_xmldecl3;
+ return yxml_piabort(x, ch);
+ }
+ if(yxml_isName(ch)) {
+ x->state = YXMLS_pi1;
+ return yxml_piname(x, ch);
+ }
+ break;
+ case YXMLS_xmldecl3:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'v') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_ver0;
+ x->string = (unsigned char *)"ersion";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl4:
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_xmldecl5;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_xmldecl9;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl5:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_xmldecl9;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'e') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_enc0;
+ x->string = (unsigned char *)"ncoding";
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'s') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_std0;
+ x->string = (unsigned char *)"tandalone";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl6:
+ if(yxml_isSP(ch)) {
+ x->state = YXMLS_xmldecl7;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_xmldecl9;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl7:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_xmldecl9;
+ return YXML_OK;
+ }
+ if(ch == (unsigned char)'s') {
+ x->state = YXMLS_string;
+ x->nextstate = YXMLS_std0;
+ x->string = (unsigned char *)"tandalone";
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl8:
+ if(yxml_isSP(ch))
+ return YXML_OK;
+ if(ch == (unsigned char)'?') {
+ x->state = YXMLS_xmldecl9;
+ return YXML_OK;
+ }
+ break;
+ case YXMLS_xmldecl9:
+ if(ch == (unsigned char)'>') {
+ x->state = YXMLS_misc1;
+ return YXML_OK;
+ }
+ break;
+ }
+ return YXML_ESYN;
+}
+
+
+yxml_ret_t yxml_eof(yxml_t *x) {
+ if(x->state != YXMLS_misc3)
+ return YXML_EEOF;
+ return YXML_OK;
+}
+
+
+/* vim: set noet sw=4 ts=4: */