2
0
mirror of https://github.com/proxiodev/RedisBungee.git synced 2026-05-03 11:40:29 +00:00

235 Commits

Author SHA1 Message Date
AlessioDP
c3888c8f65 Fix NullPointerException due plugin instance for jedis tasks in UUIDTranslator/AbstractDataManager (#57) 2022-12-25 06:26:53 +04:00
c8362a44ec add config option to restore old kick behavior pre 0.9.0 2022-11-27 12:29:13 +04:00
31e461a11c change url of big uuid cache 2022-11-27 12:10:29 +04:00
a9ea04c2c0 missed location were it wasn't using the constant 2022-11-24 01:41:47 +04:00
ddfc689c2d expose CachedUUIDEntry Class 2022-11-16 11:32:20 +04:00
ae6961ef24 bump version 2022-11-16 08:22:57 +04:00
8318bcd1bf update some logs in configloader 2022-11-16 08:22:30 +04:00
0b9fd6d7ff Make sure server field is withen map than calling redis seperatly 2022-11-14 08:45:55 +04:00
a526298d1c change one of the comments in PlayerUtils 2022-11-14 08:42:54 +04:00
c69b1e214e add missing final keyword to PROXY_TIMEOUT var 2022-11-14 08:40:46 +04:00
e5f0075a58 add messages config, change some jedis/redis depercated apis, bump to 0.9.0, new logging from another locations handling. 2022-11-11 20:30:16 +04:00
748bc13568 Fix ssl connections on PooledConnections
useSSL in configloader wasn't passing it to the ConnectionProvider
2022-11-05 15:44:51 +04:00
5e3ce725de Include some changes to ssl notes
and little change for compatiblity pool
2022-11-05 15:43:23 +04:00
92bb0030de move PlayerUtils code from platforms to the api 2022-11-02 08:38:25 +04:00
d8c21edc7a Bump version to 0.8.1 2022-11-02 07:58:13 +04:00
87a2b93537 update jitpack location 2022-11-01 10:13:36 +04:00
0f5d5b2440 Fixed Typos caused JedisPooled to be JedisCluster withen the abstract api 2022-11-01 09:59:08 +04:00
21f543581c add jitpack.yml
should fix the velocity not compiling on jitpack
2022-10-31 15:17:52 +04:00
bc266ae1fa Update javadocs for Jedis Cluster/Pooled instances 2022-10-30 00:10:56 +04:00
8dc42d071a Add copyright header to source code 2022-10-30 00:02:09 +04:00
71b959936e Move cluster summoner to Provider cluster 2022-10-25 15:52:50 +04:00
28419b3168 remove git versions 2022-10-25 08:18:30 +04:00
a382a298a1 Make summoner create customized JedisPooled that can't be closed 2022-10-25 08:04:48 +04:00
4d58ee1742 add todo list for later into config loader 2022-10-19 07:46:59 +04:00
af4e180b2c fix nullpointer in JedisPooledSummoner
Jedis pool when null it calls shutdown so added an check to check if its null
2022-10-17 08:27:55 +04:00
c1ad902bd3 upgrade jedis 2022-10-15 18:13:44 +04:00
74ed18e9b3 Make sure to run LoginEvent Last fixes #48 2022-09-14 08:09:43 +04:00
a51ff98909 update readme 2022-08-02 17:52:34 +04:00
80a4791d12 api changes in events, move Listener serialization into Util class 2022-07-30 22:36:29 +04:00
fdd537b276 fix javadocs again, add getServerFor in velocity 2022-07-27 19:06:57 +04:00
17897bc112 make RedisBungeeAPI class platform dependant and make Abstract version of it 2022-07-27 17:43:51 +04:00
576bcc39d2 update readme 2022-07-26 20:52:42 +04:00
508125023e update readme 2022-07-26 20:46:32 +04:00
43b2d20e39 update readme 2022-07-26 20:45:39 +04:00
64af12044e Change Plugin instance of Bungeecord to RedisBungee to maintain old plugin compatibility 2022-07-26 18:42:29 +04:00
2ae9b5d480 add log option, check connection for JedisPooled 2022-07-26 17:55:16 +04:00
d77e909e7d block when exhausted, new config options for the compatibility pool max connections 2022-07-26 17:52:00 +04:00
ee76fa0b3d Change Jedis -> JedisPooled, and make tasks use UnifiedJedis
since JedisCluster, JedisPooled are Childern of UnifiedJedis
2022-07-26 17:48:00 +04:00
mohammed Alteniji
f303f2c202 Update README.md 2022-07-26 15:43:56 +04:00
mohammed Alteniji
4e46dc5536 Update README.md 2022-07-26 15:41:57 +04:00
mohammed Alteniji
b58e503ec7 Update README.md 2022-07-26 15:38:01 +04:00
mohammed Alteniji
b16a7d4cbc Update README.md 2022-07-26 15:36:28 +04:00
5a7001a68c remove else when checking if password is null 2022-07-26 15:14:14 +04:00
81a8fd218e move PlayerUtils and change the name 2022-07-26 12:49:44 +04:00
92f5e04edf change method naming of updateProxiesIds 2022-07-26 12:42:42 +04:00
e7b241edd6 git rid of lua system 2022-07-26 12:40:14 +04:00
86c6e9464d internal changes 2022-07-26 10:58:00 +04:00
1828cd854c add config option for later use 2022-07-25 20:04:21 +04:00
6910c96f67 fix mess up in API 2022-07-25 19:13:25 +04:00
8f602407b5 fix java docs again 2022-07-25 18:49:49 +04:00
2c4fc00ec3 single class for loading config 2022-07-25 16:49:57 +04:00
11e867730c this has to be false, if absent 2022-07-23 10:31:12 +04:00
830077282d fix java docs 2022-07-23 08:24:03 +04:00
2b2ae88587 change RuntimeException to IllegalStateException 2022-07-23 08:21:36 +04:00
9ceccb6dd2 fix #38
somehow pull request #39 forgotten to add thrid Legacy channel
2022-07-22 23:52:15 +04:00
dea384a203 change wrong default max connection from 8 to 10 2022-07-22 17:08:51 +04:00
e4012a8d7b change config in readme, move config-version below 2022-07-22 16:12:56 +04:00
2c8c5fc1cf as new config, check if ye wana load legacy commands 2022-07-22 16:10:06 +04:00
5c6cf405fa config changes 2022-07-22 15:12:32 +04:00
0408e2923b update to config handling, fix null pointer in pubsub handler, config changes
rename config in jar, config api, fixed null pointer in pubsub listener, use map allow muiltable port and hosts for the cluster
2022-07-22 12:29:39 +04:00
5d1167c20f change original author name, and include it in velocity version 2022-07-21 16:37:32 +04:00
79694fcbb2 fix some typos in readme 2022-07-21 16:26:16 +04:00
1d889f28b9 update readme 2022-07-21 16:23:07 +04:00
f274301036 add firePayload for cleanup as its used in integerty check 2022-07-21 09:36:10 +04:00
c787c76eca fix null being passed into jedis on ServerConnectedEvent 2022-07-20 13:25:26 +04:00
9da5845da3 move Heartbeat task into its own class 2022-07-20 13:21:24 +04:00
4d3f1a551e General improvements, remove player cleanup from RedisUtils class and bug fixes
fixed bug where PlayerNetoworkJoin is not fireing, and also fixed wrong arguments being passed on server change network event, Moved Payload creation to it own Util class, Remove player creation from RedisUtils class and move it to GenericPlayerUtils, and also renamed Bungeecord/Velocity Player Util classes to <PLATFORM>PlayerUtils.java, removed the use of reflection to create event instances
2022-07-20 12:39:10 +04:00
17e6e12c24 oops, bungeecord code was not updated to handle kick if player inside same proxy 2022-07-19 15:43:58 +04:00
c207b4a0a2 oops, removed leftover debug stuff 2022-07-19 15:36:42 +04:00
c848126aa7 Implement network kick 2022-07-19 15:30:45 +04:00
f722b8c9d3 fix glist command when UUID to name translation fails by returning the uuid 2022-07-19 07:41:21 +04:00
d806bc2d41 reduce integrity check to run from 1 min to 30 seconds 2022-07-19 07:21:17 +04:00
2e8342f5c3 serverToPlayers was getting injected with null values as a server 2022-07-19 07:19:32 +04:00
SirSalad
c125137a74 Upload other artifacts (#41) 2022-07-19 04:30:39 +04:00
aff185a85b last bug was caused by confusion due Proxy Called server in some cases Which resulted in new API methods and some refactor 2022-07-17 15:38:00 +04:00
482dfc5141 oops, fixed bug where player servers would return the proxy than server 2022-07-17 15:14:46 +04:00
6a6e303334 updated config, added supported for velocity 2022-07-17 09:51:24 +04:00
a9600a7d8a check whatever password is empty or not 2022-07-17 09:14:39 +04:00
36136d0c0f remove leftover debug from cluster testing and handle config correctly 2022-07-17 08:31:55 +04:00
319b5eb553 pom file relocation was changed by refactor undoing.. 2022-07-17 07:44:00 +04:00
38de948c6b updated bungeecord to support Redis cluster 2022-07-17 07:41:18 +04:00
019bb30c09 remove lua files and do it in java, due Redis not supporting lua quite well in cluster mode, some scripts were added recently will be kept 2022-07-17 07:12:57 +04:00
f1f74b6456 check connection in JedisClusterSummoner too 2022-07-17 05:40:32 +04:00
b0ab5e3cb4 readd support for redis versions below 6, change method of the summoner 2022-07-17 05:25:50 +04:00
b214e3dad7 change internal package to api 2022-07-16 16:50:09 +04:00
81a2af8179 add JedisCLuster methods to the api 2022-07-16 16:32:10 +04:00
b910fa5eb6 add new lua scripts for cluster info/time requesting 2022-07-16 10:14:06 +04:00
b15b9dbb96 create constructor with plugin parameter in RedisClass 2022-07-16 09:39:36 +04:00
44f9a0945d API converted to support RedisCluster 2022-07-16 09:18:33 +04:00
e986d5f1fb change something again in readme 2022-07-16 07:09:04 +04:00
ce93bd5e07 change section of usage 2022-07-16 07:06:30 +04:00
870e6113db readme: fix refernce to the closed pull request 2022-07-16 06:49:10 +04:00
86b0904546 readme: add disclaimer to support version of velocity, fix typo in velocity setup 2022-07-16 06:48:00 +04:00
f4dbb00991 change config option 2022-07-16 03:00:51 +04:00
b6e4badc05 readd removed methods to maintain old plugins, prepare for redis cluster mode 2022-07-16 02:58:48 +04:00
78ae6ff7b6 update readme javadocs section 2022-07-16 02:33:58 +04:00
87aaf22073 add to makeJavaDocs 2022-07-16 02:22:46 +04:00
a74e4b6a15 change pom vars due deprecation 2022-07-16 02:21:20 +04:00
d6d8c30343 fix java docs 2022-07-16 02:15:34 +04:00
933fceba9d add velocity maven setup, update bungeecord section 2022-07-16 02:12:05 +04:00
a8b369dde3 Revert "add builder helper to handle compile errors in velocity version template plugin"
This reverts commit 414f29e99c.
2022-07-16 00:37:47 +04:00
414f29e99c add builder helper to handle compile errors in velocity version template plugin 2022-07-15 11:00:04 +04:00
Adrian
21f6cdeaeb re-implement the commands, right usage of logger, general improvements to the listener (#39)
Co-authored-by: mohammed jasem alaajel <xrambad@gmail.com>
2022-07-15 10:52:53 +04:00
5f07ba6b66 fix the worker file 2022-07-15 10:13:08 +04:00
673b966dda Revert "remove workflows as it stoped working months ago due unknown reason"
This reverts commit 69d435d455.
2022-07-15 10:11:44 +04:00
69d435d455 remove workflows as it stoped working months ago due unknown reason 2022-07-15 10:05:01 +04:00
3a8baae56f change versioning scheme on both velocity + bungeecord 2022-07-15 09:57:30 +04:00
803ae36d00 for some reason last commit did not include the subscribe as i should added it 2022-07-15 05:38:30 +04:00
ceef1f5184 rename listener classes depending on the platform, readd plugin message back #38 2022-07-15 03:05:53 +04:00
cd4f2aded4 Revert "fix bungeecord incorrect channel names"
This reverts commit d8a220bcc2.
2022-07-15 01:43:22 +04:00
d8a220bcc2 fix bungeecord incorrect channel names 2022-07-15 01:35:54 +04:00
mohammed Alteniji
c3d23a3601 Update bug_report.md 2022-07-11 02:16:13 +04:00
mohammed Alteniji
ea600c5b44 Update README.md 2022-07-09 23:53:50 +04:00
86f2d89cf4 due velocity including some libs by default, had to move depends to api than the parent, and exlude it from being included in shade 2022-07-09 14:43:31 +04:00
70d1aadd56 remove no support 2022-07-08 08:19:50 +04:00
183c227b77 update readme again, intellji markdown preview is bad :/ 2022-07-08 08:17:15 +04:00
640b26067a update readme 2022-07-08 08:15:42 +04:00
660bb239bb update readme 2022-07-08 08:14:22 +04:00
dd9c9e368d update readme 2022-07-08 08:07:50 +04:00
76787455d8 make velocity init on constructor than init method, change runables to lambdas and some generic removeals with supressing unchecked for eval lua 2022-07-08 06:47:50 +04:00
2c11cb179a rename internal method 2022-07-08 03:23:44 +04:00
9f05bd3438 more changes 2022-07-08 02:39:05 +04:00
mohammed Alteniji
c04a911fbe Merge pull request #36 from Booquefius/patch-1 2022-07-07 05:10:34 +04:00
0311a893c7 remove leftover of the removed method javadocs 2022-07-07 04:49:52 +04:00
5a51d5c1b3 oops removed weird copy 2022-07-07 04:41:45 +04:00
afac7a3d51 fixed the config for velocity, revert 2022-07-07 04:16:09 +04:00
bdda99bc81 added Velocity support (NOT TESTED), it will not compile yet due config impl not done yet 2022-07-07 02:24:08 +04:00
ea665fd70f add new generic for velocity 2022-07-07 00:13:11 +04:00
f7285ff4f1 removed get JedisPool, refactored jedis summoners, new SinglePoolJedisSummoner, start version 0.8.0 2022-07-06 23:05:35 +04:00
Booquefius
28d1667fe9 typo 2022-07-03 19:33:44 -04:00
mohammed Alteniji
839c8cd615 Update README.md 2022-07-03 17:31:38 +04:00
mohammed Alteniji
83dff6b280 Update README.md 2022-07-01 01:25:39 +04:00
mohammed Alteniji
0b868ffd6f Update README.md 2022-07-01 01:24:48 +04:00
mohammed Alteniji
d7c8544b99 Update README.md 2022-06-30 15:03:16 +04:00
2391692dd3 bump version to 0.7.3 2022-06-29 18:16:33 +04:00
39c45b3eab change password field in the config 2022-06-29 18:16:16 +04:00
b3bc51b96f FIX mistake in pubsub listener as its resending spam to pubsub 2022-06-29 18:11:19 +04:00
08c4901f47 removed .png file, updated gitignore 2022-06-29 17:47:25 +04:00
a39c4654fb center 2022-06-17 06:10:29 +04:00
2ceac5a079 move supported redis versions up 2022-06-17 06:03:56 +04:00
814dabbb6a update supported redis versions 2022-06-17 06:02:20 +04:00
mohammed Alteniji
1a76c260b8 add fork into the title 2022-06-17 05:08:29 +04:00
24c9407358 remove useless line 2022-06-15 08:10:22 +04:00
eee36923c1 readd commands after i forgotten about them 2022-06-15 06:32:22 +04:00
6e02ab70db remove not needed BungeEvents submodule and merge it to bungee plugin 2022-06-15 06:05:30 +04:00
mohammed Alteniji
9493576067 Merge pull request #30 from Simonsator/develop
Use the correct jitpack dependency in the Readme
2022-05-27 17:05:34 +04:00
mohammed Alteniji
02c9c3c75a Update README.md 2022-05-27 17:05:01 +04:00
Simonsator
0326c4490a Use the correct jitpack dependency
The previously mentioned dependency does not exist. "RedisBungee-Bungee" iwith the group id "com.github.limework.redisbungee" is the correct one
2022-05-26 10:27:28 +02:00
d34db3da44 update readme 2022-05-26 05:02:31 +04:00
5e18c4adb5 update readme 2022-05-26 04:50:08 +04:00
ade1604987 update readme, fix complation, 0.7.2 2022-05-26 00:53:25 +04:00
7b2db2899e update gitignore 2022-05-26 00:42:40 +04:00
bdda7c13c9 update readme, fix relocation 2022-05-25 12:50:26 +04:00
mohammed Alteniji
9696726fb5 Update README.md 2022-05-25 09:11:19 +04:00
86eeacbb1c disable relocation 2022-04-14 21:41:45 +04:00
43fe50a87f javadocs last changes hopefully 2022-04-14 16:26:33 +04:00
dba8dc8ab7 .gitignore 2022-04-14 16:07:52 +04:00
8bfefc1ab0 seperate events 2022-04-14 15:59:02 +04:00
2bc30ce5f3 update git ignore 2022-04-14 15:54:13 +04:00
070e416fac more java docs stuff + added jedis pool method and deprecate it 2022-04-14 14:34:56 +04:00
102f8591ab java docs 2022-04-14 14:16:34 +04:00
4d0222d2ae added some javadocs info + added jedis summoner class for future release 2022-04-14 14:12:47 +04:00
e53f8a02ba add glist command *reset will be added later 2022-04-14 01:56:17 +04:00
08c7d89749 revert payloads due mass errors 2022-04-14 01:40:16 +04:00
d3889d8bd9 forgot to implement some methods 2022-04-14 01:16:31 +04:00
baab263355 add the updated config 2022-04-13 22:26:36 +04:00
df92d15743 comments were removed accdenitly 2022-04-13 22:23:15 +04:00
3461bbcd1b java docs 2022-04-13 22:19:42 +04:00
7de457b6fa finished but untest 2022-04-13 22:17:38 +04:00
9f09ed21f1 more progress 2022-04-13 20:22:07 +04:00
17fdeda147 more progress 2022-04-13 20:08:46 +04:00
61dec7f03c seperate the internals from bungeecord api for easily supporting platform | progress 60% 2022-04-13 17:15:08 +04:00
165ba84791 update depends 2022-04-13 12:50:58 +04:00
b683d5e226 remove mvn wrapper 2022-04-13 12:29:12 +04:00
b27ddb6cf2 update readme 2022-04-13 12:28:10 +04:00
7c8748e262 update readme 2022-04-13 12:22:35 +04:00
mohammed Alteniji
2df0b9fc60 Update README.md 2022-03-22 16:57:07 +04:00
mohammed Alteniji
bd7d0f4d66 Update README.md 2022-03-22 15:28:41 +04:00
45b4ec65df #24 2021-12-20 05:21:53 +04:00
mohammed Alteniji
bdd02043ea Update README.md 2021-11-16 15:03:16 +04:00
mohammed jasem alaajel
e8439950e3 Update README.md 2021-09-19 19:46:54 +04:00
mohammed jasem alaajel
fbed68e1ab Update README.md 2021-09-19 19:43:25 +04:00
mohammed jasem alaajel
a006c343a7 change redis version check method. 2021-08-09 14:16:07 +04:00
mohammed jasem alaajel
c9798e2371 supresss unused warnings 2021-08-09 14:05:02 +04:00
mohammed jasem alaajel
5caabc8aa4 prepare for a release 2021-07-31 18:15:02 +04:00
mohammed jasem alaajel
9b4a49f66a don't display exception when can't replace the executor service 2021-07-31 17:07:42 +04:00
mohammed jasem alaajel
785b23ecfb fix java docs issue 2021-07-30 02:20:39 +04:00
mohammed jasem alaajel
d8e0ea71a2 oops 2021-07-30 02:13:39 +04:00
mohammed jasem alaajel
ac66899f98 new api functions 2021-07-30 02:12:01 +04:00
mohammed jasem alaajel
a8673c563b change version 2021-07-30 02:09:53 +04:00
mohammed jasem alaajel
8a4dd53e58 bump depends versions in pom.xml 2021-07-30 02:00:32 +04:00
mohammed jasem alaajel
85711c5688 changed ReadMe 2021-07-22 02:22:11 +04:00
mohammed jasem alaajel
86ebc9c0d2 Fix: Player being added into the Redis database if LoginEvent was cancelled..... (#16) 2021-07-22 02:06:36 +04:00
mohammed jasem alaajel
49ac1ba9ef bump jedis version 2021-07-21 20:50:42 +04:00
mohammed jasem alaajel
d29e51b4a6 Update README.md 2021-07-21 16:33:06 +04:00
mohammed jasem alaajel
1620df961b bumped version 2021-07-21 16:20:48 +04:00
foss-mc
ede9a7cba9 Allow unsuccessful replacement of BungeeCord thread pool (#15)
* Allow unsuccessful replacement of BungeeCord thread pool

Port https://github.com/minecrafter/RedisBungee/pull/76

Some BungeeCord forks have different implementation of thread pool and RedisBungee won't load in such case

* Update RedisBungee.java
2021-07-21 16:13:25 +04:00
mohammed jasem alaajel
701c87db02 add another warning 2021-07-20 17:11:13 +04:00
mohammed jasem alaajel
0ac5a5eebb add warning 2021-07-20 17:08:17 +04:00
mohammed jasem alaajel
0758afeba1 update README.md 2021-07-20 14:55:54 +04:00
mohammed jasem alaajel
9227224248 fix the test checks 2021-07-20 14:52:03 +04:00
mohammed jasem alaajel
ba9ca03e1b oops its should be redis 6.0 not 6.2 2021-07-20 14:49:45 +04:00
williamd5
6c35f23e16 readme.md git clone in current dir, fix link formatting (#13)
* git clone in current dir, fix link formatting

No need to make the user clone into a new dir, then move into this new dir. They will most likely already have an empty dir already created or one with this name might already exist, e.g. a fork repository.

* Update README.md
2021-07-04 17:07:45 +04:00
mohammed jasem alaajel
16720487f0 Update issue templates 2021-06-24 03:58:50 +04:00
mohammed jasem alaajel
6391d8d92c Update issue templates 2021-06-24 03:57:52 +04:00
mohammed jasem alaajel
51115746c7 older versions or redis than (6.2) is no longer supported 2021-06-24 03:54:44 +04:00
mohammed jasem alaajel
f53829481a updated the notice 2021-06-24 03:54:06 +04:00
mohammed jasem alaajel
9441624c09 added java 16 support 2021-06-24 03:35:05 +04:00
mohammed jasem alaajel
4fa152b65f Update README.md 2021-06-22 04:07:02 +04:00
mohammed jasem alaajel
0e9388568a Update README.md 2021-06-22 04:04:53 +04:00
mohammed jasem alaajel
f5a2362b68 Update issue templates 2021-06-22 04:03:30 +04:00
mohammed jasem alaajel
513c1ae2ea Forgotten a word 2021-06-22 02:45:27 +04:00
mohammed jasem alaajel
2a8ab73c2b Update README.md 2021-06-22 01:33:33 +04:00
mohammed jasem alaajel
02a117228a Update README.md 2021-06-12 14:49:29 +04:00
mohammed jasem alaajel
3d1fd57c89 Update README.md 2021-05-31 05:33:00 +04:00
mohammed jasem alaajel
334c17d0d8 Update README.md 2021-05-24 00:45:13 +04:00
mohammed jasem alaajel
5ec5b7af9e Update README.md 2021-05-22 18:17:28 +04:00
mohammed jasem alaajel
ad18d168a8 Update README.md 2021-05-22 18:15:32 +04:00
mohammed jasem alaajel
96793e81c5 code clean up and lambda etc 2021-05-21 16:51:44 +04:00
mohammed jasem alaajel
39b3c03604 fixed the placeholder api problem 2021-05-21 03:34:37 +04:00
weihao
373e1c16d4 Persistent random server id generation (#5)
* Allow random server id

* Assign and save random server id

* Add logging for server id

* Remove unused

* Update notes

* updated readme

* fixed bug from last update

* updated config

* fixed typo of in one of the authors name

Co-authored-by: mohammed jasem alaajel <34905970+ham1255@users.noreply.github.com>
2021-05-21 01:06:34 +04:00
mohammed jasem alaajel
56042af4eb Update README.md 2021-05-19 16:58:45 +04:00
mohammed jasem alaajel
8df8d96ced add random ids! 2021-05-17 22:42:57 +04:00
mohammed jasem alaajel
4c1ffa2b01 changed this to warn instead of severe 2021-05-17 04:11:09 +04:00
mohammed jasem alaajel
02caf2da5e Removed Limework maven server 2021-05-17 00:53:42 +04:00
mohammed jasem alaajel
a005b46467 Update README.md 2021-05-16 21:59:04 +04:00
mohammed jasem alaajel
65abb29558 Update README.md 2021-05-16 21:56:55 +04:00
mohammed jasem alaajel
5169d873a8 Update maven.yml 2021-05-16 21:54:14 +04:00
mohammed jasem alaajel
f0fbaab0b1 Changed default config redirection 2021-05-14 15:55:33 +04:00
mohammed jasem alaajel
4091da2ed8 Update README.md 2021-05-14 15:15:45 +04:00
mohammed jasem alaajel
86bef752de Update README.md 2021-05-14 15:12:07 +04:00
Govindas
17e203d7be small mistake 2021-05-14 10:53:00 +00:00
Govindas
ab8db5570c Update README.md 2021-05-14 10:52:12 +00:00
Govindas
571a318969 Update README.md 2021-05-14 10:51:41 +00:00
mohammed jasem alaajel
f07eed220d Update README.md 2021-05-13 23:12:40 +04:00
90 changed files with 5317 additions and 2591 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,32 @@
---
name: Bug report
about: if you find a bug please report it here...
title: ''
labels: waiting
assignees: ham1255
---
**Describe the bug**
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Redis version? it should be at least 6 and above.**
**Bungeecord version or (the bungee fork name eg: waterfall) and your plugins**
**console logs?**
please provide any errors if there any.

10
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Question
about: ask your questions here
title: ''
labels: question
assignees: GovindasOM, ham1255
---
## What is your question?

View File

@@ -1,13 +1,13 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
name: RedisBungee Build
on:
push:
branches: [ master ]
branches: [ develop ]
pull_request:
branches: [ master ]
branches: [ develop ]
jobs:
build:
@@ -19,14 +19,24 @@ jobs:
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '8'
java-version: '11'
distribution: 'adopt'
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Download a Build Artifact
- name: Upload Bungee
uses: actions/upload-artifact@v2.2.3
with:
# Artifact name
name: Redis_JAR
name: RedisBungee-Bungee
# Destination path
path: target/RedisBungee*
path: RedisBungee-Bungee/target/*
- name: Upload Velocity
uses: actions/upload-artifact@v2.2.3
with:
name: RedisBungee-Velocity
path: RedisBungee-Velocity/target/*
- name: Upload API
uses: actions/upload-artifact@v2.2.3
with:
name: RedisBungee-API
path: RedisBungee-API/target/*

43
.gitignore vendored
View File

@@ -1,36 +1,43 @@
# Eclipse stuff
/.classpath
/.project
/.settings
.classpath
.project
.settings
# random files
.png
.jpg
# netbeans
/nbproject
/nbactions.xml
/nb-configuration.xml
nbproject
nbactions.xml
nb-configuration.xml
# we use maven!
/build.xml
build.xml
# maven
/target
/dependency-reduced-pom.xml
*/target
*/dependency-reduced-pom.xml
target
dependency-reduced-pom.xml
# vim
.*.sw[a-p]
# various other potential build files
/build
/bin
/dist
/manifest.mf
build
bin
dist
manifest.mf
# Mac filesystem dust
/.DS_Store
.DS_Store
# intellij
*.iml
*.ipr
*.iws
.idea/
.idea/
*.versionBackup
*.versionsBackup
# java docs
javadoc

View File

@@ -1,117 +0,0 @@
/*
* Copyright 2007-present the original author or authors.
*
* 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
*
* http://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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

Binary file not shown.

View File

@@ -1,2 +0,0 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

172
README.md
View File

@@ -1,39 +1,153 @@
# Limework fork of RedisBungee
# RedisBungee fork By Limework
The original project of RedisBungee is no longer maintained, so we have forked the plugin.
Spigot link: [click](https://www.spigotmc.org/resources/redisbungee.87700/)
RedisBungee uses [Redis](https://redis.io) with Java client [Jedis](https://github.com/redis/jedis/)
to Synchronize players data between [BungeeCord](https://github.com/SpigotMC/BungeeCord) or [Velocity*](https://github.com/PaperMC/Velocity) proxies
this fork was made due the maintainer of redisBungee became inactive, so we took the place to develop it!
Velocity*: *version 3.1.2 or above is only supported, any version below that might work but might be unstable* [#40](https://github.com/ProxioDev/RedisBungee/pull/40)
RedisBungee bridges [Redis](http://redis.io) and BungeeCord together.
## compatibility with original RedisBungee in Bungeecord ecosystem
This fork ensures compatibility with old plugins, so it should work as drop replacement,
but since Api has been split from the platform there some changes that have to be done, so your plugin might not work if:
RedisBungee was used on thechunk server which we think was shutdown due website not loading...
~~This is the solution deployed on [The Chunk](http://thechunk.net) to make sure our multi-Bungee setup flows smoothly together.~~
* there is none at the moment, please report any findings at the issue page.
this will be deployed soon on [Govindas limework!](https://Limework.net)
Cluster mode compatibility in version 0.8.0:
## Compiling
Now you can use maven without installing it using Maven wrapper [github?](https://github.com/takari/maven-wrapper) :)
RedisBungee is distributed as a [maven](http://maven.apache.org) project. To compile it and install it in your local Maven repository:
git clone https://github.com/Limework/RedisBungee.git
cd RedisBungee
mvnw clean install
## Javadocs
The current version is 0.6-snapshot! will be updated every version number change!
https://limework.net/JavaDocs/RedisBungee/
## Configuration
**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The default [config](https://github.com/minecrafter/RedisBungee/blob/master/src/main/resources/example_config.yml) is saved when the plugin first starts.
## License!
If you are using static legacy method `RedisBungee#getPool()` it might fail in:
* if Cluster mode is enabled, due fact its Uses different classes
* if JedisPool compatibility mode is disabled in the config due fact project internally switched to JedisPooled than Jedis
*
## License
This project is distributed under Eclipse Public License 1.0
which you can find it [here](https://github.com/Limework/RedisBungee/blob/master/LICENSE)
You can find it [here](https://github.com/proxiodev/RedisBungee/blob/master/LICENSE)
you can find the original redisBungee by minecrafter [here](https://github.com/minecrafter/RedisBungee) or spigot page [here](https://www.spigotmc.org/resources/redisbungee.13494/)
You can find the original RedisBungee is by [astei](https://github.com/astei) and project can be found [here](https://github.com/minecrafter/RedisBungee) or spigot page [here, but its no longer available](https://www.spigotmc.org/resources/redisbungee.13494/)
## notes
If you are looking to use Original RedisBungee without a change to internals,
with critical bugs fixed, please use version [0.6.5](https://github.com/ProxioDev/RedisBungee/releases/tag/0.6.5) and java docs For legacy Version [0.6.5](https://proxiodev.github.io/RedisBungee-JavaDocs/0.6.5-SNAPSHOT/)
as its last version before internal changes. please note that you will not get support for any old builds unless critical bugs effecting both 0.6.5 and 0.7.0 or above.
*if you are here for transferring players to another proxy when the first proxy crashes or whatever this plugin won't do it, tell mojang to implement transfer packet*
[Click here, for more information about transfer packet](https://hypixel.net/threads/why-do-we-need-transfer-packets.1390307/)
SpigotMC resource page: [click](https://www.spigotmc.org/resources/redisbungee.87700/)
## Supported Redis versions
| Redis version | Supported |
|:-------------:|:---------:|
| 1.x.x | &#x2716; |
| 2.x.x | &#x2716; |
| 3.x.x | &#x2714; |
| 4.x.x | &#x2714; |
| 5.x.x | &#x2714; |
| 6.x.x | &#x2714; |
| 7.x.x | &#x2714; |
## Implementing RedisBungee in your plugin: [![RedisBungee Build](https://github.com/proxiodev/RedisBungee/actions/workflows/maven.yml/badge.svg)](https://github.com/Limework/RedisBungee/actions/workflows/maven.yml) [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee)
RedisBungee is distributed as a [maven](https://maven.apache.org) project.
By using jitpack [![](https://jitpack.io/v/ProxioDev/redisbungee.svg)](https://jitpack.io/#ProxioDev/redisbungee)
## Setup jitpack repository
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
```
## [BungeeCord](https://github.com/SpigotMC/BungeeCord)
add this in your project dependencies
```xml
<dependency>
<groupId>com.github.proxiodev.redisbungee</groupId>
<artifactId>RedisBungee-Bungee</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
```
then in your project plugin.yml add `RedisBungee` to `depends` like this
```yaml
name: "yourplugin"
main: your.main.class
version: 1.0.0-SNAPSHOT
author: idk
depends: [ RedisBungee ]
```
## [Velocity](https://github.com/PaperMC/Velocity)
```xml
<dependency>
<groupId>com.github.proxiodev.redisbungee</groupId>
<artifactId>RedisBungee-Velocity</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
```
then to make your plugin depends on RedisBungee, make sure your plugin class Annotation have `@Dependency(id = "redisbungee")` like this
```java
@Plugin(
id = "myplugin",
name = "My Plugin",
version = "0.1.0-beta",
dependencies = {
@Dependency(id = "redisbungee")
}
)
public class PluginMainClass {
}
```
## Getting the latest commits to your code
If you want to use the latest commits without waiting for releases.
first, install it to your maven local repo
```bash
git clone https://github.com/ProxioDev/RedisBungee.git
cd RedisBungee
mvn clean install
```
then use any of these in your project.
```xml
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-Bungee</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
```
```xml
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-Velocity</artifactId>
<version>VERSION</version>
<scope>provided</scope>
</dependency>
```
## Javadocs
For current version [0.8.0](https://proxiodev.github.io/RedisBungee-JavaDocs/0.8.0-SNAPSHOT/)
## Configuration
**REDISBUNGEE REQUIRES A REDIS SERVER**, preferably with reasonably low latency. The default [config](https://github.com/ProxioDev/RedisBungee/blob/develop/RedisBungee-API/src/main/resources/config.yml) is saved when the plugin first starts.
## Support
You can join our matrix room [here](https://matrix.to/#/!zhedzmRNSZXfuOPZUB:govindas.net?via=govindas.net&via=matrix.org)
![icon](https://matrix.org/images/matrix-logo-white.svg)
## YourKit
YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/).
![YourKit](https://www.yourkit.com/images/yklogo.png)

94
RedisBungee-API/pom.xml Normal file
View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>RedisBungee</artifactId>
<groupId>com.imaginarycode.minecraft</groupId>
<version>0.10.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>RedisBungee-API</artifactId>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<configuration>
</configuration>
<executions>
<execution>
<id>bundle-sources</id>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
<goal>test-jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<source>8</source>
<destDir>../javadoc/${project.name}</destDir>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-yaml</artifactId>
<version>3.7.2</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,32 +1,59 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import lombok.NonNull;
import net.md_5.bungee.api.config.ServerInfo;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import org.checkerframework.checker.nullness.qual.NonNull;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPooled;
import java.net.InetAddress;
import java.util.*;
/**
* This class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungee#getApi()}.
* This abstract class is extended by platform plugin to provide some platform specific methods.
* overall its general contains all methods needed by external usage.
*
* @author tuxed
* @since 0.2.3
* @author Ham1255
* @since 0.8.0
*/
public class RedisBungeeAPI {
private final RedisBungee plugin;
private final List<String> reservedChannels;
@SuppressWarnings("unused")
public abstract class AbstractRedisBungeeAPI {
protected final RedisBungeePlugin<?> plugin;
private static AbstractRedisBungeeAPI abstractRedisBungeeAPI;
protected final List<String> reservedChannels;
RedisBungeeAPI(RedisBungee plugin) {
this.plugin = plugin;
AbstractRedisBungeeAPI(RedisBungeePlugin<?> plugin) {
// this does make sure that no one can place first initiated API class.
if (abstractRedisBungeeAPI == null) {
abstractRedisBungeeAPI = this;
}
this.reservedChannels = ImmutableList.of(
"redisbungee-allservers",
"redisbungee-" + RedisBungee.getConfiguration().getServerId(),
"redisbungee-" + plugin.getConfiguration().getProxyId(),
"redisbungee-data"
);
this.plugin = plugin;
}
/**
@@ -53,12 +80,11 @@ public class RedisBungeeAPI {
* Get the server where the specified player is playing. This function also deals with the case of local players
* as well, and will return local information on them.
*
* @param player a player name
* @return a {@link net.md_5.bungee.api.config.ServerInfo} for the server the player is on.
* @param player a player uuid
* @return a String name for the server the player is on.
*/
public final ServerInfo getServerFor(@NonNull UUID player) {
String server = plugin.getDataManager().getServer(player);
return plugin.getProxy().getServerInfo(server);
public final String getServerNameFor(@NonNull UUID player) {
return plugin.getDataManager().getServer(player);
}
/**
@@ -94,7 +120,7 @@ public class RedisBungeeAPI {
* @since 0.2.5
*/
public final Multimap<String, UUID> getServerToPlayers() {
return plugin.serversToPlayers();
return plugin.serverToPlayersCache();
}
/**
@@ -165,8 +191,8 @@ public class RedisBungeeAPI {
*
* @param proxyId a proxy ID
* @param command the command to send and execute
* @see #getServerId()
* @see #getAllServers()
* @see #getProxyId()
* @see #getAllProxies()
* @since 0.2.5
*/
public final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
@@ -174,7 +200,8 @@ public class RedisBungeeAPI {
}
/**
* Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for {@link com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent} to fire.
* Sends a message to a PubSub channel. The channel has to be subscribed to on this, or another redisbungee instance for
* PubSubMessageEvent to fire.
*
* @param channel The PubSub channel
* @param message the message body to send
@@ -185,14 +212,38 @@ public class RedisBungeeAPI {
}
/**
* Get the current BungeeCord server ID for this server.
* Get the current BungeeCord / Velocity proxy ID for this server.
*
* @return the current server ID
* @see #getAllProxies()
* @since 0.8.0
*/
public final String getProxyId() {
return plugin.getConfiguration().getProxyId();
}
/**
* Get the current BungeeCord / Velocity proxy ID for this server.
*
* @return the current server ID
* @see #getAllServers()
* @since 0.2.5
* @deprecated to avoid confusion between A server and A proxy see #getProxyId()
*/
@Deprecated
public final String getServerId() {
return RedisBungee.getConfiguration().getServerId();
return getProxyId();
}
/**
* Get all the linked proxies in this network.
*
* @return the list of all proxies
* @see #getProxyId()
* @since 0.8.0
*/
public final List<String> getAllProxies() {
return plugin.getProxiesIds();
}
/**
@@ -201,19 +252,21 @@ public class RedisBungeeAPI {
* @return the list of all proxies
* @see #getServerId()
* @since 0.2.5
* @deprecated to avoid confusion between A server and A proxy see see {@link #getAllProxies()}
*/
@Deprecated
public final List<String> getAllServers() {
return plugin.getServerIds();
return getAllProxies();
}
/**
* Register (a) PubSub channel(s), so that you may handle {@link com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent} for it.
* Register (a) PubSub channel(s), so that you may handle PubSubMessageEvent for it.
*
* @param channels the channels to register
* @since 0.3
*/
public final void registerPubSubChannels(String... channels) {
RedisBungee.getPubSubListener().addChannel(channels);
plugin.getPubSubListener().addChannel(channels);
}
/**
@@ -227,7 +280,7 @@ public class RedisBungeeAPI {
Preconditions.checkArgument(!reservedChannels.contains(channel), "attempting to unregister internal channel");
}
RedisBungee.getPubSubListener().removeChannel(channels);
plugin.getPubSubListener().removeChannel(channels);
}
/**
@@ -290,10 +343,127 @@ public class RedisBungeeAPI {
*
* @param name the UUID to fetch the name for
* @param expensiveLookups whether or not to perform potentially expensive lookups
* @return the UUID for the name
* @return the {@link UUID} for the name
* @since 0.3.2
*/
public final UUID getUuidFromName(@NonNull String name, boolean expensiveLookups) {
return plugin.getUuidTranslator().getTranslatedUuid(name, expensiveLookups);
}
/**
* Kicks a player from the network
*
* @param playerName player name
* @param message kick message that player will see on kick
* @since 0.8.0
*/
public void kickPlayer(String playerName, String message) {
plugin.kickPlayer(playerName, message);
}
/**
* Kicks a player from the network
*
* @param playerUUID player name
* @param message kick message that player will see on kick
* @since 0.8.0
*/
public void kickPlayer(UUID playerUUID, String message) {
plugin.kickPlayer(playerUUID, message);
}
/**
* This gives you instance of Jedis
*
* @return {@link Jedis}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#SINGLE}
* @see #getJedisPool()
* @since 0.7.0
*/
public Jedis requestJedis() {
if (getMode() == RedisBungeeMode.SINGLE) {
return getJedisPool().getResource();
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
/**
* This gets Redis Bungee {@link JedisPool}
*
* @return {@link JedisPool}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#SINGLE}
* @throws IllegalStateException if JedisPool compatibility mode is disabled in the config
* @since 0.6.5
*/
public JedisPool getJedisPool() {
if (getMode() == RedisBungeeMode.SINGLE) {
JedisPool jedisPool = ((JedisPooledSummoner) this.plugin.getSummoner()).getCompatibilityJedisPool();
if (jedisPool == null) {
throw new IllegalStateException("JedisPool compatibility mode is disabled");
}
return jedisPool;
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
/**
* This gives you an instance of JedisCluster that can't be closed
* see {@link com.imaginarycode.minecraft.redisbungee.api.summoners.NotClosableJedisCluster}
*
* @return {@link redis.clients.jedis.JedisCluster}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#CLUSTER}
* @since 0.8.0
*/
public JedisCluster requestClusterJedis() {
if (getMode() == RedisBungeeMode.CLUSTER) {
return ((JedisClusterSummoner) this.plugin.getSummoner()).obtainResource();
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.CLUSTER);
}
}
/**
* This gives you an instance of JedisPooled that can't be closed
* see {@link com.imaginarycode.minecraft.redisbungee.api.summoners.NotClosableJedisPooled}
*
* @return {@link redis.clients.jedis.JedisPooled}
* @throws IllegalStateException if the {@link #getMode()} is not equal to {@link RedisBungeeMode#SINGLE}
* @since 0.8.0
*/
public JedisPooled requestJedisPooled() {
if (getMode() == RedisBungeeMode.SINGLE) {
return ((JedisPooledSummoner) this.plugin.getSummoner()).obtainResource();
} else {
throw new IllegalStateException("Mode is not " + RedisBungeeMode.SINGLE);
}
}
/**
* returns Summoner class responsible for Single Jedis {@link redis.clients.jedis.JedisPooled} with {@link JedisPool}, Cluster Jedis {@link redis.clients.jedis.JedisCluster} handling
*
* @return {@link Summoner}
* @since 0.8.0
*/
public Summoner<?> getSummoner() {
return this.plugin.getSummoner();
}
/**
* shows what mode is RedisBungee is on
*
* @return {@link RedisBungeeMode}
* @since 0.8.0
*/
public RedisBungeeMode getMode() {
return this.plugin.getRedisBungeeMode();
}
public static AbstractRedisBungeeAPI getAbstractRedisBungeeAPI() {
return abstractRedisBungeeAPI;
}
}

View File

@@ -0,0 +1,310 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.net.InetAddresses;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* This class manages all the data that RedisBungee fetches from Redis, along with updates to that data.
*
* @since 0.3.3
*/
public abstract class AbstractDataManager<P, PL, PD, PS> {
protected final RedisBungeePlugin<P> plugin;
private final Cache<UUID, String> serverCache = createCache();
private final Cache<UUID, String> proxyCache = createCache();
private final Cache<UUID, InetAddress> ipCache = createCache();
private final Cache<UUID, Long> lastOnlineCache = createCache();
private final Gson gson = new Gson();
public AbstractDataManager(RedisBungeePlugin<P> plugin) {
this.plugin = plugin;
}
private static <K, V> Cache<K, V> createCache() {
// TODO: Allow customization via cache specification, ala ServerListPlus
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
public String getServer(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return plugin.isPlayerOnAServer(player) ? plugin.getPlayerServerName(player) : null;
try {
return serverCache.get(uuid, new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "server"), "user not found");
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.logFatal("Unable to get server");
throw new RuntimeException("Unable to get server for " + uuid, e);
}
}
public String getProxy(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return plugin.getConfiguration().getProxyId();
try {
return proxyCache.get(uuid, new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
return Objects.requireNonNull(unifiedJedis.hget("player:" + uuid, "proxy"), "user not found");
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.logFatal("Unable to get proxy");
throw new RuntimeException("Unable to get proxy for " + uuid, e);
}
}
public InetAddress getIp(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return plugin.getPlayerIp(player);
try {
return ipCache.get(uuid, new RedisTask<InetAddress>(plugin) {
@Override
public InetAddress unifiedJedisTask(UnifiedJedis unifiedJedis) {
String result = unifiedJedis.hget("player:" + uuid, "ip");
if (result == null)
throw new NullPointerException("user not found");
return InetAddresses.forString(result);
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.logFatal("Unable to get IP");
throw new RuntimeException("Unable to get IP for " + uuid, e);
}
}
public long getLastOnline(final UUID uuid) {
P player = plugin.getPlayer(uuid);
if (player != null)
return 0;
try {
return lastOnlineCache.get(uuid, new RedisTask<Long>(plugin) {
@Override
public Long unifiedJedisTask(UnifiedJedis unifiedJedis) {
String result = unifiedJedis.hget("player:" + uuid, "online");
return result == null ? -1 : Long.parseLong(result);
}
});
} catch (ExecutionException e) {
plugin.logFatal("Unable to get last time online");
throw new RuntimeException("Unable to get last time online for " + uuid, e);
}
}
protected void invalidate(UUID uuid) {
ipCache.invalidate(uuid);
lastOnlineCache.invalidate(uuid);
serverCache.invalidate(uuid);
proxyCache.invalidate(uuid);
}
// Invalidate all entries related to this player, since they now lie. (call invalidate(uuid))
public abstract void onPostLogin(PL event);
// Invalidate all entries related to this player, since they now lie. (call invalidate(uuid))
public abstract void onPlayerDisconnect(PD event);
public abstract void onPubSubMessage(PS event);
public abstract boolean handleKick(UUID target, String message);
protected void handlePubSubMessage(String channel, String message) {
if (!channel.equals("redisbungee-data"))
return;
// Partially deserialize the message so we can look at the action
JsonObject jsonObject = JsonParser.parseString(message).getAsJsonObject();
final String source = jsonObject.get("source").getAsString();
if (source.equals(plugin.getConfiguration().getProxyId()))
return;
DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString());
switch (action) {
case JOIN:
final DataManagerMessage<LoginPayload> message1 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<LoginPayload>>() {
}.getType());
proxyCache.put(message1.getTarget(), message1.getSource());
lastOnlineCache.put(message1.getTarget(), (long) 0);
ipCache.put(message1.getTarget(), message1.getPayload().getAddress());
plugin.executeAsync(() -> {
Object event = plugin.createPlayerJoinedNetworkEvent(message1.getTarget());
plugin.fireEvent(event);
});
break;
case LEAVE:
final DataManagerMessage<LogoutPayload> message2 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<LogoutPayload>>() {
}.getType());
invalidate(message2.getTarget());
lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp());
plugin.executeAsync(() -> {
Object event = plugin.createPlayerLeftNetworkEvent(message2.getTarget());
plugin.fireEvent(event);
});
break;
case SERVER_CHANGE:
final DataManagerMessage<ServerChangePayload> message3 = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<ServerChangePayload>>() {
}.getType());
serverCache.put(message3.getTarget(), message3.getPayload().getServer());
plugin.executeAsync(() -> {
Object event = plugin.createPlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer());
plugin.fireEvent(event);
});
break;
case KICK:
final DataManagerMessage<KickPayload> kickPayload = gson.fromJson(jsonObject, new TypeToken<DataManagerMessage<KickPayload>>() {
}.getType());
plugin.executeAsync(() -> handleKick(kickPayload.target, kickPayload.payload.message));
break;
}
}
public static class DataManagerMessage<T extends Payload> {
private final UUID target;
private final String source;
private final Action action; // for future use!
private final T payload;
public DataManagerMessage(UUID target, String source, Action action, T payload) {
this.target = target;
this.source = source;
this.action = action;
this.payload = payload;
}
public UUID getTarget() {
return target;
}
public String getSource() {
return source;
}
public Action getAction() {
return action;
}
public T getPayload() {
return payload;
}
public enum Action {
JOIN,
LEAVE,
KICK,
SERVER_CHANGE
}
}
public static abstract class Payload {
}
public static class KickPayload extends Payload {
private final String message;
public KickPayload(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static class LoginPayload extends Payload {
private final InetAddress address;
public LoginPayload(InetAddress address) {
this.address = address;
}
public InetAddress getAddress() {
return address;
}
}
public static class ServerChangePayload extends Payload {
private final String server;
private final String oldServer;
public ServerChangePayload(String server, String oldServer) {
this.server = server;
this.oldServer = oldServer;
}
public String getServer() {
return server;
}
public String getOldServer() {
return oldServer;
}
}
public static class LogoutPayload extends Payload {
private final long timestamp;
public LogoutPayload(long timestamp) {
this.timestamp = timestamp;
}
public long getTimestamp() {
return timestamp;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.io.ByteArrayDataOutput;
import com.google.gson.Gson;
import java.net.InetAddress;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public abstract class AbstractRedisBungeeListener<LE, PLE, PD, SC, PP, PM, PS> {
protected final RedisBungeePlugin<?> plugin;
protected final List<InetAddress> exemptAddresses;
protected final Gson gson = new Gson();
public AbstractRedisBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
this.plugin = plugin;
this.exemptAddresses = exemptAddresses;
}
public void onLogin(LE event) {}
public abstract void onPostLogin(PLE event);
public abstract void onPlayerDisconnect(PD event);
public abstract void onServerChange(SC event);
public abstract void onPing(PP event);
public abstract void onPluginMessage(PM event);
public abstract void onPubSubMessage(PS event);
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import redis.clients.jedis.JedisPubSub;
public class JedisPubSubHandler extends JedisPubSub {
private final RedisBungeePlugin<?> plugin;
public JedisPubSubHandler(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Override
public void onMessage(final String s, final String s2) {
if (s2.trim().length() == 0) return;
plugin.executeAsync(new Runnable() {
@Override
public void run() {
Object event = plugin.createPubSubEvent(s, s2);
plugin.fireEvent(event);
}
});
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class PubSubListener implements Runnable {
private JedisPubSubHandler jpsh;
private final Set<String> addedChannels = new HashSet<String>();
private final RedisBungeePlugin<?> plugin;
public PubSubListener(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
@Override
public void run() {
RedisTask<Void> subTask = new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
jpsh = new JedisPubSubHandler(plugin);
addedChannels.add("redisbungee-" + plugin.getConfiguration().getProxyId());
addedChannels.add("redisbungee-allservers");
addedChannels.add("redisbungee-data");
unifiedJedis.subscribe(jpsh, addedChannels.toArray(new String[0]));
return null;
}
};
try {
subTask.execute();
} catch (Exception e) {
plugin.logWarn("PubSub error, attempting to recover in 5 secs.");
plugin.executeAsyncAfter(this, TimeUnit.SECONDS, 5);
}
}
public void addChannel(String... channel) {
addedChannels.addAll(Arrays.asList(channel));
jpsh.subscribe(channel);
}
public void removeChannel(String... channel) {
Arrays.asList(channel).forEach(addedChannels::remove);
jpsh.unsubscribe(channel);
}
public void poison() {
addedChannels.clear();
jpsh.unsubscribe();
}
}

View File

@@ -0,0 +1,15 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
public enum RedisBungeeMode {
SINGLE, CLUSTER
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.events.EventsPlatform;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
/**
* This Class has all internal methods needed by every redis bungee plugin, and it can be used to implement another platforms than bungeecord or another forks of RedisBungee
*
* @author Ham1255
* @since 0.7.0
*/
public interface RedisBungeePlugin<P> extends EventsPlatform {
default void initialize() {
}
default void stop() {
}
Summoner<?> getSummoner();
RedisBungeeConfiguration getConfiguration();
int getCount();
default int getCurrentCount() {
return new RedisTask<Long>(this) {
@Override
public Long unifiedJedisTask(UnifiedJedis unifiedJedis) {
long total = 0;
long redisTime = getRedisTime(unifiedJedis);
Map<String, String> heartBeats = unifiedJedis.hgetAll("heartbeats");
for (Map.Entry<String, String> stringStringEntry : heartBeats.entrySet()) {
String k = stringStringEntry.getKey();
String v = stringStringEntry.getValue();
long heartbeatTime = Long.parseLong(v);
if (heartbeatTime + RedisUtil.PROXY_TIMEOUT >= redisTime) {
total = total + unifiedJedis.scard("proxy:" + k + ":usersOnline");
}
}
return total;
}
}.execute().intValue();
}
Set<String> getLocalPlayersAsUuidStrings();
AbstractDataManager<P, ?, ?, ?> getDataManager();
default Set<UUID> getPlayers() {
return new RedisTask<Set<UUID>>(this) {
@Override
public Set<UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
try {
List<String> keys = new ArrayList<>();
for (String i : getProxiesIds()) {
keys.add("proxy:" + i + ":usersOnline");
}
if (!keys.isEmpty()) {
Set<String> users = unifiedJedis.sunion(keys.toArray(new String[0]));
if (users != null && !users.isEmpty()) {
for (String user : users) {
try {
setBuilder = setBuilder.add(UUID.fromString(user));
} catch (IllegalArgumentException ignored) {
}
}
}
}
} catch (JedisConnectionException e) {
// Redis server has disappeared!
logFatal("Unable to get connection from pool - did your Redis server go away?");
throw new RuntimeException("Unable to get all players online", e);
}
return setBuilder.build();
}
}.execute();
}
AbstractRedisBungeeAPI getAbstractRedisBungeeApi();
UUIDTranslator getUuidTranslator();
Multimap<String, UUID> serverToPlayersCache();
default Multimap<String, UUID> serversToPlayers() {
return new RedisTask<Multimap<String, UUID>>(this) {
@Override
public Multimap<String, UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
for (String serverId : getProxiesIds()) {
Set<String> players = unifiedJedis.smembers("proxy:" + serverId + ":usersOnline");
for (String player : players) {
String playerServer = unifiedJedis.hget("player:" + player, "server");
if (playerServer == null) {
continue;
}
builder.put(playerServer, UUID.fromString(player));
}
}
return builder.build();
}
}.execute();
}
default Set<UUID> getPlayersOnProxy(String proxyId) {
checkArgument(getProxiesIds().contains(proxyId), proxyId + " is not a valid proxy ID");
return new RedisTask<Set<UUID>>(this) {
@Override
public Set<UUID> unifiedJedisTask(UnifiedJedis unifiedJedis) {
Set<String> users = unifiedJedis.smembers("proxy:" + proxyId + ":usersOnline");
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
for (String user : users) {
builder.add(UUID.fromString(user));
}
return builder.build();
}
}.execute();
}
default void sendProxyCommand(String proxyId, String command) {
checkArgument(getProxiesIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid");
sendChannelMessage("redisbungee-" + proxyId, command);
}
List<String> getProxiesIds();
default List<String> getCurrentProxiesIds(boolean lagged) {
return new RedisTask<List<String>>(this) {
@Override
public List<String> unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
long time = getRedisTime(unifiedJedis);
ImmutableList.Builder<String> servers = ImmutableList.builder();
Map<String, String> heartbeats = unifiedJedis.hgetAll("heartbeats");
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
try {
long stamp = Long.parseLong(entry.getValue());
if (lagged ? time >= stamp + RedisUtil.PROXY_TIMEOUT : time <= stamp + RedisUtil.PROXY_TIMEOUT) {
servers.add(entry.getKey());
} else if (time > stamp + RedisUtil.PROXY_TIMEOUT) {
logWarn(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
unifiedJedis.hdel("heartbeats", entry.getKey());
}
} catch (NumberFormatException ignored) {
}
}
return servers.build();
} catch (JedisConnectionException e) {
logFatal("Unable to fetch server IDs");
e.printStackTrace();
return Collections.singletonList(getConfiguration().getProxyId());
}
}
}.execute();
}
PubSubListener getPubSubListener();
default void sendChannelMessage(String channel, String message) {
new RedisTask<Void>(this) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
unifiedJedis.publish(channel, message);
} catch (JedisConnectionException e) {
// Redis server has disappeared!
logFatal("Unable to get connection from pool - did your Redis server go away?");
throw new RuntimeException("Unable to publish channel message", e);
}
return null;
}
}.execute();
}
void executeAsync(Runnable runnable);
void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time);
boolean isOnlineMode();
void logInfo(String msg);
void logWarn(String msg);
void logFatal(String msg);
P getPlayer(UUID uuid);
P getPlayer(String name);
UUID getPlayerUUID(String player);
String getPlayerName(UUID player);
String getPlayerServerName(P player);
boolean isPlayerOnAServer(P player);
InetAddress getPlayerIp(P player);
default void sendProxyCommand(String cmd) {
sendProxyCommand(getConfiguration().getProxyId(), cmd);
}
default Long getRedisTime(UnifiedJedis unifiedJedis) {
List<Object> data = (List<Object>) unifiedJedis.sendCommand(Protocol.Command.TIME);
List<String> times = new ArrayList<>();
data.forEach((o) -> times.add(new String((byte[])o)));
return getRedisTime(times);
}
default long getRedisTime(List<String> timeRes) {
return Long.parseLong(timeRes.get(0));
}
default void kickPlayer(UUID playerUniqueId, String message) {
// first handle on origin proxy if player not found publish the payload
if (!getDataManager().handleKick(playerUniqueId, message)) {
new RedisTask<Void>(this) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PayloadUtils.kickPlayerPayload(playerUniqueId, message, unifiedJedis);
return null;
}
}.execute();
}
}
default void kickPlayer(String playerName, String message) {
// fetch the uuid from name
UUID playerUUID = getUuidTranslator().getTranslatedUuid(playerName, true);
kickPlayer(playerUUID, message);
}
RedisBungeeMode getRedisBungeeMode();
void updateProxiesIds();
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.*;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import redis.clients.jedis.providers.PooledConnectionProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
public interface ConfigLoader {
default void loadConfig(RedisBungeePlugin<?> plugin, File dataFolder) throws IOException {
loadConfig(plugin, dataFolder.toPath());
}
default void loadConfig(RedisBungeePlugin<?> plugin, Path dataFolder) throws IOException {
Path configFile = createConfigFile(dataFolder);
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(configFile).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
if (node.getNode("config-version").getInt(0) != RedisBungeeConfiguration.CONFIG_VERSION) {
handleOldConfig(dataFolder);
node = yamlConfigurationFileLoader.load();
}
final boolean useSSL = node.getNode("useSSL").getBoolean(false);
final boolean overrideBungeeCommands = node.getNode("override-bungee-commands").getBoolean(false);
final boolean registerLegacyCommands = node.getNode("register-legacy-commands").getBoolean(false);
final boolean restoreOldKickBehavior = node.getNode("disable-kick-when-online").getBoolean(false);
String redisPassword = node.getNode("redis-password").getString("");
String proxyId = node.getNode("proxy-id").getString("test-1");
final int maxConnections = node.getNode("max-redis-connections").getInt(10);
List<String> exemptAddresses;
try {
exemptAddresses = node.getNode("exempt-ip-addresses").getList(TypeToken.of(String.class));
} catch (ObjectMappingException e) {
exemptAddresses = Collections.emptyList();
}
// check redis password
if ((redisPassword.isEmpty() || redisPassword.equals("none"))) {
redisPassword = null;
plugin.logWarn("password is empty");
}
if (useSSL) {
plugin.logInfo("Using ssl");
}
// Configuration sanity checks.
if (proxyId == null || proxyId.isEmpty()) {
String genId = UUID.randomUUID().toString();
plugin.logInfo("Generated proxy id " + genId + " and saving it to config.");
node.getNode("proxy-id").setValue(genId);
yamlConfigurationFileLoader.save(node);
proxyId = genId;
plugin.logInfo("proxy id was generated: " + proxyId);
} else {
plugin.logInfo("Loaded proxy id " + proxyId);
}
RedisBungeeConfiguration configuration = new RedisBungeeConfiguration(proxyId, exemptAddresses, registerLegacyCommands, overrideBungeeCommands, getMessagesFromPath(createMessagesFile(dataFolder)), restoreOldKickBehavior);
Summoner<?> summoner;
RedisBungeeMode redisBungeeMode;
if (node.getNode("cluster-mode-enabled").getBoolean(false)) {
plugin.logInfo("RedisBungee MODE: CLUSTER");
Set<HostAndPort> hostAndPortSet = new HashSet<>();
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(maxConnections);
poolConfig.setBlockWhenExhausted(true);
node.getNode("redis-cluster-servers").getChildrenList().forEach((childNode) -> {
Map<Object, ? extends ConfigurationNode> hostAndPort = childNode.getChildrenMap();
String host = hostAndPort.get("host").getString();
int port = hostAndPort.get("port").getInt();
hostAndPortSet.add(new HostAndPort(host, port));
});
plugin.logInfo(hostAndPortSet.size() + " cluster nodes were specified");
if (hostAndPortSet.isEmpty()) {
throw new RuntimeException("No redis cluster servers specified");
}
summoner = new JedisClusterSummoner(new ClusterConnectionProvider(hostAndPortSet, DefaultJedisClientConfig.builder().password(redisPassword).ssl(useSSL).socketTimeoutMillis(5000).timeoutMillis(10000).build(), poolConfig));
redisBungeeMode = RedisBungeeMode.CLUSTER;
} else {
plugin.logInfo("RedisBungee MODE: SINGLE");
final String redisServer = node.getNode("redis-server").getString("127.0.0.1");
final int redisPort = node.getNode("redis-port").getInt(6379);
if (redisServer != null && redisServer.isEmpty()) {
throw new RuntimeException("No redis server specified");
}
JedisPool jedisPool = null;
if (node.getNode("enable-jedis-pool-compatibility").getBoolean(true)) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(node.getNode("compatibility-max-connections").getInt(3));
config.setBlockWhenExhausted(true);
jedisPool = new JedisPool(config, redisServer, redisPort, 5000, redisPassword, useSSL);
plugin.logInfo("Compatibility JedisPool was created");
}
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(maxConnections);
poolConfig.setBlockWhenExhausted(true);
summoner = new JedisPooledSummoner(new PooledConnectionProvider(new ConnectionFactory(new HostAndPort(redisServer, redisPort), DefaultJedisClientConfig.builder().timeoutMillis(5000).ssl(useSSL).password(redisPassword).build()), poolConfig), jedisPool);
redisBungeeMode = RedisBungeeMode.SINGLE;
}
plugin.logInfo("Successfully connected to Redis.");
onConfigLoad(configuration, summoner, redisBungeeMode);
}
void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode);
default ImmutableMap<RedisBungeeConfiguration.MessageType, String> getMessagesFromPath(Path path) throws IOException {
final YAMLConfigurationLoader yamlConfigurationFileLoader = YAMLConfigurationLoader.builder().setPath(path).build();
ConfigurationNode node = yamlConfigurationFileLoader.load();
HashMap<RedisBungeeConfiguration.MessageType, String> messages = new HashMap<>();
messages.put(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION, node.getNode("logged-in-other-location").getString("§cLogged in from another location."));
messages.put(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN, node.getNode("already-logged-in").getString("§cYou are already logged in!"));
return ImmutableMap.copyOf(messages);
}
default Path createMessagesFile(Path dataFolder) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
Path file = dataFolder.resolve("messages.yml");
if (Files.notExists(file)) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream("messages.yml")) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
}
default Path createConfigFile(Path dataFolder) throws IOException {
if (Files.notExists(dataFolder)) {
Files.createDirectory(dataFolder);
}
Path file = dataFolder.resolve("config.yml");
if (Files.notExists(file)) {
try (InputStream in = getClass().getClassLoader().getResourceAsStream("config.yml")) {
Files.createFile(file);
assert in != null;
Files.copy(in, file, StandardCopyOption.REPLACE_EXISTING);
}
}
return file;
}
default void handleOldConfig(Path dataFolder) throws IOException {
Path oldConfigFolder = dataFolder.resolve("old_config");
if (Files.notExists(oldConfigFolder)) {
Files.createDirectory(oldConfigFolder);
}
Path oldConfigPath = dataFolder.resolve("config.yml");
Files.move(oldConfigPath, oldConfigFolder.resolve(UUID.randomUUID() + "_config.yml"));
createConfigFile(dataFolder);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.config;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
public class RedisBungeeConfiguration {
public enum MessageType {
LOGGED_IN_OTHER_LOCATION,
ALREADY_LOGGED_IN
}
private final ImmutableMap<MessageType, String> messages;
public static final int CONFIG_VERSION = 1;
private final String proxyId;
private final List<InetAddress> exemptAddresses;
private final boolean registerLegacyCommands;
private final boolean overrideBungeeCommands;
private final boolean restoreOldKickBehavior;
public RedisBungeeConfiguration(String proxyId, List<String> exemptAddresses, boolean registerLegacyCommands, boolean overrideBungeeCommands, ImmutableMap<MessageType, String> messages, boolean restoreOldKickBehavior) {
this.proxyId = proxyId;
this.messages = messages;
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
for (String s : exemptAddresses) {
addressBuilder.add(InetAddresses.forString(s));
}
this.exemptAddresses = addressBuilder.build();
this.registerLegacyCommands = registerLegacyCommands;
this.overrideBungeeCommands = overrideBungeeCommands;
this.restoreOldKickBehavior = restoreOldKickBehavior;
}
public String getProxyId() {
return proxyId;
}
public List<InetAddress> getExemptAddresses() {
return exemptAddresses;
}
public boolean doRegisterLegacyCommands() {
return registerLegacyCommands;
}
public boolean doOverrideBungeeCommands() {
return overrideBungeeCommands;
}
public ImmutableMap<MessageType, String> getMessages() {
return messages;
}
public boolean restoreOldKickBehavior() {
return restoreOldKickBehavior;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID;
/**
* Since each platform have their own events' implementation for example Bungeecord events extends Event while velocity don't
*
* @author Ham1255
* @since 0.7.0
*
*/
public interface EventsPlatform {
IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server);
IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid);
IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid);
IPubSubMessageEvent createPubSubEvent(String channel, String message);
void fireEvent(Object event);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID;
public interface IPlayerChangedServerNetworkEvent extends RedisBungeeEvent {
UUID getUuid();
String getServer();
String getPreviousServer();
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID;
public interface IPlayerJoinedNetworkEvent extends RedisBungeeEvent {
UUID getUuid();
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.events;
import java.util.UUID;
public interface IPlayerLeftNetworkEvent extends RedisBungeeEvent {
UUID getUuid();
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.events;
public interface IPubSubMessageEvent extends RedisBungeeEvent {
String getChannel();
String getMessage();
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.events;
interface RedisBungeeEvent {
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import java.io.IOException;
import java.time.Duration;
public class JedisClusterSummoner implements Summoner<JedisCluster> {
public final ClusterConnectionProvider clusterConnectionProvider;
public JedisClusterSummoner(ClusterConnectionProvider clusterConnectionProvider) {
this.clusterConnectionProvider = clusterConnectionProvider;
// test the connection
JedisCluster jedisCluster = obtainResource();
jedisCluster.set("random_data", "0");
jedisCluster.del("random_data");
}
@Override
public void close() throws IOException {
this.clusterConnectionProvider.close();
}
@Override
public JedisCluster obtainResource() {
return new NotClosableJedisCluster(this.clusterConnectionProvider, 60, Duration.ofSeconds(30000));
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.PooledConnectionProvider;
import java.io.IOException;
public class JedisPooledSummoner implements Summoner<JedisPooled> {
private final PooledConnectionProvider connectionProvider;
private final JedisPool jedisPool;
public JedisPooledSummoner(PooledConnectionProvider connectionProvider, JedisPool jedisPool) {
this.connectionProvider = connectionProvider;
this.jedisPool = jedisPool;
// test connections
if (jedisPool != null) {
try (Jedis jedis = this.jedisPool.getResource()) {
// Test the connection to make sure configuration is right
jedis.ping();
}
}
final JedisPooled jedisPooled = this.obtainResource();
jedisPooled.set("random_data", "0");
jedisPooled.del("random_data");
}
@Override
public JedisPooled obtainResource() {
// create UnClosable JedisPool *disposable*
return new NotClosableJedisPooled(this.connectionProvider);
}
public JedisPool getCompatibilityJedisPool() {
return this.jedisPool;
}
@Override
public void close() throws IOException {
if (this.jedisPool != null) {
this.jedisPool.close();
}
this.connectionProvider.close();
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.ClusterConnectionProvider;
import redis.clients.jedis.providers.PooledConnectionProvider;
import java.time.Duration;
public class NotClosableJedisCluster extends JedisCluster {
NotClosableJedisCluster(ClusterConnectionProvider provider, int maxAttempts, Duration maxTotalRetriesDuration) {
super(provider, maxAttempts, maxTotalRetriesDuration);
}
@Override
public void close() {
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.providers.PooledConnectionProvider;
public class NotClosableJedisPooled extends JedisPooled {
NotClosableJedisPooled(PooledConnectionProvider provider) {
super(provider);
}
@Override
public void close() {
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.summoners;
import java.io.Closeable;
/**
* This class intended for future release to support redis sentinel or redis clusters
*
* @author Ham1255
* @since 0.7.0
*
*/
public interface Summoner<P> extends Closeable {
P obtainResource();
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class HeartbeatTask extends RedisTask<Void>{
public final static TimeUnit REPEAT_INTERVAL_TIME_UNIT = TimeUnit.SECONDS;
public final static int INTERVAL = 1;
private final AtomicInteger globalPlayerCount;
public HeartbeatTask(RedisBungeePlugin<?> plugin, AtomicInteger globalPlayerCount) {
super(plugin);
this.globalPlayerCount = globalPlayerCount;
}
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
long redisTime = plugin.getRedisTime(unifiedJedis);
unifiedJedis.hset("heartbeats", plugin.getConfiguration().getProxyId(), String.valueOf(redisTime));
} catch (JedisConnectionException e) {
// Redis server has disappeared!
plugin.logFatal("Unable to update heartbeat - did your Redis server go away?");
e.printStackTrace();
return null;
}
try {
plugin.updateProxiesIds();
globalPlayerCount.set(plugin.getCurrentCount());
} catch (Throwable e) {
plugin.logFatal("Unable to update data - did your Redis server go away?");
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.util.RedisUtil;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.UnifiedJedis;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class InitialUtils {
public static void checkRedisVersion(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
// This is more portable than INFO <section>
String info = new String((byte[]) unifiedJedis.sendCommand(Protocol.Command.INFO));
for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) {
String version = s.split(":")[1];
plugin.logInfo(version + " <- redis version");
if (!RedisUtil.isRedisVersionRight(version)) {
plugin.logFatal("Your version of Redis (" + version + ") is not at least version 3.0 RedisBungee requires a newer version of Redis.");
throw new RuntimeException("Unsupported Redis version detected");
}
long uuidCacheSize = unifiedJedis.hlen("uuid-cache");
if (uuidCacheSize > 750000) {
plugin.logInfo("Looks like you have a really big UUID cache! Run https://github.com/ProxioDev/Brains");
}
break;
}
}
return null;
}
}.execute();
}
public static void checkIfRecovering(RedisBungeePlugin<?> plugin, Path dataFolder) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
Path crashFile = dataFolder.resolve("restarted_from_crash.txt");
if (Files.exists(crashFile)) {
try {
Files.delete(crashFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
plugin.logInfo("crash file was deleted");
} else if (unifiedJedis.hexists("heartbeats", plugin.getConfiguration().getProxyId())) {
try {
long value = Long.parseLong(unifiedJedis.hget("heartbeats", plugin.getConfiguration().getProxyId()));
long redisTime = plugin.getRedisTime(unifiedJedis);
if (redisTime < value + RedisUtil.PROXY_TIMEOUT) {
logImposter(plugin);
throw new RuntimeException("Possible impostor instance!");
}
} catch (NumberFormatException ignored) {
}
}
return null;
}
}.execute();
}
private static void logImposter(RedisBungeePlugin<?> plugin) {
plugin.logFatal("You have launched a possible impostor Velocity / Bungeecord instance. Another instance is already running.");
plugin.logFatal("For data consistency reasons, RedisBungee will now disable itself.");
plugin.logFatal("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.UnifiedJedis;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public abstract class IntegrityCheckTask extends RedisTask<Void> {
public static int INTERVAL = 30;
public static TimeUnit TIMEUNIT = TimeUnit.SECONDS;
public IntegrityCheckTask(RedisBungeePlugin<?> plugin) {
super(plugin);
}
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
Set<String> players = plugin.getLocalPlayersAsUuidStrings();
Set<String> playersInRedis = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline");
List<String> lagged = plugin.getCurrentProxiesIds(true);
// Clean up lagged players.
for (String s : lagged) {
Set<String> laggedPlayers = unifiedJedis.smembers("proxy:" + s + ":usersOnline");
unifiedJedis.del("proxy:" + s + ":usersOnline");
if (!laggedPlayers.isEmpty()) {
plugin.logInfo("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
for (String laggedPlayer : laggedPlayers) {
PlayerUtils.cleanUpPlayer(laggedPlayer, unifiedJedis, true);
}
}
}
Set<String> absentLocally = new HashSet<>(playersInRedis);
absentLocally.removeAll(players);
Set<String> absentInRedis = new HashSet<>(players);
absentInRedis.removeAll(playersInRedis);
for (String member : absentLocally) {
boolean found = false;
for (String proxyId : plugin.getProxiesIds()) {
if (proxyId.equals(plugin.getConfiguration().getProxyId())) continue;
if (unifiedJedis.sismember("proxy:" + proxyId + ":usersOnline", member)) {
// Just clean up the set.
found = true;
break;
}
}
if (!found) {
PlayerUtils.cleanUpPlayer(member, unifiedJedis, false);
plugin.logWarn("Player found in set that was not found locally and globally: " + member);
} else {
unifiedJedis.srem("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline", member);
plugin.logWarn("Player found in set that was not found locally, but is on another proxy: " + member);
}
}
// due unifiedJedis does not support pipelined.
//Pipeline pipeline = jedis.pipelined();
for (String player : absentInRedis) {
// Player not online according to Redis but not BungeeCord.
handlePlatformPlayer(player, unifiedJedis);
}
} catch (Throwable e) {
plugin.logFatal("Unable to fix up stored player data");
e.printStackTrace();
}
return null;
}
public abstract void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis);
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisClusterSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.JedisPooledSummoner;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import redis.clients.jedis.UnifiedJedis;
import java.util.concurrent.Callable;
public abstract class RedisTask<V> implements Runnable, Callable<V> {
protected final Summoner<?> summoner;
protected final AbstractRedisBungeeAPI api;
protected RedisBungeePlugin<?> plugin;
@Override
public V call() throws Exception {
return execute();
}
public RedisTask(AbstractRedisBungeeAPI api) {
this.api = api;
this.summoner = api.getSummoner();
}
public RedisTask(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
this.api = plugin.getAbstractRedisBungeeApi();
this.summoner = api.getSummoner();
}
public abstract V unifiedJedisTask(UnifiedJedis unifiedJedis);
@Override
public void run() {
this.execute();
}
public V execute(){
// JedisCluster, JedisPooled in fact is just UnifiedJedis does not need new instance since its single instance anyway.
if (api.getMode() == RedisBungeeMode.SINGLE) {
JedisPooledSummoner jedisSummoner = (JedisPooledSummoner) summoner;
return this.unifiedJedisTask(jedisSummoner.obtainResource());
} else if (api.getMode() == RedisBungeeMode.CLUSTER) {
JedisClusterSummoner jedisClusterSummoner = (JedisClusterSummoner) summoner;
return this.unifiedJedisTask(jedisClusterSummoner.obtainResource());
}
return null;
}
public RedisBungeePlugin<?> getPlugin() {
if (plugin == null) {
throw new NullPointerException("Plugin is null in the task");
}
return plugin;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.tasks;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.UnifiedJedis;
import java.util.Set;
public class ShutdownUtils {
public static void shutdownCleanup(RedisBungeePlugin<?> plugin) {
new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hdel("heartbeats", plugin.getConfiguration().getProxyId());
if (unifiedJedis.scard("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline") > 0) {
Set<String> players = unifiedJedis.smembers("proxy:" + plugin.getConfiguration().getProxyId() + ":usersOnline");
for (String member : players)
PlayerUtils.cleanUpPlayer(member, unifiedJedis, true);
}
return null;
}
}.execute();
}
}

View File

@@ -0,0 +1,24 @@
package com.imaginarycode.minecraft.redisbungee.api.util;
import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting
public class RedisUtil {
public final static int PROXY_TIMEOUT = 30;
public static boolean isRedisVersionRight(String redisVersion) {
String[] args = redisVersion.split("\\.");
if (args.length < 2) {
return false;
}
int major = Integer.parseInt(args[0]);
int minor = Integer.parseInt(args[1]);
return major >= 3 && minor >= 0;
}
// Ham1255: i am keeping this if some plugin uses this *IF*
@Deprecated
public static boolean canUseLua(String redisVersion) {
// Need to use >=3 to use Lua optimizations.
return isRedisVersionRight(redisVersion);
}
}

View File

@@ -1,14 +1,12 @@
package com.imaginarycode.minecraft.redisbungee.util;
package com.imaginarycode.minecraft.redisbungee.api.util.io;
import com.google.common.io.ByteStreams;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class IOUtil {
public static String readInputStreamAsString(InputStream is) {
String string;

View File

@@ -0,0 +1,41 @@
package com.imaginarycode.minecraft.redisbungee.api.util.payload;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.UUID;
public class PayloadUtils {
private static final Gson gson = new Gson();
public static void playerJoinPayload(UUID uuid, UnifiedJedis unifiedJedis, InetAddress inetAddress) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.JOIN,
new AbstractDataManager.LoginPayload(inetAddress))));
}
public static void playerQuitPayload(String uuid, UnifiedJedis unifiedJedis, long timestamp) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
UUID.fromString(uuid), AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.LEAVE,
new AbstractDataManager.LogoutPayload(timestamp))));
}
public static void playerServerChangePayload(UUID uuid, UnifiedJedis unifiedJedis, String newServer, String oldServer) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.SERVER_CHANGE,
new AbstractDataManager.ServerChangePayload(newServer, oldServer))));
}
public static void kickPlayerPayload(UUID uuid, String message, UnifiedJedis unifiedJedis) {
unifiedJedis.publish("redisbungee-data", gson.toJson(new AbstractDataManager.DataManagerMessage<>(
uuid, AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId(), AbstractDataManager.DataManagerMessage.Action.KICK,
new AbstractDataManager.KickPayload(message))));
}
}

View File

@@ -0,0 +1,57 @@
package com.imaginarycode.minecraft.redisbungee.api.util.player;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerJoinPayload;
import static com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils.playerQuitPayload;
public class PlayerUtils {
public static void cleanUpPlayer(String uuid, UnifiedJedis rsc, boolean firePayload) {
final long timestamp = System.currentTimeMillis();
final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid, rsc);
rsc.srem("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid);
if (!isKickedFromOtherLocation) {
rsc.hdel("player:" + uuid, "server", "ip", "proxy");
rsc.hset("player:" + uuid, "online", String.valueOf(timestamp));
}
if (firePayload && !isKickedFromOtherLocation) {
playerQuitPayload(uuid, rsc, timestamp);
}
}
public static void setKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) {
// set anything for sake of exists check. then expire it after 2 seconds. should be great?
unifiedJedis.set("kicked-other-location::" + uuid, "0");
unifiedJedis.expire("kicked-other-location::" + uuid, 2);
}
public static boolean isKickedOtherLocation(String uuid, UnifiedJedis unifiedJedis) {
return unifiedJedis.exists("kicked-other-location::" + uuid);
}
public static void createPlayer(UUID uuid, UnifiedJedis unifiedJedis, String currentServer, InetAddress hostname, boolean fireEvent) {
final boolean isKickedFromOtherLocation = isKickedOtherLocation(uuid.toString(), unifiedJedis);
Map<String, String> playerData = new HashMap<>(4);
playerData.put("online", "0");
playerData.put("ip", hostname.getHostName());
playerData.put("proxy", AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId());
if (currentServer != null) {
playerData.put("server", currentServer);
}
unifiedJedis.sadd("proxy:" + AbstractRedisBungeeAPI.getAbstractRedisBungeeAPI().getProxyId() + ":usersOnline", uuid.toString());
unifiedJedis.hset("player:" + uuid, playerData);
if (fireEvent && !isKickedFromOtherLocation) {
playerJoinPayload(uuid, unifiedJedis, hostname);
}
}
}

View File

@@ -0,0 +1,39 @@
package com.imaginarycode.minecraft.redisbungee.api.util.serialize;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.io.ByteArrayDataOutput;
import java.util.Collection;
import java.util.Map;
public class Serializations {
public static void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
output.writeInt(collection.elementSet().size());
for (Multiset.Entry<String> entry : collection.entrySet()) {
output.writeUTF(entry.getElement());
output.writeInt(entry.getCount());
}
}
@SuppressWarnings("SameParameterValue")
public static void serializeMultimap(Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
output.writeInt(collection.keySet().size());
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
output.writeUTF(entry.getKey());
if (includeNames) {
serializeCollection(entry.getValue(), output);
} else {
output.writeInt(entry.getValue().size());
}
}
}
public static void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
output.writeInt(collection.size());
for (Object o : collection) {
output.writeUTF(o.toString());
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import java.util.Calendar;
import java.util.UUID;
public class CachedUUIDEntry {
private final String name;
private final UUID uuid;
private final Calendar expiry;
public CachedUUIDEntry(String name, UUID uuid, Calendar expiry) {
this.name = name;
this.uuid = uuid;
this.expiry = expiry;
}
public String getName() {
return name;
}
public UUID getUuid() {
return uuid;
}
public Calendar getExpiry() {
return expiry;
}
public boolean expired() {
return Calendar.getInstance().after(expiry);
}
}

View File

@@ -1,24 +1,23 @@
package com.imaginarycode.minecraft.redisbungee.util.uuid;
package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.ResponseBody;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class NameFetcher {
@Setter
private static OkHttpClient httpClient;
private static final Gson gson = new Gson();
public static void setHttpClient(OkHttpClient httpClient) {
NameFetcher.httpClient = httpClient;
}
public static List<String> nameHistoryFromUuid(UUID uuid) throws IOException {
String url = "https://api.mojang.com/user/profiles/" + uuid.toString().replace("-", "") + "/names";
@@ -29,7 +28,7 @@ public class NameFetcher {
Type listType = new TypeToken<List<Name>>() {
}.getType();
List<Name> names = RedisBungee.getGson().fromJson(response, listType);
List<Name> names = gson.fromJson(response, listType);
List<String> humanNames = new ArrayList<>();
for (Name name : names) {

View File

@@ -1,9 +1,8 @@
package com.imaginarycode.minecraft.redisbungee.util.uuid;
package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.common.collect.ImmutableList;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import com.google.gson.Gson;
import com.squareup.okhttp.*;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
@@ -18,8 +17,13 @@ public class UUIDFetcher implements Callable<Map<String, UUID>> {
private static final MediaType JSON = MediaType.parse("application/json");
private final List<String> names;
private final boolean rateLimiting;
private static final Gson gson = new Gson();
public static void setHttpClient(OkHttpClient httpClient) {
UUIDFetcher.httpClient = httpClient;
}
@Setter
private static OkHttpClient httpClient;
private UUIDFetcher(List<String> names, boolean rateLimiting) {
@@ -39,12 +43,12 @@ public class UUIDFetcher implements Callable<Map<String, UUID>> {
Map<String, UUID> uuidMap = new HashMap<>();
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
for (int i = 0; i < requests; i++) {
String body = RedisBungee.getGson().toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
String body = gson.toJson(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
Request request = new Request.Builder().url(PROFILE_URL).post(RequestBody.create(JSON, body)).build();
ResponseBody responseBody = httpClient.newCall(request).execute().body();
String response = responseBody.string();
responseBody.close();
Profile[] array = RedisBungee.getGson().fromJson(response, Profile[].class);
Profile[] array = gson.fromJson(response, Profile[].class);
for (Profile profile : array) {
UUID uuid = UUIDFetcher.getUUID(profile.id);
uuidMap.put(profile.name, uuid);

View File

@@ -0,0 +1,198 @@
package com.imaginarycode.minecraft.redisbungee.api.util.uuid;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import org.checkerframework.checker.nullness.qual.NonNull;
import redis.clients.jedis.UnifiedJedis;
import redis.clients.jedis.exceptions.JedisException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
public final class UUIDTranslator {
private static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
private static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
private final RedisBungeePlugin<?> plugin;
private final Map<String, CachedUUIDEntry> nameToUuidMap = new ConcurrentHashMap<>(128, 0.5f, 4);
private final Map<UUID, CachedUUIDEntry> uuidToNameMap = new ConcurrentHashMap<>(128, 0.5f, 4);
private static final Gson gson = new Gson();
public UUIDTranslator(RedisBungeePlugin<?> plugin) {
this.plugin = plugin;
}
private void addToMaps(String name, UUID uuid) {
// This is why I like LocalDate...
// Cache the entry for three days.
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 3);
// Create the entry and populate the local maps
CachedUUIDEntry entry = new CachedUUIDEntry(name, uuid, calendar);
nameToUuidMap.put(name.toLowerCase(), entry);
uuidToNameMap.put(uuid, entry);
}
public UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) {
// If the player is online, give them their UUID.
// Remember, local data > remote data.
if (plugin.getPlayer(player) != null)
return plugin.getPlayerUUID(player);
// Check if it exists in the map
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase());
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired())
return cachedUUIDEntry.getUuid();
else
nameToUuidMap.remove(player);
}
// Check if we can exit early
if (UUID_PATTERN.matcher(player).find()) {
return UUID.fromString(player);
}
if (MOJANGIAN_UUID_PATTERN.matcher(player).find()) {
// Reconstruct the UUID
return UUIDFetcher.getUUID(player);
}
// If we are in offline mode, UUID generation is simple.
// We don't even have to cache the UUID, since this is easy to recalculate.
if (!plugin.isOnlineMode()) {
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
}
RedisTask<UUID> redisTask = new RedisTask<UUID>(plugin) {
@Override
public UUID unifiedJedisTask(UnifiedJedis unifiedJedis) {
String stored = unifiedJedis.hget("uuid-cache", player.toLowerCase());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
unifiedJedis.hdel("uuid-cache", player.toLowerCase());
// Doesn't hurt to also remove the UUID entry as well.
unifiedJedis.hdel("uuid-cache", entry.getUuid().toString());
} else {
nameToUuidMap.put(player.toLowerCase(), entry);
uuidToNameMap.put(entry.getUuid(), entry);
return entry.getUuid();
}
}
// That didn't work. Let's ask Mojang.
if (!expensiveLookups || !plugin.isOnlineMode())
return null;
Map<String, UUID> uuidMap1;
try {
uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
} catch (Exception e) {
plugin.logFatal("Unable to fetch UUID from Mojang for " + player);
return null;
}
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
if (entry.getKey().equalsIgnoreCase(player)) {
persistInfo(entry.getKey(), entry.getValue(), unifiedJedis);
return entry.getValue();
}
}
return null;
}
};
// Let's try Redis.
try {
return redisTask.execute();
} catch (JedisException e) {
plugin.logFatal("Unable to fetch UUID for " + player);
}
return null; // Nope, game over!
}
public String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) {
// If the player is online, give them their UUID.
// Remember, local data > remote data.
if (plugin.getPlayer(player) != null)
return plugin.getPlayerName(player);
// Check if it exists in the map
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player);
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired())
return cachedUUIDEntry.getName();
else
uuidToNameMap.remove(player);
}
RedisTask<String> redisTask = new RedisTask<String>(plugin) {
@Override
public String unifiedJedisTask(UnifiedJedis unifiedJedis) {
String stored = unifiedJedis.hget("uuid-cache", player.toString());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = gson.fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
unifiedJedis.hdel("uuid-cache", player.toString());
// Doesn't hurt to also remove the named entry as well.
// TODO: Since UUIDs are fixed, we could look up the name and see if the UUID matches.
unifiedJedis.hdel("uuid-cache", entry.getName());
} else {
nameToUuidMap.put(entry.getName().toLowerCase(), entry);
uuidToNameMap.put(player, entry);
return entry.getName();
}
}
if (!expensiveLookups || !plugin.isOnlineMode())
return null;
// That didn't work. Let's ask Mojang. This call may fail, because Mojang is insane.
String name;
try {
List<String> nameHist = NameFetcher.nameHistoryFromUuid(player);
name = Iterables.getLast(nameHist, null);
} catch (Exception e) {
plugin.logFatal("Unable to fetch name from Mojang for " + player);
return null;
}
if (name != null) {
persistInfo(name, player, unifiedJedis);
return name;
}
return null;
}
};
// Okay, it wasn't locally cached. Let's try Redis.
try {
return redisTask.execute();
} catch (JedisException e) {
plugin.logFatal("Unable to fetch name for " + player);
return null;
}
}
public void persistInfo(String name, UUID uuid, UnifiedJedis unifiedJedis) {
addToMaps(name, uuid);
String json = gson.toJson(uuidToNameMap.get(uuid));
unifiedJedis.hset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
}
}

View File

@@ -0,0 +1,77 @@
# RedisBungee configuration file.
# Cluster Mode
# enabling this option will enable cluster mode.
cluster-mode-enabled: false
# FORMAT:
# redis-cluster-servers:
# - host: 127.0.0.1
# port: 2020
# - host: 127.0.0.1
# port: 2021
# - host: 127.0.0.1
# port: 2021
# you can set single server and Jedis will automatically discover cluster nodes,
# but might fail if this single redis node is down when Proxy startup, its recommended put the all the nodes
redis-cluster-servers:
- host: 127.0.0.1
port: 6379
# Get Redis from http://redis.io/
# The Redis server you use.
# these settings are ignored when cluster mode is enabled.
redis-server: 127.0.0.1
redis-port: 6379
# OPTIONAL but recommended: If your Redis server uses AUTH, set the password required.
redis-password: ""
# Maximum connections that will be maintained to the Redis server.
# The default is 10. This setting should be left as-is unless you have some wildly
# inefficient plugins or a lot of players.
max-redis-connections: 10
# since redis can support ssl by version 6 you can use ssl / tls in redis bungee too!
# but there is more configuration needed to work see https://github.com/ProxioDev/RedisBungee/issues/18
# Keep note that SSL/TLS connections will decrease redis performance so use it when needed.
useSSL: false
# An identifier for this BungeeCord / Velocity instance. Will randomly generate if leaving it blank.
proxy-id: "test-1"
# since version 0.8.0 Internally now uses JedisPooled instead of Jedis, JedisPool.
# which will break compatibility with old plugins that uses RedisBungee JedisPool
# so to mitigate this issue, we will instruct RedisBungee to init an JedisPool for compatibility reasons.
# enabled by default
# ignored when cluster mode is enabled
enable-jedis-pool-compatibility: true
# max connections for the compatibility pool
compatibility-max-connections: 3
# Register redis bungee legacy commands
# if this disabled override-bungee-commands will be ignored
register-legacy-commands: false
# Whether or not RedisBungee should install its version of regular BungeeCord commands.
# Often, the RedisBungee commands are desired, but in some cases someone may wish to
# override the commands using another plugin.
#
# If you are just denying access to the commands, RedisBungee uses the default BungeeCord
# permissions - just deny them and access will be denied.
#
# Please note that with build 787+, most commands overridden by RedisBungee were moved to
# modules, and these must be disabled or overridden yourself.
override-bungee-commands: false
# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic
# restart scripts.
exempt-ip-addresses: []
# restore old login when online behavior before 0.9.0 update
# uncomment to enable it
# disable-kick-when-online: true
# Config version DO NOT CHANGE!!!!
config-version: 1

View File

@@ -0,0 +1,2 @@
logged-in-other-location: "§cYou logged in from another location!"
already-logged-in: "§cYou are already logged in!"

147
RedisBungee-Bungee/pom.xml Normal file
View File

@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>RedisBungee</artifactId>
<groupId>com.imaginarycode.minecraft</groupId>
<version>0.10.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>RedisBungee-Bungee</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<source>8</source>
<includeDependencySources>true</includeDependencySources>
<dependencySourceExcludes>
<dependencySourceExclude>net.md-5:*</dependencySourceExclude>
</dependencySourceExcludes>
<destDir>../javadoc/${project.name}</destDir>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>redis.clients.jedis</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
</shadedPattern>
</relocation>
<relocation>
<pattern>redis.clients.util</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons.pool</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
</shadedPattern>
</relocation>
<relocation>
<pattern>com.squareup.okhttp</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
</shadedPattern>
</relocation>
<relocation>
<pattern>okio</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio</shadedPattern>
</relocation>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.google
</shadedPattern>
</relocation>
<relocation>
<pattern>org.json</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.json</shadedPattern>
</relocation>
<relocation>
<pattern>org.checkerframework</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.checkframework
</shadedPattern>
</relocation>
<relocation>
<pattern>ninja.leaping.configurate</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.configurate
</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-API</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-API</artifactId>
<version>${project.parent.version}</version>
<classifier>javadoc</classifier>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.util.UUID;
public class BungeeDataManager extends AbstractDataManager<ProxiedPlayer, PostLoginEvent, PlayerDisconnectEvent, PubSubMessageEvent> implements Listener {
public BungeeDataManager(RedisBungeePlugin<ProxiedPlayer> plugin) {
super(plugin);
}
@Override
@EventHandler
public void onPostLogin(PostLoginEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@EventHandler
public void onPubSubMessage(PubSubMessageEvent event) {
handlePubSubMessage(event.getChannel(), event.getMessage());
}
@Override
public boolean handleKick(UUID target, String message) {
// check if the player is online on this proxy
ProxiedPlayer player = plugin.getPlayer(target);
if (player == null) return false;
player.disconnect(TextComponent.fromLegacyText(message));
return true;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import redis.clients.jedis.UnifiedJedis;
public class BungeePlayerUtils {
public static void createBungeePlayer(ProxiedPlayer player, UnifiedJedis unifiedJedis, boolean fireEvent) {
String serverName = null;
if (player.getServer() != null) {
serverName = player.getServer().getInfo().getName();
}
PendingConnection pendingConnection = player.getPendingConnection();
PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, pendingConnection.getAddress().getAddress(), fireEvent);
}
}

View File

@@ -0,0 +1,362 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.tasks.*;
import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.*;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeeMode;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import com.squareup.okhttp.Dispatcher;
import com.squareup.okhttp.OkHttpClient;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
import net.md_5.bungee.api.plugin.Plugin;
import redis.clients.jedis.*;
import java.io.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
public class RedisBungee extends Plugin implements RedisBungeePlugin<ProxiedPlayer>, ConfigLoader {
private static RedisBungeeAPI apiStatic;
private AbstractRedisBungeeAPI api;
private RedisBungeeMode redisBungeeMode;
private PubSubListener psl = null;
private Summoner<?> summoner;
private UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private BungeeDataManager dataManager;
private OkHttpClient httpClient;
private volatile List<String> proxiesIds;
private final AtomicInteger globalPlayerCount = new AtomicInteger();
private Future<?> integrityCheck;
private Future<?> heartbeatTask;
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
@Override
public RedisBungeeConfiguration getConfiguration() {
return this.configuration;
}
@Override
public int getCount() {
return this.globalPlayerCount.get();
}
@Override
public Set<String> getLocalPlayersAsUuidStrings() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (ProxiedPlayer player : getProxy().getPlayers()) {
builder.add(player.getUniqueId().toString());
}
return builder.build();
}
@Override
public AbstractDataManager<ProxiedPlayer, ?, ?, ?> getDataManager() {
return this.dataManager;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public Multimap<String, UUID> serverToPlayersCache() {
try {
return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getProxiesIds() {
return proxiesIds;
}
@Override
public PubSubListener getPubSubListener() {
return this.psl;
}
@Override
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().runAsync(this, runnable);
}
@Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().schedule(this, runnable, time, timeUnit);
}
@Override
public void fireEvent(Object event) {
this.getProxy().getPluginManager().callEvent((Event) event);
}
@Override
public boolean isOnlineMode() {
return this.getProxy().getConfig().isOnlineMode();
}
@Override
public void logInfo(String msg) {
this.getLogger().info(msg);
}
@Override
public void logWarn(String msg) {
this.getLogger().warning(msg);
}
@Override
public void logFatal(String msg) {
this.getLogger().severe(msg);
}
@Override
public ProxiedPlayer getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid);
}
@Override
public ProxiedPlayer getPlayer(String name) {
return this.getProxy().getPlayer(name);
}
@Override
public UUID getPlayerUUID(String player) {
return this.getProxy().getPlayer(player).getUniqueId();
}
@Override
public String getPlayerName(UUID player) {
return this.getProxy().getPlayer(player).getName();
}
@Override
public String getPlayerServerName(ProxiedPlayer player) {
return player.getServer().getInfo().getName();
}
@Override
public boolean isPlayerOnAServer(ProxiedPlayer player) {
return player.getServer() != null;
}
@Override
public InetAddress getPlayerIp(ProxiedPlayer player) {
return player.getAddress().getAddress();
}
@Override
public void initialize() {
ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory();
ScheduledExecutorService service = Executors.newScheduledThreadPool(24, factory);
try {
Field field = Plugin.class.getDeclaredField("service");
field.setAccessible(true);
ExecutorService builtinService = (ExecutorService) field.get(this);
field.set(this, service);
builtinService.shutdownNow();
} catch (IllegalAccessException | NoSuchFieldException e) {
getLogger().log(Level.WARNING, "Can't replace BungeeCord thread pool with our own");
getLogger().log(Level.INFO, "skipping replacement.....");
}
try {
loadConfig(this, getDataFolder());
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
}
// init the api class
this.api = new RedisBungeeAPI(this);
apiStatic = (RedisBungeeAPI) this.api;
// init the http lib
httpClient = new OkHttpClient();
Dispatcher dispatcher = new Dispatcher(getExecutorService());
httpClient.setDispatcher(dispatcher);
NameFetcher.setHttpClient(httpClient);
UUIDFetcher.setHttpClient(httpClient);
InitialUtils.checkRedisVersion(this);
// check if this proxy is recovering from a crash and start heart the beat.
InitialUtils.checkIfRecovering(this, getDataFolder().toPath());
updateProxiesIds();
uuidTranslator = new UUIDTranslator(this);
heartbeatTask = service.scheduleAtFixedRate(new HeartbeatTask(this, this.globalPlayerCount), 0, HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT);
dataManager = new BungeeDataManager(this);
getProxy().getPluginManager().registerListener(this, new RedisBungeeBungeeListener(this, configuration.getExemptAddresses()));
getProxy().getPluginManager().registerListener(this, dataManager);
psl = new PubSubListener(this);
getProxy().getScheduler().runAsync(this, psl);
IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) {
@Override
public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) {
ProxiedPlayer proxiedPlayer = ProxyServer.getInstance().getPlayer(UUID.fromString(player));
if (proxiedPlayer == null)
return; // We'll deal with it later.
BungeePlayerUtils.createBungeePlayer(proxiedPlayer, unifiedJedis, false);
}
};
integrityCheck = service.scheduleAtFixedRate(integrityCheckTask::execute, 0, IntegrityCheckTask.INTERVAL, IntegrityCheckTask.TIMEUNIT);
// register plugin messages channel.
getProxy().registerChannel("legacy:redisbungee");
getProxy().registerChannel("RedisBungee");
if (configuration.doRegisterLegacyCommands()) {
// register commands
if (configuration.doOverrideBungeeCommands()) {
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand(this));
}
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerIds(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this));
}
}
@Override
public void stop() {
// Poison the PubSub listener
if (psl != null) {
psl.poison();
}
if (integrityCheck != null) {
integrityCheck.cancel(true);
}
if (heartbeatTask != null) {
heartbeatTask.cancel(true);
}
getProxy().getPluginManager().unregisterListeners(this);
ShutdownUtils.shutdownCleanup(this);
try {
this.summoner.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Summoner<?> getSummoner() {
return this.summoner;
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
}
@Override
public void updateProxiesIds() {
proxiesIds = getCurrentProxiesIds(false);
}
@Override
public void onEnable() {
initialize();
}
@Override
public void onDisable() {
stop();
}
@Override
public IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
return new PlayerChangedServerNetworkEvent(uuid, previousServer, server);
}
@Override
public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
return new PlayerJoinedNetworkEvent(uuid);
}
@Override
public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
return new PlayerLeftNetworkEvent(uuid);
}
@Override
public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
return new PubSubMessageEvent(channel, message);
}
@Override
public void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
this.configuration = configuration;
this.redisBungeeMode = mode;
this.summoner = summoner;
}
/**
* This returns an instance of {@link RedisBungeeAPI}
*
* @deprecated Please use {@link RedisBungeeAPI#getRedisBungeeApi()} this class intended to for old plugins that no longer updated.
*
* @return the {@link AbstractRedisBungeeAPI} object instance.
*/
@Deprecated
public static RedisBungeeAPI getApi() {
return apiStatic;
}
@Deprecated
public JedisPool getPool() {
return api.getJedisPool();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
/**
* This platform class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()}
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
*
* @author tuxed
* @since 0.2.3 | updated 0.8.0
*/
public class RedisBungeeAPI extends AbstractRedisBungeeAPI {
private static RedisBungeeAPI redisBungeeApi;
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin);
if (redisBungeeApi == null) {
redisBungeeApi = this;
}
}
/**
* Get the server where the specified player is playing. This function also deals with the case of local players
* as well, and will return local information on them.
*
* @param player a player uuid
* @return {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID)
*/
public final ServerInfo getServerFor(@NonNull UUID player) {
return ((Plugin) this.plugin).getProxy().getServerInfo(this.getServerNameFor(player));
}
/**
* Api instance
*
* @return the API instance.
* @since 0.6.5
*/
public static RedisBungeeAPI getRedisBungeeApi() {
return redisBungeeApi;
}
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.*;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset;
import static net.md_5.bungee.event.EventPriority.HIGHEST;
public class RedisBungeeBungeeListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, PlayerDisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> implements Listener {
public RedisBungeeBungeeListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
super(plugin, exemptAddresses);
}
@Override
@EventHandler(priority = HIGHEST)
public void onLogin(LoginEvent event) {
event.registerIntent((Plugin) plugin);
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
if (event.isCancelled()) {
return null;
}
if (plugin.getConfiguration().restoreOldKickBehavior()) {
for (String s : plugin.getProxiesIds()) {
if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) {
event.setCancelled(true);
event.setCancelReason(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN));
return null;
}
}
} else if (api.isPlayerOnline(event.getConnection().getUniqueId())) {
PlayerUtils.setKickedOtherLocation(event.getConnection().getUniqueId().toString(), unifiedJedis);
api.kickPlayer(event.getConnection().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
}
return null;
} finally {
event.completeIntent((Plugin) plugin);
}
}
});
}
@Override
@EventHandler
public void onPostLogin(PostLoginEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
plugin.getUuidTranslator().persistInfo(event.getPlayer().getName(), event.getPlayer().getUniqueId(), unifiedJedis);
BungeePlayerUtils.createBungeePlayer(event.getPlayer(), unifiedJedis, true);
return null;
}
});
}
@Override
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true);
return null;
}
});
}
@Override
@EventHandler
public void onServerChange(ServerConnectedEvent event) {
final String currentServer = event.getServer().getInfo().getName();
final String oldServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName();
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName());
PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer);
return null;
}
});
}
@Override
@EventHandler
public void onPing(ProxyPingEvent event) {
if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) {
return;
}
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
if (forced != null && event.getConnection().getListener().isPingPassthrough()) {
return;
}
event.getResponse().getPlayers().setOnline(plugin.getCount());
}
@Override
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
if ((event.getTag().equals("legacy:redisbungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) {
final String currentChannel = event.getTag();
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
plugin.executeAsync(() -> {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
switch (subchannel) {
case "PlayerList":
out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
original = plugin.getPlayers();
} else {
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = new HashSet<>();
for (UUID uuid : original)
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false));
out.writeUTF(Joiner.on(',').join(players));
break;
case "PlayerCount":
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.getCount());
} else {
out.writeUTF(type);
try {
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
break;
case "LastOnline":
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
break;
case "ServerPlayers":
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT":
includesUsers = false;
break;
case "PLAYERS":
includesUsers = true;
break;
default:
// TODO: Should I raise an error?
return;
}
out.writeUTF(type1);
if (includesUsers) {
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : multimap.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
serializeMultimap(human, true, out);
} else {
serializeMultiset(multimap.keys(), out);
}
break;
case "Proxy":
out.writeUTF("Proxy");
out.writeUTF(plugin.getConfiguration().getProxyId());
break;
case "PlayerProxy":
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
break;
default:
return;
}
((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
});
}
}
@Override
@EventHandler
public void onPubSubMessage(PubSubMessageEvent event) {
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) {
String message = event.getMessage();
if (message.startsWith("/"))
message = message.substring(1);
plugin.logInfo("Invoking command via PubSub: /" + message);
((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message);
}
}
}

View File

@@ -1,24 +1,31 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
import java.util.Collection;
import java.util.Collections;
/**
* This class is the CommandSender that RedisBungee uses to dispatch commands to BungeeCord.
* <p>
* It inherits all permissions of the console command sender. Sending messages and modifying permissions are no-ops.
*
* @author tuxed
* @since 0.2.3
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RedisBungeeCommandSender implements CommandSender {
static final RedisBungeeCommandSender instance = new RedisBungeeCommandSender();
private static final RedisBungeeCommandSender singleton;
static {
singleton = new RedisBungeeCommandSender();
}
public static RedisBungeeCommandSender getSingleton() {
return singleton;
}
@Override
public String getName() {
@@ -27,37 +34,37 @@ public class RedisBungeeCommandSender implements CommandSender {
@Override
public void sendMessage(String s) {
// no-op
}
@Override
public void sendMessages(String... strings) {
// no-op
}
@Override
public void sendMessage(BaseComponent... baseComponents) {
// no-op
}
@Override
public void sendMessage(BaseComponent baseComponent) {
// no-op
}
@Override
public Collection<String> getGroups() {
return Collections.emptySet();
return null;
}
@Override
public void addGroups(String... strings) {
// no-op
}
@Override
public void removeGroups(String... strings) {
// no-op
}
@Override
@@ -67,7 +74,7 @@ public class RedisBungeeCommandSender implements CommandSender {
@Override
public void setPermission(String s, boolean b) {
// no-op
}
@Override

View File

@@ -1,8 +1,20 @@
package com.imaginarycode.minecraft.redisbungee;
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.BaseComponent;
@@ -21,12 +33,12 @@ import java.util.UUID;
/**
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
* <p>
* All classes use the {@link RedisBungeeAPI}.
* All classes use the {@link AbstractRedisBungeeAPI}.
*
* @author tuxed
* @since 0.2.3
*/
class RedisBungeeCommands {
public class RedisBungeeCommands {
private static final BaseComponent[] NO_PLAYER_SPECIFIED =
new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create();
private static final BaseComponent[] PLAYER_NOT_FOUND =
@@ -41,7 +53,7 @@ class RedisBungeeCommands {
public static class GlistCommand extends Command {
private final RedisBungee plugin;
GlistCommand(RedisBungee plugin) {
public GlistCommand(RedisBungee plugin) {
super("glist", "bungeecord.command.list", "redisbungee", "rglist");
this.plugin = plugin;
}
@@ -51,14 +63,16 @@ class RedisBungeeCommands {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
int count = RedisBungee.getApi().getPlayerCount();
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
.append(playerPlural(count) + " currently online.").create();
if (args.length > 0 && args[0].equals("showall")) {
Multimap<String, UUID> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing.
String playerName = plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false);
human.put(entry.getKey(), playerName != null ? playerName : entry.getValue().toString());
}
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
TextComponent serverName = new TextComponent();
@@ -85,7 +99,7 @@ class RedisBungeeCommands {
public static class FindCommand extends Command {
private final RedisBungee plugin;
FindCommand(RedisBungee plugin) {
public FindCommand(RedisBungee plugin) {
super("find", "bungeecord.command.find", "rfind");
this.plugin = plugin;
}
@@ -101,7 +115,7 @@ class RedisBungeeCommands {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
ServerInfo si = RedisBungee.getApi().getServerFor(uuid);
ServerInfo si = plugin.getProxy().getServerInfo(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid));
if (si != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.BLUE);
@@ -121,7 +135,7 @@ class RedisBungeeCommands {
public static class LastSeenCommand extends Command {
private final RedisBungee plugin;
LastSeenCommand(RedisBungee plugin) {
public LastSeenCommand(RedisBungee plugin) {
super("lastseen", "redisbungee.command.lastseen", "rlastseen");
this.plugin = plugin;
}
@@ -137,7 +151,7 @@ class RedisBungeeCommands {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
long secs = RedisBungee.getApi().getLastOnline(uuid);
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
TextComponent message = new TextComponent();
if (secs == 0) {
message.setColor(ChatColor.GREEN);
@@ -161,7 +175,7 @@ class RedisBungeeCommands {
public static class IpCommand extends Command {
private final RedisBungee plugin;
IpCommand(RedisBungee plugin) {
public IpCommand(RedisBungee plugin) {
super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
this.plugin = plugin;
}
@@ -177,7 +191,7 @@ class RedisBungeeCommands {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
InetAddress ia = RedisBungee.getApi().getPlayerIp(uuid);
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
if (ia != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
@@ -197,7 +211,7 @@ class RedisBungeeCommands {
public static class PlayerProxyCommand extends Command {
private final RedisBungee plugin;
PlayerProxyCommand(RedisBungee plugin) {
public PlayerProxyCommand(RedisBungee plugin) {
super("pproxy", "redisbungee.command.pproxy");
this.plugin = plugin;
}
@@ -213,7 +227,7 @@ class RedisBungeeCommands {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
String proxy = RedisBungee.getApi().getProxy(uuid);
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
if (proxy != null) {
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
@@ -233,7 +247,7 @@ class RedisBungeeCommands {
public static class SendToAll extends Command {
private final RedisBungee plugin;
SendToAll(RedisBungee plugin) {
public SendToAll(RedisBungee plugin) {
super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
}
@@ -242,7 +256,7 @@ class RedisBungeeCommands {
public void execute(CommandSender sender, String[] args) {
if (args.length > 0) {
String command = Joiner.on(" ").skipNulls().join(args);
RedisBungee.getApi().sendProxyCommand(command);
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command);
TextComponent message = new TextComponent();
message.setColor(ChatColor.GREEN);
message.setText("Sent the command /" + command + " to all proxies.");
@@ -256,7 +270,7 @@ class RedisBungeeCommands {
public static class ServerId extends Command {
private final RedisBungee plugin;
ServerId(RedisBungee plugin) {
public ServerId(RedisBungee plugin) {
super("serverid", "redisbungee.command.serverid", "rserverid");
this.plugin = plugin;
}
@@ -264,21 +278,23 @@ class RedisBungeeCommands {
@Override
public void execute(CommandSender sender, String[] args) {
TextComponent textComponent = new TextComponent();
textComponent.setText("You are on " + RedisBungee.getApi().getServerId() + ".");
textComponent.setText("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".");
textComponent.setColor(ChatColor.YELLOW);
sender.sendMessage(textComponent);
}
}
public static class ServerIds extends Command {
public ServerIds() {
private final RedisBungee plugin;
public ServerIds(RedisBungee plugin) {
super("serverids", "redisbungee.command.serverids");
this.plugin =plugin;
}
@Override
public void execute(CommandSender sender, String[] strings) {
TextComponent textComponent = new TextComponent();
textComponent.setText("All server IDs: " + Joiner.on(", ").join(RedisBungee.getApi().getAllServers()));
textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()));
textComponent.setColor(ChatColor.YELLOW);
sender.sendMessage(textComponent);
}
@@ -287,7 +303,7 @@ class RedisBungeeCommands {
public static class PlistCommand extends Command {
private final RedisBungee plugin;
PlistCommand(RedisBungee plugin) {
public PlistCommand(RedisBungee plugin) {
super("plist", "redisbungee.command.plist", "rplist");
this.plugin = plugin;
}
@@ -297,16 +313,16 @@ class RedisBungeeCommands {
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
String proxy = args.length >= 1 ? args[0] : RedisBungee.getConfiguration().getServerId();
if (!plugin.getServerIds().contains(proxy)) {
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId();
if (!plugin.getProxiesIds().contains(proxy)) {
sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
return;
}
Set<UUID> players = RedisBungee.getApi().getPlayersOnProxy(proxy);
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
.append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create();
if (args.length >= 2 && args[1].equals("showall")) {
Multimap<String, UUID> serverToPlayers = RedisBungee.getApi().getServerToPlayers();
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : serverToPlayers.entries()) {
if (players.contains(entry.getValue())) {
@@ -334,23 +350,4 @@ class RedisBungeeCommands {
});
}
}
public static class DebugCommand extends Command {
private final RedisBungee plugin;
DebugCommand(RedisBungee plugin) {
super("rdebug", "redisbungee.command.debug");
this.plugin = plugin;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
TextComponent poolActiveStat = new TextComponent("Currently active pool objects: " + plugin.getPool().getNumActive());
TextComponent poolIdleStat = new TextComponent("Currently idle pool objects: " + plugin.getPool().getNumIdle());
TextComponent poolWaitingStat = new TextComponent("Waiting on free objects: " + plugin.getPool().getNumWaiters());
sender.sendMessage(poolActiveStat);
sender.sendMessage(poolIdleStat);
sender.sendMessage(poolWaitingStat);
}
}
}
}

View File

@@ -1,6 +1,16 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import lombok.ToString;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import net.md_5.bungee.api.plugin.Event;
import java.util.UUID;
@@ -14,8 +24,7 @@ import java.util.UUID;
*
* @since 0.3.4
*/
@ToString
public class PlayerChangedServerNetworkEvent extends Event {
public class PlayerChangedServerNetworkEvent extends Event implements IPlayerChangedServerNetworkEvent {
private final UUID uuid;
private final String previousServer;
private final String server;
@@ -26,14 +35,17 @@ public class PlayerChangedServerNetworkEvent extends Event {
this.server = server;
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public String getServer() {
return server;
}
@Override
public String getPreviousServer() {
return previousServer;
}

View File

@@ -1,6 +1,16 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import lombok.ToString;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import net.md_5.bungee.api.plugin.Event;
import java.util.UUID;
@@ -14,14 +24,14 @@ import java.util.UUID;
*
* @since 0.3.4
*/
@ToString
public class PlayerJoinedNetworkEvent extends Event {
public class PlayerJoinedNetworkEvent extends Event implements IPlayerJoinedNetworkEvent {
private final UUID uuid;
public PlayerJoinedNetworkEvent(UUID uuid) {
this.uuid = uuid;
}
@Override
public UUID getUuid() {
return uuid;
}

View File

@@ -1,6 +1,16 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import lombok.ToString;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import net.md_5.bungee.api.plugin.Event;
import java.util.UUID;
@@ -14,14 +24,14 @@ import java.util.UUID;
*
* @since 0.3.4
*/
@ToString
public class PlayerLeftNetworkEvent extends Event {
public class PlayerLeftNetworkEvent extends Event implements IPlayerLeftNetworkEvent {
private final UUID uuid;
public PlayerLeftNetworkEvent(UUID uuid) {
this.uuid = uuid;
}
@Override
public UUID getUuid() {
return uuid;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import net.md_5.bungee.api.plugin.Event;
/**
* This event is posted when a PubSub message is received.
* <p>
* <strong>Warning</strong>: This event is fired in a separate thread!
*
* @since 0.2.6
*/
public class PubSubMessageEvent extends Event implements IPubSubMessageEvent {
private final String channel;
private final String message;
public PubSubMessageEvent(String channel, String message) {
this.channel = channel;
this.message = message;
}
@Override
public String getChannel() {
return channel;
}
@Override
public String getMessage() {
return message;
}
}

View File

@@ -1,9 +1,6 @@
name: ${project.name}
name: RedisBungee
main: com.imaginarycode.minecraft.redisbungee.RedisBungee
version: ${project.version}
author: Chunker and Govindas limework
authors:
- chunkr
- Govindas Limework
version: ${project.parent.version}
author: "astei, ProxioDev"
# This is used so that we can automatically override default BungeeCord behavior.
softDepends: ["cmd_find", "cmd_list"]

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>RedisBungee</artifactId>
<groupId>com.imaginarycode.minecraft</groupId>
<version>0.10.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>RedisBungee-Velocity</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>add-plugin-info</id>
<goals>
<goal>filter-sources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<includeDependencySources>true</includeDependencySources>
<dependencySourceExcludes>
<dependencySourceExclude>com.velocitypowered:*</dependencySourceExclude>
</dependencySourceExcludes>
<source>11</source>
<destDir>../javadoc/${project.name}</destDir>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>redis.clients.jedis</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
</shadedPattern>
</relocation>
<relocation>
<pattern>redis.clients.util</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons.pool</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
</shadedPattern>
</relocation>
<relocation>
<pattern>com.squareup.okhttp</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
</shadedPattern>
</relocation>
<relocation>
<pattern>okio</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio</shadedPattern>
</relocation>
<relocation>
<pattern>org.json</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.json</shadedPattern>
</relocation>
<relocation>
<pattern>org.checkerframework</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.checkframework
</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-API</artifactId>
<version>${project.parent.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-yaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee-API</artifactId>
<version>${project.parent.version}</version>
<classifier>javadoc</classifier>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-yaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>3.1.2-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,15 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
public class PomData {
public final static String VERSION = "${project.parent.version}";
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
/**
* This platform class exposes some internal RedisBungee functions. You obtain an instance of this object by invoking {@link RedisBungeeAPI#getRedisBungeeApi()}
* or somehow you got the Plugin instance by you can call the api using {@link RedisBungeePlugin#getAbstractRedisBungeeApi()}.
*
* @author tuxed
* @since 0.2.3 | updated 0.8.0
*/
public class RedisBungeeAPI extends AbstractRedisBungeeAPI {
private static RedisBungeeAPI redisBungeeApi;
public RedisBungeeAPI(RedisBungeePlugin<?> plugin) {
super(plugin);
if (redisBungeeApi == null) {
redisBungeeApi = this;
}
}
/**
* Get the server where the specified player is playing. This function also deals with the case of local players
* as well, and will return local information on them.
*
* @param player a player uuid
* @return {@link ServerInfo} Can be null if proxy can't find it.
* @see #getServerNameFor(UUID)
*/
public final ServerInfo getServerFor(@NonNull UUID player) {
return ((RedisBungeeVelocityPlugin) this.plugin).getProxy().getServer(this.getServerNameFor(player)).map((RegisteredServer::getServerInfo)).orElse(null);
}
/**
* Api instance
*
* @return the API instance.
* @since 0.6.5
*/
public static RedisBungeeAPI getRedisBungeeApi() {
return redisBungeeApi;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import net.kyori.adventure.permission.PermissionChecker;
public class RedisBungeeCommandSource implements CommandSource {
private static final RedisBungeeCommandSource singleton;
private final PermissionChecker permissionChecker = PermissionChecker.always(net.kyori.adventure.util.TriState.TRUE);
static {
singleton = new RedisBungeeCommandSource();
}
public static RedisBungeeCommandSource getSingleton() {
return singleton;
}
@Override
public boolean hasPermission(String permission) {
return this.permissionChecker.test(permission);
}
@Override
public Tristate getPermissionValue(String s) {
return Tristate.TRUE;
}
@Override
public PermissionChecker getPermissionChecker() {
return this.permissionChecker;
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.api.AbstractRedisBungeeListener;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.imaginarycode.minecraft.redisbungee.api.tasks.RedisTask;
import com.imaginarycode.minecraft.redisbungee.api.util.payload.PayloadUtils;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.velocitypowered.api.event.Continuation;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import redis.clients.jedis.UnifiedJedis;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultimap;
import static com.imaginarycode.minecraft.redisbungee.api.util.serialize.Serializations.serializeMultiset;
public class RedisBungeeVelocityListener extends AbstractRedisBungeeListener<LoginEvent, PostLoginEvent, DisconnectEvent, ServerConnectedEvent, ProxyPingEvent, PluginMessageEvent, PubSubMessageEvent> {
// Some messages are using legacy characters
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
public RedisBungeeVelocityListener(RedisBungeePlugin<?> plugin, List<InetAddress> exemptAddresses) {
super(plugin, exemptAddresses);
}
@Subscribe(order = PostOrder.LAST)
public void onLogin(LoginEvent event, Continuation continuation) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
try {
if (!event.getResult().isAllowed()) {
return null;
}
if (plugin.getConfiguration().restoreOldKickBehavior()) {
for (String s : plugin.getProxiesIds()) {
if (unifiedJedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) {
event.setResult(ResultedEvent.ComponentResult.denied(serializer.deserialize(plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.ALREADY_LOGGED_IN))));
return null;
}
}
} else if (api.isPlayerOnline(event.getPlayer().getUniqueId())) {
PlayerUtils.setKickedOtherLocation(event.getPlayer().getUniqueId().toString(), unifiedJedis);
api.kickPlayer(event.getPlayer().getUniqueId(), plugin.getConfiguration().getMessages().get(RedisBungeeConfiguration.MessageType.LOGGED_IN_OTHER_LOCATION));
}
return null;
} finally {
continuation.resume();
}
}
});
}
@Override
@Subscribe
public void onPostLogin(PostLoginEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), unifiedJedis);
VelocityPlayerUtils.createVelocityPlayer(event.getPlayer(), unifiedJedis, true);
return null;
}
});
}
@Override
@Subscribe
public void onPlayerDisconnect(DisconnectEvent event) {
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
PlayerUtils.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), unifiedJedis, true);
return null;
}
});
}
@Override
@Subscribe
public void onServerChange(ServerConnectedEvent event) {
final String currentServer = event.getServer().getServerInfo().getName();
final String oldServer = event.getPreviousServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
plugin.executeAsync(new RedisTask<Void>(plugin) {
@Override
public Void unifiedJedisTask(UnifiedJedis unifiedJedis) {
unifiedJedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", currentServer);
PayloadUtils.playerServerChangePayload(event.getPlayer().getUniqueId(), unifiedJedis, currentServer, oldServer);
return null;
}
});
}
@Override
@Subscribe(order = PostOrder.EARLY)
public void onPing(ProxyPingEvent event) {
if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) {
return;
}
ServerPing.Builder ping = event.getPing().asBuilder();
ping.onlinePlayers(plugin.getCount());
event.setPing(ping.build());
}
@Override
@Subscribe
public void onPluginMessage(PluginMessageEvent event) {
if (!(event.getSource() instanceof ServerConnection) || !RedisBungeeVelocityPlugin.IDENTIFIERS.contains(event.getIdentifier())) {
return;
}
event.setResult(ForwardResult.handled());
plugin.executeAsync(() -> {
ByteArrayDataInput in = event.dataAsDataStream();
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
switch (subchannel) {
case "PlayerList":
out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
original = plugin.getPlayers();
} else {
try {
original = plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = original.stream()
.map(uuid -> plugin.getUuidTranslator().getNameFromUuid(uuid, false))
.collect(Collectors.toSet());
out.writeUTF(Joiner.on(',').join(players));
break;
case "PlayerCount":
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.getCount());
} else {
out.writeUTF(type);
try {
out.writeInt(plugin.getAbstractRedisBungeeApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
break;
case "LastOnline":
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(plugin.getAbstractRedisBungeeApi().getLastOnline(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(user, true))));
break;
case "ServerPlayers":
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT":
includesUsers = false;
break;
case "PLAYERS":
includesUsers = true;
break;
default:
// TODO: Should I raise an error?
return;
}
out.writeUTF(type1);
if (includesUsers) {
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : multimap.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
serializeMultimap(human, true, out);
} else {
serializeMultiset(multimap.keys(), out);
}
break;
case "Proxy":
out.writeUTF("Proxy");
out.writeUTF(plugin.getConfiguration().getProxyId());
break;
case "PlayerProxy":
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(plugin.getAbstractRedisBungeeApi().getProxy(Objects.requireNonNull(plugin.getUuidTranslator().getTranslatedUuid(username, true))));
break;
default:
return;
}
((ServerConnection) event.getSource()).sendPluginMessage(event.getIdentifier(), out.toByteArray());
});
}
@Override
@Subscribe
public void onPubSubMessage(PubSubMessageEvent event) {
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getAbstractRedisBungeeApi().getProxyId())) {
String message = event.getMessage();
if (message.startsWith("/"))
message = message.substring(1);
plugin.logInfo("Invoking command via PubSub: /" + message);
((RedisBungeeVelocityPlugin) plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message);
}
}
}

View File

@@ -0,0 +1,379 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.imaginarycode.minecraft.redisbungee.api.*;
import com.imaginarycode.minecraft.redisbungee.api.config.ConfigLoader;
import com.imaginarycode.minecraft.redisbungee.api.config.RedisBungeeConfiguration;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.summoners.Summoner;
import com.imaginarycode.minecraft.redisbungee.api.tasks.*;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDFetcher;
import com.imaginarycode.minecraft.redisbungee.api.util.uuid.UUIDTranslator;
import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.squareup.okhttp.Dispatcher;
import com.squareup.okhttp.OkHttpClient;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.scheduler.ScheduledTask;
import org.slf4j.Logger;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.io.*;
import java.net.InetAddress;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@Plugin(id = "redisbungee", name = "RedisBungee", version = PomData.VERSION, url = "https://github.com/ProxioDev/RedisBungee", authors = {"astei", "ProxioDev"})
public class RedisBungeeVelocityPlugin implements RedisBungeePlugin<Player>, ConfigLoader {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
private final AbstractRedisBungeeAPI api;
private final PubSubListener psl;
private Summoner<?> jedisSummoner;
private RedisBungeeMode redisBungeeMode;
private final UUIDTranslator uuidTranslator;
private RedisBungeeConfiguration configuration;
private final VelocityDataManager dataManager;
private final OkHttpClient httpClient;
private volatile List<String> proxiesIds;
private final AtomicInteger globalPlayerCount = new AtomicInteger();
private ScheduledTask integrityCheck;
private ScheduledTask heartbeatTask;
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
public static final List<ChannelIdentifier> IDENTIFIERS = List.of(
MinecraftChannelIdentifier.create("legacy", "redisbungee"),
new LegacyChannelIdentifier("RedisBungee"),
// This is needed for clients before 1.13
new LegacyChannelIdentifier("legacy:redisbungee")
);
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
@Inject
public RedisBungeeVelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) {
this.server = server;
this.logger = logger;
this.dataFolder = dataDirectory;
try {
loadConfig(this, dataDirectory);
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
} catch (JedisConnectionException e) {
throw new RuntimeException("Unable to connect to your Redis server!", e);
}
this.api = new RedisBungeeAPI(this);
InitialUtils.checkRedisVersion(this);
// check if this proxy is recovering from a crash and start heart the beat.
InitialUtils.checkIfRecovering(this, getDataFolder());
uuidTranslator = new UUIDTranslator(this);
dataManager = new VelocityDataManager(this);
psl = new PubSubListener(this);
this.httpClient = new OkHttpClient();
Dispatcher dispatcher = new Dispatcher(Executors.newFixedThreadPool(6));
this.httpClient.setDispatcher(dispatcher);
NameFetcher.setHttpClient(httpClient);
UUIDFetcher.setHttpClient(httpClient);
}
@Override
public RedisBungeeConfiguration getConfiguration() {
return this.configuration;
}
@Override
public int getCount() {
return this.globalPlayerCount.get();
}
@Override
public Set<String> getLocalPlayersAsUuidStrings() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (Player player : getProxy().getAllPlayers()) {
builder.add(player.getUniqueId().toString());
}
return builder.build();
}
@Override
public AbstractDataManager<Player, ?, ?, ?> getDataManager() {
return this.dataManager;
}
@Override
public Summoner<?> getSummoner() {
return this.jedisSummoner;
}
@Override
public AbstractRedisBungeeAPI getAbstractRedisBungeeApi() {
return this.api;
}
@Override
public UUIDTranslator getUuidTranslator() {
return this.uuidTranslator;
}
@Override
public Multimap<String, UUID> serverToPlayersCache() {
try {
return this.serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, this::serversToPlayers);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getProxiesIds() {
return proxiesIds;
}
@Override
public PubSubListener getPubSubListener() {
return this.psl;
}
@Override
public void executeAsync(Runnable runnable) {
this.getProxy().getScheduler().buildTask(this, runnable).schedule();
}
@Override
public void executeAsyncAfter(Runnable runnable, TimeUnit timeUnit, int time) {
this.getProxy().getScheduler().buildTask(this, runnable).delay(time, timeUnit).schedule();
}
@Override
public void fireEvent(Object event) {
this.getProxy().getEventManager().fireAndForget(event);
}
@Override
public boolean isOnlineMode() {
return this.getProxy().getConfiguration().isOnlineMode();
}
@Override
public void logInfo(String msg) {
this.getLogger().info(msg);
}
@Override
public void logWarn(String msg) {
this.getLogger().warn(msg);
}
@Override
public void logFatal(String msg) {
this.getLogger().error(msg);
}
@Override
public Player getPlayer(UUID uuid) {
return this.getProxy().getPlayer(uuid).orElse(null);
}
@Override
public Player getPlayer(String name) {
return this.getProxy().getPlayer(name).orElse(null);
}
@Override
public UUID getPlayerUUID(String player) {
return this.getProxy().getPlayer(player).map(Player::getUniqueId).orElse(null);
}
@Override
public String getPlayerName(UUID player) {
return this.getProxy().getPlayer(player).map(Player::getUsername).orElse(null);
}
@Override
public String getPlayerServerName(Player player) {
return player.getCurrentServer().map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
}
@Override
public boolean isPlayerOnAServer(Player player) {
return player.getCurrentServer().isPresent();
}
@Override
public InetAddress getPlayerIp(Player player) {
return player.getRemoteAddress().getAddress();
}
@Override
public void initialize() {
updateProxiesIds();
// start heartbeat task
heartbeatTask = getProxy().getScheduler().buildTask(this, new HeartbeatTask(this, this.globalPlayerCount)).repeat(HeartbeatTask.INTERVAL, HeartbeatTask.REPEAT_INTERVAL_TIME_UNIT).schedule();
getProxy().getEventManager().register(this, new RedisBungeeVelocityListener(this, configuration.getExemptAddresses()));
getProxy().getEventManager().register(this, dataManager);
getProxy().getScheduler().buildTask(this, psl).schedule();
IntegrityCheckTask integrityCheckTask = new IntegrityCheckTask(this) {
@Override
public void handlePlatformPlayer(String player, UnifiedJedis unifiedJedis) {
Player playerProxied = getProxy().getPlayer(UUID.fromString(player)).orElse(null);
if (playerProxied == null)
return; // We'll deal with it later.
VelocityPlayerUtils.createVelocityPlayer(playerProxied, unifiedJedis, false);
}
};
integrityCheck = getProxy().getScheduler().buildTask(this, integrityCheckTask::execute).repeat(30, TimeUnit.SECONDS).schedule();
// register plugin messages
IDENTIFIERS.forEach(getProxy().getChannelRegistrar()::register);
// register legacy commands
if (configuration.doRegisterLegacyCommands()) {
// Override Velocity commands
if (configuration.doOverrideBungeeCommands()) {
getProxy().getCommandManager().register("glist", new RedisBungeeCommands.GlistCommand(this), "redisbungee", "rglist");
}
getProxy().getCommandManager().register("sendtoall", new RedisBungeeCommands.SendToAll(this), "rsendtoall");
getProxy().getCommandManager().register("serverid", new RedisBungeeCommands.ServerId(this), "rserverid");
getProxy().getCommandManager().register("serverids", new RedisBungeeCommands.ServerIds(this));
getProxy().getCommandManager().register("pproxy", new RedisBungeeCommands.PlayerProxyCommand(this));
getProxy().getCommandManager().register("plist", new RedisBungeeCommands.PlistCommand(this), "rplist");
getProxy().getCommandManager().register("lastseen", new RedisBungeeCommands.LastSeenCommand(this), "rlastseen");
getProxy().getCommandManager().register("ip", new RedisBungeeCommands.IpCommand(this), "playerip", "rip", "rplayerip");
getProxy().getCommandManager().register("find", new RedisBungeeCommands.FindCommand(this), "rfind");
}
}
@Override
public void stop() {
// Poison the PubSub listener
if (psl != null) {
psl.poison();
}
if (integrityCheck != null) {
integrityCheck.cancel();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
ShutdownUtils.shutdownCleanup(this);
try {
this.jedisSummoner.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
this.httpClient.getDispatcher().getExecutorService().shutdown();
try {
this.httpClient.getDispatcher().getExecutorService().awaitTermination(20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void onConfigLoad(RedisBungeeConfiguration configuration, Summoner<?> summoner, RedisBungeeMode mode) {
this.jedisSummoner = summoner;
this.configuration = configuration;
this.redisBungeeMode = mode;
}
@Override
public RedisBungeeMode getRedisBungeeMode() {
return this.redisBungeeMode;
}
@Override
public void updateProxiesIds() {
this.proxiesIds = this.getCurrentProxiesIds(false);
}
@Subscribe
public void proxyInit(ProxyInitializeEvent event) {
initialize();
}
@Subscribe
public void proxyShutdownEvent(ProxyShutdownEvent event) {
stop();
}
@Override
public IPlayerChangedServerNetworkEvent createPlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
return new PlayerChangedServerNetworkEvent(uuid, previousServer, server);
}
@Override
public IPlayerJoinedNetworkEvent createPlayerJoinedNetworkEvent(UUID uuid) {
return new PlayerJoinedNetworkEvent(uuid);
}
@Override
public IPlayerLeftNetworkEvent createPlayerLeftNetworkEvent(UUID uuid) {
return new PlayerLeftNetworkEvent(uuid);
}
@Override
public IPubSubMessageEvent createPubSubEvent(String channel, String message) {
return new PubSubMessageEvent(channel, message);
}
public ProxyServer getProxy() {
return server;
}
public Logger getLogger() {
return logger;
}
public Path getDataFolder() {
return this.dataFolder;
}
public InputStream getResourceAsStream(String name) {
return this.getClass().getClassLoader().getResourceAsStream(name);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.api.AbstractDataManager;
import com.imaginarycode.minecraft.redisbungee.api.RedisBungeePlugin;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.UUID;
public class VelocityDataManager extends AbstractDataManager<Player, PostLoginEvent, DisconnectEvent, PubSubMessageEvent> {
public VelocityDataManager(RedisBungeePlugin<Player> plugin) {
super(plugin);
}
@Override
@Subscribe
public void onPostLogin(PostLoginEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@Subscribe
public void onPlayerDisconnect(DisconnectEvent event) {
invalidate(event.getPlayer().getUniqueId());
}
@Override
@Subscribe
public void onPubSubMessage(PubSubMessageEvent event) {
handlePubSubMessage(event.getChannel(), event.getMessage());
}
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection();
@Override
public boolean handleKick(UUID target, String message) {
Player player = plugin.getPlayer(target);
if (player == null) {
return false;
}
player.disconnect(serializer.deserialize(message));
return true;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee;
import com.imaginarycode.minecraft.redisbungee.api.util.player.PlayerUtils;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import redis.clients.jedis.UnifiedJedis;
import java.util.Optional;
public class VelocityPlayerUtils {
protected static void createVelocityPlayer(Player player, UnifiedJedis unifiedJedis, boolean fireEvent) {
Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
String serverName = null;
if (optionalServerConnection.isPresent()) {
serverName = optionalServerConnection.get().getServerInfo().getName();
}
PlayerUtils.createPlayer(player.getUniqueId(), unifiedJedis, serverName, player.getRemoteAddress().getAddress(), fireEvent);
}
}

View File

@@ -0,0 +1,362 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.commands;
import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.imaginarycode.minecraft.redisbungee.AbstractRedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
/**
* This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
* <p>
* All classes use the {@link AbstractRedisBungeeAPI}.
*
* @author tuxed
* @since 0.2.3
*/
public class RedisBungeeCommands {
private static final Component NO_PLAYER_SPECIFIED =
Component.text("You must specify a player name.", NamedTextColor.RED);
private static final Component PLAYER_NOT_FOUND =
Component.text("No such player found.", NamedTextColor.RED);
private static final Component NO_COMMAND_SPECIFIED =
Component.text("You must specify a command to be run.", NamedTextColor.RED);
private static String playerPlural(int num) {
return num == 1 ? num + " player is" : num + " players are";
}
public static class GlistCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public GlistCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
int count = plugin.getAbstractRedisBungeeApi().getPlayerCount();
Component playersOnline = Component.text(playerPlural(count) + " currently online.", NamedTextColor.YELLOW);
CommandSource sender = invocation.source();
if (invocation.arguments().length > 0 && invocation.arguments()[0].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
// if for any reason UUID translation fails just return the uuid as name, to make command finish executing.
String playerName = plugin.getUuidTranslator().getNameFromUuid(value, false);
human.put(key, playerName != null ? playerName : value.toString());
});
for (String server : new TreeSet<>(serverToPlayers.keySet())) {
Component serverName = Component.text("[" + server + "] ", NamedTextColor.GREEN);
Component serverCount = Component.text("(" + serverToPlayers.get(server).size() + "): ", NamedTextColor.YELLOW);
Component serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(Component.text("To see all players online, use /glist showall.", NamedTextColor.YELLOW));
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("velocity.command.server");
}
}
public static class FindCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public FindCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
ServerInfo si = plugin.getProxy().getServer(plugin.getAbstractRedisBungeeApi().getServerNameFor(uuid)).map(RegisteredServer::getServerInfo).orElse(null);
if (si != null) {
Component message = Component.text(args[0] + " is on " + si.getName() + ".", NamedTextColor.BLUE);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.find");
}
}
public static class LastSeenCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public LastSeenCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
long secs = plugin.getAbstractRedisBungeeApi().getLastOnline(uuid);
TextComponent.Builder message = Component.text();
if (secs == 0) {
message.color(NamedTextColor.GREEN);
message.content(args[0] + " is currently online.");
} else if (secs != -1) {
message.color(NamedTextColor.BLUE);
message.content(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
} else {
message.color(NamedTextColor.RED);
message.content(args[0] + " has never been online.");
}
sender.sendMessage(message.build());
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.lastseen");
}
}
public static class IpCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public IpCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
InetAddress ia = plugin.getAbstractRedisBungeeApi().getPlayerIp(uuid);
if (ia != null) {
TextComponent message = Component.text(args[0] + " is connected from " + ia.toString() + ".", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.ip");
}
}
public static class PlayerProxyCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public PlayerProxyCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
if (args.length > 0) {
UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
if (uuid == null) {
sender.sendMessage(PLAYER_NOT_FOUND);
return;
}
String proxy = plugin.getAbstractRedisBungeeApi().getProxy(uuid);
if (proxy != null) {
TextComponent message = Component.text(args[0] + " is connected to " + proxy + ".", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(PLAYER_NOT_FOUND);
}
} else {
sender.sendMessage(NO_PLAYER_SPECIFIED);
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.pproxy");
}
}
public static class SendToAll implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public SendToAll(RedisBungeeVelocityPlugin plugin) {
//super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
this.plugin = plugin;
}
@Override
public void execute(final Invocation invocation) {
String[] args = invocation.arguments();
CommandSource sender = invocation.source();
if (args.length > 0) {
String command = Joiner.on(" ").skipNulls().join(args);
plugin.getAbstractRedisBungeeApi().sendProxyCommand(command);
TextComponent message = Component.text("Sent the command /" + command + " to all proxies.", NamedTextColor.GREEN);
sender.sendMessage(message);
} else {
sender.sendMessage(NO_COMMAND_SPECIFIED);
}
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.sendtoall");
}
}
public static class ServerId implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public ServerId(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
invocation.source().sendMessage(Component.text("You are on " + plugin.getAbstractRedisBungeeApi().getProxyId() + ".", NamedTextColor.YELLOW));
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.serverid");
}
}
public static class ServerIds implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public ServerIds(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
invocation.source().sendMessage(
Component.text("All server IDs: " + Joiner.on(", ").join(plugin.getAbstractRedisBungeeApi().getAllProxies()), NamedTextColor.YELLOW));
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.serverids");
}
}
public static class PlistCommand implements SimpleCommand {
private final RedisBungeeVelocityPlugin plugin;
public PlistCommand(RedisBungeeVelocityPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(Invocation invocation) {
CommandSource sender = invocation.source();
String[] args = invocation.arguments();
plugin.getProxy().getScheduler().buildTask(plugin, () -> {
String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getProxyId();
if (!plugin.getProxiesIds().contains(proxy)) {
sender.sendMessage(Component.text(proxy + " is not a valid proxy. See /serverids for valid proxies.", NamedTextColor.RED));
return;
}
Set<UUID> players = plugin.getAbstractRedisBungeeApi().getPlayersOnProxy(proxy);
Component playersOnline = Component.text(playerPlural(players.size()) + " currently on proxy " + proxy + ".", NamedTextColor.YELLOW);
if (args.length >= 2 && args[1].equals("showall")) {
Multimap<String, UUID> serverToPlayers = plugin.getAbstractRedisBungeeApi().getServerToPlayers();
Multimap<String, String> human = HashMultimap.create();
serverToPlayers.forEach((key, value) -> {
if (players.contains(value)) {
human.put(key, plugin.getUuidTranslator().getNameFromUuid(value, false));
}
});
for (String server : new TreeSet<>(human.keySet())) {
TextComponent serverName = Component.text("[" + server + "] ", NamedTextColor.RED);
TextComponent serverCount = Component.text("(" + human.get(server).size() + "): ", NamedTextColor.YELLOW);
TextComponent serverPlayers = Component.text(Joiner.on(", ").join(human.get(server)), NamedTextColor.WHITE);
sender.sendMessage(Component.textOfChildren(serverName, serverCount, serverPlayers));
}
sender.sendMessage(playersOnline);
} else {
sender.sendMessage(playersOnline);
sender.sendMessage(Component.text("To see all players online, use /plist " + proxy + " showall.", NamedTextColor.YELLOW));
}
}).schedule();
}
@Override
public boolean hasPermission(Invocation invocation) {
return invocation.source().hasPermission("redisbungee.command.plist");
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerChangedServerNetworkEvent;
import java.util.UUID;
/**
* This event is sent when a player connects to a new server. RedisBungee sends the event only when
* the proxy the player has been connected to is different than the local proxy.
* <p>
* This event corresponds to {@link com.velocitypowered.api.event.player.ServerConnectedEvent}, and is fired
* asynchronously.
*
* @since 0.3.4
*/
public class PlayerChangedServerNetworkEvent implements IPlayerChangedServerNetworkEvent {
private final UUID uuid;
private final String previousServer;
private final String server;
public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
this.uuid = uuid;
this.previousServer = previousServer;
this.server = server;
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public String getServer() {
return server;
}
@Override
public String getPreviousServer() {
return previousServer;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerJoinedNetworkEvent;
import java.util.UUID;
/**
* This event is sent when a player joins the network. RedisBungee sends the event only when
* the proxy the player has been connected to is different than the local proxy.
* <p>
* This event corresponds to {@link com.velocitypowered.api.event.connection.PostLoginEvent}, and is fired
* asynchronously.
*
* @since 0.3.4
*/
public class PlayerJoinedNetworkEvent implements IPlayerJoinedNetworkEvent {
private final UUID uuid;
public PlayerJoinedNetworkEvent(UUID uuid) {
this.uuid = uuid;
}
@Override
public UUID getUuid() {
return uuid;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPlayerLeftNetworkEvent;
import java.util.UUID;
/**
* This event is sent when a player disconnects. RedisBungee sends the event only when
* the proxy the player has been connected to is different than the local proxy.
* <p>
* This event corresponds to {@link com.velocitypowered.api.event.connection.DisconnectEvent}, and is fired
* asynchronously.
*
* @since 0.3.4
*/
public class PlayerLeftNetworkEvent implements IPlayerLeftNetworkEvent {
private final UUID uuid;
public PlayerLeftNetworkEvent(UUID uuid) {
this.uuid = uuid;
}
@Override
public UUID getUuid() {
return uuid;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2013-present RedisBungee contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
*
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.imaginarycode.minecraft.redisbungee.events;
import com.imaginarycode.minecraft.redisbungee.api.events.IPubSubMessageEvent;
/**
* This event is posted when a PubSub message is received.
* <p>
* <strong>Warning</strong>: This event is fired in a separate thread!
*
* @since 0.2.6
*/
public class PubSubMessageEvent implements IPubSubMessageEvent {
private final String channel;
private final String message;
public PubSubMessageEvent(String channel, String message) {
this.channel = channel;
this.message = message;
}
@Override
public String getChannel() {
return channel;
}
@Override
public String getMessage() {
return message;
}
}

2
jitpack.yml Normal file
View File

@@ -0,0 +1,2 @@
jdk:
- openjdk11

3
makeJavadocs.sh Normal file
View File

@@ -0,0 +1,3 @@
rm -rf javadoc
mkdir javadoc
mvn clean install javadoc:javadoc -pl RedisBungee-API,RedisBungee-Bungee,RedisBungee-Velocity -am

310
mvnw vendored
View File

@@ -1,310 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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
#
# http://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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored
View File

@@ -1,182 +0,0 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

125
pom.xml
View File

@@ -6,132 +6,25 @@
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee</artifactId>
<version>0.6.1</version>
<distributionManagement>
<repository>
<id>Limework-Maven-Public</id>
<url>http://mc.limework.net:8081/repository/public-maven/</url>
</repository>
</distributionManagement>
<repositories>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<inceptionYear>2013</inceptionYear>
<packaging>pom</packaging>
<version>0.10.0-SNAPSHOT</version>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>redis.clients.jedis</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedis
</shadedPattern>
</relocation>
<relocation>
<pattern>redis.clients.util</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.jedisutil
</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons.pool</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.commonspool
</shadedPattern>
</relocation>
<relocation>
<pattern>com.squareup.okhttp</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okhttp
</shadedPattern>
</relocation>
<relocation>
<pattern>okio</pattern>
<shadedPattern>com.imaginarycode.minecraft.redisbungee.internal.okio
</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<!-- Main modules -->
<module>RedisBungee-API</module>
<module>RedisBungee-Bungee</module>
<module>RedisBungee-Velocity</module>
</modules>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.4.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.16-R0.5-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,258 +0,0 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.net.InetAddresses;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PlayerLeftNetworkEvent;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import redis.clients.jedis.Jedis;
import java.net.InetAddress;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
/**
* This class manages all the data that RedisBungee fetches from Redis, along with updates to that data.
*
* @since 0.3.3
*/
public class DataManager implements Listener {
private final RedisBungee plugin;
private final Cache<UUID, String> serverCache = createCache();
private final Cache<UUID, String> proxyCache = createCache();
private final Cache<UUID, InetAddress> ipCache = createCache();
private final Cache<UUID, Long> lastOnlineCache = createCache();
public DataManager(RedisBungee plugin) {
this.plugin = plugin;
}
private static <K, V> Cache<K, V> createCache() {
// TODO: Allow customization via cache specification, ala ServerListPlus
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
private final JsonParser parser = new JsonParser();
public String getServer(final UUID uuid) {
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
if (player != null)
return player.getServer() != null ? player.getServer().getInfo().getName() : null;
try {
return serverCache.get(uuid, new Callable<String>() {
@Override
public String call() throws Exception {
try (Jedis tmpRsc = plugin.getPool().getResource()) {
return Objects.requireNonNull(tmpRsc.hget("player:" + uuid, "server"), "user not found");
}
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.getLogger().log(Level.SEVERE, "Unable to get server", e);
throw new RuntimeException("Unable to get server for " + uuid, e);
}
}
public String getProxy(final UUID uuid) {
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
if (player != null)
return RedisBungee.getConfiguration().getServerId();
try {
return proxyCache.get(uuid, new Callable<String>() {
@Override
public String call() throws Exception {
try (Jedis tmpRsc = plugin.getPool().getResource()) {
return Objects.requireNonNull(tmpRsc.hget("player:" + uuid, "proxy"), "user not found");
}
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.getLogger().log(Level.SEVERE, "Unable to get proxy", e);
throw new RuntimeException("Unable to get proxy for " + uuid, e);
}
}
public InetAddress getIp(final UUID uuid) {
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
if (player != null)
return player.getAddress().getAddress();
try {
return ipCache.get(uuid, new Callable<InetAddress>() {
@Override
public InetAddress call() throws Exception {
try (Jedis tmpRsc = plugin.getPool().getResource()) {
String result = tmpRsc.hget("player:" + uuid, "ip");
if (result == null)
throw new NullPointerException("user not found");
return InetAddresses.forString(result);
}
}
});
} catch (ExecutionException | UncheckedExecutionException e) {
if (e.getCause() instanceof NullPointerException && e.getCause().getMessage().equals("user not found"))
return null; // HACK
plugin.getLogger().log(Level.SEVERE, "Unable to get IP", e);
throw new RuntimeException("Unable to get IP for " + uuid, e);
}
}
public long getLastOnline(final UUID uuid) {
ProxiedPlayer player = plugin.getProxy().getPlayer(uuid);
if (player != null)
return 0;
try {
return lastOnlineCache.get(uuid, new Callable<Long>() {
@Override
public Long call() throws Exception {
try (Jedis tmpRsc = plugin.getPool().getResource()) {
String result = tmpRsc.hget("player:" + uuid, "online");
return result == null ? -1 : Long.valueOf(result);
}
}
});
} catch (ExecutionException e) {
plugin.getLogger().log(Level.SEVERE, "Unable to get last time online", e);
throw new RuntimeException("Unable to get last time online for " + uuid, e);
}
}
private void invalidate(UUID uuid) {
ipCache.invalidate(uuid);
lastOnlineCache.invalidate(uuid);
serverCache.invalidate(uuid);
proxyCache.invalidate(uuid);
}
@EventHandler
public void onPostLogin(PostLoginEvent event) {
// Invalidate all entries related to this player, since they now lie.
invalidate(event.getPlayer().getUniqueId());
}
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
// Invalidate all entries related to this player, since they now lie.
invalidate(event.getPlayer().getUniqueId());
}
@EventHandler
public void onPubSubMessage(PubSubMessageEvent event) {
if (!event.getChannel().equals("redisbungee-data"))
return;
// Partially deserialize the message so we can look at the action
JsonObject jsonObject = parser.parse(event.getMessage()).getAsJsonObject();
String source = jsonObject.get("source").getAsString();
if (source.equals(RedisBungee.getConfiguration().getServerId()))
return;
DataManagerMessage.Action action = DataManagerMessage.Action.valueOf(jsonObject.get("action").getAsString());
switch (action) {
case JOIN:
final DataManagerMessage<LoginPayload> message1 = RedisBungee.getGson().fromJson(jsonObject, new TypeToken<DataManagerMessage<LoginPayload>>() {
}.getType());
proxyCache.put(message1.getTarget(), message1.getSource());
lastOnlineCache.put(message1.getTarget(), (long) 0);
ipCache.put(message1.getTarget(), message1.getPayload().getAddress());
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
plugin.getProxy().getPluginManager().callEvent(new PlayerJoinedNetworkEvent(message1.getTarget()));
}
});
break;
case LEAVE:
final DataManagerMessage<LogoutPayload> message2 = RedisBungee.getGson().fromJson(jsonObject, new TypeToken<DataManagerMessage<LogoutPayload>>() {
}.getType());
invalidate(message2.getTarget());
lastOnlineCache.put(message2.getTarget(), message2.getPayload().getTimestamp());
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
plugin.getProxy().getPluginManager().callEvent(new PlayerLeftNetworkEvent(message2.getTarget()));
}
});
break;
case SERVER_CHANGE:
final DataManagerMessage<ServerChangePayload> message3 = RedisBungee.getGson().fromJson(jsonObject, new TypeToken<DataManagerMessage<ServerChangePayload>>() {
}.getType());
serverCache.put(message3.getTarget(), message3.getPayload().getServer());
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
plugin.getProxy().getPluginManager().callEvent(new PlayerChangedServerNetworkEvent(message3.getTarget(), message3.getPayload().getOldServer(), message3.getPayload().getServer()));
}
});
break;
}
}
@Getter
@RequiredArgsConstructor
static class DataManagerMessage<T> {
private final UUID target;
private final String source = RedisBungee.getApi().getServerId();
private final Action action; // for future use!
private final T payload;
enum Action {
JOIN,
LEAVE,
SERVER_CHANGE
}
}
@Getter
@RequiredArgsConstructor
static class LoginPayload {
private final InetAddress address;
}
@Getter
@RequiredArgsConstructor
static class ServerChangePayload {
private final String server;
private final String oldServer;
}
@Getter
@RequiredArgsConstructor
static class LogoutPayload {
private final long timestamp;
}
}

View File

@@ -1,570 +0,0 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.*;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.util.*;
import com.imaginarycode.minecraft.redisbungee.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDFetcher;
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDTranslator;
import com.squareup.okhttp.Dispatcher;
import com.squareup.okhttp.OkHttpClient;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import static com.google.common.base.Preconditions.checkArgument;
/**
* The RedisBungee plugin.
* <p>
* The only function of interest is {@link #getApi()}, which exposes some functions in this class.
*/
public final class RedisBungee extends Plugin {
@Getter
private static Gson gson = new Gson();
private static RedisBungeeAPI api;
@Getter(AccessLevel.PACKAGE)
private static PubSubListener psl = null;
@Getter
private JedisPool pool;
@Getter
private UUIDTranslator uuidTranslator;
@Getter(AccessLevel.PACKAGE)
private static RedisBungeeConfiguration configuration;
@Getter
private DataManager dataManager;
@Getter
private static OkHttpClient httpClient;
private volatile List<String> serverIds;
private final AtomicInteger nagAboutServers = new AtomicInteger();
private final AtomicInteger globalPlayerCount = new AtomicInteger();
private Future<?> integrityCheck;
private Future<?> heartbeatTask;
private boolean usingLua;
private LuaManager.Script serverToPlayersScript;
private LuaManager.Script getPlayerCountScript;
private static final Object SERVER_TO_PLAYERS_KEY = new Object();
private final Cache<Object, Multimap<String, UUID>> serverToPlayersCache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
/**
* Fetch the {@link RedisBungeeAPI} object created on plugin start.
*
* @return the {@link RedisBungeeAPI} object
*/
public static RedisBungeeAPI getApi() {
return api;
}
static PubSubListener getPubSubListener() {
return psl;
}
final List<String> getServerIds() {
return serverIds;
}
private List<String> getCurrentServerIds(boolean nag, boolean lagged) {
try (Jedis jedis = pool.getResource()) {
long time = getRedisTime(jedis.time());
int nagTime = 0;
if (nag) {
nagTime = nagAboutServers.decrementAndGet();
if (nagTime <= 0) {
nagAboutServers.set(10);
}
}
ImmutableList.Builder<String> servers = ImmutableList.builder();
Map<String, String> heartbeats = jedis.hgetAll("heartbeats");
for (Map.Entry<String, String> entry : heartbeats.entrySet()) {
try {
long stamp = Long.parseLong(entry.getValue());
if (lagged ? time >= stamp + 30 : time <= stamp + 30)
servers.add(entry.getKey());
else if (nag && nagTime <= 0) {
getLogger().severe(entry.getKey() + " is " + (time - stamp) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat.");
jedis.hdel("heartbeats", entry.getKey());
}
} catch (NumberFormatException ignored) {
}
}
return servers.build();
} catch (JedisConnectionException e) {
getLogger().log(Level.SEVERE, "Unable to fetch server IDs", e);
return Collections.singletonList(configuration.getServerId());
}
}
public Set<UUID> getPlayersOnProxy(String server) {
checkArgument(getServerIds().contains(server), server + " is not a valid proxy ID");
try (Jedis jedis = pool.getResource()) {
Set<String> users = jedis.smembers("proxy:" + server + ":usersOnline");
ImmutableSet.Builder<UUID> builder = ImmutableSet.builder();
for (String user : users) {
builder.add(UUID.fromString(user));
}
return builder.build();
}
}
final Multimap<String, UUID> serversToPlayers() {
try {
return serverToPlayersCache.get(SERVER_TO_PLAYERS_KEY, new Callable<Multimap<String, UUID>>() {
@Override
public Multimap<String, UUID> call() throws Exception {
Collection<String> data = (Collection<String>) serverToPlayersScript.eval(ImmutableList.<String>of(), getServerIds());
ImmutableMultimap.Builder<String, UUID> builder = ImmutableMultimap.builder();
String key = null;
for (String s : data) {
if (key == null) {
key = s;
continue;
}
builder.put(key, UUID.fromString(s));
key = null;
}
return builder.build();
}
});
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
final int getCount() {
return globalPlayerCount.get();
}
final int getCurrentCount() {
Long count = (Long) getPlayerCountScript.eval(ImmutableList.<String>of(), ImmutableList.<String>of());
return count.intValue();
}
private Set<String> getLocalPlayersAsUuidStrings() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (ProxiedPlayer player : getProxy().getPlayers()) {
builder.add(player.getUniqueId().toString());
}
return builder.build();
}
final Set<UUID> getPlayers() {
ImmutableSet.Builder<UUID> setBuilder = ImmutableSet.builder();
if (pool != null) {
try (Jedis rsc = pool.getResource()) {
List<String> keys = new ArrayList<>();
for (String i : getServerIds()) {
keys.add("proxy:" + i + ":usersOnline");
}
if (!keys.isEmpty()) {
Set<String> users = rsc.sunion(keys.toArray(new String[keys.size()]));
if (users != null && !users.isEmpty()) {
for (String user : users) {
try {
setBuilder = setBuilder.add(UUID.fromString(user));
} catch (IllegalArgumentException ignored) {
}
}
}
}
} catch (JedisConnectionException e) {
// Redis server has disappeared!
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
throw new RuntimeException("Unable to get all players online", e);
}
}
return setBuilder.build();
}
final void sendProxyCommand(@NonNull String proxyId, @NonNull String command) {
checkArgument(getServerIds().contains(proxyId) || proxyId.equals("allservers"), "proxyId is invalid");
sendChannelMessage("redisbungee-" + proxyId, command);
}
final void sendChannelMessage(String channel, String message) {
try (Jedis jedis = pool.getResource()) {
jedis.publish(channel, message);
} catch (JedisConnectionException e) {
// Redis server has disappeared!
getLogger().log(Level.SEVERE, "Unable to get connection from pool - did your Redis server go away?", e);
throw new RuntimeException("Unable to publish channel message", e);
}
}
private long getRedisTime(List<String> timeRes) {
return Long.parseLong(timeRes.get(0));
}
@Override
public void onEnable() {
ThreadFactory factory = ((ThreadPoolExecutor) getExecutorService()).getThreadFactory();
getExecutorService().shutdownNow();
ScheduledExecutorService service;
try {
Field field = Plugin.class.getDeclaredField("service");
field.setAccessible(true);
field.set(this, service = Executors.newScheduledThreadPool(24, factory));
} catch (Exception e) {
throw new RuntimeException("Can't replace BungeeCord thread pool with our own", e);
}
try {
loadConfig();
} catch (IOException e) {
throw new RuntimeException("Unable to load/save config", e);
} catch (JedisConnectionException e) {
throw new RuntimeException("Unable to connect to your Redis server!", e);
}
if (pool != null) {
try (Jedis tmpRsc = pool.getResource()) {
// This is more portable than INFO <section>
String info = tmpRsc.info();
for (String s : info.split("\r\n")) {
if (s.startsWith("redis_version:")) {
String version = s.split(":")[1];
if (!(usingLua = RedisUtil.canUseLua(version))) {
getLogger().warning("Your version of Redis (" + version + ") is not at least version 2.6. RedisBungee requires a newer version of Redis.");
throw new RuntimeException("Unsupported Redis version detected");
} else {
LuaManager manager = new LuaManager(this);
serverToPlayersScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/server_to_players.lua")));
getPlayerCountScript = manager.createScript(IOUtil.readInputStreamAsString(getResourceAsStream("lua/get_player_count.lua")));
}
break;
}
}
tmpRsc.hset("heartbeats", configuration.getServerId(), tmpRsc.time().get(0));
long uuidCacheSize = tmpRsc.hlen("uuid-cache");
if (uuidCacheSize > 750000) {
getLogger().info("Looks like you have a really big UUID cache! Run https://www.spigotmc.org/resources/redisbungeecleaner.8505/ as soon as possible.");
}
}
serverIds = getCurrentServerIds(true, false);
uuidTranslator = new UUIDTranslator(this);
heartbeatTask = service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try (Jedis rsc = pool.getResource()) {
long redisTime = getRedisTime(rsc.time());
rsc.hset("heartbeats", configuration.getServerId(), String.valueOf(redisTime));
} catch (JedisConnectionException e) {
// Redis server has disappeared!
getLogger().log(Level.SEVERE, "Unable to update heartbeat - did your Redis server go away?", e);
return;
}
try {
serverIds = getCurrentServerIds(true, false);
globalPlayerCount.set(getCurrentCount());
} catch (Throwable e) {
getLogger().log(Level.SEVERE, "Unable to update data - did your Redis server go away?", e);
}
}
}, 0, 3, TimeUnit.SECONDS);
dataManager = new DataManager(this);
if (configuration.isRegisterBungeeCommands()) {
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.GlistCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.FindCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.LastSeenCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.IpCommand(this));
}
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.SendToAll(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerId(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.ServerIds());
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlayerProxyCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.PlistCommand(this));
getProxy().getPluginManager().registerCommand(this, new RedisBungeeCommands.DebugCommand(this));
api = new RedisBungeeAPI(this);
getProxy().getPluginManager().registerListener(this, new RedisBungeeListener(this, configuration.getExemptAddresses()));
getProxy().getPluginManager().registerListener(this, dataManager);
psl = new PubSubListener();
getProxy().getScheduler().runAsync(this, psl);
integrityCheck = service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try (Jedis tmpRsc = pool.getResource()) {
Set<String> players = getLocalPlayersAsUuidStrings();
Set<String> playersInRedis = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
List<String> lagged = getCurrentServerIds(false, true);
// Clean up lagged players.
for (String s : lagged) {
Set<String> laggedPlayers = tmpRsc.smembers("proxy:" + s + ":usersOnline");
tmpRsc.del("proxy:" + s + ":usersOnline");
if (!laggedPlayers.isEmpty()) {
getLogger().info("Cleaning up lagged proxy " + s + " (" + laggedPlayers.size() + " players)...");
for (String laggedPlayer : laggedPlayers) {
RedisUtil.cleanUpPlayer(laggedPlayer, tmpRsc);
}
}
}
Set<String> absentLocally = new HashSet<>(playersInRedis);
absentLocally.removeAll(players);
Set<String> absentInRedis = new HashSet<>(players);
absentInRedis.removeAll(playersInRedis);
for (String member : absentLocally) {
boolean found = false;
for (String proxyId : getServerIds()) {
if (proxyId.equals(configuration.getServerId())) continue;
if (tmpRsc.sismember("proxy:" + proxyId + ":usersOnline", member)) {
// Just clean up the set.
found = true;
break;
}
}
if (!found) {
RedisUtil.cleanUpPlayer(member, tmpRsc);
getLogger().warning("Player found in set that was not found locally and globally: " + member);
} else {
tmpRsc.srem("proxy:" + configuration.getServerId() + ":usersOnline", member);
getLogger().warning("Player found in set that was not found locally, but is on another proxy: " + member);
}
}
Pipeline pipeline = tmpRsc.pipelined();
for (String player : absentInRedis) {
// Player not online according to Redis but not BungeeCord.
getLogger().warning("Player " + player + " is on the proxy but not in Redis.");
ProxiedPlayer proxiedPlayer = ProxyServer.getInstance().getPlayer(UUID.fromString(player));
if (proxiedPlayer == null)
continue; // We'll deal with it later.
RedisUtil.createPlayer(proxiedPlayer, pipeline, true);
}
pipeline.sync();
} catch (Throwable e) {
getLogger().log(Level.SEVERE, "Unable to fix up stored player data", e);
}
}
}, 0, 1, TimeUnit.MINUTES);
}
getProxy().registerChannel("legacy:redisbungee");
getProxy().registerChannel("RedisBungee");
}
@Override
public void onDisable() {
if (pool != null) {
// Poison the PubSub listener
psl.poison();
integrityCheck.cancel(true);
heartbeatTask.cancel(true);
getProxy().getPluginManager().unregisterListeners(this);
try (Jedis tmpRsc = pool.getResource()) {
tmpRsc.hdel("heartbeats", configuration.getServerId());
if (tmpRsc.scard("proxy:" + configuration.getServerId() + ":usersOnline") > 0) {
Set<String> players = tmpRsc.smembers("proxy:" + configuration.getServerId() + ":usersOnline");
for (String member : players)
RedisUtil.cleanUpPlayer(member, tmpRsc);
}
}
pool.destroy();
}
}
private void loadConfig() throws IOException, JedisConnectionException {
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
}
File file = new File(getDataFolder(), "config.yml");
if (!file.exists()) {
file.createNewFile();
try (InputStream in = getResourceAsStream("example_config.yml");
OutputStream out = new FileOutputStream(file)) {
ByteStreams.copy(in, out);
}
}
final Configuration configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file);
final String redisServer = configuration.getString("redis-server", "localhost");
final int redisPort = configuration.getInt("redis-port", 6379);
final boolean useSSL = configuration.getBoolean("useSSL");
String redisPassword = configuration.getString("redis-password");
String serverId = configuration.getString("server-id");
if (redisPassword != null && (redisPassword.isEmpty() || redisPassword.equals("none"))) {
redisPassword = null;
}
// Configuration sanity checks.
if (serverId == null || serverId.isEmpty()) {
throw new RuntimeException("server-id is not specified in the configuration or is empty");
}
if (redisServer != null && !redisServer.isEmpty()) {
final String finalRedisPassword = redisPassword;
FutureTask<JedisPool> task = new FutureTask<>(new Callable<JedisPool>() {
@Override
public JedisPool call() throws Exception {
// Create the pool...
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(configuration.getInt("max-redis-connections", 8));
return new JedisPool(config, redisServer, redisPort, 0, finalRedisPassword, useSSL);
}
});
getProxy().getScheduler().runAsync(this, task);
try {
pool = task.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Unable to create Redis pool", e);
}
// Test the connection
try (Jedis rsc = pool.getResource()) {
rsc.ping();
// If that worked, now we can check for an existing, alive Bungee:
File crashFile = new File(getDataFolder(), "restarted_from_crash.txt");
if (crashFile.exists()) {
crashFile.delete();
} else if (rsc.hexists("heartbeats", serverId)) {
try {
long value = Long.parseLong(rsc.hget("heartbeats", serverId));
long redisTime = getRedisTime(rsc.time());
if (redisTime < value + 20) {
getLogger().severe("You have launched a possible impostor BungeeCord instance. Another instance is already running.");
getLogger().severe("For data consistency reasons, RedisBungee will now disable itself.");
getLogger().severe("If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check.");
throw new RuntimeException("Possible impostor instance!");
}
} catch (NumberFormatException ignored) {
}
}
FutureTask<Void> task2 = new FutureTask<>(new Callable<Void>() {
@Override
public Void call() throws Exception {
httpClient = new OkHttpClient();
Dispatcher dispatcher = new Dispatcher(getExecutorService());
httpClient.setDispatcher(dispatcher);
NameFetcher.setHttpClient(httpClient);
UUIDFetcher.setHttpClient(httpClient);
RedisBungee.configuration = new RedisBungeeConfiguration(RedisBungee.this.getPool(), configuration);
return null;
}
});
getProxy().getScheduler().runAsync(this, task2);
try {
task2.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Unable to create HTTP client", e);
}
getLogger().log(Level.INFO, "Successfully connected to Redis.");
} catch (JedisConnectionException e) {
pool.destroy();
pool = null;
throw e;
}
} else {
throw new RuntimeException("No redis server specified!");
}
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
class PubSubListener implements Runnable {
private JedisPubSubHandler jpsh;
private Set<String> addedChannels = new HashSet<String>();
@Override
public void run() {
boolean broken = false;
try (Jedis rsc = pool.getResource()) {
try {
jpsh = new JedisPubSubHandler();
addedChannels.add("redisbungee-" + configuration.getServerId());
addedChannels.add("redisbungee-allservers");
addedChannels.add("redisbungee-data");
rsc.subscribe(jpsh, addedChannels.toArray(new String[0]));
} catch (Exception e) {
// FIXME: Extremely ugly hack
// Attempt to unsubscribe this instance and try again.
getLogger().log(Level.INFO, "PubSub error, attempting to recover.", e);
try {
jpsh.unsubscribe();
} catch (Exception e1) {
/* This may fail with
- java.net.SocketException: Broken pipe
- redis.clients.jedis.exceptions.JedisConnectionException: JedisPubSub was not subscribed to a Jedis instance
*/
}
broken = true;
}
} catch (JedisConnectionException e) {
getLogger().log(Level.INFO, "PubSub error, attempting to recover in 5 secs.");
getProxy().getScheduler().schedule(RedisBungee.this, PubSubListener.this, 5, TimeUnit.SECONDS);
}
if (broken) {
run();
}
}
public void addChannel(String... channel) {
addedChannels.addAll(Arrays.asList(channel));
jpsh.subscribe(channel);
}
public void removeChannel(String... channel) {
addedChannels.removeAll(Arrays.asList(channel));
jpsh.unsubscribe(channel);
}
public void poison() {
addedChannels.clear();
jpsh.unsubscribe();
}
}
private class JedisPubSubHandler extends JedisPubSub {
@Override
public void onMessage(final String s, final String s2) {
if (s2.trim().length() == 0) return;
getProxy().getScheduler().runAsync(RedisBungee.this, new Runnable() {
@Override
public void run() {
getProxy().getPluginManager().callEvent(new PubSubMessageEvent(s, s2));
}
});
}
}
}

View File

@@ -1,36 +0,0 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.collect.ImmutableList;
import com.google.common.net.InetAddresses;
import lombok.Getter;
import net.md_5.bungee.config.Configuration;
import redis.clients.jedis.JedisPool;
import java.net.InetAddress;
import java.util.List;
public class RedisBungeeConfiguration {
@Getter
private final JedisPool pool;
@Getter
private final String serverId;
@Getter
private final boolean registerBungeeCommands;
@Getter
private final List<InetAddress> exemptAddresses;
public RedisBungeeConfiguration(JedisPool pool, Configuration configuration) {
this.pool = pool;
this.serverId = configuration.getString("server-id");
this.registerBungeeCommands = configuration.getBoolean("register-bungee-commands", true);
List<String> stringified = configuration.getStringList("exempt-ip-addresses");
ImmutableList.Builder<InetAddress> addressBuilder = ImmutableList.builder();
for (String s : stringified) {
addressBuilder.add(InetAddresses.forString(s));
}
this.exemptAddresses = addressBuilder.build();
}
}

View File

@@ -1,291 +0,0 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import com.imaginarycode.minecraft.redisbungee.util.RedisCallable;
import lombok.AllArgsConstructor;
import net.md_5.bungee.api.AbstractReconnectHandler;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.*;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.net.InetAddress;
import java.util.*;
@AllArgsConstructor
public class RedisBungeeListener implements Listener {
private static final BaseComponent[] ALREADY_LOGGED_IN =
new ComponentBuilder("You are already logged on to this server.").color(ChatColor.RED)
.append("\n\nIt may help to try logging in again in a few minutes.\nIf this does not resolve your issue, please contact staff.")
.color(ChatColor.GRAY)
.create();
private static final BaseComponent[] ONLINE_MODE_RECONNECT =
new ComponentBuilder("Whoops! You need to reconnect.").color(ChatColor.RED)
.append("\n\nWe found someone online using your username. They were kicked and you may reconnect.\nIf this does not work, please contact staff.")
.color(ChatColor.GRAY)
.create();
private final RedisBungee plugin;
private final List<InetAddress> exemptAddresses;
@EventHandler(priority = EventPriority.LOWEST)
public void onLogin(final LoginEvent event) {
event.registerIntent(plugin);
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
@Override
protected Void call(Jedis jedis) {
try {
if (event.isCancelled()) {
return null;
}
// We make sure they aren't trying to use an existing player's name.
// This is problematic for online-mode servers as they always disconnect old clients.
if (plugin.getProxy().getConfig().isOnlineMode()) {
ProxiedPlayer player = plugin.getProxy().getPlayer(event.getConnection().getName());
if (player != null) {
event.setCancelled(true);
// TODO: Make it accept a BaseComponent[] like everything else.
event.setCancelReason(TextComponent.toLegacyText(ONLINE_MODE_RECONNECT));
return null;
}
}
for (String s : plugin.getServerIds()) {
if (jedis.sismember("proxy:" + s + ":usersOnline", event.getConnection().getUniqueId().toString())) {
event.setCancelled(true);
// TODO: Make it accept a BaseComponent[] like everything else.
event.setCancelReason(TextComponent.toLegacyText(ALREADY_LOGGED_IN));
return null;
}
}
Pipeline pipeline = jedis.pipelined();
plugin.getUuidTranslator().persistInfo(event.getConnection().getName(), event.getConnection().getUniqueId(), pipeline);
RedisUtil.createPlayer(event.getConnection(), pipeline, false);
// We're not publishing, the API says we only publish at PostLoginEvent time.
pipeline.sync();
return null;
} finally {
event.completeIntent(plugin);
}
}
});
}
@EventHandler
public void onPostLogin(final PostLoginEvent event) {
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
@Override
protected Void call(Jedis jedis) {
jedis.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
event.getPlayer().getUniqueId(), DataManager.DataManagerMessage.Action.JOIN,
new DataManager.LoginPayload(event.getPlayer().getAddress().getAddress()))));
return null;
}
});
}
@EventHandler
public void onPlayerDisconnect(final PlayerDisconnectEvent event) {
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
@Override
protected Void call(Jedis jedis) {
Pipeline pipeline = jedis.pipelined();
RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), pipeline);
pipeline.sync();
return null;
}
});
}
@EventHandler
public void onServerChange(final ServerConnectedEvent event) {
final String currentServer = event.getPlayer().getServer() == null ? null : event.getPlayer().getServer().getInfo().getName();
plugin.getProxy().getScheduler().runAsync(plugin, new RedisCallable<Void>(plugin) {
@Override
protected Void call(Jedis jedis) {
jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getInfo().getName());
jedis.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
event.getPlayer().getUniqueId(), DataManager.DataManagerMessage.Action.SERVER_CHANGE,
new DataManager.ServerChangePayload(event.getServer().getInfo().getName(), currentServer))));
return null;
}
});
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPing(final ProxyPingEvent event) {
if (exemptAddresses.contains(event.getConnection().getAddress().getAddress())) {
return;
}
ServerInfo forced = AbstractReconnectHandler.getForcedHost(event.getConnection());
if (forced != null && event.getConnection().getListener().isPingPassthrough()) {
return;
}
event.getResponse().getPlayers().setOnline(plugin.getCount());
}
@EventHandler
public void onPluginMessage(final PluginMessageEvent event) {
if ((event.getTag().equals("legacy:RedisBungee") || event.getTag().equals("RedisBungee")) && event.getSender() instanceof Server) {
final String currentChannel = event.getTag();
final byte[] data = Arrays.copyOf(event.getData(), event.getData().length);
plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
@Override
public void run() {
ByteArrayDataInput in = ByteStreams.newDataInput(data);
String subchannel = in.readUTF();
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String type;
switch (subchannel) {
case "PlayerList":
out.writeUTF("PlayerList");
Set<UUID> original = Collections.emptySet();
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
original = plugin.getPlayers();
} else {
try {
original = RedisBungee.getApi().getPlayersOnServer(type);
} catch (IllegalArgumentException ignored) {
}
}
Set<String> players = new HashSet<>();
for (UUID uuid : original)
players.add(plugin.getUuidTranslator().getNameFromUuid(uuid, false));
out.writeUTF(Joiner.on(',').join(players));
break;
case "PlayerCount":
out.writeUTF("PlayerCount");
type = in.readUTF();
if (type.equals("ALL")) {
out.writeUTF("ALL");
out.writeInt(plugin.getCount());
} else {
out.writeUTF(type);
try {
out.writeInt(RedisBungee.getApi().getPlayersOnServer(type).size());
} catch (IllegalArgumentException e) {
out.writeInt(0);
}
}
break;
case "LastOnline":
String user = in.readUTF();
out.writeUTF("LastOnline");
out.writeUTF(user);
out.writeLong(RedisBungee.getApi().getLastOnline(plugin.getUuidTranslator().getTranslatedUuid(user, true)));
break;
case "ServerPlayers":
String type1 = in.readUTF();
out.writeUTF("ServerPlayers");
Multimap<String, UUID> multimap = RedisBungee.getApi().getServerToPlayers();
boolean includesUsers;
switch (type1) {
case "COUNT":
includesUsers = false;
break;
case "PLAYERS":
includesUsers = true;
break;
default:
// TODO: Should I raise an error?
return;
}
out.writeUTF(type1);
if (includesUsers) {
Multimap<String, String> human = HashMultimap.create();
for (Map.Entry<String, UUID> entry : multimap.entries()) {
human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
}
serializeMultimap(human, true, out);
} else {
serializeMultiset(multimap.keys(), out);
}
break;
case "Proxy":
out.writeUTF("Proxy");
out.writeUTF(RedisBungee.getConfiguration().getServerId());
break;
case "PlayerProxy":
String username = in.readUTF();
out.writeUTF("PlayerProxy");
out.writeUTF(username);
out.writeUTF(RedisBungee.getApi().getProxy(plugin.getUuidTranslator().getTranslatedUuid(username, true)));
break;
default:
return;
}
((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
}
});
}
}
private void serializeMultiset(Multiset<String> collection, ByteArrayDataOutput output) {
output.writeInt(collection.elementSet().size());
for (Multiset.Entry<String> entry : collection.entrySet()) {
output.writeUTF(entry.getElement());
output.writeInt(entry.getCount());
}
}
private void serializeMultimap(Multimap<String, String> collection, boolean includeNames, ByteArrayDataOutput output) {
output.writeInt(collection.keySet().size());
for (Map.Entry<String, Collection<String>> entry : collection.asMap().entrySet()) {
output.writeUTF(entry.getKey());
if (includeNames) {
serializeCollection(entry.getValue(), output);
} else {
output.writeInt(entry.getValue().size());
}
}
}
private void serializeCollection(Collection<?> collection, ByteArrayDataOutput output) {
output.writeInt(collection.size());
for (Object o : collection) {
output.writeUTF(o.toString());
}
}
@EventHandler
public void onPubSubMessage(PubSubMessageEvent event) {
if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + RedisBungee.getApi().getServerId())) {
String message = event.getMessage();
if (message.startsWith("/"))
message = message.substring(1);
plugin.getLogger().info("Invoking command via PubSub: /" + message);
plugin.getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.instance, message);
}
}
}

View File

@@ -1,73 +0,0 @@
package com.imaginarycode.minecraft.redisbungee;
import com.google.common.annotations.VisibleForTesting;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@VisibleForTesting
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RedisUtil {
protected static void createPlayer(ProxiedPlayer player, Pipeline pipeline, boolean fireEvent) {
createPlayer(player.getPendingConnection(), pipeline, fireEvent);
if (player.getServer() != null)
pipeline.hset("player:" + player.getUniqueId().toString(), "server", player.getServer().getInfo().getName());
}
protected static void createPlayer(PendingConnection connection, Pipeline pipeline, boolean fireEvent) {
Map<String, String> playerData = new HashMap<>(4);
playerData.put("online", "0");
playerData.put("ip", connection.getAddress().getAddress().getHostAddress());
playerData.put("proxy", RedisBungee.getConfiguration().getServerId());
pipeline.sadd("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", connection.getUniqueId().toString());
pipeline.hmset("player:" + connection.getUniqueId().toString(), playerData);
if (fireEvent) {
pipeline.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
connection.getUniqueId(), DataManager.DataManagerMessage.Action.JOIN,
new DataManager.LoginPayload(connection.getAddress().getAddress()))));
}
}
public static void cleanUpPlayer(String player, Jedis rsc) {
rsc.srem("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", player);
rsc.hdel("player:" + player, "server", "ip", "proxy");
long timestamp = System.currentTimeMillis();
rsc.hset("player:" + player, "online", String.valueOf(timestamp));
rsc.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
UUID.fromString(player), DataManager.DataManagerMessage.Action.LEAVE,
new DataManager.LogoutPayload(timestamp))));
}
public static void cleanUpPlayer(String player, Pipeline rsc) {
rsc.srem("proxy:" + RedisBungee.getApi().getServerId() + ":usersOnline", player);
rsc.hdel("player:" + player, "server", "ip", "proxy");
long timestamp = System.currentTimeMillis();
rsc.hset("player:" + player, "online", String.valueOf(timestamp));
rsc.publish("redisbungee-data", RedisBungee.getGson().toJson(new DataManager.DataManagerMessage<>(
UUID.fromString(player), DataManager.DataManagerMessage.Action.LEAVE,
new DataManager.LogoutPayload(timestamp))));
}
public static boolean canUseLua(String redisVersion) {
// Need to use >=2.6 to use Lua optimizations.
String[] args = redisVersion.split("\\.");
if (args.length < 2) {
return false;
}
int major = Integer.parseInt(args[0]);
int minor = Integer.parseInt(args[1]);
return major >= 3 || (major == 2 && minor >= 6);
}
}

View File

@@ -1,27 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.events;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.md_5.bungee.api.plugin.Event;
/**
* This event is posted when a PubSub message is received.
* <p>
* <strong>Warning</strong>: This event is fired in a separate thread!
*
* @since 0.2.6
*/
@RequiredArgsConstructor
@ToString
public class PubSubMessageEvent extends Event {
private final String channel;
private final String message;
public String getChannel() {
return channel;
}
public String getMessage() {
return message;
}
}

View File

@@ -1,44 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.util;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import lombok.RequiredArgsConstructor;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;
import java.util.List;
@RequiredArgsConstructor
public class LuaManager {
private final RedisBungee plugin;
public Script createScript(String script) {
try (Jedis jedis = plugin.getPool().getResource()) {
String hash = jedis.scriptLoad(script);
return new Script(script, hash);
}
}
@RequiredArgsConstructor
public class Script {
private final String script;
private final String hashed;
public Object eval(List<String> keys, List<String> args) {
Object data;
try (Jedis jedis = plugin.getPool().getResource()) {
try {
data = jedis.evalsha(hashed, keys, args);
} catch (JedisDataException e) {
if (e.getMessage().startsWith("NOSCRIPT")) {
data = jedis.eval(script, keys, args);
} else {
throw e;
}
}
}
return data;
}
}
}

View File

@@ -1,45 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.util;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import lombok.AllArgsConstructor;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.util.concurrent.Callable;
import java.util.logging.Level;
@AllArgsConstructor
public abstract class RedisCallable<T> implements Callable<T>, Runnable {
private final RedisBungee plugin;
@Override
public T call() {
return run(false);
}
public void run() {
call();
}
private T run(boolean retry) {
try (Jedis jedis = plugin.getPool().getResource()) {
return call(jedis);
} catch (JedisConnectionException e) {
plugin.getLogger().log(Level.SEVERE, "Unable to get connection", e);
if (!retry) {
// Wait one second before retrying the task
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
throw new RuntimeException("task failed to run", e1);
}
return run(true);
}
}
throw new RuntimeException("task failed to run");
}
protected abstract T call(Jedis jedis);
}

View File

@@ -1,198 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.util.uuid;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ProxyServer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.exceptions.JedisException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.regex.Pattern;
@RequiredArgsConstructor
public final class UUIDTranslator {
private static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
private static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}");
private final RedisBungee plugin;
private final Map<String, CachedUUIDEntry> nameToUuidMap = new ConcurrentHashMap<>(128, 0.5f, 4);
private final Map<UUID, CachedUUIDEntry> uuidToNameMap = new ConcurrentHashMap<>(128, 0.5f, 4);
private void addToMaps(String name, UUID uuid) {
// This is why I like LocalDate...
// Cache the entry for three days.
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 3);
// Create the entry and populate the local maps
CachedUUIDEntry entry = new CachedUUIDEntry(name, uuid, calendar);
nameToUuidMap.put(name.toLowerCase(), entry);
uuidToNameMap.put(uuid, entry);
}
public final UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) {
// If the player is online, give them their UUID.
// Remember, local data > remote data.
if (ProxyServer.getInstance().getPlayer(player) != null)
return ProxyServer.getInstance().getPlayer(player).getUniqueId();
// Check if it exists in the map
CachedUUIDEntry cachedUUIDEntry = nameToUuidMap.get(player.toLowerCase());
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired())
return cachedUUIDEntry.getUuid();
else
nameToUuidMap.remove(player);
}
// Check if we can exit early
if (UUID_PATTERN.matcher(player).find()) {
return UUID.fromString(player);
}
if (MOJANGIAN_UUID_PATTERN.matcher(player).find()) {
// Reconstruct the UUID
return UUIDFetcher.getUUID(player);
}
// If we are in offline mode, UUID generation is simple.
// We don't even have to cache the UUID, since this is easy to recalculate.
if (!plugin.getProxy().getConfig().isOnlineMode()) {
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(Charsets.UTF_8));
}
// Let's try Redis.
try (Jedis jedis = plugin.getPool().getResource()) {
String stored = jedis.hget("uuid-cache", player.toLowerCase());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = RedisBungee.getGson().fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
jedis.hdel("uuid-cache", player.toLowerCase());
// Doesn't hurt to also remove the UUID entry as well.
jedis.hdel("uuid-cache", entry.getUuid().toString());
} else {
nameToUuidMap.put(player.toLowerCase(), entry);
uuidToNameMap.put(entry.getUuid(), entry);
return entry.getUuid();
}
}
// That didn't work. Let's ask Mojang.
if (!expensiveLookups || !ProxyServer.getInstance().getConfig().isOnlineMode())
return null;
Map<String, UUID> uuidMap1;
try {
uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call();
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Unable to fetch UUID from Mojang for " + player, e);
return null;
}
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
if (entry.getKey().equalsIgnoreCase(player)) {
persistInfo(entry.getKey(), entry.getValue(), jedis);
return entry.getValue();
}
}
} catch (JedisException e) {
plugin.getLogger().log(Level.SEVERE, "Unable to fetch UUID for " + player, e);
}
return null; // Nope, game over!
}
public final String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) {
// If the player is online, give them their UUID.
// Remember, local data > remote data.
if (ProxyServer.getInstance().getPlayer(player) != null)
return ProxyServer.getInstance().getPlayer(player).getName();
// Check if it exists in the map
CachedUUIDEntry cachedUUIDEntry = uuidToNameMap.get(player);
if (cachedUUIDEntry != null) {
if (!cachedUUIDEntry.expired())
return cachedUUIDEntry.getName();
else
uuidToNameMap.remove(player);
}
// Okay, it wasn't locally cached. Let's try Redis.
try (Jedis jedis = plugin.getPool().getResource()) {
String stored = jedis.hget("uuid-cache", player.toString());
if (stored != null) {
// Found an entry value. Deserialize it.
CachedUUIDEntry entry = RedisBungee.getGson().fromJson(stored, CachedUUIDEntry.class);
// Check for expiry:
if (entry.expired()) {
jedis.hdel("uuid-cache", player.toString());
// Doesn't hurt to also remove the named entry as well.
// TODO: Since UUIDs are fixed, we could look up the name and see if the UUID matches.
jedis.hdel("uuid-cache", entry.getName());
} else {
nameToUuidMap.put(entry.getName().toLowerCase(), entry);
uuidToNameMap.put(player, entry);
return entry.getName();
}
}
if (!expensiveLookups || !ProxyServer.getInstance().getConfig().isOnlineMode())
return null;
// That didn't work. Let's ask Mojang. This call may fail, because Mojang is insane.
String name;
try {
List<String> nameHist = NameFetcher.nameHistoryFromUuid(player);
name = Iterables.getLast(nameHist, null);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Unable to fetch name from Mojang for " + player, e);
return null;
}
if (name != null) {
persistInfo(name, player, jedis);
return name;
}
return null;
} catch (JedisException e) {
plugin.getLogger().log(Level.SEVERE, "Unable to fetch name for " + player, e);
return null;
}
}
public final void persistInfo(String name, UUID uuid, Jedis jedis) {
addToMaps(name, uuid);
String json = RedisBungee.getGson().toJson(uuidToNameMap.get(uuid));
jedis.hmset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
}
public final void persistInfo(String name, UUID uuid, Pipeline jedis) {
addToMaps(name, uuid);
String json = RedisBungee.getGson().toJson(uuidToNameMap.get(uuid));
jedis.hmset("uuid-cache", ImmutableMap.of(name.toLowerCase(), json, uuid.toString(), json));
}
@RequiredArgsConstructor
@Getter
private class CachedUUIDEntry {
private final String name;
private final UUID uuid;
private final Calendar expiry;
public boolean expired() {
return Calendar.getInstance().after(expiry);
}
}
}

View File

@@ -1,35 +0,0 @@
# RedisBungee configuration file.
# PLEASE READ THE WIKI: https://github.com/Limework/RedisBungee/wiki
# The Redis server you use.
# Get Redis from http://redis.io/
redis-server: 127.0.0.1
redis-port: 6379
# OPTIONAL: If your Redis server uses AUTH, set the password required.
redis-password: ""
# Maximum connections that will be maintained to the Redis server.
# The default is 8. This setting should be left as-is unless you have some wildly
# inefficient plugins or a lot of players.
max-redis-connections: 8
# since redis can support ssl by version 6 you can use ssl in redis bungee too!
# you must disable this if redis version is under 6 you must disable this or connection wont work!!!
useSSL: false
# An identifier for this BungeeCord instance.
server-id: test1
# Whether or not RedisBungee should install its version of regular BungeeCord commands.
# Often, the RedisBungee commands are desired, but in some cases someone may wish to
# override the commands using another plugin.
#
# If you are just denying access to the commands, RedisBungee uses the default BungeeCord
# permissions - just deny them and access will be denied.
#
# Please note that with build 787+, most commands overridden by RedisBungee were moved to
# modules, and these must be disabled or overridden yourself.
register-bungee-commands: true
# A list of IP addresses for which RedisBungee will not modify the response for, useful for automatic
# restart scripts.
exempt-ip-addresses: []

View File

@@ -1,24 +0,0 @@
local c = redis.call
local curTime = c("TIME")
local time = tonumber(curTime[1])
local heartbeats = c("HGETALL", "heartbeats")
local total = 0
local key
for _, v in ipairs(heartbeats) do
if not key then
key = v
else
local n = tonumber(v)
if n then
if n + 30 >= time then
total = total + c("SCARD", "proxy:" .. key .. ":usersOnline")
end
end
key = nil
end
end
return total

View File

@@ -1,18 +0,0 @@
local call = redis.call
local ipairs = ipairs
local serverToData = {}
for _, proxy in ipairs(ARGV) do
local players = call("SMEMBERS", "proxy:" .. proxy .. ":usersOnline")
for _, player in ipairs(players) do
local server = call("HGET", "player:" .. player, "server")
if server then
local sz = #serverToData
serverToData[sz + 1] = server
serverToData[sz + 2] = player
end
end
end
return serverToData

View File

@@ -1,17 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.test;
import com.imaginarycode.minecraft.redisbungee.RedisUtil;
import org.junit.Assert;
import org.junit.Test;
public class RedisUtilTest {
@Test
public void testRedisLuaCheck() {
Assert.assertTrue(RedisUtil.canUseLua("2.6.0"));
Assert.assertFalse(RedisUtil.canUseLua("2.2.12"));
Assert.assertFalse(RedisUtil.canUseLua("1.2.4"));
Assert.assertTrue(RedisUtil.canUseLua("2.8.4"));
Assert.assertTrue(RedisUtil.canUseLua("3.0.0"));
Assert.assertTrue(RedisUtil.canUseLua("3.2.1"));
}
}

View File

@@ -1,47 +0,0 @@
package com.imaginarycode.minecraft.redisbungee.test;
import com.imaginarycode.minecraft.redisbungee.util.uuid.NameFetcher;
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDFetcher;
import com.squareup.okhttp.OkHttpClient;
import org.junit.Test;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class UUIDNameTest {
private String[] uuidsToTest = {"68ec43f7234b41b48764dfb38b9ffe8c", "652a2bc4e8cd405db7b698156ee2dc09"};
private String[] namesToTest = {"vemacs"};
@Test
public void testUuidToName() throws IOException {
OkHttpClient httpClient = new OkHttpClient();
NameFetcher.setHttpClient(httpClient);
for (String uuid : uuidsToTest) {
List<String> names = NameFetcher.nameHistoryFromUuid(UUIDFetcher.getUUID(uuid));
String currentName = names.get(names.size() - 1);
System.out.println("Current name for UUID " + uuid + " is " + currentName);
}
}
@Test
public void testNameToUuid() throws IOException {
OkHttpClient httpClient = new OkHttpClient();
UUIDFetcher.setHttpClient(httpClient);
for (String name : namesToTest) {
Map<String, UUID> uuidMap1;
try {
uuidMap1 = new UUIDFetcher(Collections.singletonList(name)).call();
for (Map.Entry<String, UUID> entry : uuidMap1.entrySet()) {
if (entry.getKey().equalsIgnoreCase(name)) {
System.out.println("Current UUID for name " + name + " is " + entry.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}