{***************************************************************************}
{ TMS Twitter Cloud access                                                  }
{ for Delphi & C++Builder                                                   }
{                                                                           }
{ written by TMS Software                                                   }
{            copyright © 2012                                               }
{            Email : info@tmssoftware.com                                   }
{            Web : http://www.tmssoftware.com                               }
{                                                                           }
{ The source code is given as is. The author is not responsible             }
{ for any possible damage done due to the use of this code.                 }
{ The component can be freely used in any application. The complete         }
{ source code remains property of the author and may not be distributed,    }
{ published, given or sold in any form as such. No parts of the source      }
{ code can be included in any other component or application without        }
{ written authorization of the author.                                      }
{***************************************************************************}

unit CloudTwitter;

interface

uses
  Classes, CloudBase, SysUtils, Windows, DBXJSON;

type
  TTwitterProfile = class;

  TTWitterStatus = class(TPersistent)
  private
    FUser: TTwitterProfile;
    FFavorited: boolean;
    FSource: string;
    FInReplyToID: integer;
    FID: uint64;
    FInReplyToScreenName: string;
    FCreatedAt: TDateTime;
    FText: string;
    FInReplyToStatusID: integer;
    procedure SetUser(const Value: TTwitterProfile);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    procedure FromJSON(jo: TJSONObject);
    property User: TTwitterProfile read FUser write SetUser;
    property Text: string read FText write FText;
    property Favorited: boolean read FFavorited write FFavorited;
    property CreatedAt: TDateTime read FCreatedAt write FCreatedAt;
    property ID: uint64 read FID write FID;
    property InReplyToID: integer read FInReplyToID write FInReplyToID;
    property Source: string read FSource write FSource;
    property InReplyToScreenName: string read FInReplyToScreenName write FInReplyToScreenName;
    property InReplyToStatusID: integer read FInReplyToStatusID write FInReplyToStatusID;
  end;

  TTwitterProfile = class(TPersistent)
  private
    FID: integer;
    FScreenName: string;
    FFollowersCount: integer;
    FListedCount: integer;
    FName: string;
    FLocation: string;
    FFriendsCount: integer;
    FImageUrl: string;
    FFavoritesCount: integer;
    FStatusCount: integer;
    FURL: string;
    FDescription: string;
    FCreatedAt: TDateTime;
    FGeoEnabled: boolean;
    FIsProtected: boolean;
    FTimeZone: string;
    FStatus: TTwitterStatus;
  public
    constructor Create(Reference: boolean = false);
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    procedure FromJSON(jo: TJSONObject);
  published
    property CreatedAt: TDateTime read FCreatedAt write FCreatedAt;
    property ScreenName: string read FScreenName write FScreenName;
    property Description: string read FDescription write FDescription;
    property ID: integer read FID write FID;
    property Location: string read FLocation write FLocation;
    property Name: string read FName write FName;
    property ImageURL: string read FImageUrl write FImageURL;
    property StatusCount: integer read FStatusCount write FStatusCount;
    property FollowersCount: integer read FFollowersCount write FFollowersCount;
    property FriendsCount: integer read FFriendsCount write FFriendsCount;
    property FavoritesCount: integer read FFavoritesCount write FFavoritesCount;
    property ListedCount: integer read FListedCount write FListedCount;
    property IsProtected: boolean read FIsProtected write FIsProtected;
    property GeoEnabled: boolean read FGeoEnabled write FGeoEnabled;
    property TimeZone: string read FTimeZone write FTimeZone;
    property URL: string read FURL write FURL;
    property Status: TTWitterStatus read FStatus write FStatus;
  end;

  TProfileItem = class(TCollectionItem)
  private
    FProfile: TTwitterProfile;
    procedure SetProfile(const Value: TTwitterProfile);
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    property Profile: TTwitterProfile read FProfile write SetProfile;
  end;

  TProfileList = class(TCollection)
  private
    function GetItemProfile(Index: integer): TTwitterProfile;
    procedure SetItemProfile(Index: integer; const Value: TTwitterProfile);
  public
    constructor Create;
    function Find(const ID: integer): TTwitterProfile; overload;
    function Find(const ID: string): TTwitterProfile; overload;
    function Add: TTwitterProfile;
    property Items[Index: integer]: TTwitterProfile read GetItemProfile write SetItemProfile; default;
  end;

  TStatusItem = class(TCollectionItem)
  private
    FStatus: TTwitterStatus;
    procedure SetStatus(const Value: TTwitterStatus);

  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    property Status: TTwitterStatus read FStatus write SetStatus;
  end;

  TStatusList = class(TCollection)
  private
    function GetItemStatus(Index: integer): TTwitterStatus;
    procedure SetItemStatus(Index: integer; const Value: TTwitterStatus);
  public
    constructor Create;
    property Items[Index: integer]: TTwitterStatus read GetItemStatus write SetItemStatus; default;
    function Add: TTwitterStatus;
  end;

  TTwitterStatuses = (twHome,twMentions,twUser);

  TAdvTwitter = class(TCloudBase)
  private
    FProfile: TTwitterProfile;
    FFollowerList: TProfileList;
    FFriendList: TProfileList;
    FStatusList: TStatusList;
    FMentionList: TStatusList;
    FUserList: TStatusList;
    FSearchList: TStatusList;
    CallBackURL: string;
    procedure SetProfile(const Value: TTwitterProfile);
  protected
    {$IF CompilerVersion >= 23.0}
    procedure DoCallBack(ASender: TObject;
      const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
      Headers: OleVariant; var Cancel: WordBool);
    {$ELSE}
    procedure DoCallBack(ASender: TObject;
      const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
      Headers: OleVariant; var Cancel: WordBool);
    {$IFEND}
    procedure GetAuthToken;
    procedure JSONToProfile(jo: TJSONObject; Profile: TTWitterProfile);
    function GetStatusesInt(List: TStatusList; StatusType: TTwitterStatuses; Count: integer = 100; SinceID: Int64 = -1; MaxID: Int64 = -1; UserID: integer = -1): integer;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure DoAuth; override;
    function TestTokens: boolean; override;
    function Tweet(const Msg: string): string;
    function Retweet(const ID: Int64): string;
    function DirectMessage(const Msg: string; ScreenName: string): string;
    function TweetWithMedia(const Msg, FileName: string): string;
    function GetFollowers: integer;
    function GetFriends: integer;
    function GetStatuses(Count: integer = 100; SinceID: Int64 = -1; MaxID: Int64 = -1): integer;
    function GetMentions(Count: integer = 100; SinceID: Int64 = -1; MaxID: Int64 = -1): integer;
    function Search(const Query: string; Count: integer = 100; SinceID: integer = -1): TStatusList;
//    procedure GetUserInfo;
    function GetAccountInfo: boolean;
    function GetProfileInfo(const ID: integer; Profile: TTwitterProfile): boolean; overload;
    function GetProfileInfo(const ID: string; Profile: TTwitterProfile): boolean; overload;
    function GetProfileInfo(Profile: TTwitterProfile): boolean; overload;
    function GetProfileListInfo(ProfileList: TProfileList): boolean;
    property Profile: TTwitterProfile read FProfile write SetProfile;
    property Followers: TProfileList read FFollowerList;
    property Friends: TProfileList read FFriendList;
    property Statuses: TStatusList read FStatusList;
    property Mentions: TStatusList read FMentionList;
    property UserTimeLine: TStatusList read FUserList;
  end;

implementation

uses
  Dialogs, Math;

{ TAdvTwitter }

constructor TAdvTwitter.Create(AOwner: TComponent);
begin
  inherited;
  FProfile := TTwitterProfile.Create;
  FFollowerList := TProfileList.Create;
  FFriendList := TProfileList.Create;
  FStatusList := TStatusList.Create;
  FMentionList := TStatusList.Create;
  FUserList := TStatusList.Create;
  FSearchList := TStatusList.Create;
end;

destructor TAdvTwitter.Destroy;
begin
  FStatusList.Free;
  FProfile.Free;
  FFriendList.Free;
  FFollowerList.Free;
  FUserList.Free;
  FSearchList.Free;
  FMentionList.Free;
  inherited;
end;


procedure TAdvTwitter.DoAuth;
var
  url: string;
  sig,sigbase, token, secret: string;
  resdat: ansistring;

begin
  AddOAuthRequestParams(App.Key, Token_Access);

  url := 'https://api.twitter.com/oauth/request_token';

  sigbase := GetOAuthSigBase(url,App.Key,'GET');
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);
  RequestParams.Values['oauth_signature'] := string(URLEncodeRFC3986(sig));

  url := SignURL(url);

  resdat := HttpsGet(URL);

  ParseTokens(resdat, token, secret);

  Token_Auth := token;
  Token_Auth_Secret := secret;

  //ShowMessage(Token_Auth + #13#10 + Token_Auth_Secret);

  CallBackURL := 'http://127.0.0.1';

  url := 'https://api.twitter.com/oauth/authorize?oauth_token='+ Token_Auth+'&oauth_callback=' + URLEncode(CallBackURL);

  if Assigned(AuthBrowser) then
  begin
    AuthBrowser.OnBeforeNavigate2 := DoCallBack;
    AuthBrowser.Navigate(url);
  end
  else
  begin
    CreateOAuthForm(url, DoCallBack, nil);
  end;
end;

{$IF CompilerVersion >= 23.0}
procedure TAdvTwitter.DoCallBack(ASender: TObject; const pDisp: IDispatch;
  const URL, Flags, TargetFrameName, PostData, Headers: OleVariant;
  var Cancel: WordBool);
{$ELSE}
procedure TAdvTwitter.DoCallBack(ASender: TObject; const pDisp: IDispatch;
  var URL, Flags, TargetFrameName, PostData, Headers: OleVariant;
  var Cancel: WordBool);
{$IFEND}

var
  s:string;
begin
  s := URL;
  if pos(CallBackURL,s) > 0 then
  begin
    CloseOAuthForm;
    GetAuthToken;
  end;
end;

function TAdvTwitter.GetAccountInfo: boolean;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  jp: TJSONPair;

begin
  Result := false;
  //url := 'https://api.twitter.com/1.1/followers/ids.json'; //?cursor=-1&screen_name=BrunoFierens';

  url := 'https://api.twitter.com/1.1/account/verify_credentials.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  sigbase := 'GET&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, sig));

  //required for Twitter API 1.1
//   + 'Content-Type: application/x-www-form-urlencoded';

  //url := url + '?cursor=-1&screen_name=BrunoFierens';
  resdat := HttpsGet(url,headers);

  if (resdat <> '') then
  begin
    jv := TJSONOBject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        jo := jv as TJSONObject;

        jp := jo.Get('errors');
        if not Assigned(jp) then
        begin
          JSONToProfile(jo,FProfile);
          Result := FProfile.ScreenName <> '';
        end;
      finally
        jv.Free;
      end;
    end;
  end;
end;

procedure TAdvTwitter.GetAuthToken;
var
  url: string;
  sig,sigbase,token,secret: string;
  resdat: ansistring;
begin
  url := 'https://api.twitter.com/oauth/access_token';

  AddOAuthRequestParams(App.Key, Token_Auth);
  sigbase := GetOAuthSigBase(url,App.Key,'GET');
  sig := GetSignature(sigbase, App.Secret, Token_Auth_Secret);
  RequestParams.Values['oauth_signature'] := URLEncodeRFC3986(sig);
  url := SignURL(url);

  resdat := HttpsGet(url);

  if ParseTokens(resdat, token, secret) then
  begin
    Token_Access := token;
    Token_Access_Secret := secret;
    Log('GOT ACCESS TOKENS: ' + Token_Access + ' / ' + Token_Access_Secret);
    DoReceivedAccessToken;
  end;
end;

//procedure TAdvTwitter.GetUserInfo;
//var
//  url,sig,sigbase,ts,nonce,header: string;
//  resdat:ansistring;
//begin
//  //url := 'https://api.twitter.com/1.1/followers/ids.json'; //?cursor=-1&screen_name=BrunoFierens';
//
//  url := 'https://api.twitter.com/1.1/account/settings.json';
//
//  AddOAuthRequestParams(App.Key, Token_Access, TS, nonce);
//
//  sigbase := 'GET&' + UrlEncode(url) + '&'
//    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
//  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);
//
//  header := GetOAuthHeader(App.Key, Token_Access, TS, Nonce, sig);
//
//  //url := url + '?cursor=-1&screen_name=BrunoFierens';
//  resdat := HttpsGet(url,header);
//  //OutputDebugString(pchar(string(resdat)));
//end;

procedure TAdvTwitter.JSONToProfile(jo: TJSONObject; Profile: TTWitterProfile);
begin
  Profile.FromJSON(jo);
end;

procedure TAdvTwitter.SetProfile(const Value: TTwitterProfile);
begin
  FProfile.Assign(Value);
end;

function TAdvTwitter.GetFriends: integer;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  jp: TJSONPair;
  ja: TJSONArray;
  i: integer;

begin
  Result := -1;
  if Profile.ScreenName = '' then
    Exit;

  url := 'https://api.twitter.com/1.1/friends/ids.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  RequestParams.Values['cursor']:='-1';
  RequestParams.Values['screen_name'] := Profile.ScreenName;

  sigbase := 'GET&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  url := url + '?cursor=-1&screen_name='+URLEncode(Profile.ScreenName);
  resdat := HttpsGet(url,headers);

  jv := TJSONOBject.ParseJSONValue(string(resdat));
  if Assigned(jv) then
  begin
    try
      jo := jv as TJSONObject;
      jp := jo.Get('errors');
      if not Assigned(jp) then
      begin
        FFriendList.Clear;

        ja := GetJSONValue(jo,'ids') as TJSONArray;
        for i := 0 to ja.Size - 1 do
        begin
          FFriendList.Add.ID := StrToIntT(ja.Get(i).Value);
        end;
        Result := FFriendList.Count;

        //GetProfileListInfo(FFollowerList);
      end;
    finally
      jv.Free;
    end;
  end;
end;

function TAdvTwitter.GetFollowers: integer;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  jp: TJSONPair;
  ja: TJSONArray;
  i: integer;

begin
  Result := -1;
  if Profile.ScreenName = '' then
    Exit;

  url := 'https://api.twitter.com/1.1/followers/ids.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  RequestParams.Values['cursor']:='-1';
  RequestParams.Values['screen_name'] := Profile.ScreenName;

  sigbase := 'GET&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  url := url + '?cursor=-1&screen_name='+URLEncode(Profile.ScreenName);
  resdat := HttpsGet(url,headers);

  jv := TJSONOBject.ParseJSONValue(string(resdat));
  if Assigned(jv) then
  begin
    try
      jo := jv as TJSONObject;
      jp := jo.Get('errors');
      if not Assigned(jp) then
      begin
        FFollowerList.Clear;

        ja := GetJSONValue(jo,'ids') as TJSONArray;
        for i := 0 to ja.Size - 1 do
        begin
          FFollowerList.Add.ID := StrToIntT(ja.Get(i).Value);
        end;
        Result := FFollowerList.Count;

        //GetProfileListInfo(FFollowerList);
      end;
    finally
      jv.Free;
    end;
  end;
end;

function TAdvTwitter.GetProfileInfo(Profile: TTwitterProfile): boolean;
begin
  Result := GetProfileInfo(Profile.ID, Profile);
end;

function TAdvTwitter.GetProfileInfo(const ID: string; Profile: TTwitterProfile): boolean;
begin
  Result := GetProfileInfo(StrToIntT(ID), Profile);
end;

function TAdvTwitter.GetProfileInfo(const ID: integer; Profile: TTwitterProfile): boolean;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  ja: TJSONArray;
  i: integer;

begin
  url := 'https://api.twitter.com/1.1/users/lookup.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  RequestParams.Values['user_id'] := IntToStr(ID);

  sigbase := 'GET&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  url := url + '?user_id='+IntToStr(ID);

  resdat := HttpsGet(url,headers);

  if (resdat <> '') then
  begin
    jv := TJSONOBject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        if jv is TJSONArray then
        begin
          ja := jv as TJSONArray;

          for i := 0 to ja.Size - 1 do
          begin
            jo := ja.Get(i) as TJSONObject;
            Profile.FromJSON(jo);
          end;
        end;
      finally
        jv.Free;
      end;
    end;
  end;

  Result := Profile.ScreenName <> '';
end;

function TAdvTwitter.GetProfileListInfo(ProfileList: TProfileList): boolean;
const
  PAGESIZE = 40;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  ja: TJSONArray;
  i: integer;
  Profile: TTwitterProfile;
  IDs,ID: string;
  start: integer;
  cnt: integer;

begin
  Result := false;

  start := 0;

  repeat
    url := 'https://api.twitter.com/1.1/users/lookup.json';

    cnt := 0;

    IDs := '';

    for i := start to Min(start + PAGESIZE - 1,ProfileList.Count - 1) do
    begin
      if i > start then
        IDs := IDs + ',' + IntToStr(ProfileList.Items[i].ID)
      else
        IDs := IntToStr(ProfileList.Items[i].ID);

      inc(cnt);
    end;

    start := start + cnt;

    AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

    RequestParams.Values['user_id'] := IDs;

    sigbase := 'GET&' + UrlEncode(url) + '&'
      + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
    sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

    AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

    url := url + '?user_id='+IDs;

    resdat := HttpsGet(url,headers);

    if (resdat <> '') then
    begin
      jv := TJSONOBject.ParseJSONValue(string(resdat));
      if Assigned(jv) then
      begin
        try
          if jv is TJSONArray then
          begin
            ja := jv as TJSONArray;

            for i := 0 to ja.Size - 1 do
            begin
              jo := ja.Get(i) as TJSONObject;

              id := GetJSONProp(jo,'id');

              Profile := ProfileList.Find(id);

              if Assigned(Profile) then
                JSONToProfile(jo,Profile);
            end;
          end;
        finally
          jv.Free;
        end;
      end;
    end;

  until start >= ProfileList.Count - 1;
end;

function TAdvTwitter.GetStatuses(Count: integer = 100; SinceID: Int64 = -1; MaxID: Int64 = -1): integer;
begin
  Result := GetStatusesInt(FStatusList, twHome, Count, SinceID, MaxID);
end;

function TAdvTwitter.GetMentions(Count: integer = 100; SinceID: Int64 = -1; MaxID: Int64 = -1): integer;
begin
  Result := GetStatusesInt(FMentionList, twMentions, Count, SinceID, MaxID);
end;

function TAdvTwitter.GetStatusesInt(List: TStatusList; StatusType: TTwitterStatuses; Count: integer = 100; SinceID: Int64 = -1; MaxID: Int64 = -1; UserID: integer = -1): integer;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  ja: TJSONArray;
  i, startindex: integer;
  Status: TTWitterStatus;
begin
  Result := -1;

  if Profile.ScreenName = '' then
    Exit;

  if (Count > 100)  or (Count < 0) then
    Count := 100;

  case StatusType of
    twHome:  url := 'https://api.twitter.com/1.1/statuses/home_timeline.json';
    twMentions: url := 'https://api.twitter.com/1.1/statuses/mentions_timeline.json';
    twUser: url := 'https://api.twitter.com/1.1/statuses/user_timeline.json';
  end;

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  if MaxID >= 0 then
    RequestParams.Values['count'] := inttostr(Count + 1)
  else
    RequestParams.Values['count'] := inttostr(Count);

  if (sinceID >= 0) then
    RequestParams.Values['since_id'] := inttostr(SinceID);

  if (maxID >= 0) then
    RequestParams.Values['max_id'] := inttostr(MaxID);

  if (userID >= 0) then
    RequestParams.Values['user_id'] := inttostr(SinceID);

  sigbase := 'GET&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  if MaxID >= 0 then
  begin
    url := url + '?count='+inttostr(Count + 1);
    startindex := 1;
  end
  else
  begin
    url := url + '?count='+inttostr(Count);
    startindex := 0;
  end;

  if sinceid >= 0 then
    url := url + '&since_id='+inttostr(SinceID);

  if maxid >= 0 then
    url := url + '&max_id='+inttostr(MaxID);

  if userid >= 0 then
    url := url + '&user_id='+inttostr(UserID);

  resdat := HttpsGet(url,headers);

  jv := TJSONOBject.ParseJSONValue(string(resdat));

  if Assigned(jv) and (jv is TJSONArray) then
  begin
    ja := jv as TJSONArray;

    for i := startindex to ja.Size - 1 do
    begin
      jo := ja.Get(i) as TJSONObject;

      Status := List.Add;
      Status.FromJSON(jo);
    end;
    jv.Free;
  end;
end;

function TAdvTwitter.Search(const Query: string; Count: integer = 100; SinceID: integer = -1): TStatusList;
var
  url,sig,sigbase,ts,nonce: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv, jva: TJSONValue;
  jo: TJSONObject;
  ja: TJSONArray;
  i: integer;
  Status: TTWitterStatus;

begin
  Result := nil;

  if Profile.ScreenName = '' then
    Exit;

  if (Count > 100)  or (Count < 0) then
    Count := 100;

  url := 'https://api.twitter.com/1.1/search/tweets.json';

  //'q=%23freebandnames&since_id=24012619984051000&max_id=250126199840518145&result_type=mixed&count=4

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  RequestParams.Values['q'] := Query;
  RequestParams.Values['count'] := inttostr(Count);
  if (sinceID >= 0) then
    RequestParams.Values['since_id'] := inttostr(SinceID);

  sigbase := 'GET&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  url := url + '?q='+string(UTF8Encode(URLEncode(Query))) + '&count=' + inttostr(Count);

  if sinceid <> -1 then
    url := url + '&since_id='+inttostr(SinceID);

  resdat := HttpsGet(url,headers);

  jv := TJSONOBject.ParseJSONValue(string(resdat));

  FSearchList.Clear;

  if Assigned(jv) then
  begin
    jva := GetJSONValue((jv as TJSONObject),'statuses');

    if Assigned(jva) and (jva is TJSONArray) then
    begin
      ja := jva as TJSONArray;

      for i := 0 to ja.Size - 1 do
      begin
        jo := ja.Get(i) as TJSONObject;

        Status := FSearchList.Add;
        Status.FromJSON(jo);
      end;
    end;
    jv.Free;
  end;

  Result := FSearchList;
end;

function TAdvTwitter.DirectMessage(const Msg: string;
  ScreenName: string): string;
var
  headers: TCoreCloudHeaders;
  url,sig,sigbase,ts,nonce,postdata: string;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;

begin
  Result := '';

  url := 'https://api.twitter.com/1.1/direct_messages/new.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  RequestParams.Values['text'] := Msg;
  RequestParams.Values['screen_name'] := ScreenName;

  sigbase := 'POST&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  //required for Twitter API 1.1
  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));
  AddHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');

  postdata := 'text='+ URLEncode(Msg) +'&screen_name=' + URLEncode(ScreenName);

  HttpsPost(extractserver(url),removeserver(url),headers,ansistring(postdata),resdat);

  if resdat <> '' then
  begin
//    OutputDebugString(pchar(string(resdat)));
    jv := TJSONOBject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        jo := jv as TJSONObject;
        Result := GetJSONProp(jo,'id_str');
      finally
        jv.Free;
      end;
    end;
  end;
end;

function TAdvTwitter.TestTokens: boolean;
begin
  Result := GetAccountInfo;
end;

function TAdvTwitter.ReTweet(const ID: Int64): string;
var
  url,sig,sigbase,ts,nonce,postdata: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;

begin
  Result := '';

  url := 'https://api.twitter.com/1.1/statuses/retweet/' + IntToStr(ID)+'.json';

  ts := GenerateTimeStamp;
  nonce := GenerateNonce;

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  sigbase := 'POST&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  postdata := '';

  HttpsPost(extractserver(url),removeserver(url),headers,ansistring(postdata),resdat);

  if resdat <> '' then
  begin
//    OutputDebugString(pchar(string(resdat)));
    jv := TJSONOBject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        jo := jv as TJSONObject;
        Result := GetJSONProp(jo,'id_str');
      finally
        jv.Free;
      end;
    end;
  end;
end;

function TAdvTwitter.Tweet(const Msg: string): string;
var
  url,sig,sigbase,ts,nonce,postdata: string;
  headers: TCoreCloudHeaders;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;

begin
  Result := '';

  url := 'https://api.twitter.com/1.1/statuses/update.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  RequestParams.Values['include_entities']:='true';
  RequestParams.Values['status'] := Msg;
  RequestParams.Values['trim_user'] := 'true';

  sigbase := 'POST&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(encodeParams(RequestParams, '&', false))));

  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));
  AddHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');

  RemoveOAuthRequestParams;

  postdata := encodeParams(requestparams,'&',false);

//  postdata := 'include_entities=true&status='+ HttpEncode(URLEncode(Msg)) + '&trim_user=true';

  HttpsPost(Extractserver(url),Removeserver(url),headers,ansistring(postdata),resdat);

  if resdat <> '' then
  begin
    jv := TJSONOBject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        jo := jv as TJSONObject;
        Result := GetJSONProp(jo,'id_str');
      finally
        jv.Free;
      end;
    end;
  end;
end;

function TAdvTwitter.TweetWithMedia(const Msg, FileName: string): string;
var
  headers: TCoreCloudHeaders;
  url,sig,sigbase,ts,nonce: string;
  resdat:ansistring;
  jv: TJSONValue;
  jo: TJSONObject;
  postdata: ansistring;

begin
  Result := '';
  url := 'https://api.twitter.com/1.1/statuses/update_with_media.json';

  AddOAuthRequestParams(App.Key, Token_Access, TS, Nonce);

  sigbase := 'POST&' + UrlEncode(url) + '&'
    + string(HTTPEncode(AnsiString(EncodeParams(RequestParams, '&', false))));
  sig := GetSignature(sigbase, App.Secret, Token_Access_Secret);

  AddHeader(headers, 'Authorization', GetOAuthHeader(App.Key, Token_Access, TS, Nonce, Sig));

  postdata :=
    'Content-Disposition: form-data; name="status"'#13#10
    + #13#10
    + UTF8Encode(Msg) + #13#10
    + '--AaB03x'#13#10
    + 'Content-Disposition: form-data; name="media[]"; filename="' + ansistring(ExtractFileName(FileName)) + '"'#13#10
    + 'Content-Type: application/octet-stream'#13#10
    + #13#10;

  resdat := HttpsPut(url,ExtractFilePath(FileName),ExtractFileName(FileName),headers,postdata,hcPostMultiPart);

  if (resdat <> '') then
  begin
//    OutputDebugString(pchar(string(resdat)));
    jv := TJSONOBject.ParseJSONValue(string(resdat));
    if Assigned(jv) then
    begin
      try
        jo := jv as TJSONObject;
        Result := GetJSONProp(jo,'id_str');
      finally
        jv.Free;
      end;
    end;
  end;
end;

{ TProfileItem }

procedure TProfileItem.Assign(Source: TPersistent);
begin
  //inherited;

end;

constructor TProfileItem.Create(Collection: TCollection);
begin
  inherited;
  FProfile := TTwitterProfile.Create;
end;

destructor TProfileItem.Destroy;
begin
  FProfile.Free;
  inherited;
end;

procedure TProfileItem.SetProfile(const Value: TTwitterProfile);
begin
  FProfile.Assign(Value);
end;

{ TProfileList }

function TProfileList.Add: TTwitterProfile;
begin
  Result := TProfileItem(inherited Add).Profile;
end;

constructor TProfileList.Create;
begin
  inherited Create(TProfileItem);
end;

function TProfileList.Find(const ID: string): TTwitterProfile;
begin
  Result := Find(TCloudBase.StrToIntT(ID));
end;

function TProfileList.Find(const ID: integer): TTwitterProfile;
var
  i: integer;
begin
  Result := nil;

  for i := 0 to Count - 1 do
  begin
    if Items[i].ID = ID then
    begin
      Result := Items[i];
      break;
    end;
  end;
end;

function TProfileList.GetItemProfile(Index: integer): TTwitterProfile;
begin
  Result := TProfileItem(inherited Items[Index]).Profile;
end;

procedure TProfileList.SetItemProfile(Index: integer;
  const Value: TTwitterProfile);
begin
  TProfileItem(inherited Items[Index]).Profile.Assign(Value);
end;

{ TTwitterProfile }

procedure TTwitterProfile.Assign(Source: TPersistent);
begin
  if (Source is TTwitterProfile) then
  begin
    FCreatedAt := (Source as TTwitterProfile).CreatedAt;
    FScreenName := (Source as TTwitterProfile).ScreenName;
    FDescription := (Source as TTwitterProfile).Description;
    FID := (Source as TTwitterProfile).ID;
    FLocation := (Source as TTwitterProfile).Location;
    FName := (Source as TTwitterProfile).Name;
    FImageURL := (Source as TTwitterProfile).ImageURL;
    FStatusCount := (Source as TTwitterProfile).StatusCount;
    FFollowersCount := (Source as TTwitterProfile).FollowersCount;
    FFriendsCount := (Source as TTwitterProfile).FriendsCount;
    FFavoritesCount := (Source as TTwitterProfile).FavoritesCount;
    FListedCount := (Source as TTwitterProfile).ListedCount;
    FIsProtected := (Source as TTwitterProfile).IsProtected;
    FGeoEnabled := (Source as TTwitterProfile).GeoEnabled;
    FTimeZone := (Source as TTwitterProfile).TimeZone;
    FURL := (Source as TTwitterProfile).URL;
    FStatus.Assign((Source as TTwitterProfile).Status);
  end;
end;

constructor TTwitterProfile.Create(Reference: boolean = false);
begin
  inherited Create;
  FStatus := nil;
  if not Reference then
    FStatus := TTwitterStatus.Create;
end;

destructor TTwitterProfile.Destroy;
begin
  if Assigned(FStatus) then
    FreeAndNil(FStatus);
  inherited;
end;

procedure TTwitterProfile.FromJSON(jo: TJSONObject);
var
  jv: TJSONValue;
begin
  ScreenName := TCloudBase.GetJSONProp(jo,'screen_name');
  Name := TCloudBase.GetJSONProp(jo,'name');
  Location := TCloudBase.GetJSONProp(jo,'location');
  Description := TCloudBase.GetJSONProp(jo,'description');
  ImageURL := TCloudBase.GetJSONProp(jo,'profile_image_url');
  ListedCount := TCloudBase.StrToIntT(TCloudBase.GetJSONProp(jo,'listed_count'));
  FollowersCount := TCloudBase.StrToIntT(TCloudBase.GetJSONProp(jo,'followers_count'));
  FriendsCount := TCloudBase.StrToIntT(TCloudBase.GetJSONProp(jo,'friends_count'));
  StatusCount := TCloudBase.StrToIntT(TCloudBase.GetJSONProp(jo,'statuses_count'));
  URL := TCloudBase.GetJSONProp(jo,'url');
  GeoEnabled := TCloudBase.GetJSONValue(jo,'geo_enabled') is TJSONTrue;
  IsProtected := TCloudBase.GetJSONValue(jo,'protected') is TJSONTrue;
  TimeZone := TCloudBase.GetJSONProp(jo,'time_zone');
  jv := TCloudBase.GetJSONValue(jo,'status');
  CreatedAt := TCloudBase.LongDateStrToDateTime(TCloudBase.GetJSONProp(jo,'created_at'));

  if Assigned(jv) then
    Status.FromJSON(jv as TJSONObject);
end;

{ TTWitterStatus }

procedure TTWitterStatus.Assign(Source: TPersistent);
begin
  if (Source is TTwitterStatus) then
  begin
    FText := (Source as TTwitterStatus).Text;
    FFavorited := (Source as TTwitterStatus).Favorited;
    FCreatedAt := (Source as TTwitterStatus).CreatedAt;
    FID := (Source as TTwitterStatus).ID;
    FInReplyToID := (Source as TTwitterStatus).InReplyToID;
    FSource := (Source as TTwitterStatus).Source;
    FInReplyToScreenName := (Source as TTwitterStatus).InReplyToScreenName;
    FInReplyToStatusID := (Source as TTwitterStatus).InReplyToStatusID;
  end;
end;

constructor TTWitterStatus.Create;
begin
  inherited;
  FUser := TTwitterProfile.Create(true);
end;

destructor TTWitterStatus.Destroy;
begin
  FUser.Free;
  inherited;
end;

procedure TTWitterStatus.FromJSON(jo: TJSONObject);
var
  jv: TJSONValue;
  id_str: string;
begin
  Text := TCloudBase.GetJSONProp(jo,'text');
  Favorited := TCloudBase.GetJSONValue(jo,'favorited') is TJSONTrue;

  id_str := TCloudBase.GetJSONProp(jo,'id_str');
  ID := TCloudBase.StrToInt64T(id_str);
  Source := TCloudBase.GetJSONProp(jo,'source');
  InReplyToID := TCloudBase.StrToIntT(TCloudBase.GetJSONProp(jo, 'in_reply_to_user_id'));
  InReplyToScreenName := TCloudBase.GetJSONProp(jo, 'in_reply_to_screen_name');
  InReplyToStatusID := TCloudBase.StrToIntT(TCloudBase.GetJSONProp(jo, 'in_reply_to_status_id'));
  CreatedAt := TCloudBase.LongDateStrToDateTime(TCloudBase.GetJSONProp(jo, 'created_at'));

  jv := TCloudBase.GetJSONValue(jo,'user');

  if Assigned(jv) then
    User.FromJSON(jv as TJSONObject);
end;

procedure TTWitterStatus.SetUser(const Value: TTwitterProfile);
begin
  FUser.Assign(Value);
end;

{ TStatusItem }

constructor TStatusItem.Create(Collection: TCollection);
begin
  inherited;
  FStatus := TTWitterStatus.Create;
end;

destructor TStatusItem.Destroy;
begin
  FStatus.Free;
  inherited;
end;

procedure TStatusItem.SetStatus(const Value: TTwitterStatus);
begin
  FStatus.Assign(Value);
end;

{ TStatusList }

function TStatusList.Add: TTwitterStatus;
begin
  Result := TStatusItem(inherited Add).Status;
end;

constructor TStatusList.Create;
begin
  inherited Create(TStatusItem);
end;

function TStatusList.GetItemStatus(Index: integer): TTwitterStatus;
begin
  Result := TStatusItem(inherited Items[Index]).Status;
end;

procedure TStatusList.SetItemStatus(Index: integer;
  const Value: TTwitterStatus);
begin
  TStatusItem(inherited Items[Index]).Status.Assign(Value);
end;

end.
