github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/plugins/alexa/server.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package alexa
    20  
    21  import (
    22  	"context"
    23  	"net/http"
    24  	"os"
    25  	"sync"
    26  
    27  	"github.com/gin-gonic/gin"
    28  	"go.uber.org/atomic"
    29  
    30  	"github.com/e154/smart-home/adaptors"
    31  	"github.com/e154/smart-home/common/apperr"
    32  	m "github.com/e154/smart-home/models"
    33  	"github.com/e154/smart-home/system/bus"
    34  	"github.com/e154/smart-home/system/scripts"
    35  )
    36  
    37  // Server ...
    38  type Server struct {
    39  	engine        *gin.Engine
    40  	isStarted     *atomic.Bool
    41  	addressPort   *string
    42  	server        *http.Server
    43  	skillLock     *sync.Mutex
    44  	skills        map[string]*Skill
    45  	adaptors      *adaptors.Adaptors
    46  	config        Config
    47  	scriptService scripts.ScriptService
    48  	//gate          *client.GateClient
    49  	eventBus bus.Bus
    50  }
    51  
    52  // NewServer ...
    53  func NewServer(adaptors *adaptors.Adaptors,
    54  	config Config,
    55  	scriptService scripts.ScriptService,
    56  	//gateClient *gate_client.GateClient,
    57  	eventBus bus.Bus) *Server {
    58  	return &Server{
    59  		isStarted:     atomic.NewBool(false),
    60  		adaptors:      adaptors,
    61  		skillLock:     &sync.Mutex{},
    62  		skills:        make(map[string]*Skill),
    63  		config:        config,
    64  		scriptService: scriptService,
    65  		//gate:          gateClient,
    66  		eventBus: eventBus,
    67  	}
    68  }
    69  
    70  // Start ...
    71  func (s *Server) Start() {
    72  
    73  	if !s.isStarted.CompareAndSwap(false, true) {
    74  		return
    75  	}
    76  
    77  	s.init()
    78  
    79  	logger := NewLogger()
    80  
    81  	gin.DisableConsoleColor()
    82  	gin.DefaultWriter = logger
    83  	gin.DefaultErrorWriter = logger
    84  	gin.SetMode(gin.ReleaseMode)
    85  
    86  	s.engine = gin.New()
    87  	s.engine.POST("/*any", s.Auth, s.handlerFunc)
    88  
    89  	s.server = &http.Server{
    90  		Addr:    s.config.String(),
    91  		Handler: s.engine,
    92  	}
    93  
    94  	go func() {
    95  		if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    96  			log.Fatalf("listen: %s", err.Error())
    97  		}
    98  	}()
    99  
   100  	//todo fix
   101  	//s.gate.SetAlexaApiEngine(s.engine)
   102  
   103  	log.Infof("Serving server at %s", s.config.String())
   104  }
   105  
   106  func (s *Server) init() {
   107  
   108  	list, err := s.adaptors.AlexaSkill.ListEnabled(context.Background(), 999, 0)
   109  	if err != nil {
   110  		log.Error(err.Error())
   111  		return
   112  	}
   113  
   114  	for _, skill := range list {
   115  		s.AddSkill(skill)
   116  	}
   117  }
   118  
   119  // Stop ...
   120  func (s *Server) Stop() {
   121  	if !s.isStarted.CompareAndSwap(true, false) {
   122  		return
   123  	}
   124  
   125  	//todo fix
   126  	//s.gate.SetAlexaApiEngine(nil)
   127  
   128  	if s.server != nil {
   129  		_ = s.server.Close()
   130  	}
   131  }
   132  
   133  func (s *Server) handlerFunc(ctx *gin.Context) {
   134  
   135  	log.Info("new request")
   136  
   137  	req := &Request{}
   138  	if err := ctx.ShouldBindJSON(req); err != nil {
   139  		log.Error(err.Error())
   140  		_ = ctx.AbortWithError(400, err)
   141  		return
   142  	}
   143  
   144  	resp := NewResponse()
   145  
   146  	switch req.GetRequestType() {
   147  	case "LaunchRequest":
   148  		s.OnLaunchHandler(ctx, req, resp)
   149  	case "IntentRequest":
   150  		s.OnIntentHandle(ctx, req, resp)
   151  	case "SessionEndedRequest":
   152  		s.OnSessionEndedHandler(ctx, req, resp)
   153  	case "AudioPlayer":
   154  		s.OnAudioPlayerHandler(ctx, req, resp)
   155  	default:
   156  		http.Error(ctx.Writer, "Invalid request.", http.StatusBadRequest)
   157  	}
   158  
   159  	ctx.Writer.Header().Set("Content-Type", "application/json;charset=UTF-8")
   160  
   161  	b, _ := resp.String()
   162  	_, _ = ctx.Writer.Write(b)
   163  }
   164  
   165  // OnLaunchHandler ...
   166  func (s *Server) OnLaunchHandler(ctx *gin.Context, req *Request, resp *Response) {
   167  	s.skillLock.Lock()
   168  	defer s.skillLock.Unlock()
   169  
   170  	if skill, ok := s.skills[req.Context.System.Application.ApplicationID]; ok {
   171  		skill.OnLaunch(ctx, req, resp)
   172  	}
   173  }
   174  
   175  // OnIntentHandle ...
   176  func (s *Server) OnIntentHandle(ctx *gin.Context, req *Request, resp *Response) {
   177  	s.skillLock.Lock()
   178  	defer s.skillLock.Unlock()
   179  
   180  	if skill, ok := s.skills[req.Context.System.Application.ApplicationID]; ok {
   181  		skill.OnIntent(ctx, req, resp)
   182  	}
   183  }
   184  
   185  // OnSessionEndedHandler ...
   186  func (s *Server) OnSessionEndedHandler(ctx *gin.Context, req *Request, resp *Response) {
   187  	s.skillLock.Lock()
   188  	defer s.skillLock.Unlock()
   189  
   190  	if skill, ok := s.skills[req.Context.System.Application.ApplicationID]; ok {
   191  		skill.OnSessionEnded(ctx, req, resp)
   192  	}
   193  }
   194  
   195  // OnAudioPlayerHandler ...
   196  func (s *Server) OnAudioPlayerHandler(ctx *gin.Context, req *Request, resp *Response) {
   197  	s.skillLock.Lock()
   198  	defer s.skillLock.Unlock()
   199  
   200  	for _, skill := range s.skills {
   201  		if skill.GetAppID() != req.Context.System.Application.ApplicationID {
   202  			continue
   203  		}
   204  		//todo check
   205  		//if skill.OnAudioPlayerState != nil {
   206  		//	skill.OnAudioPlayerState(ctx, req, resp)
   207  		//}
   208  	}
   209  }
   210  
   211  // Auth ...
   212  func (s Server) Auth(ctx *gin.Context) {
   213  
   214  	if os.Getenv("DEV") == "true" {
   215  		return
   216  	}
   217  
   218  	if !IsValidAlexaRequest(ctx.Writer, ctx.Request) {
   219  		_ = ctx.AbortWithError(401, apperr.ErrBadRequestParams)
   220  		return
   221  	}
   222  }
   223  
   224  // AddSkill ...
   225  func (s *Server) AddSkill(skill *m.AlexaSkill) {
   226  	s.skillLock.Lock()
   227  	defer s.skillLock.Unlock()
   228  
   229  	if _, ok := s.skills[skill.SkillId]; !ok {
   230  		s.skills[skill.SkillId] = NewSkill(skill, s.adaptors, s.scriptService, s.eventBus)
   231  	}
   232  }
   233  
   234  // UpdateSkill ...
   235  func (s *Server) UpdateSkill(skill *m.AlexaSkill) {
   236  	s.skillLock.Lock()
   237  	defer s.skillLock.Unlock()
   238  
   239  	s.skills[skill.SkillId] = NewSkill(skill, s.adaptors, s.scriptService, s.eventBus)
   240  }
   241  
   242  // DeleteSkill ...
   243  func (s *Server) DeleteSkill(skill *m.AlexaSkill) {
   244  	s.skillLock.Lock()
   245  	defer s.skillLock.Unlock()
   246  
   247  	if _, ok := s.skills[skill.SkillId]; !ok {
   248  		delete(s.skills, skill.SkillId)
   249  	}
   250  }