Introducing the Evennia Game Directory service contrib.
This commit is contained in:
parent
e10769ac22
commit
0b3314fa20
4 changed files with 322 additions and 0 deletions
139
evennia/contrib/gamedir_client/README.md
Normal file
139
evennia/contrib/gamedir_client/README.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Evennia Game Directory Client
|
||||
|
||||
Greg Taylor 2016
|
||||
|
||||
This contrib features a client for the [Evennia Game Directory]
|
||||
(http://evennia-game-directory.appspot.com/), a listing of games built on
|
||||
Evennia. By listing your game on the directory, you make it easy for other
|
||||
people in the community to discover your creation.
|
||||
|
||||
*Note: Since this is still an early experiment, there is no notion of
|
||||
ownership for a game listing. As a consequence, we rely on the good behavior
|
||||
of our users in the early goings. If the directory is a success, we'll work
|
||||
on remedying this.*
|
||||
|
||||
## Listing your Game
|
||||
|
||||
To list your game, you'll need to enable the Evennia Game Directory client.
|
||||
Start by `cd`'ing to your game directory. From there, open up
|
||||
`server/conf/server_services_plugins.py`. It might look something like this
|
||||
if you don't have any other optional add-ons enabled:
|
||||
|
||||
"""
|
||||
Server plugin services
|
||||
|
||||
This plugin module can define user-created services for the Server to
|
||||
start.
|
||||
|
||||
This module must handle all imports and setups required to start a
|
||||
twisted service (see examples in evennia.server.server). It must also
|
||||
contain a function start_plugin_services(application). Evennia will
|
||||
call this function with the main Server application (so your services
|
||||
can be added to it). The function should not return anything. Plugin
|
||||
services are started last in the Server startup process.
|
||||
"""
|
||||
|
||||
|
||||
def start_plugin_services(server):
|
||||
"""
|
||||
This hook is called by Evennia, last in the Server startup process.
|
||||
|
||||
server - a reference to the main server application.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
To enable the client, import `EvenniaGameDirService` and fire it up after the
|
||||
Evennia server has finished starting:
|
||||
|
||||
"""
|
||||
Server plugin services
|
||||
|
||||
This plugin module can define user-created services for the Server to
|
||||
start.
|
||||
|
||||
This module must handle all imports and setups required to start a
|
||||
twisted service (see examples in evennia.server.server). It must also
|
||||
contain a function start_plugin_services(application). Evennia will
|
||||
call this function with the main Server application (so your services
|
||||
can be added to it). The function should not return anything. Plugin
|
||||
services are started last in the Server startup process.
|
||||
"""
|
||||
from evennia.contrib.gamedir_client import EvenniaGameDirService
|
||||
|
||||
|
||||
def start_plugin_services(server):
|
||||
"""
|
||||
This hook is called by Evennia, last in the Server startup process.
|
||||
|
||||
server - a reference to the main server application.
|
||||
"""
|
||||
gamedir_service = EvenniaGameDirService()
|
||||
server.services.addService(gamedir_service)
|
||||
|
||||
|
||||
Next, configure your game listing by opening up `server/conf/settings.py` and
|
||||
using the following as a starting point:
|
||||
|
||||
######################################################################
|
||||
# Contrib config
|
||||
######################################################################
|
||||
|
||||
GAMEDIR_CLIENT = {
|
||||
'game_status': 'pre-alpha',
|
||||
'listing_contact': 'me@my-game.com',
|
||||
'telnet_hostname': 'my-game.com',
|
||||
'telnet_port': 1234,
|
||||
}
|
||||
|
||||
The following section in this README.md will go over all possible values.
|
||||
|
||||
At this point, you should be all set! Simply restart your game and check the
|
||||
server logs for errors. Your listing and some game state will be sent every
|
||||
half hour.
|
||||
|
||||
## Possible GAMEDIR_CLIENT settings
|
||||
|
||||
### game_status
|
||||
|
||||
Required: **Yes**
|
||||
Must be one of: 'pre-alpha', 'alpha', 'beta', 'launched'
|
||||
|
||||
Describes the current state of your game.
|
||||
|
||||
### game_website
|
||||
|
||||
Required: No
|
||||
|
||||
The URL to your game's website, if you have one.
|
||||
|
||||
### listing_contact
|
||||
|
||||
Required: **Yes**
|
||||
|
||||
An email address for us to get in touch with in the event of a listing issue
|
||||
or backwards-incompatible change.
|
||||
|
||||
### telnet_hostname
|
||||
|
||||
Required: **Yes**
|
||||
|
||||
The hostname that players can telnet into to play your game.
|
||||
|
||||
### telnet_port
|
||||
|
||||
Required: **Yes**
|
||||
|
||||
The port that the players can telnet into to play your game.
|
||||
|
||||
## What information is being reported?
|
||||
|
||||
In addition the the details listed in the previous section, we send some
|
||||
simple usage stats that don't currently get displayed. These will help the
|
||||
Evennia maintainers get a feel for some technical specifics for games out in
|
||||
the wild.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you don't see your game appear on the listing, check your server logs. You
|
||||
should see some error messages.
|
||||
1
evennia/contrib/gamedir_client/__init__.py
Normal file
1
evennia/contrib/gamedir_client/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from evennia.contrib.gamedir_client.service import EvenniaGameDirService
|
||||
141
evennia/contrib/gamedir_client/client.py
Normal file
141
evennia/contrib/gamedir_client/client.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import urllib
|
||||
|
||||
from django.conf import settings
|
||||
from twisted.internet import defer
|
||||
from twisted.internet import protocol
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from twisted.web.client import Agent, _HTTP11ClientFactory, HTTPConnectionPool
|
||||
from twisted.web.http_headers import Headers
|
||||
from twisted.web.iweb import IBodyProducer
|
||||
from zope.interface import implements
|
||||
|
||||
from evennia.players.models import PlayerDB
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import get_evennia_version, logger
|
||||
|
||||
|
||||
class EvenniaGameDirClient(object):
|
||||
"""
|
||||
This client class is used for gathering and sending game details to the
|
||||
Evennia Game Directory. Since EGD is in the early goings, this isn't
|
||||
incredibly configurable as far as what is being sent.
|
||||
"""
|
||||
def __init__(self, on_bad_request=None):
|
||||
"""
|
||||
:param on_bad_request: Optional callable to trigger when a bad request
|
||||
was sent. This is almost always going to be due to bad config.
|
||||
"""
|
||||
self.report_host = 'http://evennia-game-directory.appspot.com'
|
||||
self.report_path = '/api/v1/game/check_in'
|
||||
self.report_url = self.report_host + self.report_path
|
||||
|
||||
self._on_bad_request = on_bad_request
|
||||
# Oh, the humanity. Silence the factory start/stop messages.
|
||||
self._conn_pool = HTTPConnectionPool(reactor)
|
||||
self._conn_pool._factory = QuietHTTP11ClientFactory
|
||||
|
||||
@inlineCallbacks
|
||||
def send_game_details(self):
|
||||
"""
|
||||
This is where the magic happens. Send details about the game to the
|
||||
Evennia Game Directory.
|
||||
"""
|
||||
status_code, response_body = yield self._form_and_send_request()
|
||||
if status_code == 200:
|
||||
logger.log_infomsg(
|
||||
"Successfully sent game details to Evennia Game Directory.")
|
||||
return
|
||||
# At this point, either EGD is having issues or the payload we sent
|
||||
# is improperly formed (probably due to mis-configuration).
|
||||
logger.log_errmsg(
|
||||
'Failed to send game details to Evennia Game Directory. HTTP '
|
||||
'status code was %s. Message was: %s' % (status_code, response_body)
|
||||
)
|
||||
if status_code == 400 and self._on_bad_request:
|
||||
# Improperly formed request. Defer to the callback as far as what
|
||||
# to do. Probably not a great idea to continue attempting to send
|
||||
# to EGD, though.
|
||||
self._on_bad_request()
|
||||
|
||||
def _form_and_send_request(self):
|
||||
agent = Agent(reactor, pool=self._conn_pool)
|
||||
headers = {
|
||||
'User-Agent': ['Evennia Game Directory Client'],
|
||||
'Content-Type': ['application/x-www-form-urlencoded'],
|
||||
}
|
||||
gd_config = settings.GAMEDIR_CLIENT
|
||||
values = {
|
||||
'game_name': settings.SERVERNAME,
|
||||
'game_status': gd_config['game_status'],
|
||||
'game_website': gd_config.get('game_website'),
|
||||
'listing_contact': gd_config['listing_contact'],
|
||||
'evennia_version': get_evennia_version(),
|
||||
'telnet_hostname': gd_config['telnet_hostname'],
|
||||
'telnet_port': gd_config['telnet_port'],
|
||||
'connected_player_count': SESSIONS.player_count(),
|
||||
'total_player_count': PlayerDB.objects.num_total_players() or 0,
|
||||
}
|
||||
data = urllib.urlencode(values)
|
||||
|
||||
d = agent.request(
|
||||
'POST', self.report_url,
|
||||
headers=Headers(headers),
|
||||
bodyProducer=StringProducer(data))
|
||||
|
||||
d.addCallback(self.handle_egd_response)
|
||||
return d
|
||||
|
||||
def handle_egd_response(self, response):
|
||||
if 200 <= response.code < 300:
|
||||
d = defer.succeed((response.code, 'OK'))
|
||||
else:
|
||||
# Go through the horrifying process of getting the response body
|
||||
# out of Twisted's plumbing.
|
||||
d = defer.Deferred()
|
||||
response.deliverBody(SimpleResponseReceiver(response.code, d))
|
||||
return d
|
||||
|
||||
|
||||
class SimpleResponseReceiver(protocol.Protocol):
|
||||
"""
|
||||
Used for pulling the response body out of an HTTP response.
|
||||
"""
|
||||
def __init__(self, status_code, d):
|
||||
self.status_code = status_code
|
||||
self.buf = ''
|
||||
self.d = d
|
||||
|
||||
def dataReceived(self, data):
|
||||
self.buf += data
|
||||
|
||||
def connectionLost(self, reason=protocol.connectionDone):
|
||||
self.d.callback((self.status_code, self.buf))
|
||||
|
||||
|
||||
class StringProducer(object):
|
||||
"""
|
||||
Used for feeding a request body to the tx HTTP client.
|
||||
"""
|
||||
implements(IBodyProducer)
|
||||
|
||||
def __init__(self, body):
|
||||
self.body = body
|
||||
self.length = len(body)
|
||||
|
||||
def startProducing(self, consumer):
|
||||
consumer.write(self.body)
|
||||
return defer.succeed(None)
|
||||
|
||||
def pauseProducing(self):
|
||||
pass
|
||||
|
||||
def stopProducing(self):
|
||||
pass
|
||||
|
||||
|
||||
class QuietHTTP11ClientFactory(_HTTP11ClientFactory):
|
||||
"""
|
||||
Silences the obnoxious factory start/stop messages in the default client.
|
||||
"""
|
||||
noisy = False
|
||||
41
evennia/contrib/gamedir_client/service.py
Normal file
41
evennia/contrib/gamedir_client/service.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from twisted.application.service import Service
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from evennia.contrib.gamedir_client.client import EvenniaGameDirClient
|
||||
from evennia.utils import logger
|
||||
|
||||
|
||||
class EvenniaGameDirService(Service):
|
||||
"""
|
||||
Twisted Service that contains a LoopingCall for sending details on a
|
||||
game to the Evennia Game Directory.
|
||||
"""
|
||||
name = 'GameDirectoryClient'
|
||||
|
||||
def __init__(self):
|
||||
self.client = EvenniaGameDirClient(
|
||||
on_bad_request=self._die_on_bad_request)
|
||||
self.loop = LoopingCall(self.client.send_game_details)
|
||||
|
||||
def startService(self):
|
||||
super(EvenniaGameDirService, self).startService()
|
||||
# TODO: Check to make sure that the client is configured.
|
||||
self.loop.start(10)
|
||||
|
||||
def stopService(self):
|
||||
if self.running == 0:
|
||||
# @reload errors if we've stopped this service.
|
||||
return
|
||||
super(EvenniaGameDirService, self).stopService()
|
||||
self.loop.stop()
|
||||
|
||||
def _die_on_bad_request(self):
|
||||
"""
|
||||
If it becomes apparent that our configuration is generating improperly
|
||||
formed messages to EGD, we don't want to keep sending bad messages.
|
||||
Stop the service so we're not wasting resources.
|
||||
"""
|
||||
logger.log_infomsg(
|
||||
"Shutting down Evennia Game Directory client service due to "
|
||||
"invalid configuration.")
|
||||
self.stopService()
|
||||
Loading…
Add table
Add a link
Reference in a new issue