plugin.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. // Copyright 2021 Girish M
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "github.com/mattermost/mattermost-server/v5/model"
  19. "github.com/pkg/errors"
  20. "io/ioutil"
  21. "math/rand"
  22. "net/http"
  23. "path/filepath"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "sync"
  28. "time"
  29. "github.com/mattermost/mattermost-server/v5/plugin"
  30. )
  31. const bjCommand = "blackjack"
  32. const bjBot = "blackjack-bot"
  33. var gameOver = false
  34. var bot = model.Bot{
  35. Username: bjBot,
  36. DisplayName: bjBot,
  37. Description: "Blackjack bot for Mattermost",
  38. }
  39. var cards = map[string]int{"ace_of_hearts": 11, "2_of_hearts": 2, "3_of_hearts": 3, "4_of_hearts": 4, "5_of_hearts": 5,
  40. "6_of_hearts": 6, "7_of_hearts": 7, "8_of_hearts": 8, "9_of_hearts": 9, "10_of_hearts": 10,
  41. "jack_of_hearts": 10, "queen_of_hearts": 10, "king_of_hearts": 10,
  42. "ace_of_spades": 11, "2_of_spades": 2, "3_of_spades": 3, "4_of_spades": 4, "5_of_spades": 5,
  43. "6_of_spades": 6, "7_of_spades": 7, "8_of_spades": 8, "9_of_spades": 9, "10_of_spades": 10,
  44. "jack_of_spades": 10, "queen_of_spades": 10, "king_of_spades": 10,
  45. "ace_of_diamonds": 11, "2_of_diamonds": 2, "3_of_diamonds": 3, "4_of_diamonds": 4, "5_of_diamonds": 5,
  46. "6_of_diamonds": 6, "7_of_diamonds": 7, "8_of_diamonds": 8, "9_of_diamonds": 9, "10_of_diamonds": 10,
  47. "jack_of_diamonds": 10, "queen_of_diamonds": 10, "king_of_diamonds": 10,
  48. "ace_of_clubs": 11, "2_of_clubs": 2, "3_of_clubs": 3, "4_of_clubs": 4, "5_of_clubs": 5, "6_of_clubs": 6,
  49. "7_of_clubs": 7, "8_of_clubs": 8, "9_of_clubs": 9, "10_of_clubs": 10, "jack_of_clubs": 10, "queen_of_clubs": 10,
  50. "king_of_clubs": 10}
  51. var playingCards []string
  52. var dealtCards []string
  53. var cardTxt = ""
  54. var score = 0
  55. // Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
  56. type Plugin struct {
  57. plugin.MattermostPlugin
  58. // configurationLock synchronizes access to the configuration.
  59. configurationLock sync.RWMutex
  60. // configuration is the active plugin configuration. Consult getConfiguration and
  61. // setConfiguration for usage.
  62. configuration *configuration
  63. }
  64. // ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
  65. func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
  66. var post *model.Post
  67. body, _ := ioutil.ReadAll(r.Body)
  68. var jsonBody map[string]interface{}
  69. _ = json.Unmarshal(body, &jsonBody)
  70. user, _ := p.API.GetUserByUsername(bjBot)
  71. p.API.LogInfo("Bot UserId for posting messages: ", user.Id)
  72. post = &model.Post{
  73. UserId: user.Id,
  74. ChannelId: jsonBody["channel_id"].(string),
  75. }
  76. switch path := r.URL.Path; path {
  77. case "/hit":
  78. if !gameOver {
  79. var cardIndex = rand.Intn(len(playingCards))
  80. dealtCards = append(dealtCards, playingCards[cardIndex])
  81. cardTxt += "![" + playingCards[cardIndex] + "](" + getImgURL(p.GetSiteURL()) + playingCards[cardIndex] + ".jpg)"
  82. //remove card from deck
  83. playingCards = append(playingCards[:cardIndex], playingCards[cardIndex+1:]...)
  84. p.API.LogInfo("Dealt cards: ", dealtCards)
  85. score = calculateScore(dealtCards)
  86. if score > 21 {
  87. gameOver = true
  88. post.Message = cardTxt + "\n**" + strconv.Itoa(score) + ". Bust! :disappointed: Try again - `/blackjack`**"
  89. score = 0
  90. } else {
  91. if score < 21 {
  92. post.Message = cardTxt
  93. var attachmentMap map[string]interface{}
  94. var result = "**Your score is " + strconv.Itoa(score) + ".**"
  95. _ = json.Unmarshal([]byte(getAttachmentJSON(getPluginURL(p.GetSiteURL()), result)), &attachmentMap)
  96. post.SetProps(attachmentMap)
  97. } else {
  98. post.Message = cardTxt + "\n**Blackjack! Congratulations, you win :moneybag: Thanks for playing! :wave:**"
  99. gameOver = true
  100. }
  101. }
  102. } else {
  103. post.Message = "**To start a new game - `/blackjack`**"
  104. }
  105. _, _ = p.API.CreatePost(post)
  106. break
  107. case "/stay":
  108. if !gameOver {
  109. gameOver = true
  110. post.Message = "**Your final score is " + strconv.Itoa(score) + ". Thanks for playing! :wave:**"
  111. } else {
  112. post.Message = "**To start a new game - `/blackjack`**"
  113. }
  114. _, _ = p.API.CreatePost(post)
  115. break
  116. default:
  117. fmt.Fprintf(w, "Welcome to Blackjack!")
  118. }
  119. }
  120. // See https://developers.mattermost.com/extend/plugins/server/reference/
  121. func (p *Plugin) OnActivate() error {
  122. if p.API.GetConfig().ServiceSettings.SiteURL == nil {
  123. p.API.LogError("SiteURL must be set. Some features will operate incorrectly if the SiteURL is not set. See documentation for details: http://about.mattermost.com/default-site-url")
  124. }
  125. if err := p.API.RegisterCommand(createBJCommand(p.GetSiteURL())); err != nil {
  126. return errors.Wrapf(err, "failed to register %s command", bjCommand)
  127. }
  128. if user, _ := p.API.GetUserByUsername(bjBot); user == nil {
  129. if _, err := p.API.CreateBot(&bot); err != nil {
  130. return errors.Wrapf(err, "failed to register %s bot", bjBot)
  131. }
  132. p.setBotIcon()
  133. }
  134. return nil
  135. }
  136. func calculateScore(dealtCards []string) int {
  137. score = 0
  138. aces := 0
  139. sort.Strings(dealtCards)
  140. for i := 0; i < len(dealtCards); i++ {
  141. if strings.Contains(dealtCards[i], "ace") {
  142. aces++
  143. } else {
  144. score += cards[dealtCards[i]]
  145. }
  146. }
  147. if aces > 0 {
  148. //adding ace value
  149. for j := 0; j < aces; j++ {
  150. score += 11
  151. if score > 21 {
  152. score -= 10
  153. } else if score == 21 && j < aces-1 {
  154. score -= 10
  155. }
  156. }
  157. }
  158. return score
  159. }
  160. func initPlayingDeck() {
  161. gameOver = false
  162. dealtCards = nil
  163. cardTxt = ""
  164. score = 0
  165. playingCards = []string{"ace_of_hearts", "2_of_hearts", "3_of_hearts",
  166. "4_of_hearts", "5_of_hearts", "6_of_hearts",
  167. "7_of_hearts", "8_of_hearts", "9_of_hearts",
  168. "10_of_hearts", "jack_of_hearts", "queen_of_hearts",
  169. "king_of_hearts",
  170. "ace_of_spades", "2_of_spades", "3_of_spades",
  171. "4_of_spades", "5_of_spades", "6_of_spades",
  172. "7_of_spades", "8_of_spades", "9_of_spades",
  173. "10_of_spades", "jack_of_spades", "queen_of_spades",
  174. "king_of_spades",
  175. "ace_of_diamonds", "2_of_diamonds", "3_of_diamonds",
  176. "4_of_diamonds", "5_of_diamonds", "6_of_diamonds",
  177. "7_of_diamonds", "8_of_diamonds", "9_of_diamonds",
  178. "10_of_diamonds", "jack_of_diamonds", "queen_of_diamonds",
  179. "king_of_diamonds",
  180. "ace_of_clubs", "2_of_clubs", "3_of_clubs",
  181. "4_of_clubs", "5_of_clubs", "6_of_clubs",
  182. "7_of_clubs", "8_of_clubs", "9_of_clubs",
  183. "10_of_clubs", "jack_of_clubs", "queen_of_clubs",
  184. "king_of_clubs"}
  185. }
  186. func (p *Plugin) ExecuteCommand(*plugin.Context, *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
  187. initPlayingDeck()
  188. rand.Seed(time.Now().UnixNano())
  189. var result = ""
  190. var siteURL = p.GetSiteURL()
  191. var attachmentMap map[string]interface{} = nil
  192. var card1Index = rand.Intn(len(playingCards))
  193. dealtCards = append(dealtCards, playingCards[card1Index])
  194. //removing dealt cards from the playing deck
  195. playingCards = append(playingCards[:card1Index], playingCards[card1Index+1:]...)
  196. var card2Index = rand.Intn(len(playingCards))
  197. dealtCards = append(dealtCards, playingCards[card2Index])
  198. //removing dealt cards from the playing deck
  199. playingCards = append(playingCards[:card2Index], playingCards[card2Index+1:]...)
  200. p.API.LogInfo("Dealt cards: ", dealtCards)
  201. score = cards[dealtCards[0]] + cards[dealtCards[1]]
  202. if score == 22 {
  203. //two aces in the first hand
  204. score = 12
  205. }
  206. var pluginURL = getPluginURL(siteURL)
  207. var imgURL = getImgURL(siteURL)
  208. cardTxt = "![" + dealtCards[0] + "](" + imgURL + dealtCards[0] + ".jpg)![" + dealtCards[1] + "](" + imgURL + dealtCards[1] + ".jpg)"
  209. if score < 21 {
  210. result = "**Your score is " + strconv.Itoa(score) + ".**"
  211. json.Unmarshal([]byte(getAttachmentJSON(pluginURL, result)), &attachmentMap)
  212. }
  213. if score == 21 {
  214. result = "\n**BlackJack! Congratulations, You win :moneybag: Thanks for playing! :wave:**"
  215. cardTxt += result
  216. gameOver = true
  217. }
  218. var cmdResp *model.CommandResponse
  219. cmdResp = &model.CommandResponse{
  220. ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
  221. Text: cardTxt,
  222. Username: bjBot,
  223. Props: attachmentMap,
  224. }
  225. return cmdResp, nil
  226. }
  227. func getAttachmentJSON(pluginURL string, result string) string {
  228. return `{
  229. "attachments": [
  230. {
  231. "text": "` + result + `",
  232. "actions": [
  233. {
  234. "name": "Hit",
  235. "integration": {
  236. "url": "` + pluginURL + `/hit",
  237. "context": {
  238. "action": "hit"
  239. }
  240. }
  241. },
  242. {
  243. "name": "Stay",
  244. "integration": {
  245. "url": "` + pluginURL + `/stay",
  246. "context": {
  247. "action": "stay"
  248. }
  249. }
  250. }
  251. ]
  252. }
  253. ]
  254. }`
  255. }
  256. func (p *Plugin) GetSiteURL() string {
  257. siteURL := ""
  258. ptr := p.API.GetConfig().ServiceSettings.SiteURL
  259. if ptr != nil {
  260. siteURL = *ptr
  261. }
  262. return siteURL
  263. }
  264. func getPluginURL(siteURL string) string {
  265. return siteURL + "/plugins/com.girishm.mattermost-blackjack"
  266. }
  267. func getImgURL(siteURL string) string {
  268. return siteURL + "/plugins/com.girishm.mattermost-blackjack/public/jpg-cards/"
  269. }
  270. func createBJCommand(siteURL string) *model.Command {
  271. return &model.Command{
  272. Trigger: bjCommand,
  273. Method: "POST",
  274. Username: bjBot,
  275. AutoComplete: true,
  276. AutoCompleteDesc: "Play Blackjack",
  277. AutoCompleteHint: "Get a score of 21 to win.",
  278. DisplayName: bjBot,
  279. Description: "Blackjack game for Mattermost",
  280. URL: siteURL,
  281. }
  282. }
  283. func (p *Plugin) setBotIcon() {
  284. bundlePath, err := p.API.GetBundlePath()
  285. if err != nil {
  286. p.API.LogError("failed to get bundle path", err)
  287. }
  288. profileImage, err := ioutil.ReadFile(filepath.Join(bundlePath, "assets", "ace_of_hearts.svg"))
  289. if err != nil {
  290. p.API.LogError("failed to read profile image", err)
  291. }
  292. user, err := p.API.GetBot(bjBot, false)
  293. if err != nil {
  294. p.API.LogError("failed to fetch bot user", err)
  295. }
  296. if appErr := p.API.SetBotIconImage(user.UserId, profileImage); appErr != nil {
  297. p.API.LogError("failed to set profile image", appErr)
  298. }
  299. }