diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e20ae39 --- /dev/null +++ b/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2019 IOTA Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..47ee085 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# IcTinder + +## About + +IcTinder is a service maintaining your Ict neighbors. It will add and remove neighbors automatically depending on their +reliability to ensure that you are always well-connected. Sign up and worry less. + +## Getting Started + +### 1) Sign up + +1) Visit the official IOTA Discord +2) Use the `!ictinder` command to start a dialog with the IcTinder bot +3) In the dialog, use `!register` to sign up +4) Keep your Discord ID and Password for later. + +### 2) Install the IXI + +1) Open your Ict Web GUI (hosted on port 2187 by default). +2) Navigate to **IXI Modules** > **Manage Modules** +3) Click on the **Install Third Party Module** button. +4) Enter `mikrohash/ictinder` and click on **Install**. +5) Refresh the page. +6) Navigate to **IXI Modules** > **IcTinder** +7) Configure IcTinder: + * `static_neighbors`: In case you have static neighbors you want to keep all the time, put them here + * `ict_gui_port` and `ict_gui_password`: Part of your Ict configuration. + * `node_address`: The address your peers can use to connect to your node (format: `HOST:PORT`). The host should never be `localhost` or similar. + * `discord_id` and `ictinder_password`: You received these values when you signed up with the IcTinder bot. +8) Press the **Save** button. + +IcTinder should now be started. Give it a few minutes to find your first neighbor. + +## FAQ + +### How does it work? + +How it works: +* IcTinder.ixi (installed on the Ict node) publishes stats about neighbors to the central API. +* The central API decides how to connect nodes based on the stats reported by their neighbors. +* IcTinder.ixi downloads the neighbors recommended by the central API and connects to them. Rinse repeat. + +### Why is it centralized? + +In the Internet-of-Things, auto-peering will be limited to devices nearby. IcTinder is only a temporary measure to +simplify the peering process while Ict is running over the cloud-based Internet. Therefore it does not make any sense to +spend too much resources on developing a decentralized peering layer which would not make it into the final version. +IcTinder can be considered an extension of the previous peer-finder bot with the difference that the whole process is +automatized and no manual neighbor adding/removing is required. Additionally, stats reported by neighbors are utilized +to improve the network. + +### I'm getting an `SSLHandShakeException` + +``` +10:57:40.500 ERROR [org.iota.ictinder.IcTinder/IcTinder] Unexpected issue occurred during syncing. - java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure + at org.iota.ict.api.HttpGateway.sendRequest(HttpGateway.java:39) +``` + +If you encounter this message in your Ict log, your JRE or JDK is not supporting the encryption used by our central API. +This can be fixed by installing the [Java Cryptography Extension](https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). +Please report if you encountered this issue and whether you were able to fix it. + +## Disclaimer + +Run this software at your own cost. We are not liable for any damage caused by running this software. +In order to use IcTinder, you will have to sign up at the central IcTinder API. We are not responsible for data breaches. +However, we will give you a custom password instead of asking you for one to minimize any risks. We will only store a +bcrypt hash of your password in our database. Your node address will be exposed to your peers in order to allow them to +connect. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..19d2ba3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'java' + id 'idea' +} + +group 'org.iota.ictinder' +version '1.0-SNAPSHOT' + +sourceCompatibility = 1.7 +repositories { + mavenCentral() + maven { url "https://jitpack.io" } +} + +dependencies { + compile 'com.github.iotaledger:ict:58189478f9574f5d5211483b8e3291acda1c4788' + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.1' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1' + compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.21' + compile 'org.json:json:20171018' +} + +task ixi(type: Jar) { + archiveName = 'ictinder.jar' + destinationDir = file("$rootDir") + from("./") { include 'module.json' } + exclude ("org/iota/ict/**") + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + with jar +} \ No newline at end of file diff --git a/module.json b/module.json new file mode 100644 index 0000000..a30b4a5 --- /dev/null +++ b/module.json @@ -0,0 +1,9 @@ +{ + "version": "1.0-SNAPSHOT", + "name": "IcTinder", + "main_class": "org.iota.ictinder.IcTinder", + "description": "IcTinder is a service maintaining your Ict neighbors. It will add and remove neighbors automatically depending on their reliability to ensure that you are always well-connected.", + "repository": "mikrohash/ictinder", + "gui_port": -1, + "supported_versions": ["0.6-SNAPSHOT"] +} \ No newline at end of file diff --git a/src/main/java/org/iota/ictinder/IcTinder.java b/src/main/java/org/iota/ictinder/IcTinder.java new file mode 100644 index 0000000..e019a60 --- /dev/null +++ b/src/main/java/org/iota/ictinder/IcTinder.java @@ -0,0 +1,314 @@ +package org.iota.ictinder; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.iota.ict.Ict; +import org.iota.ict.api.HttpGateway; +import org.iota.ict.ixi.Ixi; +import org.iota.ict.ixi.IxiModule; +import org.iota.ict.ixi.context.ConfigurableIxiContext; +import org.iota.ict.ixi.context.IxiContext; +import org.iota.ict.utils.properties.EditableProperties; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.*; + +public class IcTinder extends IxiModule { + + private static final String ICTINDER_API = "https://qubiota.com/ictinder/api.php"; + + private int guiPort; + private String nodeAddress; + private String guiPassword; + private String discordID; + private String icTinderPassword; + private Set staticNeighbors = new HashSet<>(); + private final IxiContext context = new IcTinderContext(); + private static final long SYNC_INTERVAL_MS = 3*60000; + private long last_sync = 0; + private double timeout_factor = 1; + private static final Logger LOGGER = LogManager.getLogger("IcTinder"); + + private static final String[] statKeys = {"all", "new", "requested", "invalid", "ignored"}; + + public IcTinder(Ixi ixi) { + super(ixi); + } + + @Override + public void run() { + while (isRunning()) { + sync(); + try { + Thread.sleep((long)Math.max(1, SYNC_INTERVAL_MS * timeout_factor + last_sync - System.currentTimeMillis())); + } catch (InterruptedException e) { } + } + } + + @Override + public void onTerminate() { + runningThread.interrupt(); + } + + public static void main(String[] args) throws InterruptedException { + Ict ict = new Ict(new EditableProperties().toFinal()); + (new IcTinder(ict)).start(); + Thread.sleep(100000); + } + + private void sync() { + if(nodeAddress.startsWith("localhost")) { + LOGGER.warn("Cannot sync with IcTinder API: please configure IcTinder.ixi first."); + return; + } + LOGGER.debug("syncing ..."); + last_sync = System.currentTimeMillis(); + try { + JSONArray neighborsJSON = getNeighbors(); + List currentNeighbors = extractNeighborAddresses(neighborsJSON); + Map params = buildRequestParameters(neighborsJSON); + String response = HttpGateway.sendPostRequest(ICTINDER_API, params, new HashMap()); + processResponse(currentNeighbors, response); + timeout_factor = 1; + } catch (Throwable e) { + LOGGER.error("Unexpected issue occurred during syncing.", e); + e.printStackTrace(); + timeout_factor = Math.min(timeout_factor * 1.4, 8); + } + } + + private Map buildRequestParameters(JSONArray neighborsJSON) { + Map requestParameters = new HashMap<>(); + JSONObject statsOfAllNeighbor = collectStatsOfAllNeighbors(neighborsJSON); + requestParameters.put("username", discordID); + requestParameters.put("node", nodeAddress); + requestParameters.put("password", icTinderPassword); + requestParameters.put("static", staticNeighbors.size()+""); + requestParameters.put("stats", statsOfAllNeighbor.toString()); + return requestParameters; + } + + private static JSONObject collectStatsOfAllNeighbors(JSONArray neighborsJSON) { + JSONObject allStats = new JSONObject(); + for(int i = 0; i < neighborsJSON.length(); i++) { + JSONObject neighbor = neighborsJSON.getJSONObject(i); + String neighborAddress = neighbor.getString("address"); + JSONArray statsOfRounds = neighbor.getJSONArray("stats"); + JSONObject stats = sumUpAllStats(statsOfRounds); + allStats.put(neighborAddress, stats); + } + return allStats; + } + + private static JSONObject sumUpAllStats(JSONArray statsOfRounds) { + JSONObject stats = new JSONObject(); + for(String statKey : statKeys) + stats.put(statKey, 0); + for(int j = 0; j < statsOfRounds.length(); j++) { + JSONObject statsFromSomeRound = statsOfRounds.getJSONObject(j); + if(statsFromSomeRound.getLong("timestamp") > (System.currentTimeMillis()-SYNC_INTERVAL_MS)/1000) + accumulateStats(stats, statsFromSomeRound); + } + return stats; + } + + private static void accumulateStats(JSONObject statsAccumulator, JSONObject statsToAdd) { + for(String statKey : statKeys) { + int oldValue = statsAccumulator.getInt(statKey); + int toAdd = statsToAdd.getInt(statKey); + statsAccumulator.put(statKey, oldValue + toAdd); + } + } + + private void processResponse(List currentNeighbors, String responseString) { + LOGGER.info("IcTinder API Response: " + responseString); + JSONObject responseJSON = new JSONObject(responseString); + if(!responseJSON.getBoolean("success")) + throw new RuntimeException("IcTinder API Error: " + responseJSON.getString("error")); + List neighbors = extractNeighborFromResponse(responseJSON); + updateIctNeighbor(currentNeighbors, neighbors); + //ict.updateProperties(ict.getProperties().toEditable().neighbors(newNeighbors).toFinal()); + } + + private List extractNeighborFromResponse(JSONObject response) { + JSONArray neighborsJSON = response.getJSONArray("neighbors"); + List neighbors = new LinkedList<>(); + for(Object newNeighbor : neighborsJSON) + neighbors.add(newNeighbor.toString()); + return neighbors; + } + + private void updateIctNeighbor(List currentNeighbors, List newNeighbors) { + + for(String currentNeighbor : currentNeighbors) + if(!newNeighbors.contains(currentNeighbor) && !staticNeighbors.contains(currentNeighbor)) + removeNeighbor(currentNeighbor); + + for(String newNeighbor : newNeighbors) + if(!currentNeighbors.contains(newNeighbor)) + addNeighbor(newNeighbor); + } + + private static List extractNeighborAddresses(JSONArray neighborsJSON) { + List neighbors = new LinkedList<>(); + for(int i = 0; i < neighborsJSON.length(); i++) + neighbors.add(neighborsJSON.getJSONObject(i).getString("address")); + return neighbors; + } + + private JSONArray getNeighbors() { + Map params = new HashMap<>(); + JSONObject getNeighborsResponse = sendIctApiRequest(IctApiPath.GET_NEIGHBORS, params); + return getNeighborsResponse.getJSONArray("neighbors"); + + } + + private void addNeighbor(String address) { + Map params = new HashMap<>(); + params.put("address", address); + sendIctApiRequest(IctApiPath.ADD_NEIGHBOR, params); + } + + private void removeNeighbor(String address) { + Map params = new HashMap<>(); + params.put("address", address); + sendIctApiRequest(IctApiPath.REMOVE_NEIGHBOR, params); + } + + private JSONObject sendIctApiRequest(IctApiPath path, Map params) { + params.put("password", guiPassword); + String responseString = HttpGateway.sendPostRequest(ictApi() + path, params, new HashMap()); + JSONObject responseJSON = new JSONObject(responseString); + if(!responseJSON.getBoolean("success")) + throw new RuntimeException("error returned by Ict api: " + responseJSON.getString("error")); + return responseJSON; + } + + private String ictApi() { + return "http://localhost:"+guiPort+"/"; + } + + private static class IctApiPath { + + private static final IctApiPath GET_NEIGHBORS = new IctApiPath("getNeighbors"); + private static final IctApiPath ADD_NEIGHBOR = new IctApiPath("addNeighbor"); + private static final IctApiPath REMOVE_NEIGHBOR = new IctApiPath("removeNeighbor"); + + private final String path; + + IctApiPath(String path) { + this.path = path; + } + + @Override + public String toString() { + return path; + } + } + + private enum Field { + ict_gui_port, ict_gui_password, ictinder_password, discord_id, node_address, static_neighbors; + } + + private class IcTinderContext extends ConfigurableIxiContext { + + private IcTinderContext() { + super(new JSONObject() + .put(Field.ict_gui_port.name(), 2187) + .put(Field.ict_gui_password.name(), "change_me_now") + .put(Field.ictinder_password.name(), "[ICTINDER BOT]") + .put(Field.discord_id.name(), "[ICTINDER BOT]") + .put(Field.node_address.name(), "localhost:1337") + .put(Field.static_neighbors.name(), new JSONArray())); + applyConfiguration(); + } + + @Override + protected void validateConfiguration(JSONObject newConfiguration) { + for(Field field : Field.values()) + if(!newConfiguration.has(field.name())) + throw new IllegalPropertyException(field, "does not exist"); + validateIctGUIPort(newConfiguration); + validateIctGUIPassword(newConfiguration); + validateIcTinderPassword(newConfiguration); + validateDiscordID(newConfiguration); + validateNodeAddress(newConfiguration); + validateStaticNeighbors(newConfiguration); + } + + private void validateIctGUIPort(JSONObject newConfiguration) { + if(!(newConfiguration.get(Field.ict_gui_port.name()) instanceof Integer)) + throw new IllegalPropertyException(Field.ict_gui_port, "not an integer"); + int guiPort = newConfiguration.getInt(Field.ict_gui_port.name()); + if(guiPort < 0 || guiPort > 65535) + throw new IllegalPropertyException(Field.ict_gui_port, "not in interval [0,65535]"); + } + + private void validateIctGUIPassword(JSONObject newConfiguration) { + if(!(newConfiguration.get(Field.ict_gui_password.name()) instanceof String)) + throw new IllegalPropertyException(Field.ict_gui_password, "not a String"); + String guiPassword = newConfiguration.getString(Field.ict_gui_password.name()); + if(guiPassword.length() < 8) + throw new IllegalPropertyException(Field.ict_gui_password, "too short, not secure (change ict config)"); + if(guiPassword.equals("change_me_now")) + throw new IllegalPropertyException(Field.ict_gui_password, "why haven't you changed it yet? Seriously, the password asked you to do one simple thing. 'change_me_now' is NOT a secure password."); + } + + private void validateIcTinderPassword(JSONObject newConfiguration) { + if(!(newConfiguration.get(Field.ictinder_password.name()) instanceof String)) + throw new IllegalPropertyException(Field.ictinder_password, "not a String"); + } + + private void validateDiscordID(JSONObject newConfiguration) { + if(!(newConfiguration.get(Field.discord_id.name()) instanceof String)) + throw new IllegalPropertyException(Field.discord_id, "not a String"); + String discordID = newConfiguration.getString(Field.discord_id.name()); + if(!discordID.matches("^[0-9]{14,22}$")) + throw new IllegalPropertyException(Field.discord_id, "not a discord ID (should only contain digits)"); + } + + private void validateNodeAddress(JSONObject newConfiguration) { + if(!(newConfiguration.get(Field.node_address.name()) instanceof String)) + throw new IllegalPropertyException(Field.node_address, "not a String"); + String nodeAddress = newConfiguration.getString(Field.node_address.name()); + if(!nodeAddress.matches("[a-zA-Z.\\-_0-9]+:\\d{1,5}")) + throw new IllegalPropertyException(Field.node_address, "not matching expected format 'HOST:PORT'"); + if(nodeAddress.startsWith("localhost:") || nodeAddress.startsWith("0.0.0.0:") || nodeAddress.startsWith("192.168.")) + throw new IllegalPropertyException(Field.node_address, "please specify a host that can be addressed by other Ict nodes"); + } + + private void validateStaticNeighbors(JSONObject newConfiguration) { + if(!(newConfiguration.get(Field.static_neighbors.name()) instanceof JSONArray)) + throw new IllegalPropertyException(Field.static_neighbors, "not a JSONArray"); + } + + private class IllegalPropertyException extends IllegalArgumentException { + private IllegalPropertyException(Field field, String cause) { + super("Invalid property '"+field.name()+"': " + cause + "."); + } + } + + @Override + protected void applyConfiguration() { + guiPort = configuration.getInt(Field.ict_gui_port.name()); + guiPassword = configuration.getString(Field.ict_gui_password.name()); + icTinderPassword = configuration.getString(Field.ictinder_password.name()); + discordID = configuration.getString(Field.discord_id.name()); + nodeAddress = configuration.getString(Field.node_address.name()); + staticNeighbors = stringSetFromJSONArray(configuration.getJSONArray(Field.static_neighbors.name())); + } + + private Set stringSetFromJSONArray(JSONArray array) { + Set stringSet = new HashSet<>(); + for(Object element : array.toList()) + stringSet.add(element.toString()); + return stringSet; + } + } + + @Override + public IxiContext getContext() { + return context; + } +} diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..1bbc3c2 --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "0.6-SNAPSHOT": "1.0-SNAPSHOT" +} \ No newline at end of file