From b283df4073b25c470412cd922028c12ed79a30c7 Mon Sep 17 00:00:00 2001 From: Maxallica Date: Mon, 2 Feb 2026 01:37:18 +0100 Subject: [PATCH] Added complete TS6 Viewer implementation --- .gitignore | 2 + Dockerfile | 48 ++ LICENSE | 674 ++++++++++++++++++++++++++ README.md | 128 ++++- cmd/server/config.example.json | 24 + cmd/server/main.go | 44 ++ dark.png | Bin 0 -> 56987 bytes entrypoint.sh | 60 +++ go.mod | 3 + go.sum | 0 http/http-helper.go | 108 +++++ http/router.go | 124 +++++ http/view-models.go | 33 ++ internal/config/config.go | 34 ++ internal/ts6/channels.go | 207 ++++++++ internal/ts6/clients.go | 50 ++ internal/ts6/http.go | 39 ++ internal/ts6/serverinfo.go | 90 ++++ internal/ts6/status.go | 7 + internal/web/static/dark.css | 114 +++++ internal/web/static/light.css | 110 +++++ internal/web/static/ts6viewer.js | 153 ++++++ internal/web/templates/ts6viewer.html | 70 +++ light.png | Bin 0 -> 56139 bytes 24 files changed, 2121 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 cmd/server/config.example.json create mode 100644 cmd/server/main.go create mode 100644 dark.png create mode 100644 entrypoint.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100644 http/http-helper.go create mode 100644 http/router.go create mode 100644 http/view-models.go create mode 100644 internal/config/config.go create mode 100644 internal/ts6/channels.go create mode 100644 internal/ts6/clients.go create mode 100644 internal/ts6/http.go create mode 100644 internal/ts6/serverinfo.go create mode 100644 internal/ts6/status.go create mode 100644 internal/web/static/dark.css create mode 100644 internal/web/static/light.css create mode 100644 internal/web/static/ts6viewer.js create mode 100644 internal/web/templates/ts6viewer.html create mode 100644 light.png diff --git a/.gitignore b/.gitignore index aaadf73..91f9c96 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ go.work.sum # Editor/IDE # .idea/ # .vscode/ + +cmd/server/config.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f6f2c43 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +# ------------------------- +# 1) Builder stage +# ------------------------- +FROM golang:1.20-alpine AS builder + +# Install CA certificates required for building +RUN apk add --no-cache ca-certificates + +WORKDIR /app + +# Copy local repository into the build image (portable across OSes) +COPY . /app + +# Ensure config.example exists; keep a fallback copy +RUN cp cmd/server/config.example.json cmd/server/config.json || true + +# Normalize go.mod 'go' directive to a valid major.minor form to avoid build errors +RUN if [ -f go.mod ]; then \ + sed -E -i 's/^go[[:space:]]+[0-9]+(\.[0-9]+){1,2}$/go 1.20/' go.mod || true; \ + fi + +# Build a static Linux binary for the server +RUN cd cmd/server && CGO_ENABLED=0 GOOS=linux go build -o ts6viewer . + +# ------------------------- +# 2) Runtime stage +# ------------------------- +FROM alpine:latest + +# Install CA certificates and gettext for envsubst +RUN apk add --no-cache ca-certificates gettext + +# Set working directory to where the server expects config.json when using relative path +WORKDIR /app/cmd/server + +# Copy the built app and assets from builder +COPY --from=builder /app /app + +# Copy entrypoint script and ensure it's executable +COPY entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh || true +RUN chmod +x /app/cmd/server/ts6viewer || true + +# Expose the internal port the app listens on +EXPOSE 8080 + +# Use the entrypoint to generate config and start the app +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /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/README.md b/README.md index acf5ff5..977098a 100644 --- a/README.md +++ b/README.md @@ -1 +1,127 @@ -# ts6-viewer \ No newline at end of file +# TS6 Viewer +

+ Dark Theme + Light Theme +

+ +A lightweight, fast, and modern web viewer for **TeamSpeak 6** servers. +TS6 Viewer connects to the TeamSpeak 6 WebQuery API, retrieves live server and channel data, and displays it in a clean, responsive web interface with optional dark/light themes. + +The viewer is designed to be: + +- Simple to deploy +- Fast and lightweight +- Fully client‑side auto‑refreshing +- Compatible with any TS6 server exposing WebQuery +- Customizable via a single `config.json` file + +--- + +## Features + +- Live TeamSpeak 6 server viewer +- Auto‑refresh with configurable interval +- Dark and light themes +- Channel tree rendering with clients +- Spacer and full‑width channel support +- Caching + rate‑limit protection +- Works on Windows, Linux, and Docker‑friendly environments + +--- + +## Configuration + +The application uses a `config.json` file located in the same directory as the executable. + +### Example `config.json` + +```json +{ + "_comment": "TS6 Viewer Configuration File", + "_comment2": "Rename this file to config.json and adjust the values to your setup.", + + "server_port": "8080", + "_comment_server_port": "The port on which the TS6 Viewer web interface will be available.", + + "theme": "dark", + "_comment_theme": "Choose between 'light' or 'dark' for the viewer theme.", + + "refresh_interval": 60, + "_comment_refresh_interval": "How often the viewer should auto-refresh (in seconds).", + + "teamspeak6": { + "base_url": "http://192.168.178.195:57007", + "_comment_base_url": "The WebQuery HTTP endpoint of your TeamSpeak 6 server. Usually http://:10080 or your mapped port.", + + "api_key": "geheim", + "_comment_api_key": "Your TeamSpeak 6 ServerQuery API key. It is shown ONCE in the server logs on first startup. If lost, create a new one.", + + "server_id": 1, + "_comment_server_id": "The ID of the virtual server you want to display. Default is usually 1." + } +} +``` + +--- + +## Building the Program + +The project is written in **Go**, so building is extremely simple. + +### Prerequisites + +- Go 1.20 or newer +- Git (optional) + +--- + +## Build on Linux +```bash +git clone https://github.com/Maxallica/ts6-viewer.git +cd ts6viewer + +go build -o ts6viewer +``` + +## Build Windows binary on Linux +```bash +cd "*/ts6-viewer/cmd/server" + +GOOS=windows GOARCH=amd64 go build -o ts6viewer.exe +``` + +## Running on Linux +```bash +./ts6viewer +``` + +## Build on Windows +```bash +git clone https://github.com/Maxallica/ts6-viewer.git +cd ts6viewer + +go build -o ts6viewer.exe +``` + +## Build Linux binary on Windows +```bash +cd "*/ts6-viewer/cmd/server" + +$env:GOOS="linux" +$env:GOARCH="amd64" +go build -o ts6viewer +``` + +## Running on Windows +```bash +.\ts6viewer.exe +``` + +--- + +## Navigate to the ts6viewer page +http(s)\://\:\/ts6viewer + +--- + +## ❤️ Made with love in Germany \ No newline at end of file diff --git a/cmd/server/config.example.json b/cmd/server/config.example.json new file mode 100644 index 0000000..899a462 --- /dev/null +++ b/cmd/server/config.example.json @@ -0,0 +1,24 @@ +{ + "_comment": "TS6 Viewer Configuration File", + "_comment2": "Rename this file to config.json and adjust the values to your setup.", + + "server_port": "${SERVER_PORT}", + "_comment_server_port": "The port on which the TS6 Viewer web interface will be available. Usually '8080'", + + "theme": "${THEME}", + "_comment_theme": "Choose between 'light' or 'dark' for the viewer theme. Usually 'dark'", + + "refresh_interval": "${REFRESH_INTERVAL}", + "_comment_refresh_interval": "How often the viewer should auto-refresh (in seconds). Usually '60'", + + "teamspeak6": { + "base_url": "${BASE_URL}", + "_comment_base_url": "The WebQuery HTTP endpoint of your TeamSpeak 6 server. Usually http://192.168.178.2:10080.", + + "api_key": "${API_KEY}", + "_comment_api_key": "Your TeamSpeak 6 ServerQuery API key. It is shown ONCE in the server logs on first startup. If lost, you have to create a whole new server.", + + "server_id": "${SERVER_ID}", + "_comment_server_id": "The ID of the virtual server you want to display. Default is usually '1'." + } +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..c39a669 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "log" + "net" + "net/http" + + router "ts6-viewer/http" + "ts6-viewer/internal/config" +) + +func main() { + log.Println("Starting TS6 Viewer...") + + // Load config.json + log.Println("Loading config.json") + cfg, err := config.Load("config.json") + if err != nil { + log.Fatal(err) + } + serverPort := cfg.ServerPort + log.Println("Successfully loaded config.json") + + // Create HTTP router + r := router.NewRouter(*cfg) + + // Create listener first + ln, err := net.Listen("tcp", ":"+serverPort) + if err != nil { + log.Fatal("Failed to bind port:", err) + } + + // Now we are guaranteed the port is bound → safe callback + log.Printf("HTTP server is now listening on port %s", serverPort) + + // Start the server (blocking) + srv := &http.Server{ + Handler: r, + } + + if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed { + log.Fatal("HTTP server failed:", err) + } +} diff --git a/dark.png b/dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4ec32ff3c311f220dc30b874ca0da3f31a5b5235 GIT binary patch literal 56987 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 literal 0 HcmV?d00001 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..52f8841 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -eu + +# Paths +EXAMPLE="/app/cmd/server/config.example.json" +TARGET_REL="config.json" # relative path in WORKDIR (/app/cmd/server) +TARGET_ABS="/app/cmd/server/config.json" # absolute path for clarity +BINARY="/app/cmd/server/ts6viewer" + +# Export variables used in the template (use environment variables only) +export API_KEY="${API_KEY:-}" +export SERVER_PORT="${SERVER_PORT:-8080}" +export BASE_URL="${BASE_URL:-http://127.0.0.1:10080}" +export SERVER_ID="${SERVER_ID:-1}" +export THEME="${THEME:-dark}" +export REFRESH_INTERVAL="${REFRESH_INTERVAL:-60}" + +# Minimal, useful debug output +echo "=== STARTUP ===" +# Mask API key for logs: show first 4 chars if present +if [ -n "$API_KEY" ]; then + echo "API_KEY=****${API_KEY#????}" +else + echo "API_KEY=(empty)" +fi +echo "PORT=${SERVER_PORT} BASE_URL=${BASE_URL} THEME=${THEME}" +echo "Working dir: $(pwd)" +echo + +# Quick binary check +if [ -x "$BINARY" ]; then + echo "Binary: OK -> $BINARY" +else + echo "Binary: MISSING or not executable -> $BINARY" >&2 +fi + +# Ensure example exists; if not, start the binary directly +if [ ! -f "$EXAMPLE" ]; then + echo "config.example.json not found at $EXAMPLE, starting binary directly" >&2 + exec "$BINARY" +fi + +# Generate config.json from config.example.json using envsubst +envsubst < "$EXAMPLE" > "$TARGET_ABS" || { + echo "Failed to generate config.json" >&2 + exit 1 +} + +# Ensure relative copy exists for relative loads +cp -f "$TARGET_ABS" "$TARGET_REL" || true + +# Show compact confirmation and a short preview of the generated config +size=$(wc -c < "$TARGET_ABS" 2>/dev/null || echo 0) +echo "Generated config.json (${size} bytes). Preview:" +echo "-----" +head -n 20 "$TARGET_ABS" || true +echo "-----" + +echo "Starting server..." +exec "$BINARY" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..804be69 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module ts6-viewer + +go 1.25.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/http/http-helper.go b/http/http-helper.go new file mode 100644 index 0000000..9eee6e4 --- /dev/null +++ b/http/http-helper.go @@ -0,0 +1,108 @@ +package http + +import ( + "net" + "net/http" + "time" + "ts6-viewer/internal/ts6" +) + +func getIP(r *http.Request) string { + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return host +} + +func allowRequest(ip string) bool { + mu.Lock() + defer mu.Unlock() + + last, exists := lastRequestTime[ip] + if exists && time.Since(last) < rateLimitWindow { + return false + } + + lastRequestTime[ip] = time.Now() + return true +} + +func getViewerData(baseURL, apiKey string, serverID string) (ViewerData, error) { + mu.Lock() + defer mu.Unlock() + + if time.Since(cacheTimestamp) < cacheTTL { + return cacheData, nil + } + + serverInfo, err := ts6.GetServerInfo(baseURL, apiKey, serverID) + if err != nil { + return ViewerData{}, err + } + + clients, err := ts6.GetClientList(baseURL, apiKey, serverID) + if err != nil { + return ViewerData{}, err + } + + channelTree, err := ts6.GetChannelTree(baseURL, apiKey, serverID, clients) + if err != nil { + return ViewerData{}, err + } + + view := ViewerData{ + Server: mapServer(serverInfo), + ChannelTree: mapChannelTree(channelTree), + } + + cacheData = view + cacheTimestamp = time.Now() + + return view, nil +} + +func mapServer(s *ts6.ServerInfo) *ServerView { + return &ServerView{ + Name: s.Name, + ClientsOnline: s.ClientsOnline, + MaxClients: s.MaxClients, + UptimePretty: s.UptimePretty, + ChannelsOnline: s.ChannelsOnline, + HostBannerURL: s.HostBannerURL, + ClientConnections: s.ClientConnections, + } +} + +func mapClient(c *ts6.Client) *ClientView { + return &ClientView{ + Nickname: c.Nickname, + } +} + +func mapChannel(ch *ts6.Channel) *ChannelView { + out := &ChannelView{ + Name: ch.Name, + Type: ch.Type, + Align: ch.Align, + FullWidth: ch.FullWidth, + } + + for _, c := range ch.Clients { + out.Clients = append(out.Clients, mapClient(c)) + } + + for _, child := range ch.Children { + out.Children = append(out.Children, mapChannel(child)) + } + + return out +} + +func mapChannelTree(tree []*ts6.Channel) []*ChannelView { + out := make([]*ChannelView, 0, len(tree)) + for _, ch := range tree { + out = append(out, mapChannel(ch)) + } + return out +} diff --git a/http/router.go b/http/router.go new file mode 100644 index 0000000..1b4a4da --- /dev/null +++ b/http/router.go @@ -0,0 +1,124 @@ +package http + +import ( + "encoding/json" + "html/template" + "log" + "net/http" + "os" + "path/filepath" + "strconv" + "sync" + "time" + + "ts6-viewer/internal/config" +) + +var ( + cacheData ViewerData + cacheTimestamp time.Time + cacheTTL = 3 * time.Second + + lastRequestTime = make(map[string]time.Time) + rateLimitWindow = 1 * time.Second + + mu sync.Mutex +) + +func NewRouter(cfg config.Config) http.Handler { + baseURL := cfg.Teamspeak6.BaseURL + apiKey := cfg.Teamspeak6.ApiKey + serverID := cfg.Teamspeak6.ServerID + theme := cfg.Theme + refreshIntervalStr := cfg.RefreshInterval + + refreshInterval, err := strconv.Atoi(refreshIntervalStr) + if err != nil || refreshInterval <= 0 { + refreshIntervalStr = "60" + } + + mux := http.NewServeMux() + + wd, err := os.Getwd() + if err != nil { + log.Fatal("cannot get working directory:", err) + } + + tmplPath := filepath.Join(wd, "..", "..", "internal", "web", "templates", "ts6viewer.html") + tmpl := template.Must(template.ParseFiles(tmplPath)) + + staticPath := filepath.Join(wd, "..", "..", "internal", "web", "static") + mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticPath)))) + + // ----------------------------- + // JSON endpoint (/ts6viewer/data + /ts6viewer/data/) + // ----------------------------- + dataHandler := func(w http.ResponseWriter, r *http.Request) { + ip := getIP(r) + + var raw ViewerData + var err error + + if allowRequest(ip) { + raw, err = getViewerData(baseURL, apiKey, serverID) + } else { + raw = cacheData + } + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + raw.Theme = theme + raw.RefreshInterval = refreshIntervalStr + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(raw) + } + + mux.HandleFunc("/ts6viewer/data", dataHandler) + mux.HandleFunc("/ts6viewer/data/", dataHandler) + + // ----------------------------- + // HTML view (/ts6viewer + /ts6viewer/) + // ----------------------------- + viewHandler := func(w http.ResponseWriter, r *http.Request) { + ip := getIP(r) + + var data ViewerData + var err error + + if allowRequest(ip) { + data, err = getViewerData(baseURL, apiKey, serverID) + } else { + data = cacheData + } + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data.Theme = theme + data.RefreshInterval = refreshIntervalStr + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := tmpl.Execute(w, data); err != nil { + log.Println("template error:", err) + } + } + + mux.HandleFunc("/ts6viewer", viewHandler) + mux.HandleFunc("/ts6viewer/", viewHandler) + + // ----------------------------- + // Health endpoint + // ----------------------------- + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("TS6Viewer is running!")) + }) + + return mux +} diff --git a/http/view-models.go b/http/view-models.go new file mode 100644 index 0000000..941e05c --- /dev/null +++ b/http/view-models.go @@ -0,0 +1,33 @@ +package http + +import "ts6-viewer/internal/ts6" + +type ViewerData struct { + Server *ServerView + ChannelTree []*ChannelView + Theme string + RefreshInterval string +} + +type ServerView struct { + Name string + ClientsOnline string + MaxClients string + UptimePretty string + ChannelsOnline string + HostBannerURL string + ClientConnections string +} + +type ClientView struct { + Nickname string +} + +type ChannelView struct { + Name string + Type ts6.ChannelType + Align ts6.Aligned + FullWidth bool + Clients []*ClientView + Children []*ChannelView +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..91d282d --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,34 @@ +package config + +import ( + "encoding/json" + "os" +) + +type Config struct { + ServerPort string `json:"server_port"` + Theme string `json:"theme"` + + Teamspeak6 struct { + BaseURL string `json:"base_url"` + ApiKey string `json:"api_key"` + ServerID string `json:"server_id"` + } `json:"teamspeak6"` + + RefreshInterval string `json:"refresh_interval"` +} + +func Load(path string) (*Config, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + var cfg Config + if err := json.NewDecoder(f).Decode(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/internal/ts6/channels.go b/internal/ts6/channels.go new file mode 100644 index 0000000..f2a880c --- /dev/null +++ b/internal/ts6/channels.go @@ -0,0 +1,207 @@ +package ts6 + +import ( + "fmt" + "regexp" + "strings" +) + +// Channel represents a TeamSpeak channel +type Channel struct { + CID string `json:"cid"` + PID string `json:"pid"` + ChannelOrder string `json:"channel_order"` + Name string `json:"channel_name"` + Topic string `json:"channel_topic"` + FlagPermanent string `json:"channel_flag_permanent"` + FlagSemiPermanent string `json:"channel_flag_semi_permanent"` + FlagDefault string `json:"channel_flag_default"` + FlagPassword string `json:"channel_flag_password"` + MaxClients string `json:"channel_maxclients"` + MaxFamilyClients string `json:"channel_maxfamilyclients"` + NeededTalkPower string `json:"channel_needed_talk_power"` + + // Additional informations, that wil added after response parsing + Type ChannelType `json:"-"` + Align Aligned `json:"-"` + FullWidth bool `json:"-"` + + Children []*Channel `json:"-"` + + Clients []*Client `json:"-"` +} + +// ChannelListResponse represents the TS6 API response +type ChannelListResponse struct { + Body []Channel `json:"body"` + Status Status `json:"status"` +} + +// ChannelType defines the type of a channel or spacer +type ChannelType int + +const ( + NormalChannel ChannelType = iota // Regular TS6 channel + SolidSpacer // ___ + DashSpacer // --- + DotSpacer // ... + DashDotSpacer // -.- + DashDotDotSpacer // -.. + AlignedSpacer // [cSpacer#], [lSpacer#], [rSpacer#] + RepeatingSpacer // [*spacer#]X + BlankSpacer // Empty spacer like [cSpacer0] +) + +// Aligned defines the alignment for aligned spacers +type Aligned int + +const ( + AlignLeft Aligned = iota + AlignCenter + AlignRight +) + +// GetChannelList returns all channels for a virtual server +func GetChannelList(baseURL, apiKey string, serverID string) ([]Channel, error) { + var resp ChannelListResponse + + err := doGET( + baseURL, + apiKey, + fmt.Sprintf("/%s/channellist", serverID), + &resp, + ) + if err != nil { + return nil, err + } + + if resp.Status.Code != 0 { + return nil, fmt.Errorf("ts6 error %d: %s", resp.Status.Code, resp.Status.Message) + } + + // Parse each channel name and set the additional fields + for i := range resp.Body { + ch := &resp.Body[i] + + chType, align, fullWidth, resolved := ParseChannelName(ch.Name) + + ch.Type = chType + ch.Align = align + ch.FullWidth = fullWidth + ch.Name = resolved + } + + return resp.Body, nil +} + +// GetChannelTree returns all channels including the online clients in a tree structure for a virtual server +func GetChannelTree(baseURL, apiKey string, serverID string, clients []Client) ([]*Channel, error) { + channels, err := GetChannelList(baseURL, apiKey, serverID) + if err != nil { + return nil, err + } + + channelHierachy := buildChannelTree(clients, channels) + return channelHierachy, nil +} + +func buildChannelTree(clients []Client, channels []Channel) []*Channel { + lookup := make(map[string]*Channel) + for i := range channels { + lookup[channels[i].CID] = &channels[i] + } + + for i := range clients { + cl := &clients[i] + if ch, ok := lookup[cl.CID]; ok { + ch.Clients = append(ch.Clients, cl) + } + } + + var roots []*Channel + + for i := range channels { + ch := &channels[i] + + if ch.PID == "0" { + roots = append(roots, ch) + } else { + parent, ok := lookup[ch.PID] + if ok { + parent.Children = append(parent.Children, ch) + } + } + } + + return roots +} + +// ParseChannelName parses TeamSpeak spacer syntax. +// It removes spacer commands and expands spacer patterns to a fixed width. +func ParseChannelName(name string) (ChannelType, Aligned, bool, string) { + name = strings.TrimSpace(name) + + reCmd := regexp.MustCompile(`(?i)\[(c|l|r)?spacer\d*\]`) + cmd := reCmd.FindStringSubmatch(name) + + if len(cmd) > 0 { + align := AlignLeft + if len(cmd) > 1 { + switch strings.ToLower(cmd[1]) { + case "c": + align = AlignCenter + case "r": + align = AlignRight + } + } + + // Remove TS spacer command only + clean := strings.TrimSpace(reCmd.ReplaceAllString(name, "")) + + // Blank spacer + if clean == "" { + return BlankSpacer, align, false, "" + } + + // Line spacer (___ --- ...) + if isSpacerPattern(clean) { + return SolidSpacer, align, true, clean + } + + // Aligned spacer with text + return AlignedSpacer, align, false, clean + } + + return NormalChannel, AlignLeft, false, name +} + +func isSpacerPattern(s string) bool { + switch s { + case "___", "---", "...", "-.-", "-..": + return true + default: + return false + } +} + +// repeatToWidth repeats a spacer pattern until a fixed width is filled. +// Width is approximated using a monospace character width. +func repeatToWidth(pattern string, targetPx int) string { + const charWidth = 8 // px per character (approximation) + + if pattern == "" { + return "" + } + + patternWidth := len([]rune(pattern)) * charWidth + if patternWidth == 0 { + return pattern + } + + repeats := targetPx / patternWidth + if repeats < 1 { + repeats = 1 + } + + return strings.Repeat(pattern, repeats) +} diff --git a/internal/ts6/clients.go b/internal/ts6/clients.go new file mode 100644 index 0000000..758e0f9 --- /dev/null +++ b/internal/ts6/clients.go @@ -0,0 +1,50 @@ +package ts6 + +import "fmt" + +// Client represents an online TeamSpeak client +type Client struct { + CID string `json:"cid"` + CLID string `json:"clid"` + DatabaseID string `json:"client_database_id"` + Nickname string `json:"client_nickname"` + ClientType string `json:"client_type"` +} + +// ClientListResponse represents the TS6 API response +type ClientListResponse struct { + Body []Client `json:"body"` + Status Status `json:"status"` +} + +// GetClientList returns all connected clients for a virtual server +func GetClientList(baseURL, apiKey string, serverID string) ([]Client, error) { + var resp ClientListResponse + + err := doGET( + baseURL, + apiKey, + fmt.Sprintf("/%s/clientlist", serverID), + &resp, + ) + if err != nil { + return nil, err + } + + if resp.Status.Code != 0 { + return nil, fmt.Errorf( + "ts6 error %d: %s", + resp.Status.Code, + resp.Status.Message, + ) + } + + filtered := make([]Client, 0, len(resp.Body)) + for _, c := range resp.Body { + if c.DatabaseID != "1" { + filtered = append(filtered, c) + } + } + + return filtered, nil +} diff --git a/internal/ts6/http.go b/internal/ts6/http.go new file mode 100644 index 0000000..581704b --- /dev/null +++ b/internal/ts6/http.go @@ -0,0 +1,39 @@ +package ts6 + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +// doGET performs an authenticated HTTP GET request against TS6 +func doGET(baseURL, apiKey, path string, out any) error { + req, err := http.NewRequest( + http.MethodGet, + baseURL+path, + nil, + ) + if err != nil { + return err + } + + req.Header.Set("x-api-key", apiKey) + req.Header.Set("Accept", "application/json") + + client := &http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("http error: %s", resp.Status) + } + + return json.NewDecoder(resp.Body).Decode(out) +} diff --git a/internal/ts6/serverinfo.go b/internal/ts6/serverinfo.go new file mode 100644 index 0000000..8aeff82 --- /dev/null +++ b/internal/ts6/serverinfo.go @@ -0,0 +1,90 @@ +package ts6 + +import ( + "fmt" + "strconv" +) + +// ServerInfo represents basic virtual server information +type ServerInfo struct { + ServerID string `json:"virtualserver_id"` + Name string `json:"virtualserver_name"` + Uptime string `json:"virtualserver_uptime"` + ClientsOnline string `json:"virtualserver_clientsonline"` + MaxClients string `json:"virtualserver_maxclients"` + ChannelsOnline string `json:"virtualserver_channelsonline"` + HostBannerURL string `json:"virtualserver_hostbanner_url"` + HostBannerGfxURL string `json:"virtualserver_hostbanner_gfx_url"` + NeededIdentitySecurity string `json:"virtualserver_needed_identity_security_level"` + QueryClientConnections string `json:"virtualserver_query_client_connections"` + ClientConnections string `json:"virtualserver_client_connections"` + + UptimePretty string `json:"-"` +} + +// ServerInfoResponse represents the TS6 API response +type ServerInfoResponse struct { + Body []ServerInfo `json:"body"` + Status Status `json:"status"` +} + +// GetServerInfo retrieves information about a virtual server +func GetServerInfo(baseURL, apiKey string, serverID string) (*ServerInfo, error) { + var resp ServerInfoResponse + + err := doGET( + baseURL, + apiKey, + fmt.Sprintf("/%s/serverinfo", serverID), + &resp, + ) + if err != nil { + return nil, err + } + + if resp.Status.Code != 0 { + return nil, fmt.Errorf( + "ts6 error %d: %s", + resp.Status.Code, + resp.Status.Message, + ) + } + + if len(resp.Body) != 1 { + return nil, fmt.Errorf( + "unexpected serverinfo result count: %d", + len(resp.Body), + ) + } + + info := resp.Body[0] + + if n, err := strconv.Atoi(info.ClientsOnline); err == nil { + n-- + if n < 0 { + n = 0 + } + info.ClientsOnline = strconv.Itoa(n) + } + + modifiedInfo := &info + modifiedInfo.UptimePretty = formatUptime(modifiedInfo.Uptime) + + return modifiedInfo, nil +} + +func formatUptime(secondsStr string) string { + sec, err := strconv.Atoi(secondsStr) + if err != nil || sec < 0 { + return secondsStr + } + + days := sec / 86400 + sec %= 86400 + hours := sec / 3600 + sec %= 3600 + minutes := sec / 60 + seconds := sec % 60 + + return fmt.Sprintf("%dD %02d:%02d:%02d", days, hours, minutes, seconds) +} diff --git a/internal/ts6/status.go b/internal/ts6/status.go new file mode 100644 index 0000000..3f856d2 --- /dev/null +++ b/internal/ts6/status.go @@ -0,0 +1,7 @@ +package ts6 + +// Status represents the standard TS6 API status block +type Status struct { + Code int `json:"code"` + Message string `json:"message"` +} diff --git a/internal/web/static/dark.css b/internal/web/static/dark.css new file mode 100644 index 0000000..9c25174 --- /dev/null +++ b/internal/web/static/dark.css @@ -0,0 +1,114 @@ +body { + font-family: 'Roboto', sans-serif; + background: #121212; + color: #e5e5e5; + padding: 0; + margin: 0; + font-size: 13px; +} + +h1, h2 { + margin: 6px 0; + padding-left: 6px; + font-size: 15px; + font-weight: bold; + color: #ffffff; +} + +#refreshButton { + cursor: default!important; + position: fixed; + top: 8px; + right: 8px; + background: #1f1f1f; + border: 1px solid #333; + padding: 6px 10px; + border-radius: 4px; + font-size: 14px; + font-weight: bold; + color: #e5e5e5; + z-index: 999; +} + +#refreshButtonText { + font-weight: bold; +} + +.row { + white-space: pre; + line-height: 16px; + font-size: 13px; + padding-left: 6px; + color: #e5e5e5; +} + +.row.spacer { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.row.spacer.full-width { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.row.client { + white-space: normal; + color: #ffffff; +} + +.channel { + color: #ffffff; + padding-left: 10px; +} + +.spacer { + color: #888; + white-space: pre; + font-weight: bold; + font-family: 'Roboto', sans-serif; + line-height: 16px; +} + +.spacer-mono { + font-family: "Courier New", monospace; +} + +.spacer-left { text-align: left; } +.spacer-center { text-align: center; } +.spacer-right { text-align: right; } + +.children { + margin-left: 10px; +} + +.server-info { + margin: 6px; + padding: 6px; + border: 1px solid #333; + background: #1a1a1a; + font-size: 12px; + color: #ccc; +} + +.server-info span { + display: inline-block; + width: 150px; + color: #bbb; +} + +.client { + padding-left: 10px; + color: #ffffff; +} + +.status-dot { + display: inline-block; + width: 8px; + height: 8px; + background-color: #4CAF50; + border-radius: 50%; + margin-right: 6px; + position: relative; + top: -1px; +} diff --git a/internal/web/static/light.css b/internal/web/static/light.css new file mode 100644 index 0000000..b9c47b0 --- /dev/null +++ b/internal/web/static/light.css @@ -0,0 +1,110 @@ +body { + font-family: 'Roboto', sans-serif; + background: #ffffff; + color: #000000; + padding: 0; + margin: 0; + font-size: 13px; +} + +h1, h2 { + margin: 6px 0; + padding-left: 6px; + font-size: 15px; + font-weight: bold; + color: #222; +} + +#refreshButton { + cursor: default!important; + position: fixed; + top: 8px; + right: 8px; + background: #eee; + border: 1px solid #ccc; + padding: 6px 10px; + border-radius: 4px; + font-size: 14px; + font-weight: bold; + z-index: 999; +} + +#refreshButtonText { + font-weight: bold; +} + +.row { + white-space: pre; + line-height: 16px; + font-size: 13px; + padding-left: 6px; +} + +.row.spacer { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.row.spacer.full-width { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.row.client { + white-space: normal; +} + +.channel { + color: #000; + padding-left: 10px; +} + +.spacer { + color: #777; + white-space: pre; + font-weight: bold; + font-family: 'Roboto', sans-serif; + line-height: 16px; +} + +.spacer-mono { + font-family: "Courier New", monospace; +} + +.spacer-left { text-align: left; } +.spacer-center { text-align: center; } +.spacer-right { text-align: right; } + +.children { + margin-left: 10px; +} + +.server-info { + margin: 6px; + padding: 6px; + border: 1px solid #ccc; + background: #fafafa; + font-size: 12px; +} + +.server-info span { + display: inline-block; + width: 150px; + color: #444; +} + +.client { + padding-left: 10px; + color: #000; +} + +.status-dot { + display: inline-block; + width: 8px; + height: 8px; + background-color: #4CAF50; + border-radius: 50%; + margin-right: 6px; + position: relative; + top: -1px; +} \ No newline at end of file diff --git a/internal/web/static/ts6viewer.js b/internal/web/static/ts6viewer.js new file mode 100644 index 0000000..d399e59 --- /dev/null +++ b/internal/web/static/ts6viewer.js @@ -0,0 +1,153 @@ +// ========================================== +// Refresh countdown (also controls data polling) +// ========================================== +let counter = refreshTime; + +const refreshText = document.getElementById("refreshButtonText"); + +// Update countdown every second +setInterval(() => { + counter--; + refreshText.textContent = counter; + + // When countdown reaches zero → refresh data + if (counter <= 0) { + counter = refreshTime; + fetchViewerData(); + } +}, 1000); + + +// ========================================== +// Spacer rendering helpers +// ========================================== +function repeatToWidth(element) { + const pattern = element.dataset.pattern; + if (!pattern) return; + + const width = element.clientWidth; + const charWidth = getCharWidth(element); + const repeatCount = Math.ceil(width / (pattern.length * charWidth)); + + element.textContent = pattern.repeat(repeatCount); +} + +function getCharWidth(element) { + const test = document.createElement("span"); + test.style.visibility = "hidden"; + test.style.whiteSpace = "pre"; + test.style.fontFamily = getComputedStyle(element).fontFamily; + test.textContent = "_"; + document.body.appendChild(test); + const width = test.getBoundingClientRect().width; + test.remove(); + return width; +} + +function updateAllSpacers() { + document.querySelectorAll(".full-width").forEach(repeatToWidth); +} + + +// ========================================== +// Fetch viewer data from backend +// ========================================== +async function fetchViewerData() { + try { + const response = await fetch("/ts6viewer/data"); + const data = await response.json(); + + updateServerInfo(data.Server); + updateChannelTree(data.ChannelTree); + + updateAllSpacers(); + } catch (err) { + console.error("Polling error:", err); + } +} + + +// ========================================== +// Update server info box +// ========================================== +function updateServerInfo(server) { + document.getElementById("server-name").textContent = server.Name; + + document.querySelector(".server-info").innerHTML = ` +
User: ${server.ClientsOnline} / ${server.MaxClients}
+
Client Connections: ${server.ClientConnections}
+
Uptime: ${server.UptimePretty}
+
ChannelsOnline: ${server.ChannelsOnline}
+ + `; +} + + +// ========================================== +// Render channel tree +// ========================================== +function updateChannelTree(tree) { + const container = document.getElementById("channels"); + container.innerHTML = renderChannels(tree); +} + +function renderChannels(nodes) { + let html = ""; + for (const node of nodes) { + html += renderChannel(node); + } + return html; +} + +function renderChannel(ch) { + const typeClass = ch.Type === 0 ? "channel" : "spacer"; + + const alignClassName = + ch.Align === 0 ? "spacer-left" : + ch.Align === 1 ? "spacer-center" : + "spacer-right"; + + let html = '
'; + + if (ch.Clients && ch.Clients.length > 0) { + html += '
'; + for (const c of ch.Clients) { + html += '
' + + c.Nickname + + '
'; + } + html += '
'; + } + + if (ch.Children && ch.Children.length > 0) { + html += '
'; + for (const child of ch.Children) { + html += renderChannel(child); + } + html += '
'; + } + + return html; +} + +// ========================================== +// Initial load +// ========================================== +window.addEventListener("load", () => { + fetchViewerData(); + updateAllSpacers(); +}); + +window.addEventListener("resize", updateAllSpacers); diff --git a/internal/web/templates/ts6viewer.html b/internal/web/templates/ts6viewer.html new file mode 100644 index 0000000..879271b --- /dev/null +++ b/internal/web/templates/ts6viewer.html @@ -0,0 +1,70 @@ +{{define "channel"}} +
+{{- .Name -}} +
+ +{{if .Clients}} +
+ {{range .Clients}} +
+ {{.Nickname}} +
+ {{end}} +
+{{end}} + +{{if .Children}} +
+ {{range .Children}} + {{template "channel" .}} + {{end}} +
+{{end}} +{{end}} + + + + + +TS6 Viewer + + + + + + + + + + +

{{.Server.Name}}

+ +
+
User: {{.Server.ClientsOnline}} / {{.Server.MaxClients}}
+
Client Connections: {{.Server.ClientConnections}}
+
Uptime: {{.Server.UptimePretty}}
+
ChannelsOnline: {{.Server.ChannelsOnline}}
+ +
+ +
+ {{range .ChannelTree}} + {{template "channel" .}} + {{end}} +
+ + + + + + diff --git a/light.png b/light.png new file mode 100644 index 0000000000000000000000000000000000000000..8c3660d71794a24b51861158a015f3d9d42ad678 GIT binary patch literal 56139 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 fh5QqpA0EbN_% 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 literal 0 HcmV?d00001