>> JACK FRANKS: Good morning. This is Drupal 8 integration with Slack. We'll be going over a Slack based text adventure game. I am Jack Franks. I've been doing this now this year for 30 years. Wow. I've been working with Drupal since 2013, and I work for Breakthrough Technologies. I'm going to kind of rush through the who I am and who I do because this is a pretty long and dense presentation. There's a lot to go over.
Mostly, I stay in Drupal because I love working with all of you, being in this community is one of my favorite things that I've done in my career. Like I said, I've been doing there for 30 years now.
I consider myself mostly a back end developer. That's what I enjoy the most. I can theme stuff, but I don't take any joy from it. Working with Slack was fun because most of it's done on an API level and I enjoy that.
As a youth, for a long time, I was a BBSer.
I built my first modem when I was a little kid, learned how to solder on it and used it to dial everywhere. Galaticomm was my favorite. I actually ran one for a long time. This was the first system that I had been on that allowed massive amount of other users at the same time. So, there were these text based multi user adventure games like you know, like Zork, but 32 players could all play it at once and interact and solve puzzles together. And, you know, as a kid, I had I just had so much fun with it.
As I was browsing around the Internet one day, I found the source code for it and that took me back in a pleasant way, so I decided to port it, see if I could implement it in Drupal. I started out doing it with a web socket based front end, and then figured out that I could actually integrate it with Slack and a whole bunch of people could all play it together.
So the game I imported is called Kyrandia. This world the author went on to make a graphical computer game based on this world. The original was called Kyrandia and it was available strictly for this major BBS system. Dvorak's guide to PC telecommunications writes in 1990 that Kyrandia is the most advanced multi user text adventure game created to date. And now I have ported it into PHP and Drupal.
So, let's look at a quick demo. We're going to see what the app looks like, how the interactions work, the kind of data that Drupal serves up through Slack and what it looks like in Slack, how users can interact with each other, and then what the app actually looks like.
So we have a couple of Slack windows up here. One player in a dark theme, one player in a light theme. We can say something over here, and we see it over here. I can move off to the north. Here it says that I left. And then there's you know, here's the description of the room that we're now in. You can see what I'm carrying, which would be nothing. Pick something up. Something's broken. That's all right. I can come back into the room, and it notifies me that I'm here.
Gaining levels in this game is all puzzle based. It's not experience point based or killing monsters or anything. You have to solve these particular puzzles. So to get from level one to two, in this room, you have to type kneel, and then here it gives me a description, says I'm level two. Here you can see that this player suddenly grows in strength and knowledge. We get a spell in our spell book.
So, this is how we interact with it. Sort of like in the old days with your modem, you sign on to your bulletin board, you join the game and start typing in commands, and then you get to interact with the system, and then it displays to the other players what it is that you're doing.
Usually, I'm hosting this app on a free tier Pantheon dead site. Today, I'm using ngrok so I can use it locally and make changes on the fly. We can see how some of this stuff works. There was some issues with some of that because in order to get the interactive components working when you receive the message from Slack, you have to pass back an authentication token that allows the user to interact with the components and it has to be all done within three seconds and with the with the strain on the network, with everybody working from home nowadays, and I'm presenting and recording and running it again with ngrok here locally. Most of the time, these messages do not go back and forth fast enough, so I can slow you some interactive components, but some are not working because my connection is too slow right now.
In normal life it doesn't really matter because it's pretty fast, but again today, it might not work.
The actual Slack app is a normal Slack app. The URLs are set to that Dev pantheon site. There's one called word_grammar, slack_incoming. Those are broadly useful at Slack.org. I have the Kyrandia engine, which has commands in handling this particular game, because you can have multiple games running on the same engine.
And then the migration module, which handles all of the data.
So going over word grammar, this is a text based game. So I needed to generate indefinite articles for all of the nouns, to build these text descriptions that I'm displaying everywhere. In order to do that, I needed to build out a little library to do it.
So here's the example of how it works. This is just random. It's just a service. You pass in a word, and it gives you the indefinite article. So if I have apple, it gives me an. But if I put in hour, it also knows to give me an. If I go horse, it gives me a, and unique, even though it starts with a vowel, it knows a. So it handles all the indefinite articles.
I also needed an array of word list based on the words that I passed in. And you have the option to build an oxford comma at the end, but as we know that should always be true, because we should always embrace the oxford comma.
So if I get the word list with horse, dog, chicken, cat, and elephant, it sorry, I think that I was moving my house and it clicked.
So it gives us horse, comma, space, dog, comma, space, and so on and it's smart enough to know that if there's only two items, it doesn't commaize them. And if there's one, it doesn't use the and.
The pieces of the old game that we're trying to bring in are the data and the code. In order to bring in the data, a lot of this is it's old format, you know, old C code, old message files, and it's pretty strangely formatted text for the way that we're normally passing around data nowadays. There's a bunch of things that we need to bring in, so I needed text for all of the different locations and the items and the spells that you can cast, the descriptions for when things happen, the message texts that when you do something, it tells you something and then it also tells other people in the room something, the descriptions and progression for every one of the levels in the system.
And then there's all the C code, which are all of the command routines for everything that you can type. So, you know, I did an inventory, I picked something up, you can drop something, you can say something. Everything that you do to interact with the system has its own little command routine, and I imported all of that from the old C code into PHP.
Each of the locations has special handling. So, for instance, there's a couple of places in the game where you can dig for gold or treasure or whatever. But if you're in a place that doesn't allow you to dig for those, it handles it separately, and the two places where you can dig also handle it separately. So, the locations need their own routines to handle it.
Then there's the system interaction routines like displaying messages to certain people. If you walk into a room, it needs to display it to the people in the room that you're leaving that you left, and then into the room that you're arriving into that you've arrived.
So, there was all kinds of interaction stuff that determined who gets what messages.
So we're going to talk about import all of this old data first. On the left, you can see what one of these command routines looks like. This is reading something. You can see it's just old C code. Pretty good C code, but it is old and there's a lot of pointer stuff and it handle static differently. There was some work to bring it over.
On the right here, you can see what one of the text messages looks like. So there's these big message text files just full of this stuff. We start out with this little identifier, this HLPMSG with an open brace, and a whole bunch of code, and some of them actually have, you know, kind of sophisticated ASCII art in them, which doesn't translate very well to a variably spaced font like we get in Slack.
And we can see that a few rows down, we get the closing brace, and then a T to indicate that there's a title here, which is a Kyrandia description. And here is another one. This is HLPCOM, and it starts with this.
We look at another one. Here's an actual level description record. It's a female description for level 4. Here it is. We have some S print def tokens. I needed a custom migration source that can parse all of that data. And that looks, well, kind of something like this.
So we grab the content from what our path is specified, which is the path to the beta file. Then we start splitting out all of the line feeds, break those into actual line breaks, and then we have a little reg x that splits that out. And then it walks through each of the rows, looks for the open brace, and then finds the next closed brace. And I know that I could do this with another regular expression, but these are the ones that take a little bit of time and this only has to run once for every file for every game, so it wasn't super important to really optimize it. We can see that it looks for the open brace and walks through each of the following lines until it finds the closing brace, breaks that down with the T there, so that we pull out this as our name and this as our type, and this is our description. So here's name, description, and type.
Then we add that into the row, add that into the row's array, and then we returned an iterator based on that row's array.
Now, this is something that I've been saying since I started doing Drupal 8 migration presentations that you have to remember here to return an array iterator. If you return an array here, your source won't work, and it will always return you zeros.
The documentation actually just recently has been updated to reflect that, but if you start writing a custom migration source and your iterator always seems to return nothing, just make sure that you're returning an array iterator instead of just an array.
Now, writing up that migration, it looks like this. This is Kyrandia messages. We're using the message plugin that we just went over. Specify the path to the file that we're importing. And then we're building out here a term, and it's just a Kyrandia message term, which is name, description, and user ID, they're all just going to admin, and the name pulling out the name, and then we're setting it to basic HTML because it's all ASCII, there's not really any HTML in it, and then we pull out the description.
And then again, these are listed out here. It tells you what we get and what field in the source that that actually comes from.
And now, that's all imported in. We've got a whole bunch of nodes and a whole bunch of terms. We've got all of this kind of stuff, locations, items, messages, spells, levels. We're going to have a quick look at what that looks like. Here are a bunch of locations. So if we come in and edit one of these locations, I've got all my field UI turned off... great.
Here we have level 1 with an apprentice description. Level 12 is a red wizard. We can look at messages. Al and then here are all of these kind of things that we saw before, like the help messages. So if we come into whatever EHINT7 is and load that up. So this is something that an elf. Gives you a little hint, and whatever the message, the event fires in the system that specifies this particular message, this is the text that it pulls out to give to you.
Now this still says CRON, and it is CRON, but it's not Drupal CRON. In the original code for the BBS, every eight seconds, it runs a system cycle, and the cycles do all kinds of things within the game. Within the scope of the entire bulletin board. But in the game if particular, it either increments or decrements particular effects. It will repopulate, move monsters around, that kind of stuff. I don't know if you've ever tried to run Drupal cron every eight seconds, but it is unhappy when you do that.
So rather than try to run it through Drupal cron, I set up a free service at this cron job.org, which lets me run every minute, which still slows it down. But since we don't have macros in Slack the way we used to do, like in pro cron plus or anything, this is fine enough to get you copying and pasting and typing some of the speedy things that you need to do. I now have a route that runs the cleanup routines and the game timers.
So, to play the game, you type in text commands and that's how you move around and look at things and talk to people, pick up objects, cast spells, gain levels. That's how you play the game. So you can check your inventory, you can move around. You know, if there's a diamond on the floor, you can get it, have something in your inventory. You can drop it. If there's a monster, you can beat it up. In order to make all of that happen and get this in a nice reusable format, we're using plug ins to handle each of these commands. We have an event handler, that handler then determines what kind of lug in it needs, grabs an instance of that plugin and then performs the action.
So, let's talk about events first. We'll get to plug ins in a moment. To fire an event in Drupal, symphony, it looks like this. You need a custom event class that has some properties and things on it. The package I'm sending in there is the data package that we're getting from the Slack listener. Then we take our Slack event and give it to the event dispatcher with the type of event that we're looking for, and this is a constant declared in that Slack event class.
The event dispatcher then fires that off and you have all the listeners that you set up that respond to that particular Slack event.
And then each of the Slack events does things to that Slack event object. So it adds responses that interacts with the properties, you know, variables on that particular slack event class. And then when they're all done, the dispatcher sends that slack event back to the caller here. And we can pull the response out of this. In this particular case, what we're looking for is something like maybe 200 or something to send back to Slack. So there's a couple of messages that it does here like when you set up your Slack application and put the end point in, the first thick it does is send a verification message with a challenge token, and then you have to immediately return that challenge token.
So it handles that differently than just receiving a message. If you type in hi to your bot, all it needs is a 200 there. It doesn't need a challenge token. So that's why it can set each one of these responses differently. And if it doesn't get one, there's more code down here that just gives it generic 200. If you have something special that your event needs to do, it will return.
So here's what our event class looks like. This is that Slack event class. This is what your event listeners will look for when they're subscribing to the actual event. We have the response. That's this that we can return if we get one. We have on there the results, which contain text strings that are keyed by the player ID, so that our Slack API class can send them out to the appropriate players. All of that goes into this Slack event class.
To set up a subscriber, you need to go into your module services.info.yml file. Passing in the Slack incoming Slack service. And when we name it with an event subscriber, that's how symphony knows to pass events into this. So that it knows what to listen for.
When you set up your event subscriber class, you have to have this get subscribed events, and we can tell it that we're listening for this Slack event, and we have a function defined called onSlackEvent, and it receives the slack event class as an argument. We can go and specify these in a certain priority, and it means descend. It so will run in numeric order.
So the listener I hope this isn't too small to see. This is our getSubscribedEvent function. We get the event as a parameter and now we can see what we're doing with it. So we have the Slack package. This is the JSON request that comes in from Slack. And we're doing a couple of things on this.
So here's that URL verification. The first time Slack interacts with your end point, it sends you this challenge parameter, which you then immediately have to return. That has to be returned within three seconds or it's invalid, it will try to hit that again.
We also have here a listener for the app home opened event, which we can see there's some sort of Slack building blocks in here, which can return markdown text or interactive components. There's a whole bunch of different stuff that it can do here.
And this is handled here. So if we come to the home tab, we can see this is generated off of that.
So we modify this in this app home opened event. We come back to our home tab. There's our next text.
So, home has a whole bunch of stuff that you can do with it. Any sort of interactive Slack component you can put on here. We don't have you know, just playing games and we don't have a real pressing need to put anything on here. But you can put on here whatever you like.
Now let's start talking about these plug ins. This is kind of where the magic happens here, as far as interacting with the game. So, let's talk about how to create custom plug ins. In order to create a plugin, we need a plugin class which defines annotations. You need a plugin manager class and a base class that tells all of your you know, all the plugins that you defined how to work. Here's one of the ones we have. We have this mud command plugin and we're defining look. Here we have an ID, and what module it goes to, and that's pretty much it for what we're doing here.
In order to define these annotations, you're defining each of the properties in your plugin class. So here we have our ID. Here we have our name. And then synonyms. Synonyms aren't working yet, but this is so that if you maybe a synonym to look is examine, and then it would know to run the look plugin if you typed in examine instead of look. That's not working yet.
In your module/SRC/annotation, that's where your actual plugin class lives.
Now you need a plugin manager. This is mostly boilerplate, once you've written one of these, you're just going to kind of copy and paste this around. It doesn't do a whole lot. But what it does is pretty important. You have to tell it what your name spaces are, so here we're doing mud commands.
And then you have to tell it the interface for what those plugins look like. The annotation, that plugin class that determines how you're declaring one of those plugins. You're setting up your alter info so you can, you know, use Drupal's alter system to change any of the information here. And then you're setting any special caching. And this game, in particular, doesn't have any, but you can get super creative on how you're doing caching on each of these plugins.
So this goes on the SRC directory on your module, it just kind of lives out there, and this is what runs your plugins.
So now we have our plugin interface. This is what each of the plugins the plugin classes is going to use to actually execute. So we only have one method, and that's perform, and it takes command text, which is the text to execute, whatever the player typed in. We have a node reference to whoever actually did the command, and then we have this results array, which is passed in by reference that everything that interacts with each of these perform methods adds stuff, too.
All of this is keyed by player. So here, player 1735 will get these two messages sent back to them. This player gets this message sent back to them. And the command can add as many results to this array as it wants for as many players as it wants. If I execute a command where I leave one room and enter a new room, it has to alert everyone in the old room that I'm leaving, everyone in the new room that I'm arriving. So it has all of those players identified by their player ID, and then each of the results that they are going to receive. I think I'm skipping ahead. All right.
Then we have a base class, which has some dependency injection and constructing stuff in here. Really, it's just to make everyone's life easier whenever you implement one of these plugin classes. So, let's look at one. Here we have our Kyrandia plugin, but since this is for this particular game, it needs a bunch of extra stuff. So we look at some of the dependency injection that we have. I don't know if this is big enough when we go into presentation mode. That one might be too big.
So we need an entity entity type manager. Some of these events can responds to other events. We have our word grammar service. Kyrandia has a game handler, which lets us pull out a use profile user, and then it has the plugin manager for the MUD commands themselves so that one command could chain multiple commands.
For instance, again, using the example of leaving the room, if you leave a room and enter another room, it needs to return you the description of the new room so it's effectively giving you a look command after you move, so it needs to be able to bring up another plugin and get the results for that.
So here are some of the things that it does, like these are some of the ported message handler routines, like here's giving a result you know, a message back to you. And then the game handler has the rue tone to get the message. A special perform message, which it sends another a different thing to the acting player, so it takes some arguments.
Here you can send messages to multiple parties. So now every time I have a Kyrandia plugin, it can now use all of these methods, and that makes things pretty easy.
Okay. Now, this is the part that is really exciting. How do we integrate with Slack? I have a module called Slack incoming. This is available up on Drupal.org right now. I went through all of the other Slack modules that were on Drupal.org already and didn't find that all of them could do anything that I needed to do. What I really needed was, I needed to accept messages, I needed to handle slack commands and fire off all the events and I also needed them to authenticate. Slack has a token based signing secret authentication method. We'll talk about that next.
And then we also needed a Slack API to send out requests, like doing a chat.post message, which is what your bot sends out text, and every time we see something like this, if I type look, what it comes back with here, this is all the chat.postmessage. Views.publish, it will this is any time you have an interactive surface, like when we're using the home tab, it sends out views.publish API call. So I needed something that would both take in my messages, handle all of the events for those, and then send things back out. So it's actually slack incoming outgoing, but that was kind of long.
Here's the URL where you can find this. This is up and live now. So now let's talk about this authentication. We want to make sure that someone malicious or just random isn't sending weird stuff to your app pretending to be Slack.
So since it has a signing secret, we have a form where we save that in config, and then we have an authentication provider to check that message, make sure that it signs out correctly, validates that it actually comes from Slack, and that it's been approved, and then it will allow it to go to the controller.
So that's set up as a service, and it's the Slack incoming signing secret, and if you assign this Slack incoming signing secret to any route that's, you know, for slash commands or additional API handlers, the API handlers for the most part won't need, because this event end point handles all of the event interactions that Slack will send you. But, for things like slash commands, like like here, we can type slash games, and then it will return me the list of games that we have. Those need to be authenticated as well. So for any one of those routes, you can put this Slack incoming signing secret on here, and then it will know to check each one of these any time it gets an incoming request, if it doesn't match with that signing secret, it won't go.
So, message behavior works more or less like this. So the user sends the bot a message. Then Slack sends our action end point a message. That authentication provider authenticates that it's real, and then the controller decodes that message, fires one of those Slack events, and then we have the event listeners, which process the message, generate the responses, and it has to generate a 200 response within three seconds, or it resends messages. So if you have debugging turned on and you're doing this through ngrok, maybe on a slow network, you can be debugging some sort of message that you're getting from Slack, and then it will send it multiple times in a row until it gets back a response, so that could be a little frustrating.
And our Slack events listeners use our Slack API service to call some of the Slack API messages and this is what we use to send a message back to the user.
There's a comment in here about using the key module instead of saving the Slack token in config. That is true. By the time there gets to some sort of production site, it is not just my sort of messing around with it on my own. That's a good secure way to go. For the moment, it saves it into config, and then GitHub yells at me every time I push it and then Slack makes me change the key because it's in a repository. There are a bunch of improvements that I would like to do to this, but, you know, this is all done in my off time and it's a big system.
So, slash commands. This is like that slash games that we just saw. You define that in your Slack application, and then that has to have a specific end point. So that needs a route and a controller method, and we need to authenticate it. The user I typed in slash games. Slack sends that message to that particular end point, and then we authenticate it, and then the controller method reads that, does whatever it needs to do, and returns the response, and then Slack displays that response to the user.
So this is a little bit different in that when we're receiving a message like the player runs a command and then looks around, it is giving back a 200 for that, and then sending additional chat messages to display the text to the user. For a slash command, it has to return the payload in the response. So the slash commands are handled a little bit differently.
So to get this going on your own, create a Slack work space, we go to Slack.com/create. You sign up, confirm, describe your team, you enter all this stuff in, and then by the time you're done with that, you get this nice little Slack work space.
Then you can go to API.slack.com, register or log in, and when you click your apps, you can create this new application. So we created one application, picked what work space it was going to, and when you continue, you enter in your information and press create app.
And it gives you a whole bunch of important stuff.
So now we go to the Drupal site, and we look at something that the Slack incoming module gives us. So we go to configuration, web services, Slack. And now we enter in our signing secrets, the Oauth access token, and don't worry, these are about to be replaced. So if you're trying to spoof the site, you can only do that for about another hour. And then you can specify the base Slack API URL. There's no particular reason for this to change. But just in case it needs to, there is a place to change it. And you can save that. And that all comes from here.
So here's our Slack app. Here's the credentials that it gives us. This signing secret is the important one. And in OAuth, this bot token is also important. Those are the only two things you really need to get this integrated.
Now, we need to enable events and set our end point URL. So, there's an event subscription link in the features section, and you have to turn that on. You type in the URL to your end point. So, it defaults to slack action/end point and I didn't see any reason to deviate that. So slack incoming event handler is slack/action endpoint, and here we have an ngrok URL, which points to this.
Now, as soon as you enter this in, now it tries to give you that URL verification event where you have to return the challenge URL. As soon as you do, it comes back and says verified, and now you can use that for every event interaction that you have with the site.
So we have a whole bunch of events that we can subscribe to. We're only going to cover the ones that this app needs and that's the app home opened, which is what gives us the home tab, and then we have message.im, and this is what handles the bot's direct messages, and this is how the users all interact with the game. Everything they do is by interacting with the bot, which then figures out what it needs to send to other users. So there's not like a main channel that it monitors, everything is just done with direct messages with the bot.
So once you've selected your permissions, or your events, rather, press save changes. And now we're going to generate some slash commands. So we have games and joingame. Games doesn't require a parameter. Joingame does. So we can create a new command. We have to set these up in our slack MUD controller. And then we enter them into the Slack app configuration here.
Here's what our routes look like. So we have slash command slash games. These are specific to this particular application. It's a post, and then we have our sliding secret. We have join game. Here's its controller method. And then it also needs to be signed.
So, when we declare this, or define this, we're telling it slash games. Here's the request URL, which again, just points back to this. So it goes back to /command/games. We have a description on it and it gives you a preview of what the autocomplete will look like when the users are using it.
And then same basic thing for joingame. It has a little usage hint on here that we didn't define before because /games doesn't need a parameter. And here's our little preview here.
There are some settings that we need to set, and the scopes. The scopes are pretty important. We need to add a scope that allows our module to message users. So we have to go to the OAuth in permissions and go down to scopes. Here we have to add a scope to allow us to do a chat:write. Now, this is important. I have forgotten to do this on a handful of occasions when I've created apps and I'm debugging and I'm looking. I'm getting my process. I have a log, you know, watchdog in here saying I got the message. Why isn't it writing out to the user? It's probably because you didn't specifically enable this chat:write scope. Make sure that you check this. If you are trying to send messages through Slack and they aren't appearing, first check your OAuth key, and that will probably be correct. And check that you have enabled this scope and it should probably end up sending after that.
We have another couple of ones that we don't actually need, like I don't need an IM history or the, you know, direct message history. But, yeah, they all come with whatever they were doing here. The commands and the chat:write are the important ones.
So, we need to make sure we add that bot OAuth token, and you have to copy that into your Slack incoming settings and click save. And then, you know, if you try to be secure, use the key module.
If you're just being lazy like I was, save it to config, and then you can install it to your work spaces. So it's going to verify that you want to allow it to do all these things. You want to say yes.
And now, when we go back to our work space, here we can see that it's in here, we can test the commands, and they all work.
So, this game was pretty complicated. You know, it's got dozens and dozens. I don't know if it has hundreds or not of commands that the user can take and different interactions. And again, it has different processes if you do something in one location versus a different location. So it needed a lot of testing. And I decided to do that with Behat. Kernel test would have been something different, but there's so much mocking and we're testing actual data output. To make sure that all of our S print def tokens are in the right spot and have the things they needed, so we really needed some behavior testing and it was so much faster to write this out with Behat once the app was set up and make that all work.
And that looks something like this. I'll just let this run for a bit. There's a couple of thousand steps. And then there's a bunch of manual testing in Slack. Slack also has an API tester tool called steno, which I'm not super familiar with. But it might be able to help things like debugging those interactive components when their tokens are in danger of expiring. But I haven't really gotten into it.
So everything that I showed you here is in GitHub. You can find that at tiny.cc/slackmud. The slides are up. The presentation at mid.camp/6280. You can leave feedback there as well. The Slack incoming module is on Drupal.org. So is Word Grammar. Slack Mud and Kyrandia are not. I don't actually own the source code that I've ported, so I don't know the licensing around all of that stuff.
So maybe it will end up there. Maybe not. But you can get it all in GitHub.
So, I can take questions now.
>> ANDREW OLSON: You can raise your hand and I can unmute you, or we can start in the chat channel. However you want to let us know.
>> JACK FRANKS: I saw Benji raise a hand.
>> ANDREW OLSON: All right. I'm trying to find you to unmute you, Benji. There you go. You should be able to talk, Benji.
>> JACK FRANKS: This is nice, we won't have to repeat questions now for the recording. I can't hear you, though.
>> Can you hear me now?
>> JACK FRANKS: Yes.
>> With all that C code you had to port, I wonder were you able to automate some of that, or did you have to just do it manually one function at a time?
>> JACK FRANKS: Yeah, I pretty much had to do it manually one function at a time. Some of the mistakes we often make in porting, I tried to do some optimization and apply some better principles to some of that code, and realized just how long that was taking, and then how much translation it took from the old code to the new code, which is why I ended up implementing some of those handling messages, like the perf message, because that's what the old code used so I sort of reverted back to its paradigm in order to speed up that porting. But, yeah, it was by hand for every one of those.
>> Wow. [Chuckling]
>> JACK FRANKS: But, you know, it's a labor of love. Like, I spent so much time playing this game, again, 30 years ago, that seeing how it all comes together and how it's all done, I didn't realize how some of the stuff worked.
You know, some of those some of the text that it would display. You know, those are long paragraphs and they're very, you know, descriptive and fantasy ish, and, you know, there's a lot in them. And I didn't realize when I was playing the game that they all had the same token setup. So it was the first string in there was always the character name. The second string in there was always your possessive, so his or her. And then there was an optional third one, which was a him or her. And when you go through the messages, importing them and touching them by hand, you can see that they all do follow that pattern, but when you're playing the game and they're all different messages, it was you couldn't really see that that's the way it was. It was cool to see how it all went together.
For 1990, this was pretty good solid code.
>> And for the migration, was all of that text in one file, or did you have to import a whole bunch of different files?
>> JACK FRANKS: Yeah, there were a handful of files.
>> Like one for locations and one for actions or something like that.
>> JACK FRANKS: Yeah. It needed each of these. So there are a bunch of game level oh, that was my actual game. There's a I have an error in here where it's not determine one of the dependencies. So you could have the Slack MUD games in here so they have a game record, so I have to import this first. I haven't gone in to check why.
Once that's done, you can check items. Items have multiple files in them just on their own. So here we have their item ID, their name, and the description. They could potentially have a description for what happens if you try to interact with them and you can't. And then if it's visible or if you can pick it up, and then the yeah, in some of these files, that's where the actual item description text goes. So, you know, they're declared in different ways. And these are location texts and they have IDs that get keyed off of these.
So, yeah, it was a complex migration.
>> Are all of these text files open source?
>> JACK FRANKS: They're in GitHub.
>> Okay.
>> JACK FRANKS: Again, it's 30 year old code. I don't know what the actual licensing for it is. But all of this is available in that GitHub repository that I put the link up for.
>> I guess I wonder if it would make sense to do the one time conversion from those custom text formats into XML or JSON or something, and instead of writing custom migration source, just use something standard.
>> JACK FRANKS: Yeah, I did look at doing it that way. But, you know, I wanted to also do it as a mostly Drupal solution and show how you can do complex migrations to get all this stuff in. Because I was looking at importing some other games, like having a ZIL interpreter that could play things like Zork or any of those games. And their format is yet different.
So, you know, if there's if I have to translate it just to import it and I would have to have multiple translations just to import them, I may as well just import them.
>> Cool.
>> JACK FRANKS: Any more questions? All right. Well, I really did expect more. When we're in person, there's usually a lot more interaction, a lot more questions than this.
Talk about migration. What do you want to know more specifically? Just something that came in through chat, so I'm just reading this out.
>> ANDREW OLSON: I can unmute, if interested.
>> JACK FRANKS: Yeah, please. Irina, you are now unmuted.
>> Hi, everyone. It's very interesting to do camp completely online, first time. I wanted to ask about the comments like it seems like you had to do a lot of migrations from very old files.
>> JACK FRANKS: Oh, yeah.
>> So I'd be I would love to hear what tools made it easier, what would be easier for you if you would kind of if you would do it again, what tools would you like to have?
>> JACK FRANKS: I am pretty happy with the way that I did it. So if I were to do it again, I would just have a slightly optimized version of what it is that I did here.
We can see that we've got a couple of groups, we've got some migrations in here. We look at locations. There's a couple of things here I'm doing that are locations get saved as a node, not as a term. So our fields are a little bit different than the one that I showed before.
If we store things like here, default items. So, like the game so in this particular location, it should always start out with these items in it. So, you know, these have a dependency on the actual items, and then they have to look those up. I did end up doing two sources to handle this LCS versus an MSG. I would probably standardize this, because they're very similar formats.
But really, it was just writing these couple of sources, and a handful of processes, which some of them just do very slightly, more complex functions than is easy to do in the migration configuration. You know, some of them are more complex. You know, this is pulling a description out of a different file based on a record in the source file. Here we're checking on something if it's different. Like, I think Migrate Plus comes to check if something is the same, but it doesn't really give you a flow to do something if it's different. Just a couple of things. Here's something else to pull out. This was an early iteration of it pulling a description out from a different file.
I could refactor this and make them all use the same mechanism now, but I didn't have a I didn't make a whole lot of time to go back and do that because it was all working.
>> Uh huh. Cool. Very cool. This is a very interesting use of Drupal for fun things. So this is very exciting to see how highly technical things end up in very fun results.
>> JACK FRANKS: Thanks, yeah. And speaking of Drupal for fun, make sure you talk to Irina later on if you want to contribute to Feed Migrate, which is putting all of this Drupal 8 migrate stuff into a feed context, and scheduling around it. It's a very cool project that I haven't been able to contribute a lot to. It's worth doing.
>> Jack, thank you so much for all your kind words. I hope that you will have some time on Saturday to join sprint at least for a little bit, just to say hello.
>> JACK FRANKS: Okay. Okay. We only have a few minutes left. Any other questions about this? Going to look at the Zoom chat for another few seconds just in case somebody is typing. Okay. I don't see anything.
So, you can get me on the MidCamp Slack or on Drupal Slack. My Drupal user name is franksj. I'd be more than happy to go through all of this with you. The project has a little read me on how to get it going if you're going to pull it down here. And like I said, let me know if you have any questions or want to talk about Slack integration any more. We can get word grammar and Slack incoming on Drupal.org right now. So, create some Slack apps, do some cool stuff, and then show it to me. All right. Thanks, everybody. I appreciate it.