@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.api.feed;
|
||||||
|
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class FeedClient
|
||||||
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(FeedClient.class);
|
||||||
|
|
||||||
|
public FeedResult lookupFeed() throws IOException
|
||||||
|
{
|
||||||
|
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
|
||||||
|
.addPathSegment("feed")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
logger.debug("Built URI: {}", url);
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
{
|
||||||
|
logger.debug("Error looking up feed: {}", response.message());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream in = response.body().byteStream();
|
||||||
|
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), FeedResult.class);
|
||||||
|
}
|
||||||
|
catch (JsonParseException ex)
|
||||||
|
{
|
||||||
|
throw new IOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.api.feed;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class FeedItem
|
||||||
|
{
|
||||||
|
private final FeedItemType type;
|
||||||
|
private String avatar;
|
||||||
|
private final String title;
|
||||||
|
private final String content;
|
||||||
|
private final String url;
|
||||||
|
private final long timestamp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.api.feed;
|
||||||
|
|
||||||
|
public enum FeedItemType
|
||||||
|
{
|
||||||
|
BLOG_POST,
|
||||||
|
TWEET,
|
||||||
|
OSRS_NEWS
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.api.feed;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class FeedResult
|
||||||
|
{
|
||||||
|
private List<FeedItem> items;
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import net.runelite.http.api.feed.FeedResult;
|
||||||
|
import net.runelite.http.api.feed.FeedItem;
|
||||||
|
import net.runelite.http.service.feed.blog.BlogService;
|
||||||
|
import net.runelite.http.service.feed.osrsnews.OSRSNewsService;
|
||||||
|
import net.runelite.http.service.feed.twitter.TwitterService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/feed")
|
||||||
|
public class FeedController
|
||||||
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(FeedController.class);
|
||||||
|
|
||||||
|
private BlogService blogService;
|
||||||
|
private TwitterService twitterService;
|
||||||
|
private OSRSNewsService osrsNewsService;
|
||||||
|
|
||||||
|
private final Supplier<FeedResult> feed = Suppliers.memoizeWithExpiration(() ->
|
||||||
|
{
|
||||||
|
List<FeedItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
items.addAll(blogService.getBlogPosts());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.warn(null, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
items.addAll(twitterService.getTweets());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.warn(null, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
items.addAll(osrsNewsService.getNews());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.warn(null, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeedResult(items);
|
||||||
|
}, 10, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public FeedController(BlogService blogService, TwitterService twitterService, OSRSNewsService osrsNewsService)
|
||||||
|
{
|
||||||
|
this.blogService = blogService;
|
||||||
|
this.twitterService = twitterService;
|
||||||
|
this.osrsNewsService = osrsNewsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping
|
||||||
|
public FeedResult getFeed() throws IOException
|
||||||
|
{
|
||||||
|
return feed.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed.blog;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import net.runelite.http.api.feed.FeedItem;
|
||||||
|
import net.runelite.http.api.feed.FeedItemType;
|
||||||
|
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class BlogService
|
||||||
|
{
|
||||||
|
private static final HttpUrl RSS_URL = HttpUrl.parse("https://runelite.net/atom.xml");
|
||||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
||||||
|
|
||||||
|
public List<FeedItem> getBlogPosts() throws IOException
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(RSS_URL)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
{
|
||||||
|
throw new IOException("Error getting blog posts: " + response.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputStream in = response.body().byteStream();
|
||||||
|
Document document = DocumentBuilderFactory.newInstance()
|
||||||
|
.newDocumentBuilder()
|
||||||
|
.parse(in);
|
||||||
|
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
NodeList documentItems = documentElement.getElementsByTagName("entry");
|
||||||
|
|
||||||
|
List<FeedItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(documentItems.getLength(), 3); i++)
|
||||||
|
{
|
||||||
|
Node item = documentItems.item(i);
|
||||||
|
NodeList children = item.getChildNodes();
|
||||||
|
|
||||||
|
String title = null;
|
||||||
|
String summary = null;
|
||||||
|
String link = null;
|
||||||
|
long timestamp = -1;
|
||||||
|
|
||||||
|
for (int j = 0; j < children.getLength(); j++)
|
||||||
|
{
|
||||||
|
Node childItem = children.item(j);
|
||||||
|
String nodeName = childItem.getNodeName();
|
||||||
|
|
||||||
|
switch (nodeName)
|
||||||
|
{
|
||||||
|
case "title":
|
||||||
|
title = childItem.getTextContent();
|
||||||
|
break;
|
||||||
|
case "summary":
|
||||||
|
summary = childItem.getTextContent().replace("\n", "").trim();
|
||||||
|
break;
|
||||||
|
case "link":
|
||||||
|
link = childItem.getAttributes().getNamedItem("href").getTextContent();
|
||||||
|
break;
|
||||||
|
case "updated":
|
||||||
|
timestamp = DATE_FORMAT.parse(childItem.getTextContent()).getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title == null || summary == null || link == null || timestamp == -1)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Failed to find title, summary, link and/or timestamp in the blog post feed");
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(new FeedItem(FeedItemType.BLOG_POST, title, summary, link, timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
catch (ParserConfigurationException | SAXException | ParseException e)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Failed to parse blog posts: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed.osrsnews;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import net.runelite.http.api.feed.FeedItem;
|
||||||
|
import net.runelite.http.api.feed.FeedItemType;
|
||||||
|
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class OSRSNewsService
|
||||||
|
{
|
||||||
|
private static final HttpUrl RSS_URL = HttpUrl.parse("http://services.runescape.com/m=news/latest_news.rss?oldschool=true");
|
||||||
|
private static final SimpleDateFormat PUB_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy '00:00:00 GMT'", Locale.US);
|
||||||
|
|
||||||
|
public List<FeedItem> getNews() throws IOException
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(RSS_URL)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
{
|
||||||
|
throw new IOException("Error getting OSRS news: " + response.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputStream in = response.body().byteStream();
|
||||||
|
Document document = DocumentBuilderFactory.newInstance()
|
||||||
|
.newDocumentBuilder()
|
||||||
|
.parse(in);
|
||||||
|
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
NodeList documentItems = documentElement.getElementsByTagName("item");
|
||||||
|
|
||||||
|
List<FeedItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < documentItems.getLength(); i++)
|
||||||
|
{
|
||||||
|
Node item = documentItems.item(i);
|
||||||
|
NodeList children = item.getChildNodes();
|
||||||
|
|
||||||
|
String title = null;
|
||||||
|
String description = null;
|
||||||
|
String link = null;
|
||||||
|
long timestamp = -1;
|
||||||
|
|
||||||
|
for (int j = 0; j < children.getLength(); j++)
|
||||||
|
{
|
||||||
|
Node childItem = children.item(j);
|
||||||
|
String nodeName = childItem.getNodeName();
|
||||||
|
|
||||||
|
switch (nodeName)
|
||||||
|
{
|
||||||
|
case "title":
|
||||||
|
title = childItem.getTextContent();
|
||||||
|
break;
|
||||||
|
case "description":
|
||||||
|
description = childItem.getTextContent().replace("\n", "").trim();
|
||||||
|
break;
|
||||||
|
case "link":
|
||||||
|
link = childItem.getTextContent();
|
||||||
|
break;
|
||||||
|
case "pubDate":
|
||||||
|
timestamp = PUB_DATE_FORMAT.parse(childItem.getTextContent()).getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title == null || description == null || link == null || timestamp == -1)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Failed to find title, description, link and/or timestamp in the OSRS RSS feed");
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(new FeedItem(FeedItemType.OSRS_NEWS, title, description, link, timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
catch (ParserConfigurationException | SAXException | ParseException e)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Failed to parse OSRS news: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed.twitter;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
class TwitterOAuth2TokenResponse
|
||||||
|
{
|
||||||
|
@SerializedName("token_type")
|
||||||
|
private String tokenType;
|
||||||
|
|
||||||
|
@SerializedName("access_token")
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed.twitter;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import net.runelite.http.api.feed.FeedItem;
|
||||||
|
import net.runelite.http.api.feed.FeedItemType;
|
||||||
|
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class TwitterService
|
||||||
|
{
|
||||||
|
private static final HttpUrl AUTH_URL = HttpUrl.parse("https://api.twitter.com/oauth2/token");
|
||||||
|
private static final HttpUrl LIST_STATUSES_URL = HttpUrl.parse("https://api.twitter.com/1.1/lists/statuses.json");
|
||||||
|
|
||||||
|
private final String credentials;
|
||||||
|
private final String listId;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TwitterService(
|
||||||
|
@Value("${runelite.twitter.consumerkey}") String consumerKey,
|
||||||
|
@Value("${runelite.twitter.secretkey}") String consumerSecret,
|
||||||
|
@Value("${runelite.twitter.listid}") String listId
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.credentials = consumerKey + ":" + consumerSecret;
|
||||||
|
this.listId = listId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FeedItem> getTweets() throws IOException
|
||||||
|
{
|
||||||
|
return getTweets(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeedItem> getTweets(boolean hasRetried) throws IOException
|
||||||
|
{
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
updateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpUrl url = LIST_STATUSES_URL.newBuilder()
|
||||||
|
.addQueryParameter("list_id", listId)
|
||||||
|
.addQueryParameter("count", "15")
|
||||||
|
.addQueryParameter("include_entities", "false")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header("Authorization", "Bearer " + token)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
{
|
||||||
|
switch (HttpStatus.valueOf(response.code()))
|
||||||
|
{
|
||||||
|
case BAD_REQUEST:
|
||||||
|
case UNAUTHORIZED:
|
||||||
|
updateToken();
|
||||||
|
if (!hasRetried)
|
||||||
|
{
|
||||||
|
return getTweets(true);
|
||||||
|
}
|
||||||
|
throw new InternalServerErrorException("Could not auth to Twitter after trying once: " + response.message());
|
||||||
|
default:
|
||||||
|
throw new IOException("Error getting Twitter list: " + response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream in = response.body().byteStream();
|
||||||
|
Type listType = new TypeToken<List<TwitterStatusesResponseItem>>()
|
||||||
|
{
|
||||||
|
}.getType();
|
||||||
|
List<TwitterStatusesResponseItem> statusesResponse = RuneLiteAPI.GSON
|
||||||
|
.fromJson(new InputStreamReader(in), listType);
|
||||||
|
|
||||||
|
List<FeedItem> items = new ArrayList<>();
|
||||||
|
|
||||||
|
for (TwitterStatusesResponseItem i : statusesResponse)
|
||||||
|
{
|
||||||
|
items.add(new FeedItem(FeedItemType.TWEET,
|
||||||
|
i.getUser().getProfileImageUrl(),
|
||||||
|
i.getUser().getScreenName(),
|
||||||
|
i.getText().replace("\n\n", " ").replaceAll("\n", " "),
|
||||||
|
"https://twitter.com/statuses/" + i.getId(),
|
||||||
|
getTimestampFromSnowflake(i.getId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateToken() throws IOException
|
||||||
|
{
|
||||||
|
String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(AUTH_URL)
|
||||||
|
.header("Authorization", "Basic " + encodedCredentials)
|
||||||
|
.post(new FormBody.Builder().add("grant_type", "client_credentials").build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
{
|
||||||
|
throw new IOException("Error authing to Twitter: " + response.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream in = response.body().byteStream();
|
||||||
|
TwitterOAuth2TokenResponse tokenResponse = RuneLiteAPI.GSON
|
||||||
|
.fromJson(new InputStreamReader(in), TwitterOAuth2TokenResponse.class);
|
||||||
|
|
||||||
|
if (!tokenResponse.getTokenType().equals("bearer"))
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Returned token was not a bearer token");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenResponse.getToken() == null)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Returned token was null");
|
||||||
|
}
|
||||||
|
|
||||||
|
token = tokenResponse.getToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the UTC timestamp from a Twitter snowflake as per
|
||||||
|
* https://github.com/client9/snowflake2time/blob/master/python/snowflake.py#L24
|
||||||
|
*/
|
||||||
|
private long getTimestampFromSnowflake(long snowflake)
|
||||||
|
{
|
||||||
|
return (snowflake >> 22) + 1288834974657L;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed.twitter;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
class TwitterStatusesResponseItem
|
||||||
|
{
|
||||||
|
private long id;
|
||||||
|
private String text;
|
||||||
|
private TwitterStatusesResponseItemUser user;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.http.service.feed.twitter;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
class TwitterStatusesResponseItemUser
|
||||||
|
{
|
||||||
|
@SerializedName("screen_name")
|
||||||
|
private String screenName;
|
||||||
|
|
||||||
|
@SerializedName("profile_image_url_https")
|
||||||
|
private String profileImageUrl;
|
||||||
|
}
|
||||||
@@ -4,3 +4,6 @@ minio.endpoint=http://10.96.22.171:9000
|
|||||||
minio.accesskey=AM54M27O4WZK65N6F8IP
|
minio.accesskey=AM54M27O4WZK65N6F8IP
|
||||||
minio.secretkey=/PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP
|
minio.secretkey=/PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP
|
||||||
minio.bucket=runelite
|
minio.bucket=runelite
|
||||||
|
runelite.twitter.consumerkey=moo
|
||||||
|
runelite.twitter.secretkey=cow
|
||||||
|
runelite.twitter.listid=968949795153948673
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package net.runelite.client.plugins.feed;
|
||||||
|
|
||||||
|
import net.runelite.client.config.Config;
|
||||||
|
import net.runelite.client.config.ConfigGroup;
|
||||||
|
import net.runelite.client.config.ConfigItem;
|
||||||
|
|
||||||
|
@ConfigGroup(
|
||||||
|
keyName = "feed",
|
||||||
|
name = "News Feed",
|
||||||
|
description = "Displays client and game-related news"
|
||||||
|
)
|
||||||
|
public interface FeedConfig extends Config
|
||||||
|
{
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "includeBlogPosts",
|
||||||
|
name = "Include Blog Posts",
|
||||||
|
description = "Configures whether blog posts are displayed",
|
||||||
|
position = 0
|
||||||
|
)
|
||||||
|
default boolean includeBlogPosts()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "includeTweets",
|
||||||
|
name = "Include Tweets",
|
||||||
|
description = "Configures whether tweets are displayed",
|
||||||
|
position = 1
|
||||||
|
)
|
||||||
|
default boolean includeTweets()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "includeOsrsNews",
|
||||||
|
name = "Include OSRS News",
|
||||||
|
description = "Configures whether OSRS news are displayed",
|
||||||
|
position = 2
|
||||||
|
)
|
||||||
|
default boolean includeOsrsNews()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.feed;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.font.FontRenderContext;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.Box;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.ui.PluginPanel;
|
||||||
|
import net.runelite.http.api.RuneLiteAPI;
|
||||||
|
import net.runelite.http.api.feed.FeedItem;
|
||||||
|
import net.runelite.http.api.feed.FeedItemType;
|
||||||
|
import net.runelite.http.api.feed.FeedResult;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class FeedPanel extends PluginPanel
|
||||||
|
{
|
||||||
|
private static BufferedImage RUNELITE_ICON;
|
||||||
|
private static BufferedImage OSRS_ICON;
|
||||||
|
|
||||||
|
private static final Color TWEET_BACKGROUND = new Color(15, 15, 15);
|
||||||
|
private static final Color OSRS_NEWS_BACKGROUND = new Color(36, 30, 19);
|
||||||
|
private static final Color BLOG_POST_BACKGROUND = new Color(11, 30, 41);
|
||||||
|
|
||||||
|
private static final int MAX_CONTENT_LINES = 3;
|
||||||
|
private static final int CONTENT_WIDTH = 148;
|
||||||
|
private static final int TIME_WIDTH = 20;
|
||||||
|
|
||||||
|
private static final Comparator<FeedItem> FEED_ITEM_COMPARATOR = (o1, o2) ->
|
||||||
|
{
|
||||||
|
if (o1.getType() != o2.getType())
|
||||||
|
{
|
||||||
|
if (o1.getType() == FeedItemType.BLOG_POST)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (o2.getType() == FeedItemType.BLOG_POST)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -Long.compare(o1.getTimestamp(), o2.getTimestamp());
|
||||||
|
};
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RUNELITE_ICON = ImageIO.read(FeedPanel.class.getResourceAsStream("runelite.png"));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
log.warn("Client icon failed to load", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OSRS_ICON = ImageIO.read(FeedPanel.class.getResourceAsStream("osrs.png"));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
log.warn("OSRS icon failed to load", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final FeedConfig config;
|
||||||
|
private final Supplier<FeedResult> feedSupplier;
|
||||||
|
|
||||||
|
FeedPanel(FeedConfig config, Supplier<FeedResult> feedSupplier)
|
||||||
|
{
|
||||||
|
this.config = config;
|
||||||
|
this.feedSupplier = feedSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebuildFeed()
|
||||||
|
{
|
||||||
|
FeedResult feed = feedSupplier.get();
|
||||||
|
|
||||||
|
if (feed == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() ->
|
||||||
|
{
|
||||||
|
removeAll();
|
||||||
|
|
||||||
|
feed.getItems()
|
||||||
|
.stream()
|
||||||
|
.filter(f -> f.getType() != FeedItemType.BLOG_POST || config.includeBlogPosts())
|
||||||
|
.filter(f -> f.getType() != FeedItemType.TWEET || config.includeTweets())
|
||||||
|
.filter(f -> f.getType() != FeedItemType.OSRS_NEWS || config.includeOsrsNews())
|
||||||
|
.sorted(FEED_ITEM_COMPARATOR)
|
||||||
|
.forEach(this::addItemToPanel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addItemToPanel(FeedItem item)
|
||||||
|
{
|
||||||
|
JPanel avatarAndRight = new JPanel(new BorderLayout());
|
||||||
|
avatarAndRight.setPreferredSize(new Dimension(0, 56));
|
||||||
|
|
||||||
|
JLabel avatar = new JLabel();
|
||||||
|
// width = 48+4 to compensate for the border
|
||||||
|
avatar.setPreferredSize(new Dimension(52, 48));
|
||||||
|
avatar.setBorder(new EmptyBorder(0, 4, 0, 0));
|
||||||
|
|
||||||
|
switch (item.getType())
|
||||||
|
{
|
||||||
|
case TWEET:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(item.getAvatar())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e)
|
||||||
|
{
|
||||||
|
log.warn(null, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) throws IOException
|
||||||
|
{
|
||||||
|
try (ResponseBody responseBody = response.body())
|
||||||
|
{
|
||||||
|
if (!response.isSuccessful())
|
||||||
|
{
|
||||||
|
log.warn("Failed to download image " + item.getAvatar());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatar.setIcon(new ImageIcon(ImageIO.read(responseBody.byteStream())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e)
|
||||||
|
{
|
||||||
|
log.warn(null, e);
|
||||||
|
}
|
||||||
|
avatarAndRight.setBackground(TWEET_BACKGROUND);
|
||||||
|
break;
|
||||||
|
case OSRS_NEWS:
|
||||||
|
if (OSRS_ICON != null)
|
||||||
|
{
|
||||||
|
avatar.setIcon(new ImageIcon(OSRS_ICON));
|
||||||
|
}
|
||||||
|
avatarAndRight.setBackground(OSRS_NEWS_BACKGROUND);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (RUNELITE_ICON != null)
|
||||||
|
{
|
||||||
|
avatar.setIcon(new ImageIcon(RUNELITE_ICON));
|
||||||
|
}
|
||||||
|
avatarAndRight.setBackground(BLOG_POST_BACKGROUND);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
JPanel upAndContent = new JPanel();
|
||||||
|
upAndContent.setLayout(new BoxLayout(upAndContent, BoxLayout.Y_AXIS));
|
||||||
|
upAndContent.setBorder(new EmptyBorder(4, 8, 4, 4));
|
||||||
|
upAndContent.setBackground(null);
|
||||||
|
|
||||||
|
JPanel titleAndTime = new JPanel();
|
||||||
|
titleAndTime.setLayout(new BorderLayout());
|
||||||
|
titleAndTime.setBackground(null);
|
||||||
|
|
||||||
|
Color darkerForeground = UIManager.getColor("Label.foreground").darker();
|
||||||
|
|
||||||
|
JLabel titleLabel = new JLabel(item.getTitle());
|
||||||
|
titleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
titleLabel.setBackground(null);
|
||||||
|
titleLabel.setForeground(darkerForeground);
|
||||||
|
titleLabel.setPreferredSize(new Dimension(CONTENT_WIDTH - TIME_WIDTH, 0));
|
||||||
|
|
||||||
|
Duration duration = Duration.between(Instant.ofEpochMilli(item.getTimestamp()), Instant.now());
|
||||||
|
JLabel timeLabel = new JLabel(durationToString(duration));
|
||||||
|
timeLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
timeLabel.setForeground(darkerForeground);
|
||||||
|
|
||||||
|
titleAndTime.add(titleLabel, BorderLayout.WEST);
|
||||||
|
titleAndTime.add(timeLabel, BorderLayout.EAST);
|
||||||
|
|
||||||
|
JPanel content = new JPanel(new BorderLayout());
|
||||||
|
content.setBackground(null);
|
||||||
|
|
||||||
|
JLabel contentLabel = new JLabel(lineBreakText(item.getContent(), FontManager.getRunescapeSmallFont()));
|
||||||
|
contentLabel.setBorder(new EmptyBorder(2, 0, 0, 0));
|
||||||
|
contentLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
contentLabel.setForeground(darkerForeground);
|
||||||
|
|
||||||
|
content.add(contentLabel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
upAndContent.add(titleAndTime);
|
||||||
|
upAndContent.add(content);
|
||||||
|
upAndContent.add(new Box.Filler(new Dimension(0, 0),
|
||||||
|
new Dimension(0, Short.MAX_VALUE),
|
||||||
|
new Dimension(0, Short.MAX_VALUE)));
|
||||||
|
|
||||||
|
avatarAndRight.add(avatar, BorderLayout.WEST);
|
||||||
|
avatarAndRight.add(upAndContent, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
Color backgroundColor = avatarAndRight.getBackground();
|
||||||
|
Color hoverColor = backgroundColor.brighter().brighter();
|
||||||
|
Color pressedColor = hoverColor.brighter();
|
||||||
|
|
||||||
|
avatarAndRight.addMouseListener(new MouseAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e)
|
||||||
|
{
|
||||||
|
avatarAndRight.setBackground(hoverColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e)
|
||||||
|
{
|
||||||
|
avatarAndRight.setBackground(backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e)
|
||||||
|
{
|
||||||
|
avatarAndRight.setBackground(pressedColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent e)
|
||||||
|
{
|
||||||
|
avatarAndRight.setBackground(hoverColor);
|
||||||
|
|
||||||
|
Desktop desktop = Desktop.getDesktop();
|
||||||
|
|
||||||
|
if (!desktop.isSupported(Desktop.Action.BROWSE))
|
||||||
|
{
|
||||||
|
log.info("Desktop browser is not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
desktop.browse(new URI(item.getUrl()));
|
||||||
|
}
|
||||||
|
catch (IOException | URISyntaxException ex)
|
||||||
|
{
|
||||||
|
log.warn("Unable to open URL " + item.getUrl(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add(avatarAndRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String durationToString(Duration duration)
|
||||||
|
{
|
||||||
|
if (duration.getSeconds() >= 60 * 60 * 24)
|
||||||
|
{
|
||||||
|
return (int) (duration.getSeconds() / (60 * 60 * 24)) + "d";
|
||||||
|
}
|
||||||
|
else if (duration.getSeconds() >= 60 * 60)
|
||||||
|
{
|
||||||
|
return (int) (duration.getSeconds() / (60 * 60)) + "h";
|
||||||
|
}
|
||||||
|
return (int) (duration.getSeconds() / 60) + "m";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String lineBreakText(String text, Font font)
|
||||||
|
{
|
||||||
|
StringBuilder newText = new StringBuilder("<html>");
|
||||||
|
|
||||||
|
FontRenderContext fontRenderContext = new FontRenderContext(font.getTransform(),
|
||||||
|
true, true);
|
||||||
|
|
||||||
|
int lines = 0;
|
||||||
|
int pos = 0;
|
||||||
|
String[] words = text.split(" ");
|
||||||
|
String line = "";
|
||||||
|
|
||||||
|
while (lines < MAX_CONTENT_LINES && pos < words.length)
|
||||||
|
{
|
||||||
|
String newLine = pos > 0 ? line + " " + words[pos] : words[pos];
|
||||||
|
double width = font.getStringBounds(newLine, fontRenderContext).getWidth();
|
||||||
|
|
||||||
|
if (width >= CONTENT_WIDTH)
|
||||||
|
{
|
||||||
|
newText.append(line);
|
||||||
|
newText.append("<br>");
|
||||||
|
line = "";
|
||||||
|
lines++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
line = newLine;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newText.append(line);
|
||||||
|
newText.append("</html>");
|
||||||
|
|
||||||
|
return newText.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.feed;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.events.ConfigChanged;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
|
import net.runelite.client.task.Schedule;
|
||||||
|
import net.runelite.client.ui.ClientUI;
|
||||||
|
import net.runelite.client.ui.NavigationButton;
|
||||||
|
import net.runelite.http.api.feed.FeedClient;
|
||||||
|
import net.runelite.http.api.feed.FeedResult;
|
||||||
|
|
||||||
|
@PluginDescriptor(
|
||||||
|
name = "News Feed",
|
||||||
|
loadWhenOutdated = true
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class FeedPlugin extends Plugin
|
||||||
|
{
|
||||||
|
@Inject
|
||||||
|
private ClientUI ui;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FeedConfig config;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
private FeedPanel feedPanel;
|
||||||
|
private NavigationButton navButton;
|
||||||
|
|
||||||
|
private FeedClient feedClient = new FeedClient();
|
||||||
|
private Supplier<FeedResult> feedSupplier = Suppliers.memoizeWithExpiration(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return feedClient.lookupFeed();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
log.warn(null, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, 10, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startUp() throws Exception
|
||||||
|
{
|
||||||
|
feedPanel = new FeedPanel(config, feedSupplier);
|
||||||
|
|
||||||
|
navButton = new NavigationButton(
|
||||||
|
"News Feed",
|
||||||
|
ImageIO.read(getClass().getResourceAsStream("icon.png")),
|
||||||
|
() -> feedPanel);
|
||||||
|
|
||||||
|
ui.getPluginToolbar().addNavigation(navButton);
|
||||||
|
|
||||||
|
executorService.submit(this::updateFeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutDown() throws Exception
|
||||||
|
{
|
||||||
|
ui.getPluginToolbar().removeNavigation(navButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFeed()
|
||||||
|
{
|
||||||
|
feedPanel.rebuildFeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onConfigChanged(ConfigChanged event)
|
||||||
|
{
|
||||||
|
if (event.getGroup().equals("feed"))
|
||||||
|
{
|
||||||
|
executorService.submit(this::updateFeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schedule(
|
||||||
|
period = 10,
|
||||||
|
unit = ChronoUnit.MINUTES,
|
||||||
|
asynchronous = true
|
||||||
|
)
|
||||||
|
private void updateFeedTask()
|
||||||
|
{
|
||||||
|
updateFeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
FeedConfig provideConfig(ConfigManager configManager)
|
||||||
|
{
|
||||||
|
return configManager.getConfig(FeedConfig.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Reference in New Issue
Block a user